git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v4 2/4] git-cherry-pick: Add keep-redundant-commits option

From
Neil Horman <nhorman@tuxdriver.com>
Date
Apr 11, 2012, 23:55 UTC
Message-ID
<20120411235509.GB16937@hmsreliant.think-freely.org>
In-Reply-To
<7v1unuj8az.fsf@alter.siamese.dyndns.org>
On Wed, Apr 11, 2012 at 01:57:24PM -0700, Junio C Hamano wrote:
Show 42 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > The git-cherry-pick --allow-empty command by default only preserves empty
> > commits that were originally empty, i.e only those commits for which
> > <commit>^{tree} and <commit>^^{tree} are equal.  By default commits which are
> > non-empty, but were made empty by the inclusion of a prior commit on the current
> > history are filtered out.  This option allows us to override that behavior and
> > include redundant commits as empty commits in the change history.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > ---
> >  Documentation/git-cherry-pick.txt |   12 +++++-
> >  builtin/revert.c                  |    8 +++-
> >  sequencer.c                       |   91 +++++++++++++++++++++++++++++++-----
> >  sequencer.h                       |    1 +
> >  4 files changed, 97 insertions(+), 15 deletions(-)
> >
> > diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
> > index 730237a..f96b8c5 100644
> > --- a/Documentation/git-cherry-pick.txt
> > +++ b/Documentation/git-cherry-pick.txt
> > @@ -110,7 +110,17 @@ effect to your index in a row.
> >  	behavior, allowing empty commits to be preserved automatically
> >  	in a cherry-pick. Note that when "--ff" is in effect, empty
> >  	commits that meet the "fast-forward" requirement will be kept
> > -	even without this option.
> > +	even without this option.  Note also, that use of this option only
> > +	keeps commits that were initially empty (i.e. where for commit C
> > +	C^{tree} and C^^{tree} are equal).  Commits which are made empty due to
> 
> It is OK to be technical in the log message, but I think
> 
> 	commits that were initially empty (i.e. the commit recorded the
> 	same tree as its parent)
> 
> would be far easier to read in the documentation meant for the end users.
> 
> > +	a previous commit are ignored.  To force the inclusion of those commits
> > +	use `--keep-redundant-commits`
> 
> And end the sentence with a full-stop.
> 
Copy that.
Show 8 quoted lines
> > +--keep-redundant-commits::
> > +	If a commit being cherry picked duplicates a commit already in the
> > +	current history, it will result in an empty changeset.  By default these
> > +	redundant commits are ignored.  This option overrides that behavior and
> > +	creates an empty commit object.  Implies `--allow-empty`
> 
> Likewise.
> 
ACK
Show 68 quoted lines
> > diff --git a/builtin/revert.c b/builtin/revert.c
> > index 06b00e6..4f0d979 100644
> > --- a/builtin/revert.c
> > +++ b/builtin/revert.c
> > @@ -115,13 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
> >  		OPT_END(),
> >  		OPT_END(),
> >  		OPT_END(),
> > +		OPT_END(),
> >  	};
> >  
> >  	if (opts->action == REPLAY_PICK) {
> >  		struct option cp_extra[] = {
> >  			OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
> >  			OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
> > -			OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve empty commits"),
> > +			OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
> > +			OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_if_made_empty, "keep redundant, empty commits"),
> >  			OPT_END(),
> >  		};
> >  		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
> > @@ -139,6 +141,10 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
> >  				"--abort", rollback,
> >  				NULL);
> >  
> > +	/* keep_if_made_empty implies allow_empty */
> > +	if (opts->keep_if_made_empty)
> > +		opts->allow_empty = 1;
> > +
> >  	/* Set the subcommand */
> >  	if (remove_state)
> >  		opts->subcommand = REPLAY_REMOVE_STATE;
> > diff --git a/sequencer.c b/sequencer.c
> > index 71929ba..442f364 100644
> > --- a/sequencer.c
> > +++ b/sequencer.c
> > @@ -13,6 +13,7 @@
> >  #include "rerere.h"
> >  #include "merge-recursive.h"
> >  #include "refs.h"
> > +#include "argv-array.h"
> >  
> >  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
> >  
> > @@ -258,26 +259,85 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
> >   * If we are revert, or if our cherry-pick results in a hand merge,
> >   * we had better say that the current user is responsible for that.
> >   */
> > -static int run_git_commit(const char *defmsg, struct replay_opts *opts)
> > +int run_git_commit(const char *defmsg, struct replay_opts *opts, int empty)
> >  {
> > -	/* 7 is max possible length of our args array including NULL */
> > -	const char *args[7];
> > -	int i = 0;
> > +	struct argv_array array;
> > +	int rc;
> > +
> > +	if (!empty && !opts->keep_if_made_empty) {
> > +		unsigned char head_sha1[20];
> > +		struct commit *head_commit;
> > +		int need_free = 0;
> > +
> > +		resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
> > +		head_commit = lookup_commit(head_sha1);
> 
> No error checking whatsoever?  HEAD might be pointing at a branch that
> hasn't been born, for example.
> 

Sorry, I had presumed that HEAD could be guaranteed to be good here, given that we validate it in rollback_single_pick via read_ref_full. But I can add additional checking here.

Show 12 quoted lines
> > +		if (parse_commit(head_commit) < 0)
> > +			return error(_("could not parse commit %s\n"),
> > +				     sha1_to_hex(head_commit->object.sha1));
> > +
> > +		if (!active_cache_tree) {
> > +			active_cache_tree = cache_tree();
> > +			need_free = 1;
> > +		}
> 
> I think this is wrong.  If for whatever reason the process does not have
> the index it is about to tell "git commit" to use to make a commit in-core,
> it should first be doing read_cache().

I would have thought so too, and perhaps it should, but it doesn't seem to be the case. Initially I hadn't added this code, but during testing I received a segfault indicating that the_index.cache_tree was NULL. I thought it should have been filled in via the calls to read_cache in do_recursive_merge or fast_forward_to, but it didn't seem to be the case. I added a read_cache call right before my call to cache_tree_update, but still the_index.cache_tree remained NULL. Looking at the read_cache code, it appears that cache_tree is only allocated if a CACHE_EXT_TREE is found when parsing the index. Not sure why I wouldn't have one, but certainly it didn't seem I did. The above code fixed it, which I lifted directly from write_tree_from_memory()

Show 8 quoted lines
> > +
> > +		if (!cache_tree_fully_valid(active_cache_tree))
> > +			cache_tree_update(active_cache_tree, active_cache,
> > +					  active_nr, 0);
> 
> I do not recall offhand if cache_tree_update() can give you an error, but
> if it does, it should be checked here.
> 

It can, and I'll add checking there, but I hit the segfault above prior to getting any error checknig done.

Show 8 quoted lines
> > +		rc = !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
> > +
> > +		if (need_free)
> > +			cache_tree_free(&active_cache_tree);
> 
> and I don't see a need for nuking the cache tree here, unless you are
> discarding the_index as well.
> 

Just balancing the allocation above with a free. I didn't see a need for it any longer, given that we were shortly going to call git commit and then exit this process. Is it safe to leave hanging around?

Show 36 quoted lines
> > +		if (rc)
> > +			/*
> > + 			 * The head tree and the parent tree match
> > + 			 * meaning the commit is empty.  Since it wasn't created
> > + 			 * empty (based on the previous test), we can conclude
> > + 			 * the commit has been made redundant.  Since we don't
> > + 			 * want to keep redundant commits, just skip this one
> > + 			 */
> > +			return 0;
> > +	}
> > +
> > +	argv_array_init(&array);
> > +	argv_array_push(&array, "commit");
> > +	argv_array_push(&array, "-n");
> >  
> > -	args[i++] = "commit";
> > -	args[i++] = "-n";
> >  	if (opts->signoff)
> > -		args[i++] = "-s";
> > +		argv_array_push(&array, "-s");
> >  	if (!opts->edit) {
> > -		args[i++] = "-F";
> > -		args[i++] = defmsg;
> > +		argv_array_push(&array, "-F");
> > +		argv_array_push(&array, defmsg);
> >  	}
> > +	
> >  	if (opts->allow_empty)
> > -		args[i++] = "--allow-empty";
> > +		argv_array_push(&array, "--allow-empty");
> 
> If --keep-if-made-empty is not given but --allow-empty was, it is fine to
> give --allow-empty here, but otherwise, it logically is iffy and is likely
> to become a cause of future bugs to pass --allow-empty to "git commit",
> even though the earlier check _ought_ to catch problematic cases, no?
> 

Not sure I follow your reasoning here. We need to pass allow-empty to git commit here if git cherry-pick set either allow-empty or keep-redundant-commits (the latter setting opts->empty), Can you give an example of what might be problematic in the future?

Neil
Previous: Junio C HamanoNext: Junio C Hamano
Message 7 of 16 in “Enhance git-rebases flexibiilty in handling empty commits”
  1. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 11, 2012
  2. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 11, 2012
  3. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 11, 2012
  4. Junio C HamanoApr 11, 2012
  5. Neil HormanApr 11, 2012
  6. Junio C HamanoApr 11, 2012
  7. Neil HormanApr 11, 2012
  8. Junio C HamanoApr 12, 2012
  9. Neil HormanApr 12, 2012
  10. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 11, 2012
  11. Junio C HamanoApr 11, 2012
  12. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 11, 2012
  13. Junio C HamanoApr 11, 2012
  14. Neil HormanApr 11, 2012
  15. Junio C HamanoApr 12, 2012
  16. Neil HormanApr 12, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.