threads / patch / 30112

patch, 4 partsEnhance git-rebases flexibiilty in handling empty commits

Subject: [PATCH 0/4] Enhance git-rebases flexibiilty in handling empty commits

## tl;dr

121 messages between Mar 30, 2012 and Jul 18, 2012. Diffs are folded; open one to read it.

replies: 120people: 10as markdown or json

Neil Horman· Mar 30, 2012, 19:48 UTC · lore
Hey all-
	Based on your feedback from my earlier thread on this subject, I've come
up with this series as a first pass in enhancing git-rebases ability to handle
empty commits.  I'm not sure if its exactly what everyone wants, but I think its
a good start, and it works for what I need it to do here.

I started with adding a -keep-empty option to git-cherry-pick, which allows non-fast forward commits that are empty to be cherry-pick without failing, and requiring a separate git commit --allow-empty.

Building on that, I've added --keep-empty option to git-rebase. For an automatic rebase adding --keep-empty simply passes the --keep-empty flag along to git cherry-pick so that the empty commits are preserved instead of discarded

for interactive rebases, I changed the default selection editor text somewhat. By default, empty commits are allowed in this list. With patch 4 here, empty commits are commented out automatically, unless --keep-empty is selected (in which case all commits are pick-ed). The user sees additional text indicating that empty commits are commented and if they wish to be kept, then they must be uncommented. The pick_one function then intellegently passes the --keep-empty option allong to cherry-pick as needed.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
Neil Horman· Mar 30, 2012, 19:48 UTC · re: Neil Horman · lore

[PATCH 1/4] git-cherry-pick: add keep-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operaion.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-cherry-pick.txt |    7 +++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 15 insertions(+), 2 deletions(-)
Show changes to 4 files +15 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..3e975dc 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,13 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--keep-empty:
+	If a commit is not a fast forward, or if fast forwarding is not allowed,
+	cherry-picking an empty commit will fail, indicating that an explicit
+	invokation of git commit --allow-empty is required.  This option
+	overrides that behavior, allowing empty commits to be preserved
+	automatically in a cherry-pick
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..47d5522 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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, "keep-empty", &opts->allow_empty, "preserve empty commits"),
 			OPT_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Mar 30, 2012, 20:34 UTC · re: Neil Horman · lore

Re: [PATCH 1/4] git-cherry-pick: add keep-empty option

It strikes me very strange why this is not called --allow-empty, but other than that the addition look straightforward enough to me. You would want to add a test for this, though.

Jeff King· Mar 30, 2012, 21:15 UTC · re: Neil Horman · lore

Re: [PATCH 1/4] git-cherry-pick: add keep-empty option

On Fri, Mar 30, 2012 at 03:48:39PM -0400, Neil Horman wrote:
Show 6 quoted lines
> +--keep-empty:
> +	If a commit is not a fast forward, or if fast forwarding is not allowed,
> +	cherry-picking an empty commit will fail, indicating that an explicit
> +	invokation of git commit --allow-empty is required.  This option
> +	overrides that behavior, allowing empty commits to be preserved
> +	automatically in a cherry-pick

This didn't parse very well for me. A commit cannot be "a fast forward" by itself. Fast-forwarding is an operation that depends on the relationship between commits.

I think what you are trying to say is that this option is used only if the "--ff" logic does not kick in. Maybe it would be clearer to get to the point early, and mention --ff later, like:

  --keep-empty:
          By default, cherry-picking an empty commit will fail,
          indicating that an explicit invocation of `git commit
          --allow-empty` is required. This option overrides that
          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.
Like Junio, I agree this should simply be called --allow-empty.
Show 13 quoted lines
> diff --git a/sequencer.c b/sequencer.c
> index a37846a..71929ba 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
>   */
>  static int run_git_commit(const char *defmsg, struct replay_opts *opts)
>  {
> -	/* 6 is max possible length of our args array including NULL */
> -	const char *args[6];
> +	/* 7 is max possible length of our args array including NULL */
> +	const char *args[7];
>  	int i = 0;

It might be nice to refactor this to use argv_array, which handles the allocation automatically.

> +	if (opts->allow_empty)
> +		args[i++] = "--allow-empty";
> +

What happens if I cherry-pick a commit that is not empty, but that becomes empty because its changes have already been applied?

-Peff
Neil Horman· Mar 31, 2012, 12:57 UTC · re: Neil Horman · lore

Re: [PATCH 1/4] git-cherry-pick: add keep-empty option

On Fri, Mar 30, 2012 at 03:48:39PM -0400, Neil Horman wrote:
Show 10 quoted lines
> git cherry-pick fails when picking a non-ff commit that is empty.  The advice
> given with the failure is that a git-commit --allow-empty should be issued to
> explicitly add the empty commit during the cherry pick.  This option allows a
> user to specify before hand that they want to keep the empty commit.  This
> eliminates the need to issue both a cherry pick and a commit operaion.
> 
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>

As you and Jeff noted, I can certainly change keep-empty to allow-empty, both here and in the rebase command. I'll add a test for it as well, early this comming week. Thanks!

Neil
Neil Horman· Mar 30, 2012, 19:48 UTC · re: Neil Horman · lore

[PATCH 2/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-rebase.txt |    6 ++++++
 git-rebase.sh                |    5 +++++
 2 files changed, 11 insertions(+), 0 deletions(-)
Show changes to 2 files +11 −0

Documentation/git-rebase.txt, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..9717d3e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,12 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Informs git-rebase that comits which are empty should not be
+	automatically removed.  This is at times useful when empty commits
+	are used to hold developer information and notes, but contain no real
+	code changes
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Junio C Hamano· Mar 30, 2012, 20:43 UTC · re: Neil Horman · lore

Re: [PATCH 2/4] git-rebase: add keep_empty flag

Neil Horman <nhorman@tuxdriver.com> writes:
Show 7 quoted lines
> Add a command line switch to git-rebase to allow a user the ability to specify
> that they want to keep any commits in a series that are empty.
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
The same comments on Cc: apply to all of your patches.
Show 18 quoted lines
>  Documentation/git-rebase.txt |    6 ++++++
>  git-rebase.sh                |    5 +++++
>  2 files changed, 11 insertions(+), 0 deletions(-)
>
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index 504945c..9717d3e 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -238,6 +238,12 @@ leave out at most one of A and B, in which case it defaults to HEAD.
>  	will be reset to where it was when the rebase operation was
>  	started.
>  
> +--keep-empty::
> +	Informs git-rebase that comits which are empty should not be
> +	automatically removed.  This is at times useful when empty commits
> +	are used to hold developer information and notes, but contain no real
> +	code changes
> +

Unlike "cherry-pick", I think "--keep-empty" is a better name for the option than "--allow-empty" in this context. The difference is that from the end-user's point of view, cherry-pick _replays_ commits that exist elsewhere, and you are allowing the command to replay empty ones as well, while rebase _rebuilds_ commits on the same branch, and you are telling the command to keep empty ones.

"... which are empty should not be removed" is a bit of double-negation, though. Perhaps

	--keep-empty::
		Keep the commits that do not change anything from its
		parents in the result.  This is at times useful when empty
		commits are used to hold developer information and notes
		without having any real changes.

But as I rephrased the first part, the last line may have become redundant and could safely be removed.

The patch does not seem to do anything other than accepting and silently ignoring the option, though.

Neil Horman· Mar 31, 2012, 12:59 UTC · re: Junio C Hamano · lore

Re: [PATCH 2/4] git-rebase: add keep_empty flag

On Fri, Mar 30, 2012 at 01:43:22PM -0700, Junio C Hamano wrote:
Show 12 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Add a command line switch to git-rebase to allow a user the ability to specify
> > that they want to keep any commits in a series that are empty.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > CC: Jeff King <peff@peff.net>
> > CC: Phil Hord <phil.hord@gmail.com>
> > CC: Junio C Hamano <gitster@pobox.com>
> 
> The same comments on Cc: apply to all of your patches.
> 
Roger that.
Show 38 quoted lines
> >  Documentation/git-rebase.txt |    6 ++++++
> >  git-rebase.sh                |    5 +++++
> >  2 files changed, 11 insertions(+), 0 deletions(-)
> >
> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> > index 504945c..9717d3e 100644
> > --- a/Documentation/git-rebase.txt
> > +++ b/Documentation/git-rebase.txt
> > @@ -238,6 +238,12 @@ leave out at most one of A and B, in which case it defaults to HEAD.
> >  	will be reset to where it was when the rebase operation was
> >  	started.
> >  
> > +--keep-empty::
> > +	Informs git-rebase that comits which are empty should not be
> > +	automatically removed.  This is at times useful when empty commits
> > +	are used to hold developer information and notes, but contain no real
> > +	code changes
> > +
> 
> Unlike "cherry-pick", I think "--keep-empty" is a better name for the
> option than "--allow-empty" in this context.  The difference is that from
> the end-user's point of view, cherry-pick _replays_ commits that exist
> elsewhere, and you are allowing the command to replay empty ones as well,
> while rebase _rebuilds_ commits on the same branch, and you are telling
> the command to keep empty ones.
> 
> "... which are empty should not be removed" is a bit of double-negation,
> though.  Perhaps
> 
> 	--keep-empty::
> 		Keep the commits that do not change anything from its
> 		parents in the result.  This is at times useful when empty
> 		commits are used to hold developer information and notes
> 		without having any real changes.
> 
> But as I rephrased the first part, the last line may have become redundant
> and could safely be removed.
> 
Ack, I can make the above changes.
> The patch does not seem to do anything other than accepting and silently
> ignoring the option, though.
> 

It does, as the use of the option is pushed into the type specific rebase scripts. I probably should have rolled this patch in with one of those. I'll squash it in when I repost.

Neil Horman· Mar 30, 2012, 19:48 UTC · re: Neil Horman · lore

[PATCH 3/4] git-commit-am: Allow automatic rebasing to preserve empty commits

Using the keep_empy environment variable, this change allows git-commit-am to apply empty commits to the new branch we are rebasing to

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 git-rebase--am.sh |   20 +++++++++++++++-----
 1 files changed, 15 insertions(+), 5 deletions(-)
Show changes to git-rebase--am.sh +15 −5
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..c1d1b60 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,21 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if [ -n "$keep_empty" ]
+then
+	# we have to do this the hard way.  git format-patch completly squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --keep-empty "$revisions" && move_to_original_branch
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
+	move_to_original_branch
+fi
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
-- 
1.7.7.6
Junio C Hamano· Mar 30, 2012, 20:46 UTC · re: Neil Horman · lore

Re: [PATCH 3/4] git-commit-am: Allow automatic rebasing to preserve empty commits

Neil Horman <nhorman@tuxdriver.com> writes:
> Using the keep_empy environment variable, this change allows git-commit-am to
Is it an environment variable?  I thought not.
Show 37 quoted lines
> apply empty commits to the new branch we are rebasing to
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
> ---
>  git-rebase--am.sh |   20 +++++++++++++++-----
>  1 files changed, 15 insertions(+), 5 deletions(-)
>
> diff --git a/git-rebase--am.sh b/git-rebase--am.sh
> index c815a24..c1d1b60 100644
> --- a/git-rebase--am.sh
> +++ b/git-rebase--am.sh
> @@ -20,11 +20,21 @@ esac
>  
>  test -n "$rebase_root" && root_flag=--root
>  
> -git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> -	--src-prefix=a/ --dst-prefix=b/ \
> -	--no-renames $root_flag "$revisions" |
> -git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> -move_to_original_branch
> +if [ -n "$keep_empty" ]
> +then
> +	# we have to do this the hard way.  git format-patch completly squashes
> +	# empty commits and even if it didn't the format doesn't really lend
> +	# itself well to recording empty patches.  fortunately, cherry-pick
> +	# makes this easy
> +	git cherry-pick --keep-empty "$revisions" && move_to_original_branch
> +else
> +	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> +		--src-prefix=a/ --dst-prefix=b/ \
> +		--no-renames $root_flag "$revisions" |
> +	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> +	move_to_original_branch
> +fi

Factor out the "&& move_to_original_branch" at the end of then/else, like this:

	if ...
        then
		...
        else
		...
        fi && move_to_original_branch
>  ret=$?
>  test 0 != $ret -a -d "$state_dir" && write_basic_state
>  exit $ret
Neil Horman· Mar 31, 2012, 13:01 UTC · re: Junio C Hamano · lore

Re: [PATCH 3/4] git-commit-am: Allow automatic rebasing to preserve empty commits

On Fri, Mar 30, 2012 at 01:46:13PM -0700, Junio C Hamano wrote:
Show 6 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Using the keep_empy environment variable, this change allows git-commit-am to
> 
> Is it an environment variable?  I thought not.
> 

Sorry, I use the term loosely. keep_empty is the script variable I use to track the keep-empty option. I can fix up the commit message on my repost.

Show 20 quoted lines
> > +	# makes this easy
> > +	git cherry-pick --keep-empty "$revisions" && move_to_original_branch
> > +else
> > +	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> > +		--src-prefix=a/ --dst-prefix=b/ \
> > +		--no-renames $root_flag "$revisions" |
> > +	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> > +	move_to_original_branch
> > +fi
> 
> Factor out the "&& move_to_original_branch" at the end of then/else, like
> this:
> 
> 	if ...
>         then
> 		...
>         else
> 		...
>         fi && move_to_original_branch
> 

Will do, thanks! Neil

Junio C Hamano· Mar 30, 2012, 20:47 UTC · re: Neil Horman · lore

Re: [PATCH 3/4] git-commit-am: Allow automatic rebasing to preserve empty commits

Neil Horman <nhorman@tuxdriver.com> writes:
Show 31 quoted lines
> Using the keep_empy environment variable, this change allows git-commit-am to
> apply empty commits to the new branch we are rebasing to
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
> ---
>  git-rebase--am.sh |   20 +++++++++++++++-----
>  1 files changed, 15 insertions(+), 5 deletions(-)
>
> diff --git a/git-rebase--am.sh b/git-rebase--am.sh
> index c815a24..c1d1b60 100644
> --- a/git-rebase--am.sh
> +++ b/git-rebase--am.sh
> @@ -20,11 +20,21 @@ esac
>  
>  test -n "$rebase_root" && root_flag=--root
>  
> -git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> -	--src-prefix=a/ --dst-prefix=b/ \
> -	--no-renames $root_flag "$revisions" |
> -git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> -move_to_original_branch
> +if [ -n "$keep_empty" ]
> +then
> +	# we have to do this the hard way.  git format-patch completly squashes
> +	# empty commits and even if it didn't the format doesn't really lend
> +	# itself well to recording empty patches.  fortunately, cherry-pick
> +	# makes this easy
> +	git cherry-pick --keep-empty "$revisions" && move_to_original_branch

Does cherry-pick know the "--ignore-if-in-upstream" trick? Otherwise I suspect that this will introduce a severe regression to the command, as the commits that are already in the new base you are rebasing to will all be kept as empty commits, no?

Show 11 quoted lines
> +else
> +	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> +		--src-prefix=a/ --dst-prefix=b/ \
> +		--no-renames $root_flag "$revisions" |
> +	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> +	move_to_original_branch
> +fi
> +
>  ret=$?
>  test 0 != $ret -a -d "$state_dir" && write_basic_state
>  exit $ret
Neil Horman· Mar 31, 2012, 13:03 UTC · re: Junio C Hamano · lore

Re: [PATCH 3/4] git-commit-am: Allow automatic rebasing to preserve empty commits

On Fri, Mar 30, 2012 at 01:47:56PM -0700, Junio C Hamano wrote:
Show 39 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Using the keep_empy environment variable, this change allows git-commit-am to
> > apply empty commits to the new branch we are rebasing to
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > CC: Jeff King <peff@peff.net>
> > CC: Phil Hord <phil.hord@gmail.com>
> > CC: Junio C Hamano <gitster@pobox.com>
> > ---
> >  git-rebase--am.sh |   20 +++++++++++++++-----
> >  1 files changed, 15 insertions(+), 5 deletions(-)
> >
> > diff --git a/git-rebase--am.sh b/git-rebase--am.sh
> > index c815a24..c1d1b60 100644
> > --- a/git-rebase--am.sh
> > +++ b/git-rebase--am.sh
> > @@ -20,11 +20,21 @@ esac
> >  
> >  test -n "$rebase_root" && root_flag=--root
> >  
> > -git format-patch -k --stdout --full-index --ignore-if-in-upstream \
> > -	--src-prefix=a/ --dst-prefix=b/ \
> > -	--no-renames $root_flag "$revisions" |
> > -git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
> > -move_to_original_branch
> > +if [ -n "$keep_empty" ]
> > +then
> > +	# we have to do this the hard way.  git format-patch completly squashes
> > +	# empty commits and even if it didn't the format doesn't really lend
> > +	# itself well to recording empty patches.  fortunately, cherry-pick
> > +	# makes this easy
> > +	git cherry-pick --keep-empty "$revisions" && move_to_original_branch
> 
> Does cherry-pick know the "--ignore-if-in-upstream" trick?  Otherwise I
> suspect that this will introduce a severe regression to the command, as
> the commits that are already in the new base you are rebasing to will all
> be kept as empty commits, no?
> 

Hmm, you may be correct. I'd not really thought of that. I can borrow the format-patch code for that option and incorporate it into cherry-pick to fix that up. Would that be sufficient?

Neil
Neil Horman· Mar 30, 2012, 19:48 UTC · re: Neil Horman · lore

[PATCH 4/4] git-commit-interactive: Allow rebasing to preserve empty commits

This updates git-commit-interactive to recognize and make use of the keep_empty flag. When not set, git-rebase -i will now comment out commits that are empty, and informs the user that commits which they wish to explicitly keep that are empty should be uncommented, or --keep-empty should be specified. if keep_empty is specified, all commits, regardless of their empty status are included.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 git-rebase--interactive.sh |   38 +++++++++++++++++++++++++++++++++++---
 1 files changed, 35 insertions(+), 3 deletions(-)
Show changes to git-rebase--interactive.sh +35 −3
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..97eeb21 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -191,12 +191,24 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+	is_empty=$(git show --pretty=format:%b "$@" | wc -l)
+
+	if [ $is_empty -eq 0 ]
+	then
+		empty_args=--keep-empty
+	fi
+
+	if [ -n "$keep_empty" ]
+	then
+		empty_args=--keep_empty
+	fi
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +792,24 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+	local comment_out
+
+	if [ -z "$keep_empty" ]
+	then
+		comment_out=$(git show --pretty=format:%b $shortsha1 | wc -l)
+		if [ $comment_out -eq 0 ]
+		then
+			comment_out="#pick"
+		else
+			comment_out="pick"
+		fi
+	else
+		comment_out="pick"
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +828,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -849,6 +876,11 @@ cat >> "$todo" << EOF
 # If you remove a line here THAT COMMIT WILL BE LOST.
 # However, if you remove everything, the rebase will be aborted.
 #
+# Note that commits which are empty at the time of rebasing are 
+# commented out.  If you wish to keep empty commits, either 
+# specify the --keep-empty option to the rebase command, or 
+# uncomment the commits you wish to keep
+#
 EOF
 
 has_action "$todo" ||
-- 
1.7.7.6
Junio C Hamano· Mar 30, 2012, 20:59 UTC · re: Neil Horman · lore

Re: [PATCH 4/4] git-commit-interactive: Allow rebasing to preserve empty commits

Neil Horman <nhorman@tuxdriver.com> writes:
Show 23 quoted lines
> This updates git-commit-interactive to recognize and make use of the keep_empty
> flag.  When not set, git-rebase -i will now comment out commits that are empty,
> and informs the user that commits which they wish to explicitly keep that are
> empty should be uncommented, or --keep-empty should be specified.  if keep_empty
> is specified, all commits, regardless of their empty status are included.
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
> ---
>  git-rebase--interactive.sh |   38 +++++++++++++++++++++++++++++++++++---
>  1 files changed, 35 insertions(+), 3 deletions(-)
>
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index 5812222..97eeb21 100644
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -191,12 +191,24 @@ git_sequence_editor () {
>  
>  pick_one () {
>  	ff=--ff
> +	is_empty=$(git show --pretty=format:%b "$@" | wc -l)
That is a very expensive way to see if the commit is empty, no?

If and only if commit C is empty, "git rev-parse" on C^{tree} and C^^{tree}" will yield the same tree object name.

> +	if [ $is_empty -eq 0 ]

Also this test (which by the way is against our coding style guideline) shows that the variable is misnamed.

Show 24 quoted lines
> +	then
> +		empty_args=--keep-empty
> +	fi
> +
> +	if [ -n "$keep_empty" ]
> +	then
> +		empty_args=--keep_empty
> +	fi
> +
>  	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
>  	case "$force_rebase" in '') ;; ?*) ff= ;; esac
>  	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
>  	test -d "$rewritten" &&
>  		pick_one_preserving_merges "$@" && return
> -	output git cherry-pick $ff "$@"
> +	output git cherry-pick $empty_args $ff "$@"
>  }
>  
>  pick_one_preserving_merges () {
> @@ -780,9 +792,24 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
>  	sed -n "s/^>//p" |
>  while read -r shortsha1 rest
>  do
> +	local comment_out
bashism.
> +
> +	if [ -z "$keep_empty" ]
> +	then
> +		comment_out=$(git show --pretty=format:%b $shortsha1 | wc -l)
Ditto.
> +		if [ $comment_out -eq 0 ]
> +		then
> +			comment_out="#pick"
Perhaps it is easier to read if you say "# pick"?
Show 11 quoted lines
> +		else
> +			comment_out="pick"
> +		fi
> +	else
> +		comment_out="pick"
> +	fi
> +
>  	if test t != "$preserve_merges"
>  	then
> -		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
> +		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
The variable comment_out is grossly misnamed.  Why not do it this way?
	comment_out=
	if test -z "$keep_empty" && is_empty_commit $shortsha1
        then
        	comment_out="# "
	fi
        if ...
        then
		printf "%s\n" "${leader}pick $shortsha1 $rest" >>"$todo"
Show 9 quoted lines
> @@ -849,6 +876,11 @@ cat >> "$todo" << EOF
>  # If you remove a line here THAT COMMIT WILL BE LOST.
>  # However, if you remove everything, the rebase will be aborted.
>  #
> +# Note that commits which are empty at the time of rebasing are 
> +# commented out.  If you wish to keep empty commits, either 
> +# specify the --keep-empty option to the rebase command, or 
> +# uncomment the commits you wish to keep
> +#
Good.

I do not think " either specify...rebase command, or" is necessary here, though. This message is meant to be a quick reminder, not a tutorial. Keep it short and sweet.

Also, you may probably want to add this text _only_ when you have actually commented out something.

Neil Horman· Mar 31, 2012, 13:11 UTC · re: Junio C Hamano · lore

Re: [PATCH 4/4] git-commit-interactive: Allow rebasing to preserve empty commits

On Fri, Mar 30, 2012 at 01:59:02PM -0700, Junio C Hamano wrote:
Show 31 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > This updates git-commit-interactive to recognize and make use of the keep_empty
> > flag.  When not set, git-rebase -i will now comment out commits that are empty,
> > and informs the user that commits which they wish to explicitly keep that are
> > empty should be uncommented, or --keep-empty should be specified.  if keep_empty
> > is specified, all commits, regardless of their empty status are included.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > CC: Jeff King <peff@peff.net>
> > CC: Phil Hord <phil.hord@gmail.com>
> > CC: Junio C Hamano <gitster@pobox.com>
> > ---
> >  git-rebase--interactive.sh |   38 +++++++++++++++++++++++++++++++++++---
> >  1 files changed, 35 insertions(+), 3 deletions(-)
> >
> > diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> > index 5812222..97eeb21 100644
> > --- a/git-rebase--interactive.sh
> > +++ b/git-rebase--interactive.sh
> > @@ -191,12 +191,24 @@ git_sequence_editor () {
> >  
> >  pick_one () {
> >  	ff=--ff
> > +	is_empty=$(git show --pretty=format:%b "$@" | wc -l)
> 
> That is a very expensive way to see if the commit is empty, no?
> 
> If and only if commit C is empty, "git rev-parse" on C^{tree} and
> C^^{tree}" will yield the same tree object name.
> 
Ah, you see, I'm learning something :).  I can fix that up.
Show 5 quoted lines
> > +	if [ $is_empty -eq 0 ]
> 
> Also this test (which by the way is against our coding style guideline)
> shows that the variable is misnamed.
> 
Ok, sorry about that.  I'll switch that to use test
Show 27 quoted lines
> > +	then
> > +		empty_args=--keep-empty
> > +	fi
> > +
> > +	if [ -n "$keep_empty" ]
> > +	then
> > +		empty_args=--keep_empty
> > +	fi
> > +
> >  	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
> >  	case "$force_rebase" in '') ;; ?*) ff= ;; esac
> >  	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
> >  	test -d "$rewritten" &&
> >  		pick_one_preserving_merges "$@" && return
> > -	output git cherry-pick $ff "$@"
> > +	output git cherry-pick $empty_args $ff "$@"
> >  }
> >  
> >  pick_one_preserving_merges () {
> > @@ -780,9 +792,24 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
> >  	sed -n "s/^>//p" |
> >  while read -r shortsha1 rest
> >  do
> > +	local comment_out
> 
> bashism.
> 
Roger.
Show 13 quoted lines
> > +
> > +	if [ -z "$keep_empty" ]
> > +	then
> > +		comment_out=$(git show --pretty=format:%b $shortsha1 | wc -l)
> 
> Ditto.
> 
> > +		if [ $comment_out -eq 0 ]
> > +		then
> > +			comment_out="#pick"
> 
> Perhaps it is easier to read if you say "# pick"?
> 
Sure, easy enough fix
Show 14 quoted lines
> > +		else
> > +			comment_out="pick"
> > +		fi
> > +	else
> > +		comment_out="pick"
> > +	fi
> > +
> >  	if test t != "$preserve_merges"
> >  	then
> > -		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
> > +		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
> 
> The variable comment_out is grossly misnamed.  Why not do it this way?
> 

I think you meant to assign leader there instead of comment_out, but your point is a good one :).

Show 26 quoted lines
> 	comment_out=
> 	if test -z "$keep_empty" && is_empty_commit $shortsha1
>         then
>         	comment_out="# "
> 	fi
> 
>         if ...
>         then
> 		printf "%s\n" "${leader}pick $shortsha1 $rest" >>"$todo"
> 
> > @@ -849,6 +876,11 @@ cat >> "$todo" << EOF
> >  # If you remove a line here THAT COMMIT WILL BE LOST.
> >  # However, if you remove everything, the rebase will be aborted.
> >  #
> > +# Note that commits which are empty at the time of rebasing are 
> > +# commented out.  If you wish to keep empty commits, either 
> > +# specify the --keep-empty option to the rebase command, or 
> > +# uncomment the commits you wish to keep
> > +#
> 
> Good.
> 
> I do not think " either specify...rebase command, or" is necessary here,
> though.  This message is meant to be a quick reminder, not a tutorial.
> Keep it short and sweet.
> 
Copy that, I'll tone down the verbosity.
> Also, you may probably want to add this text _only_ when you have actually
> commented out something.
> 

Easily done. thanks! Neil

Junio C Hamano· Mar 30, 2012, 20:32 UTC · re: Neil Horman · lore

Re: [PATCH 0/4] Enhance git-rebases flexibiilty in handling empty commits

Neil Horman <nhorman@tuxdriver.com> writes:
Show 7 quoted lines
> for interactive rebases, I changed the default selection editor text somewhat.
> By default, empty commits are allowed in this list.  With patch 4 here, empty
> commits are commented out automatically, unless --keep-empty is selected (in
> which case all commits are pick-ed).  The user sees additional text indicating
> that empty commits are commented and if they wish to be kept, then they must be
> uncommented.  The pick_one function then intellegently passes the --keep-empty
> option allong to cherry-pick as needed.

Sounds like a sensible UI design. I am a bit curious what it would do when you try to "rebase -i" a series of commits, all of which are empty, though.

> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
Please do not do this; Cc: goes to your e-mail header, not here.
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 0/5] Enhance git-rebases flexibiilty handling empty commits [v2]

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --ignore-if-made-empty, which allows the user to restrict the application of --allow-empty to only those commits which were initially empty (not those commits made empty by a prior commit). It also enhances git-rebase to add a --keep-empty option, which implies the use of the above new options to cherry-pick.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 1/5] argv-array: Add argv_array_pop function [v2]

As a convienience, it would be nice if we could pop entries off the argv_array structs so that if they had multiple uses in a function, we wouldn't have to clear them and repopulate common entries. This patch adds the argv_array_pop function to do just that. Common entries can be added to an argv_array first, then useage specific ones can be added on the end and removed later on.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 argv-array.c |   12 ++++++++++++
 argv-array.h |    1 +
 2 files changed, 13 insertions(+), 0 deletions(-)
Show changes to 2 files +13 −0

argv-array.c, argv-array.h

diff --git a/argv-array.c b/argv-array.c
index a4e0420..ce24a48 100644
--- a/argv-array.c
+++ b/argv-array.c
@@ -39,6 +39,18 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
 	argv_array_push_nodup(array, strbuf_detach(&v, NULL));
 }
 
+int argv_array_pop(struct argv_array *array, unsigned int num)
+{
+	if (num > array->argc)
+		return -1;
+
+	for(num--; num>0; num--) {
+		free((char **)array->argv[num]);
+		array->argv[num] = NULL;
+	}
+	return 0;
+}
+
 void argv_array_clear(struct argv_array *array)
 {
 	if (array->argv != empty_argv) {
diff --git a/argv-array.h b/argv-array.h
index 74dd2b1..8233243 100644
--- a/argv-array.h
+++ b/argv-array.h
@@ -15,6 +15,7 @@ void argv_array_init(struct argv_array *);
 void argv_array_push(struct argv_array *, const char *);
 __attribute__((format (printf,2,3)))
 void argv_array_pushf(struct argv_array *, const char *fmt, ...);
+int argv_array_pop(struct argv_array *, unsigned int num);
 void argv_array_clear(struct argv_array *);
 
 #endif /* ARGV_ARRAY_H */
-- 
1.7.7.6
Junio C Hamano· Apr 5, 2012, 20:12 UTC · re: Neil Horman · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

Neil Horman <nhorman@tuxdriver.com> writes:
Show 7 quoted lines
> As a convienience, it would be nice if we could pop entries off the argv_array
> structs so that if they had multiple uses in a function, we wouldn't have to
> clear them and repopulate common entries.  This patch adds the argv_array_pop
> function to do just that.  Common entries can be added to an argv_array first,
> then useage specific ones can be added on the end and removed later on.
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> CC: Jeff King <peff@peff.net>
> CC: Phil Hord <phil.hord@gmail.com>
> CC: Junio C Hamano <gitster@pobox.com>
> ---
Please don't do "Cc:" here; they belong to your e-mail header.
Show 12 quoted lines
> diff --git a/argv-array.c b/argv-array.c
> index a4e0420..ce24a48 100644
> --- a/argv-array.c
> +++ b/argv-array.c
> @@ -39,6 +39,18 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
>  	argv_array_push_nodup(array, strbuf_detach(&v, NULL));
>  }
>  
> +int argv_array_pop(struct argv_array *array, unsigned int num)
> +{
> +	if (num > array->argc)
> +		return -1;

If your use case is "After using an argv_array for the first invocation, truncate it while keeping the common ones that appear early, so that ones that are specific to the second invocation can be pushed", it strikes me somewhat odd why you would want to specify "how many to pop".

Wouldn't argv_array_truncate() or argv_array_setlen() make more sense?
> +	for(num--; num>0; num--) {
Gaah.
Show 21 quoted lines
> +		free((char **)array->argv[num]);
> +		array->argv[num] = NULL;
> +	}
> +	return 0;
> +}
> +
>  void argv_array_clear(struct argv_array *array)
>  {
>  	if (array->argv != empty_argv) {
> diff --git a/argv-array.h b/argv-array.h
> index 74dd2b1..8233243 100644
> --- a/argv-array.h
> +++ b/argv-array.h
> @@ -15,6 +15,7 @@ void argv_array_init(struct argv_array *);
>  void argv_array_push(struct argv_array *, const char *);
>  __attribute__((format (printf,2,3)))
>  void argv_array_pushf(struct argv_array *, const char *fmt, ...);
> +int argv_array_pop(struct argv_array *, unsigned int num);
>  void argv_array_clear(struct argv_array *);
>  
>  #endif /* ARGV_ARRAY_H */
Neil Horman· Apr 5, 2012, 23:24 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 01:12:51PM -0700, Junio C Hamano wrote:
Show 18 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > As a convienience, it would be nice if we could pop entries off the argv_array
> > structs so that if they had multiple uses in a function, we wouldn't have to
> > clear them and repopulate common entries.  This patch adds the argv_array_pop
> > function to do just that.  Common entries can be added to an argv_array first,
> > then useage specific ones can be added on the end and removed later on.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> 
> 
> > CC: Jeff King <peff@peff.net>
> > CC: Phil Hord <phil.hord@gmail.com>
> > CC: Junio C Hamano <gitster@pobox.com>
> > ---
> 
> Please don't do "Cc:" here; they belong to your e-mail header.
> 
You mean place them below the snip line?  I can do that.
Show 18 quoted lines
> > diff --git a/argv-array.c b/argv-array.c
> > index a4e0420..ce24a48 100644
> > --- a/argv-array.c
> > +++ b/argv-array.c
> > @@ -39,6 +39,18 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
> >  	argv_array_push_nodup(array, strbuf_detach(&v, NULL));
> >  }
> >  
> > +int argv_array_pop(struct argv_array *array, unsigned int num)
> > +{
> > +	if (num > array->argc)
> > +		return -1;
> 
> If your use case is "After using an argv_array for the first invocation,
> truncate it while keeping the common ones that appear early, so that ones
> that are specific to the second invocation can be pushed", it strikes me
> somewhat odd why you would want to specify "how many to pop".
> 

Why? It seems perfectly logical to me to be able to, as a convienience, specify how many items to pop, and the api call seems pleasantly symmetric to the push[f] calls.

> Wouldn't argv_array_truncate() or argv_array_setlen() make more sense?
> 

No, truncate is vague about its meaning. Truncate to zero would be useless, as its equivalent to the clear call at that point, and truncating to a specific value is exactly the same as what I have currently, minus the symmetry to the push[f] calls. Likewise setlen doesn't seem to fit in properly, plus theres the possibility there of believing the api call might be able to set a length longer than what exists in the array already, which, while silly, requires additional error checking. Popping a fixed number of elements off an argv_array that have previously been pushed makes perfectly good sense to me.

Show 5 quoted lines
> 
> > +	for(num--; num>0; num--) {
> 
> Gaah.
> 

Eeek :). If you want something else equally....equal here, please ask for it. I prefer for loops, but if you would rather have a while loop here, I'm fine with that.

Regards Neil

Neil Horman· Apr 6, 2012, 00:12 UTC · re: Neil Horman · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 07:24:29PM -0400, Neil Horman wrote:
Show 21 quoted lines
> On Thu, Apr 05, 2012 at 01:12:51PM -0700, Junio C Hamano wrote:
> > Neil Horman <nhorman@tuxdriver.com> writes:
> > 
> > > As a convienience, it would be nice if we could pop entries off the argv_array
> > > structs so that if they had multiple uses in a function, we wouldn't have to
> > > clear them and repopulate common entries.  This patch adds the argv_array_pop
> > > function to do just that.  Common entries can be added to an argv_array first,
> > > then useage specific ones can be added on the end and removed later on.
> > >
> > > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > 
> > 
> > > CC: Jeff King <peff@peff.net>
> > > CC: Phil Hord <phil.hord@gmail.com>
> > > CC: Junio C Hamano <gitster@pobox.com>
> > > ---
> > 
> > Please don't do "Cc:" here; they belong to your e-mail header.
> > 
> You mean place them below the snip line?  I can do that.
> 

Actually, I can't do that, git-send-email looks for CC: in the patch text, and git-format-patch automatically inserts the snip line. I can put the cc's on the command line, but if git-send-email is parsing this out of the wrong place, that seems like a bug. FWIW, CC's in this location are standard practice for kernel patch submissions. Neil

Jeff King· Apr 6, 2012, 00:19 UTC · re: Neil Horman · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 07:24:29PM -0400, Neil Horman wrote:
Show 8 quoted lines
> > > CC: Jeff King <peff@peff.net>
> > > CC: Phil Hord <phil.hord@gmail.com>
> > > CC: Junio C Hamano <gitster@pobox.com>
> > > ---
> > 
> > Please don't do "Cc:" here; they belong to your e-mail header.
> > 
> You mean place them below the snip line?  I can do that.

No, I think he means to drop them entirely; that information is already in the list of people you have cc'd in the email.

Show 13 quoted lines
> > > +int argv_array_pop(struct argv_array *array, unsigned int num)
> > > +{
> > > +	if (num > array->argc)
> > > +		return -1;
> > 
> > If your use case is "After using an argv_array for the first invocation,
> > truncate it while keeping the common ones that appear early, so that ones
> > that are specific to the second invocation can be pushed", it strikes me
> > somewhat odd why you would want to specify "how many to pop".
> > 
> Why?  It seems perfectly logical to me to be able to, as a convienience, specify
> how many items to pop, and the api call seems pleasantly symmetric to the
> push[f] calls.

I don't mind a "pop" call if there is a good use, but personally I find your use case to be hard to read. Your patch 3/5 does this:

  /* make a partial argv */
  argv_array_init(&array);
  argv_array_push(&array, "commit");
  argv_array_push(&array, "-n");
  /* now do some speculative command */
  if (some_logic) {
          argv_array_push(&array, "--porcelain");
          if (run_command(array.argv)) {
              argv_array_clear(&array);
              return 0;
          }
          argv_array_pop(&array, 1);
  }
  /* and then possibly proceed to reuse part of the array */
  argv_array_push(&array, ...);
  argv_array_push(&array, ...);
  run_command(array.argv);

It saves you having to repeat "commit -n", but at the expense of making the logic much harder to read. I think this is much easier to read:

  if (some_logic) {
          const char *argv[] = { "commit", "-n", "--porcelain", NULL };
          if (run_command(argv))
                  return 0;
  }
  argv_array_init(&array);
  argv_array_push(&array, "commit");
  argv_array_push(&array, "-n");
  argv_array_push(&array, /* other options */);
  run_command(array.argv);

You repeat "-n", but it is very clear what goes into the speculative command and what goes into the final command (and there is no chance of the "1" in your pop becoming stale and sending cruft to the real command).

That being said, I think Junio commented on 3/5 that "git commit --porcelain" is not the right way of doing your speculative command anyway, so the two commands would not end up sharing any argv anyway.

Show 7 quoted lines
> > > +	for(num--; num>0; num--) {
> > 
> > Gaah.
> > 
> Eeek :).  If you want something else equally....equal here, please ask for it.
> I prefer for loops, but if you would rather have a while loop here, I'm fine
> with that.

I think he may have been responding to the style (lack of whitespace). I also find a side-effecting initializer a little non-idiomatic. And indeed, I think it causes a bug in this case. "num" is an unsigned int. So what happens to the loop when num is 0 coming in?

I think a more traditional way of writing this would be:
  while (num--) {
          free((char **)array->argv[num);
          array->argv[num] = NULL;
  }
-Peff
Neil Horman· Apr 6, 2012, 01:12 UTC · re: Jeff King · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 08:19:42PM -0400, Jeff King wrote:
Show 14 quoted lines
> On Thu, Apr 05, 2012 at 07:24:29PM -0400, Neil Horman wrote:
> 
> > > > CC: Jeff King <peff@peff.net>
> > > > CC: Phil Hord <phil.hord@gmail.com>
> > > > CC: Junio C Hamano <gitster@pobox.com>
> > > > ---
> > > 
> > > Please don't do "Cc:" here; they belong to your e-mail header.
> > > 
> > You mean place them below the snip line?  I can do that.
> 
> No, I think he means to drop them entirely; that information is already
> in the list of people you have cc'd in the email.
> 

No, its not, thats what I'm saying. git send-email parses the above information to build the CC list. I can take it out and add the cc's to the command line of git-send-email, but if parsing the above info is incorrect, that seems like a bug that needs fixing, one which will get resistance, because its the standard way alot of other lists use the CC: tag.

Show 56 quoted lines
> > > > +int argv_array_pop(struct argv_array *array, unsigned int num)
> > > > +{
> > > > +	if (num > array->argc)
> > > > +		return -1;
> > > 
> > > If your use case is "After using an argv_array for the first invocation,
> > > truncate it while keeping the common ones that appear early, so that ones
> > > that are specific to the second invocation can be pushed", it strikes me
> > > somewhat odd why you would want to specify "how many to pop".
> > > 
> > Why?  It seems perfectly logical to me to be able to, as a convienience, specify
> > how many items to pop, and the api call seems pleasantly symmetric to the
> > push[f] calls.
> 
> I don't mind a "pop" call if there is a good use, but personally I find
> your use case to be hard to read. Your patch 3/5 does this:
> 
>   /* make a partial argv */
>   argv_array_init(&array);
>   argv_array_push(&array, "commit");
>   argv_array_push(&array, "-n");
> 
>   /* now do some speculative command */
>   if (some_logic) {
>           argv_array_push(&array, "--porcelain");
>           if (run_command(array.argv)) {
>               argv_array_clear(&array);
>               return 0;
>           }
>           argv_array_pop(&array, 1);
>   }
> 
>   /* and then possibly proceed to reuse part of the array */
>   argv_array_push(&array, ...);
>   argv_array_push(&array, ...);
>   run_command(array.argv);
> 
> It saves you having to repeat "commit -n", but at the expense of making
> the logic much harder to read. I think this is much easier to read:
> 
>   if (some_logic) {
>           const char *argv[] = { "commit", "-n", "--porcelain", NULL };
>           if (run_command(argv))
>                   return 0;
>   }
> 
>   argv_array_init(&array);
>   argv_array_push(&array, "commit");
>   argv_array_push(&array, "-n");
>   argv_array_push(&array, /* other options */);
>   run_command(array.argv);
> 
> You repeat "-n", but it is very clear what goes into the speculative
> command and what goes into the final command (and there is no chance of
> the "1" in your pop becoming stale and sending cruft to the real command).
> 
Ok, I can see the use of the argv array above being more readable, but (I think
your) comment from the first iteration of this patch, suggested re-doing this
logic to use struct argv_array.  I do like the above better.  I'll redo that.
 
> That being said, I think Junio commented on 3/5 that "git commit
> --porcelain" is not the right way of doing your speculative command
> anyway, so the two commands would not end up sharing any argv anyway.
>
I'm still not completely convinced about that, given that commit --porcelain
effectively does a git diff-index, but I'll defer to the experts on that,
diff-index works just as well for this.  And if I use your above static array
implementation instead, I can get rid of the pop api addition entirely.
 
Show 9 quoted lines
> > > > +	for(num--; num>0; num--) {
> > > 
> > > Gaah.
> > > 
> > Eeek :).  If you want something else equally....equal here, please ask for it.
> > I prefer for loops, but if you would rather have a while loop here, I'm fine
> > with that.
> 
> I think he may have been responding to the style (lack of whitespace). I

That was really my problem with it. I appreciate the direct comment. A note about lack of whitespace is something I can work with, Gaah is not :)

> also find a side-effecting initializer a little non-idiomatic. And
> indeed, I think it causes a bug in this case. "num" is an unsigned int.
> So what happens to the loop when num is 0 coming in?
> 

That should be caught by the case checking logic at the top of the function. Although I think you're right, the 1 case I think has an OBO error. Its moot anyway, if I use your static array approach above, I'll remove all of this.

Show 7 quoted lines
> I think a more traditional way of writing this would be:
> 
>   while (num--) {
>           free((char **)array->argv[num);
>           array->argv[num] = NULL;
>   }
> 

Thats certainly another way to do it, and I'm happy to do that if its the consensus. I just happen to prefer for loops (for no particular reason, its just me). But again, using your approach above, this will all get removed.

Thanks for the review.  I'll fix this up, and have a new version in a few days.

Best Neil

> -Peff
> 
Junio C Hamano· Apr 6, 2012, 01:20 UTC · re: Neil Horman · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

Neil Horman <nhorman@tuxdriver.com> writes:
Show 20 quoted lines
> On Thu, Apr 05, 2012 at 01:12:51PM -0700, Junio C Hamano wrote:
>> Neil Horman <nhorman@tuxdriver.com> writes:
>> 
>> > As a convienience, it would be nice if we could pop entries off the argv_array
>> > structs so that if they had multiple uses in a function, we wouldn't have to
>> > clear them and repopulate common entries.  This patch adds the argv_array_pop
>> > function to do just that.  Common entries can be added to an argv_array first,
>> > then useage specific ones can be added on the end and removed later on.
>> >
>> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
>> 
>> 
>> > CC: Jeff King <peff@peff.net>
>> > CC: Phil Hord <phil.hord@gmail.com>
>> > CC: Junio C Hamano <gitster@pobox.com>
>> > ---
>> 
>> Please don't do "Cc:" here; they belong to your e-mail header.
>> 
> You mean place them below the snip line?  I can do that.

No. When you review and fix typo in format-patch output, you can add these to the e-mail header part and git-send-email will pick them up just fine.

Show 6 quoted lines
>> If your use case is "After using an argv_array for the first invocation,
>> truncate it while keeping the common ones that appear early, so that ones
>> that are specific to the second invocation can be pushed", it strikes me
>> somewhat odd why you would want to specify "how many to pop".
>> 
> Why?

You know you have N common ones. You push some more, perhaps with a sequence of "if (condition) argv_push();" and use it. Now it is time to reprepare it for the second usage. Wouldn't it be more natural to say

	argv_array_setlen(N);
        /* start pushing the specific ones for next invocation */
        argv_array_push();
        if (condition)
        	argv_array_push();
Show 7 quoted lines
>> > +	for(num--; num>0; num--) {
>> 
>> Gaah.
>> 
> Eeek :).  If you want something else equally....equal here, please ask for it.
> I prefer for loops, but if you would rather have a while loop here, I'm fine
> with that.

Please look at the existing "for ()" loop in the same file, and then go read the CodingGuidelines.

Jeff King· Apr 6, 2012, 02:20 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 06:20:53PM -0700, Junio C Hamano wrote:
Show 12 quoted lines
> >> > CC: Jeff King <peff@peff.net>
> >> > CC: Phil Hord <phil.hord@gmail.com>
> >> > CC: Junio C Hamano <gitster@pobox.com>
> >> > ---
> >> 
> >> Please don't do "Cc:" here; they belong to your e-mail header.
> >> 
> > You mean place them below the snip line?  I can do that.
> 
> No.  When you review and fix typo in format-patch output, you can add
> these to the e-mail header part and git-send-email will pick them up just
> fine.
I think there is a legitimate conflict of interest here.

It's not clear exactly what "cc" tags in a commit message mean, because it is really a per-project thing. I don't work on the kernel, but I always took their cc tag to mean "these are people interested in this topic area". Send-email helpfully picks up that hint and cc's them on the emailed patch. And when the patch is applied, those cc lines remain, because people reading "git log" much later may find a bug in the patch, and it is helpful to tell them the people interested in the area.

In git.git, though, we don't typically use such cc tags. Perhaps because we are a much smaller project than the kernel, or perhaps for other logistical reasons. And even if we did, the cc list above does not really meet the guideline I gave. They are people who happened to review your patch or comment on the list, not people who are interested forever in a particular subsystem.

So from the maintainer's and the project's perspective, those cc lines are useless noise.

But from the submitter's point of view, it may be convenient to tell git "these are people who have reviewed _this_ patch series", and have it automatically cc them on each iteration of the series without re-typing their addresses. And because of the send-email behavior I mentioned above, the "cc" tags are a convenient place to put it.

So it is a piece of information that is useful to the submitter, but not to the maintainer. Where can the submitter put it that will help themselves, but not bother the maintainer? I wonder if the right solution would be an option for send-email to respect cc lines, but strip them out of the body of the sent patch.

-Peff
Jonathan Nieder· Apr 6, 2012, 04:19 UTC · re: Jeff King · lore

Cc tags in the commit message (Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2])

Jeff King wrote:
> It's not clear exactly what "cc" tags in a commit message mean, because
> it is really a per-project thing. I don't work on the kernel, but I
> always took their cc tag to mean "these are people interested in this
> topic area".
Here's a link from a previous time it came up: [1]

:) Jonathan

[1] http://thread.gmane.org/gmane.comp.version-control.git/157707/focus=157757
Junio C Hamano· Apr 6, 2012, 06:55 UTC · re: Jeff King · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

Jeff King <peff@peff.net> writes:
Show 5 quoted lines
> So from the maintainer's and the project's perspective, those cc lines
> are useless noise.
> ...
> So it is a piece of information that is useful to the submitter, but not
> to the maintainer.
... nor the user of the project history, as you said earlier.

I agree that submitters would benefit from an automated way to propagate these addresses from reroll to reroll, and in a larger workflow "tweak and record while amending a commit for reroll" would be a natural place to do so, so I can see why it is tempting to abuse Cc: in the body of the message, but not knowing other ways is not a good excuse for such an abuse. A workable solution is already available [*1*]: commit with a "---" line followed by Cc: and whatever extra junk while amending.

Even if it did not work to write "---" before the extra Cc:, I do not think it is such a big deal, though. As the output file from format-patch is meant to be read into an editor for final proof-reading, it shouldn't be too much extra burden to move those Cc: at the end of the log message to the header part of the message while doing so anyway.

[Footnote]
*1* Here is a trivial demonstration.

I amended the 1.7.10-rc4 on a throw-away branch to make "git show -s" show this:

-- >8 --
commit aaac56582ce1820551d48bf2b2364cb00f0345cb
Author: Junio C Hamano <gitster@pobox.com>
Date:   Tue Apr 3 09:25:49 2012 -0700
    Git 1.7.10-rc4
    
    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    ---
    Cc: Test user <junio@pobox.com>
    
    This is a cover letter for small one.
-- 8< --

Feeding output from "git format-patch -1 HEAD" for this patch to send-email does add the "Test user" to the list of recipients, like so:

-- >8 --
0001-Git-1.7.10-rc4.txt
(body) Adding cc: Test user <junio@pobox.com> from line 'Cc: Test user <junio@pobox.com>'
Dry-OK. Log says:
Sendmail: /usr/bin/msmtp -f gitster@pobox.com -i git@vger.kernel.org junio@pobox.com
From: Junio C Hamano <gitster@pobox.com>
To: git@vger.kernel.org
Cc: Test user <junio@pobox.com>
Subject: [PATCH] Git 1.7.10-rc4
Date: Thu,  5 Apr 2012 23:47:09 -0700
Message-Id: <1333694829-4295-1-git-send-email-gitster@pobox.com>
X-Mailer: git-send-email 1.7.10.rc4.54.g1d5dd3
Result: OK
-- 8< --
Jeff King· Apr 6, 2012, 07:33 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 11:55:18PM -0700, Junio C Hamano wrote:
Show 7 quoted lines
> I agree that submitters would benefit from an automated way to propagate
> these addresses from reroll to reroll, and in a larger workflow "tweak and
> record while amending a commit for reroll" would be a natural place to do
> so, so I can see why it is tempting to abuse Cc: in the body of the
> message, but not knowing other ways is not a good excuse for such an
> abuse.  A workable solution is already available [*1*]: commit with a
> "---" line followed by Cc: and whatever extra junk while amending.

I've played with that workflow before. While it's a neat trick, note that something like "format-patch -s" will put the signoff at the end, like:

  commit subject
  commit body
  ---
  Cc: whomever
  Signed-off-by: you

which is not what you want. We could perhaps teach it to be smarter about finding a "---", though I worry about hurting people who do not use an email-based workflow, since they otherwise don't have to care about "---".

About a year ago I had an RFC series to let "git commit" parse off the "---" bit and turn it into a git-note (mostly for keeping track of changes to the series between versions). It does solve that problem, and response was reasonably positive. It does make things more complicated, though, because IIRC you have to turn on note-rewriting manually to keep the notes attached as you rebase. Ultimately I didn't follow up because I've found that I just don't end up keeping a lot of notes. I tend to do the re-roll and then send it out pretty soon afterward, so I just write any notes in the emails as they go out.

For complex "cc" lists and the like, I have a (fairly hacky) script that takes an existing message as input and generates a format-patch series with the to, cc, and in-reply-to fields filled in (and then I ship the result out via my regular MUA after proof-reading and tweaking). Potentially git-send-email could do the same thing.

-Peff
Junio C Hamano· Apr 6, 2012, 16:49 UTC · re: Jeff King · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

Jeff King <peff@peff.net> writes:
Show 14 quoted lines
> On Thu, Apr 05, 2012 at 11:55:18PM -0700, Junio C Hamano wrote:
> ...
> I've played with that workflow before. While it's a neat trick, note
> that something like "format-patch -s" will put the signoff at the end,
> like:
>
>   commit subject
>
>   commit body
>   ---
>   Cc: whomever
>   Signed-off-by: you
>
> which is not what you want.

Well, then "don't do it then". The reason the Cc: was abused in this thread originally is about recording the people who reviewed, so you are doing an amend of an existing commit in an editor. Why wouldn't the commit that was already reviewed at least once didn't have S-o-b in the first place? In other words, isn't the "-s" option in "format-patch" a useless feature creep that would only help those with broken workflows?

Show 12 quoted lines
> About a year ago I had an RFC series to let "git commit" parse off the
> "---" bit and turn it into a git-note ...
> ... Ultimately I didn't follow up because
> I've found that I just don't end up keeping a lot of notes. I tend to do
> the re-roll and then send it out pretty soon afterward, so I just write
> any notes in the emails as they go out.
>
> For complex "cc" lists and the like, I have a (fairly hacky) script that
> takes an existing message as input and generates a format-patch series
> with the to, cc, and in-reply-to fields filled in (and then I ship the
> result out via my regular MUA after proof-reading and tweaking).
> Potentially git-send-email could do the same thing.
Perhaps.
Neil Horman· Apr 6, 2012, 18:02 UTC · re: Jeff King · lore

Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2]

On Thu, Apr 05, 2012 at 10:20:58PM -0400, Jeff King wrote:
Show 25 quoted lines
> On Thu, Apr 05, 2012 at 06:20:53PM -0700, Junio C Hamano wrote:
> 
> > >> > CC: Jeff King <peff@peff.net>
> > >> > CC: Phil Hord <phil.hord@gmail.com>
> > >> > CC: Junio C Hamano <gitster@pobox.com>
> > >> > ---
> > >> 
> > >> Please don't do "Cc:" here; they belong to your e-mail header.
> > >> 
> > > You mean place them below the snip line?  I can do that.
> > 
> > No.  When you review and fix typo in format-patch output, you can add
> > these to the e-mail header part and git-send-email will pick them up just
> > fine.
> 
> I think there is a legitimate conflict of interest here.
> 
> It's not clear exactly what "cc" tags in a commit message mean, because
> it is really a per-project thing. I don't work on the kernel, but I
> always took their cc tag to mean "these are people interested in this
> topic area". Send-email helpfully picks up that hint and cc's them on
> the emailed patch. And when the patch is applied, those cc lines remain,
> because people reading "git log" much later may find a bug in the patch,
> and it is helpful to tell them the people interested in the area.
> 

Thats more or less what its for. Mostly CC's on a patch on lkml are meant to direct peoples attention to patches for subsystems their interested in. teh kernels get_maintainers script generates these typically. With the volume of traffic on lkml its often easy for patches to get misplaced by individual maintainers. Recording that info in the commit message, while possibly useful, is most often seen as a harmless side effect.

The major advantage of CC's in the commit log is that it lets you skip the format-patch stage. I.E. you can just git-send-email directly and have all the right people CC-ed. Thats very handy when you have a large changeset and the CC list differs for individual patches.

Neil
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 2/5] git-cherry-pick: add allow-empty option [v2]

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operaion.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 17 insertions(+), 2 deletions(-)
Show changes to 4 files +17 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..c283d8c 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty:
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

Since we'll be using git-cherry-pick to enhance git-rebase's ability to preserve empty commits, we open ourselves to the possibility of preserving commits that are made empty by a previous merge as well, which is almost certainly not what we want (most of the time). To handle this, we can add the ignore-if-made-empty option. If enabled, it will look at cherry-picked commits, and if the origional sha1 has the same tree as its parent, then the cherry-pick is comitted as an empty commit, otherwise the commit is skipped (because it previously made changes to the tree, but no longer does).

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-cherry-pick.txt |   10 +++-
 builtin/revert.c                  |    2 +
 sequencer.c                       |  113 ++++++++++++++++++++++++++++++++-----
 sequencer.h                       |    1 +
 4 files changed, 111 insertions(+), 15 deletions(-)
Show changes to 4 files +111 −14

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index c283d8c..bb7eb4a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,7 +103,7 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
---allow-empty:
+--allow-empty:::
 	By default, cherry-picking an empty commit will fail,
 	indicating that an explicit invocation of `git commit
 	--allow-empty` is required. This option overrides that
@@ -112,6 +112,14 @@ effect to your index in a row.
 	commits that meet the "fast-forward" requirement will be kept
 	even without this option.
 
+--ignore-if-made-empty::
+	If the --allow-empty option is used, all empty commits are kept,
+	including those which were made empty due to a previous change.
+	While this may be desireable, likely it is not.  This option
+	restricts the scope of --allow-empty to only those commits which
+	were created as empty commits (i.e. if for commit C,  C^{tree} and 
+	C^^{tree} are identical).
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index 06b00e6..0fa76ca 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -115,6 +115,7 @@ 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) {
@@ -122,6 +123,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 			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, "ignore-if-made-empty", &opts->ignore_if_made_empty, "ignore commits already in tree"),
 			OPT_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index 71929ba..a512598 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,107 @@ 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)
-{
-	/* 7 is max possible length of our args array including NULL */
-	const char *args[7];
-	int i = 0;
+static int run_git_commit(const char *defmsg, struct replay_opts *opts, int empty)
+{
+	struct argv_array array;
+	int rc;
+
+	argv_array_init(&array);
+	argv_array_push(&array, "commit");
+	argv_array_push(&array, "-n");
+
+	if ((!empty) && (opts->ignore_if_made_empty)) {
+		/* Note: This implies --dry-run */
+		argv_array_push(&array, "--porcelain");
+		if (run_command_v_opt(array.argv, RUN_GIT_CMD) == 1) {
+			/* The dry run exit code of 1 tells us this is
+ 			 * an empty commit, just skip it.
+ 			 */
+			argv_array_clear(&array);
+			return 0;
+		}
+		argv_array_pop(&array, 1);
+	}
+
 
-	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");
+
+
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
+
+static int is_origional_commit_empty(struct commit *commit)
+{
+	struct argv_array argv_array;
+	struct child_process cp;
+	char ptree[41], pptree[41];
+	int pipefd[2];
+	FILE *output;
+	int ret = 0;
+
+	if (pipe2(pipefd, 0) < 0)
+		return 0;
+
+	output = xfdopen(pipefd[0], "r");
+
+	argv_array_init(&argv_array);
+	memset(&cp, 0, sizeof(struct child_process));
 
-	args[i] = NULL;
+	argv_array_push(&argv_array, "rev-parse");
+	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	cp.git_cmd = 1;
+	cp.no_stdin = 1;
+	cp.no_stderr = 1;
+	cp.out = pipefd[1];
+	cp.argv = argv_array.argv;
+
+	if (start_command(&cp) < 0)
+		goto out;
+
+	if (fscanf(output, "%s\n", ptree) < 1)
+		goto out;
+
+	finish_command(&cp);
+
+	fclose(output);
+	close(pipefd[0]);
+	argv_array_clear(&argv_array);
+
+	if (pipe2(pipefd, 0) < 0)
+		return 0;
+
+	output = xfdopen(pipefd[0], "r");
+
+	argv_array_push(&argv_array, "rev-parse");
+	argv_array_pushf(&argv_array, "%s^^{tree}", sha1_to_hex(commit->object.sha1));
+	cp.argv = argv_array.argv;
+
+	if (start_command(&cp) < 0)
+		goto out;
+
+	if (fscanf(output, "%s\n", pptree) < 1)
+		goto out;
+
+	finish_command(&cp);
+	close(pipefd[0]);
+
+	if (!strncmp(ptree, pptree, 40))
+		ret = 1;
+out:
+	fclose(output);
+	argv_array_clear(&argv_array);	
+	return ret;
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +371,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +497,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_origional_commit_empty(commit);
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -435,7 +520,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		rerere(opts->allow_rerere_auto);
 	} else {
 		if (!opts->no_commit)
-			res = run_git_commit(defmsg, opts);
+			res = run_git_commit(defmsg, opts, empty_commit);
 	}
 
 	free_message(&msg);
diff --git a/sequencer.h b/sequencer.h
index e2cd725..3e1106f 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int ignore_if_made_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Apr 5, 2012, 21:01 UTC · re: Neil Horman · lore

Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

Neil Horman <nhorman@tuxdriver.com> writes:
> Subject: Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]
Please don't do this.  The tools do not strip the garbage at the end.
Instead, do it like either one of these:
	Subject: [PATCH 3/5 (v2)] mumble mumble...
	Subject: [PATCH v2 3/5] mumble mumble...

The latter is more appropriate when resending everything, including the ones that did not change from the earlier round.

Show 10 quoted lines
> Since we'll be using git-cherry-pick to enhance git-rebase's ability to preserve
> empty commits, we open ourselves to the possibility of preserving commits that
> are made empty by a previous merge as well, which is almost certainly not what
> we want (most of the time).  To handle this, we can add the ignore-if-made-empty
> option.  If enabled, it will look at cherry-picked commits, and if the origional
> sha1 has the same tree as its parent, then the cherry-pick is comitted as an
> empty commit, otherwise the commit is skipped (because it previously made
> changes to the tree, but no longer does).
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
Show 10 quoted lines
> diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
> index c283d8c..bb7eb4a 100644
> --- a/Documentation/git-cherry-pick.txt
> +++ b/Documentation/git-cherry-pick.txt
> @@ -103,7 +103,7 @@ effect to your index in a row.
>  	cherry-pick'ed commit, then a fast forward to this commit will
>  	be performed.
>  
> ---allow-empty:
> +--allow-empty:::

A single colon is already buggy, but three colons is equally bad. Please fix that in the original point this bug was introduced. I guess it was 2/5 of this series?

Show 6 quoted lines
> @@ -112,6 +112,14 @@ effect to your index in a row.
>  	commits that meet the "fast-forward" requirement will be kept
>  	even without this option.
>  
> +--ignore-if-made-empty::
> +	If the --allow-empty option is used, all empty commits are kept,

Literal strings the user may type are typically set in tt font (i.e. `--allow-empty`).

> +	including those which were made empty due to a previous change.
> +	While this may be desireable, likely it is not.

Mental note. Up to this point, the reader is told that "--allow-empty" alone is likely to do a wrong thing.

> +	This option
> +	restricts the scope of --allow-empty to only those commits which
> +	were created as empty commits ...

And the user is asked to give "--ignore-if-made-empty" in addition to "--allow-empty" to get a saner and more likely to be useful behaviour. Isn't that backwards? This "the other option is insane, and please make it saner" option needs a lot more typing than the more insane option.

I would expect that "--allow-empty" would by default filter ones that are originally non-empty but are made unnecessary (we are allowing empty commits in the original history to be cherry-picked, but the general principle that unnecessary commits must not be picked still is in effect). If you want to give a user the other more insane mode of operation, it is OK to let the user give a different option *instead* *of* the saner "--allow-empty".

Perhaps name that "--keep-unnecessary-commit" (it is no longer about allowing empty commits in the original history to be picked; it is about keeping unnecessary and irrelevant commits in the resulting history).

And error out if both options are given.
Show 10 quoted lines
> +static int run_git_commit(const char *defmsg, struct replay_opts *opts, int empty)
> +{
> +	struct argv_array array;
> +	int rc;
> +
> +	argv_array_init(&array);
> +	argv_array_push(&array, "commit");
> +	argv_array_push(&array, "-n");
> +
> +	if ((!empty) && (opts->ignore_if_made_empty)) {
Style: lose the extra and unnecessary parentheses, especially when the
expression inside them are so trivial, i.e.
	if (!empty && opts->ignore_if_made_empty) {

although I suspect that redesign of the command line interface might make it a moot point to polish this part of the code without major rethinking.

> +		/* Note: This implies --dry-run */
> +		argv_array_push(&array, "--porcelain");
> +		if (run_command_v_opt(array.argv, RUN_GIT_CMD) == 1) {

The only thing you want to check at this point is if the contents of the index being committed is the same as the tree of HEAD. Why do we even want to incur this much overhead?

Isn't running "diff-index --cached HEAD" sufficient?
> +			/* The dry run exit code of 1 tells us this is
> + 			 * an empty commit, just skip it.
> + 			 */
	/*
         * Our multi-line comments are
         * formatted this way.
         */
Show 24 quoted lines
> +			argv_array_clear(&array);
> +			return 0;
> +		}
> +		argv_array_pop(&array, 1);
> +	}
> +
>  
>  	if (opts->signoff)
> +		argv_array_push(&array, "-s");
>  	if (!opts->edit) {
> +		argv_array_push(&array, "-F");
> +		argv_array_push(&array, defmsg);
>  	}
> +	
>  	if (opts->allow_empty)
> +		argv_array_push(&array, "--allow-empty");
> +
> +
> +	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
> +	argv_array_clear(&array);
> +	return rc;
> +}
> +
> +static int is_origional_commit_empty(struct commit *commit)
What is origional?
Is there a reason why this series is not marked as WIP or RFC?

I am starting to wonder if it is worth spending time on careful reviewing, or it would be sufficient to give a cursory review quickly to give you more time to polish your re-roll.

Show 40 quoted lines
> +{
> +	struct argv_array argv_array;
> +	struct child_process cp;
> +	char ptree[41], pptree[41];
> +	int pipefd[2];
> +	FILE *output;
> +	int ret = 0;
> +
> +	if (pipe2(pipefd, 0) < 0)
> +		return 0;
> +
> +	output = xfdopen(pipefd[0], "r");
> +
> +	argv_array_init(&argv_array);
> +	memset(&cp, 0, sizeof(struct child_process));
>  
> -	args[i] = NULL;
> +	argv_array_push(&argv_array, "rev-parse");
> +	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));
>  
> -	return run_command_v_opt(args, RUN_GIT_CMD);
> +	cp.git_cmd = 1;
> +	cp.no_stdin = 1;
> +	cp.no_stderr = 1;
> +	cp.out = pipefd[1];
> +	cp.argv = argv_array.argv;
> +
> +	if (start_command(&cp) < 0)
> +		goto out;
> +
> +	if (fscanf(output, "%s\n", ptree) < 1)
> +		goto out;
> +
> +	finish_command(&cp);
> +
> +	fclose(output);
> +	close(pipefd[0]);
> +	argv_array_clear(&argv_array);
> +
> +	if (pipe2(pipefd, 0) < 0)
Huh?  "man pipe2" and see if it is portable.
Neil Horman· Apr 5, 2012, 23:45 UTC · re: Junio C Hamano · lore

Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

On Thu, Apr 05, 2012 at 02:01:43PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Subject: Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]
> 
> Please don't do this.  The tools do not strip the garbage at the end.
> 
> Instead, do it like either one of these:
> 
> 	Subject: [PATCH 3/5 (v2)] mumble mumble...
> 	Subject: [PATCH v2 3/5] mumble mumble...
> 
> The latter is more appropriate when resending everything, including the
> ones that did not change from the earlier round.
> 

Thats fine, I typically use tools that strip everything inside all braces, I'll update this on my next re-roll.

Show 26 quoted lines
> > Since we'll be using git-cherry-pick to enhance git-rebase's ability to preserve
> > empty commits, we open ourselves to the possibility of preserving commits that
> > are made empty by a previous merge as well, which is almost certainly not what
> > we want (most of the time).  To handle this, we can add the ignore-if-made-empty
> > option.  If enabled, it will look at cherry-picked commits, and if the origional
> > sha1 has the same tree as its parent, then the cherry-pick is comitted as an
> > empty commit, otherwise the commit is skipped (because it previously made
> > changes to the tree, but no longer does).
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> 
> > diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
> > index c283d8c..bb7eb4a 100644
> > --- a/Documentation/git-cherry-pick.txt
> > +++ b/Documentation/git-cherry-pick.txt
> > @@ -103,7 +103,7 @@ effect to your index in a row.
> >  	cherry-pick'ed commit, then a fast forward to this commit will
> >  	be performed.
> >  
> > ---allow-empty:
> > +--allow-empty:::
> 
> A single colon is already buggy, but three colons is equally bad.  Please
> fix that in the original point this bug was introduced.  I guess it was
> 2/5 of this series?
> 
Crap, thanks, I missed that.  I'll square that away
Show 10 quoted lines
> > @@ -112,6 +112,14 @@ effect to your index in a row.
> >  	commits that meet the "fast-forward" requirement will be kept
> >  	even without this option.
> >  
> > +--ignore-if-made-empty::
> > +	If the --allow-empty option is used, all empty commits are kept,
> 
> Literal strings the user may type are typically set in tt font
> (i.e. `--allow-empty`).
> 
Ack, ok.
Show 29 quoted lines
> > +	including those which were made empty due to a previous change.
> > +	While this may be desireable, likely it is not.
> 
> Mental note.  Up to this point, the reader is told that "--allow-empty"
> alone is likely to do a wrong thing.
> 
> > +	This option
> > +	restricts the scope of --allow-empty to only those commits which
> > +	were created as empty commits ...
> 
> And the user is asked to give "--ignore-if-made-empty" in addition to
> "--allow-empty" to get a saner and more likely to be useful behaviour.
> Isn't that backwards?  This "the other option is insane, and please make
> it saner" option needs a lot more typing than the more insane option.
> 
> I would expect that "--allow-empty" would by default filter ones that are
> originally non-empty but are made unnecessary (we are allowing empty
> commits in the original history to be cherry-picked, but the general
> principle that unnecessary commits must not be picked still is in effect).
> If you want to give a user the other more insane mode of operation, it is
> OK to let the user give a different option *instead* *of* the saner
> "--allow-empty".
> 
> Perhaps name that "--keep-unnecessary-commit" (it is no longer about
> allowing empty commits in the original history to be picked; it is about
> keeping unnecessary and irrelevant commits in the resulting history).
> 
> And error out if both options are given.
> 

Yeah, ok. I wasn't really thinking about the direct use case, as I had expected the typical use to be in the rebase scripts, where this is already wired up, but I see your point. I'll reverse the logic.

Show 14 quoted lines
> > +static int run_git_commit(const char *defmsg, struct replay_opts *opts, int empty)
> > +{
> > +	struct argv_array array;
> > +	int rc;
> > +
> > +	argv_array_init(&array);
> > +	argv_array_push(&array, "commit");
> > +	argv_array_push(&array, "-n");
> > +
> > +	if ((!empty) && (opts->ignore_if_made_empty)) {
> 
> Style: lose the extra and unnecessary parentheses, especially when the
> expression inside them are so trivial, i.e.
> 
Ok
Show 14 quoted lines
> 	if (!empty && opts->ignore_if_made_empty) {
> 
> although I suspect that redesign of the command line interface might make
> it a moot point to polish this part of the code without major rethinking.
> 
> > +		/* Note: This implies --dry-run */
> > +		argv_array_push(&array, "--porcelain");
> > +		if (run_command_v_opt(array.argv, RUN_GIT_CMD) == 1) {
> 
> The only thing you want to check at this point is if the contents of the
> index being committed is the same as the tree of HEAD.  Why do we even
> want to incur this much overhead?
> 
> Isn't running "diff-index --cached HEAD" sufficient?

Well, I was looking at the git commit code to see what exactly --dry-run did, and I thought that it was roughly equivalent to diff-index. But if its lower overhead to do a diff-index instead, I'll happily change it.

Show 10 quoted lines
> 
> > +			/* The dry run exit code of 1 tells us this is
> > + 			 * an empty commit, just skip it.
> > + 			 */
> 
> 	/*
>          * Our multi-line comments are
>          * formatted this way.
>          */
> 
Ok.
Show 27 quoted lines
> > +			argv_array_clear(&array);
> > +			return 0;
> > +		}
> > +		argv_array_pop(&array, 1);
> > +	}
> > +
> >  
> >  	if (opts->signoff)
> > +		argv_array_push(&array, "-s");
> >  	if (!opts->edit) {
> > +		argv_array_push(&array, "-F");
> > +		argv_array_push(&array, defmsg);
> >  	}
> > +	
> >  	if (opts->allow_empty)
> > +		argv_array_push(&array, "--allow-empty");
> > +
> > +
> > +	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
> > +	argv_array_clear(&array);
> > +	return rc;
> > +}
> > +
> > +static int is_origional_commit_empty(struct commit *commit)
> 
> What is origional?
> 
A spelling error :).   Thanks.
> Is there a reason why this series is not marked as WIP or RFC?
> 

Because its not. It works perfectly well, and its fairly close to done. So far your review has turned up one significant operational change, which is easily re-worked and a few stylistic nits. Thats how review works, I don't see whats wrong with that.

> I am starting to wonder if it is worth spending time on careful reviewing,
> or it would be sufficient to give a cursory review quickly to give you
> more time to polish your re-roll.
> 
I'm not sure what you think is so egregious about this changeset, but if
you have a specific problem, please let me know.  We all make errors, thats why
we review work like this.  All your comment above does is toss a purposeless
insult into the conversation. 
 
Show 43 quoted lines
> > +{
> > +	struct argv_array argv_array;
> > +	struct child_process cp;
> > +	char ptree[41], pptree[41];
> > +	int pipefd[2];
> > +	FILE *output;
> > +	int ret = 0;
> > +
> > +	if (pipe2(pipefd, 0) < 0)
> > +		return 0;
> > +
> > +	output = xfdopen(pipefd[0], "r");
> > +
> > +	argv_array_init(&argv_array);
> > +	memset(&cp, 0, sizeof(struct child_process));
> >  
> > -	args[i] = NULL;
> > +	argv_array_push(&argv_array, "rev-parse");
> > +	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));
> >  
> > -	return run_command_v_opt(args, RUN_GIT_CMD);
> > +	cp.git_cmd = 1;
> > +	cp.no_stdin = 1;
> > +	cp.no_stderr = 1;
> > +	cp.out = pipefd[1];
> > +	cp.argv = argv_array.argv;
> > +
> > +	if (start_command(&cp) < 0)
> > +		goto out;
> > +
> > +	if (fscanf(output, "%s\n", ptree) < 1)
> > +		goto out;
> > +
> > +	finish_command(&cp);
> > +
> > +	fclose(output);
> > +	close(pipefd[0]);
> > +	argv_array_clear(&argv_array);
> > +
> > +	if (pipe2(pipefd, 0) < 0)
> 
> Huh?  "man pipe2" and see if it is portable.
> 

Crud, I completely forgot that pipe2 was _GNU_SOURCE only. I'll rework that, thanks.

Regards Neil

Junio C Hamano· Apr 6, 2012, 01:20 UTC · re: Neil Horman · lore

Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

Neil Horman <nhorman@tuxdriver.com> writes:
Show 8 quoted lines
> On Thu, Apr 05, 2012 at 02:01:43PM -0700, Junio C Hamano wrote:
>
>> I am starting to wonder if it is worth spending time on careful reviewing,
>> or it would be sufficient to give a cursory review quickly to give you
>> more time to polish your re-roll.
>> 
> I'm not sure what you think is so egregious about this changeset, but if
> you have a specific problem, please let me know.

There is no insult involved. I just didn't know where to start, because the series was littered with many issues from high level design (e.g. does the command line interface and API addition make sense?) to low level styles (e.g. does the new code imitate the style of the existing code around it?), and in between (e.g. pipe2() is never used in the codebase without this patch. Is it portable enough?). It was clear that it needed a lot more work to lose a WIP label (the quality standard in this project is slightly higher than "the end result seems to compile--let's ship it").

In other words, I was simply being honest.
> We all make errors, thats why
> we review work like this.  All your comment above does is toss a purposeless
> insult into the conversation. 

Making mistakes is one thing. Sending a series that is not sufficiently proofread is a completely different matter. The review process is not a replacement for your own proofreading. It comes after that.

If you did proofread the patch [3/5], you would have noticed that the fix you made to the documentation is a fix for patch [2/5]. You are in much better position than I or other reviewers to notice it---after all, it is your addition. The same for the typo in the mysteriously named function.

How else do you expect me to react to such a series?
Neil Horman· Apr 6, 2012, 18:06 UTC · re: Junio C Hamano · lore

Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

On Thu, Apr 05, 2012 at 02:01:43PM -0700, Junio C Hamano wrote:
><snip>
Show 29 quoted lines
> > +	including those which were made empty due to a previous change.
> > +	While this may be desireable, likely it is not.
> 
> Mental note.  Up to this point, the reader is told that "--allow-empty"
> alone is likely to do a wrong thing.
> 
> > +	This option
> > +	restricts the scope of --allow-empty to only those commits which
> > +	were created as empty commits ...
> 
> And the user is asked to give "--ignore-if-made-empty" in addition to
> "--allow-empty" to get a saner and more likely to be useful behaviour.
> Isn't that backwards?  This "the other option is insane, and please make
> it saner" option needs a lot more typing than the more insane option.
> 
> I would expect that "--allow-empty" would by default filter ones that are
> originally non-empty but are made unnecessary (we are allowing empty
> commits in the original history to be cherry-picked, but the general
> principle that unnecessary commits must not be picked still is in effect).
> If you want to give a user the other more insane mode of operation, it is
> OK to let the user give a different option *instead* *of* the saner
> "--allow-empty".
> 
> Perhaps name that "--keep-unnecessary-commit" (it is no longer about
> allowing empty commits in the original history to be picked; it is about
> keeping unnecessary and irrelevant commits in the resulting history).
> 
> And error out if both options are given.
> 

I'm working on this today, and in doing so, I don't think I like the one or the other option approach. I think it makes more sense to use an --option / --option-harder model here. Theres precident for this in a few other git command (git format-patch has --find-copies and --find-copies harder). I think we can do an --allow-empty option and a --keep-redundant-commits option, where the former explicitly keeps commits that were non-empty but are now because their changes have already been applied. the latter option then naturally implies the former.

Neil
Johannes Sixt· Apr 6, 2012, 18:30 UTC · re: Neil Horman · lore

Re: [PATCH 3/5] git-cherry-pick: Add ignore-if-made-empty option [v2]

Am 05.04.2012 21:39, schrieb Neil Horman:
Show 12 quoted lines
> +	if (pipe2(pipefd, 0) < 0)
> +		return 0;
> +	output = xfdopen(pipefd[0], "r");
> +	memset(&cp, 0, sizeof(struct child_process));
> +	... setup cp ...
> +	if (start_command(&cp) < 0)
> +		goto out;
> +	if (fscanf(output, "%s\n", ptree)<  1)
> +		goto out;
> +	finish_command(&cp);
> +	fclose(output);
> +	close(pipefd[0]);

Instead of this sequence (I quoted only the relevant pieces), use the following:

	memset(&cp, 0, sizeof(struct child_process));
	cp.out = -1;
	... set other pieces in cp ...
	if (start_command(&cp) < 0)
		goto out;
	read_in_full(cp.out, ptree, sizeof(ptree));
	/* add suitable error reporting above */
	close(cp.out);
	if (!finish_command(&cp))
		goto out;
i.e.,
1. let start_command create the pipe for you by setting cp.out = -1,
2. avoid fscanf() if read_in_full() is equally simple to use,
3. close the pipe before finish_command(),
4. check the return code of finish_command().
-- Hannes
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 4/5] git-cherry-pick: Add test to validate new options [v2]

Since we've added the --allow-empty and --ignore-if-made-empty options to git cherry-pick we should also add a test to ensure that its working properly

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 t/t3505-cherry-pick-empty.sh |   31 ++++++++++++++++++++++++++++++-
 1 files changed, 30 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +30 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..049ed28 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,28 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fouth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" && {
+		git cherry-pick empty-branch2
+		test "$?" = 1 
+	}
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master && {
+		git cherry-pick --allow-empty empty-branch2
+		test "$?" = 0
+	}
+'
+
+test_expect_success 'cherry pick with --ignore-if-made-empty' '
+	git checkout master && {
+		git cherry-pick --allow-empty --ignore-if-made-empty empty-branch2
+		test "$?" = 0
+	}
+'
+
 test_done
-- 
1.7.7.6
Neil Horman· Apr 5, 2012, 19:39 UTC · re: Neil Horman · lore

[PATCH 5/5] git-rebase: add keep_empty flag [v2]

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Jeff King <peff@peff.net>
CC: Phil Hord <phil.hord@gmail.com>
CC: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   40 +++++++++++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 60 insertions(+), 8 deletions(-)
Show changes to 4 files +60 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..a7194f7 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if [ -n "$keep_empty" ]
+then
+	# we have to do this the hard way.  git format-patch completly squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty --ignore-if-made-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..beb06cf 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,12 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	ptree=$(git rev-parse "$1"^{tree})
+	pptree=$(git rev-parse "$1"^^{tree})
+	return test "$ptree" = "$pptree"
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +197,23 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
+	if is_emtpy_commit $@ 
+	then
+		empty_args="--allow-empty --ignore-if-made-empty"
+	fi
+
+	if test -n "$keep_empty" 
+	then
+		empty_args="--allow_empty --ignore-if-made-empty"
+	fi
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +797,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $sha1
+	then
+		comment_out="# pick"
+	else
+		comment_out="pick"
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +826,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +876,15 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	cat >> "$todo" << EOF
+	# Note that commits which are empty at the time of rebasing are 
+	# commented out. 
+	EOF
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Neil Horman· Apr 10, 2012, 15:47 UTC · re: Neil Horman · lore

[PATCH v3 0/4] Enhance git-rebases flexibiilty in handling empty commits

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --keep-redundant-commits, which allows the user to also keep commits that were made empty via conflict resolution. It also enhances git-rebase to add a --keep-empty option which enables rebases to preserve empty commits.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
V3)
	* reversed the --ignore-if-empty-logic to by default only keep initially
	empty commits
	* replaced --ignore-if-empty with --keep-redundant-commits, to allow
	empty commits that are made empty via conflict resolution, in addition
	to commits that were created as empty
	* reworked is_original_commit_empty to be more efficient and portable
	* Misc sylistic and spelling cleanups
Neil Horman· Apr 10, 2012, 15:47 UTC · re: Neil Horman · lore

[PATCH v3 1/4] git-cherry-pick: add allow-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/commit.c                  |    6 +++---
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 5 files changed, 20 insertions(+), 5 deletions(-)
Show changes to 5 files +20 −5

Documentation/git-cherry-pick.txt, builtin/commit.c, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..730237a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty::
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/commit.c b/builtin/commit.c
index 3714582..0cd10ab 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -56,10 +56,10 @@ N_("You asked to amend the most recent commit, but doing so would make\n"
 "remove the commit entirely with \"git reset HEAD^\".\n");
 
 static const char empty_cherry_pick_advice[] =
-N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
-"If you wish to commit it anyway, use:\n"
+N_("The previous cherry-pick is empty.\n"
+"If the commit was created empty, please use:\n"
 "\n"
-"    git commit --allow-empty\n"
+"    git cherry-pick --allow-empty\n"
 "\n"
 "Otherwise, please use 'git reset'\n");
 
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Apr 10, 2012, 16:45 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
Show 47 quoted lines
> git cherry-pick fails when picking a non-ff commit that is empty.  The advice
> given with the failure is that a git-commit --allow-empty should be issued to
> explicitly add the empty commit during the cherry pick.  This option allows a
> user to specify before hand that they want to keep the empty commit.  This
> eliminates the need to issue both a cherry pick and a commit operation.
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> ---
>  Documentation/git-cherry-pick.txt |    9 +++++++++
>  builtin/commit.c                  |    6 +++---
>  builtin/revert.c                  |    2 ++
>  sequencer.c                       |    7 +++++--
>  sequencer.h                       |    1 +
>  5 files changed, 20 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
> index fed5097..730237a 100644
> --- a/Documentation/git-cherry-pick.txt
> +++ b/Documentation/git-cherry-pick.txt
> @@ -103,6 +103,15 @@ effect to your index in a row.
>  	cherry-pick'ed commit, then a fast forward to this commit will
>  	be performed.
>  
> +--allow-empty::
> +	By default, cherry-picking an empty commit will fail,
> +	indicating that an explicit invocation of `git commit
> +	--allow-empty` is required. This option overrides that
> +	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.
> +
>  --strategy=<strategy>::
>  	Use the given merge strategy.  Should only be used once.
>  	See the MERGE STRATEGIES section in linkgit:git-merge[1]
> diff --git a/builtin/commit.c b/builtin/commit.c
> index 3714582..0cd10ab 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -56,10 +56,10 @@ N_("You asked to amend the most recent commit, but doing so would make\n"
>  "remove the commit entirely with \"git reset HEAD^\".\n");
>  
>  static const char empty_cherry_pick_advice[] =
> -N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
> -"If you wish to commit it anyway, use:\n"
> +N_("The previous cherry-pick is empty.\n"
> +"If the commit was created empty, please use:\n"

After reading this three times, I have to say that the updated wording do not look like an improvement for two reasons.

 (1) After a failed cherry-pick, the index can match the current HEAD for
     two reasons.  Either the original cherry-pick was attempting to pick
     an empty commit (which is likely to be a mistake unless you are doing
     something unusual like creating an empty commit in the first place),
     or the change in the original commit was already found in the current
     version (may be result of a conflict resolution).  The message before
     your change used "possibly" to hint this, and if the reader gets it,
     it is understandable why the reader is seeing this advise.  Updated
     message loses this information by simply saying "is empty".
 (2) The message is given by the "git commit" command.  "If the commit was
     created empty" looks confusing.  Even though I can understand that
     "the commit" refers to the original commit the user tried to
     cherry-pick before running this command while reviewing this patch, I
     suspect that the reader who sees this message may not be able to tell
     if the "git commit" command created a possibly empty commit and then
     telling the user to do something further on _that_ commit, or if it
     is referring to the commit the user tried to pick with the previous
     "git cherry-pick" command.

That is, unless you are making "git cherry-pick --allow-empty" not to stop and leave it to "git commit" to clean it up. If that were the case (which is not, after applying this patch alone), then this message will be issued only when a conflict resolution resulted in an empty commit, so "If the commit you were trying to cherry-pick was empty to begin with" would not apply, either.

Neil Horman· Apr 10, 2012, 18:13 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Tue, Apr 10, 2012 at 09:45:46AM -0700, Junio C Hamano wrote:
Show 63 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > git cherry-pick fails when picking a non-ff commit that is empty.  The advice
> > given with the failure is that a git-commit --allow-empty should be issued to
> > explicitly add the empty commit during the cherry pick.  This option allows a
> > user to specify before hand that they want to keep the empty commit.  This
> > eliminates the need to issue both a cherry pick and a commit operation.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > ---
> >  Documentation/git-cherry-pick.txt |    9 +++++++++
> >  builtin/commit.c                  |    6 +++---
> >  builtin/revert.c                  |    2 ++
> >  sequencer.c                       |    7 +++++--
> >  sequencer.h                       |    1 +
> >  5 files changed, 20 insertions(+), 5 deletions(-)
> >
> > diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
> > index fed5097..730237a 100644
> > --- a/Documentation/git-cherry-pick.txt
> > +++ b/Documentation/git-cherry-pick.txt
> > @@ -103,6 +103,15 @@ effect to your index in a row.
> >  	cherry-pick'ed commit, then a fast forward to this commit will
> >  	be performed.
> >  
> > +--allow-empty::
> > +	By default, cherry-picking an empty commit will fail,
> > +	indicating that an explicit invocation of `git commit
> > +	--allow-empty` is required. This option overrides that
> > +	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.
> > +
> >  --strategy=<strategy>::
> >  	Use the given merge strategy.  Should only be used once.
> >  	See the MERGE STRATEGIES section in linkgit:git-merge[1]
> > diff --git a/builtin/commit.c b/builtin/commit.c
> > index 3714582..0cd10ab 100644
> > --- a/builtin/commit.c
> > +++ b/builtin/commit.c
> > @@ -56,10 +56,10 @@ N_("You asked to amend the most recent commit, but doing so would make\n"
> >  "remove the commit entirely with \"git reset HEAD^\".\n");
> >  
> >  static const char empty_cherry_pick_advice[] =
> > -N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
> > -"If you wish to commit it anyway, use:\n"
> > +N_("The previous cherry-pick is empty.\n"
> > +"If the commit was created empty, please use:\n"
> 
> After reading this three times, I have to say that the updated wording do
> not look like an improvement for two reasons.
> 
>  (1) After a failed cherry-pick, the index can match the current HEAD for
>      two reasons.  Either the original cherry-pick was attempting to pick
>      an empty commit (which is likely to be a mistake unless you are doing
>      something unusual like creating an empty commit in the first place),
>      or the change in the original commit was already found in the current
>      version (may be result of a conflict resolution).  The message before
>      your change used "possibly" to hint this, and if the reader gets it,
>      it is understandable why the reader is seeing this advise.  Updated
>      message loses this information by simply saying "is empty".
> 

I can re-instate the possible language if you like, I don't have a problem with that.

>  (2) The message is given by the "git commit" command.  "If the commit was
>      created empty" looks confusing.  Even though I can understand that

Its coded within the git commit command code, but is only ever displayed if whence is GIT_CHERRY_PICK, so as far as I can see, from a users perspective, this will only be seen if they type git cherry-pick on the command line.

Show 8 quoted lines
>      "the commit" refers to the original commit the user tried to
>      cherry-pick before running this command while reviewing this patch, I
>      suspect that the reader who sees this message may not be able to tell
>      if the "git commit" command created a possibly empty commit and then
>      telling the user to do something further on _that_ commit, or if it
>      is referring to the commit the user tried to pick with the previous
>      "git cherry-pick" command.
> 

Given that this message is displayed because the index and HEAD have no changes leading to what would be an empty commit, which is by default no allowed unless expressly enabled, I don't think its that confusing at all. "The commit" can only refer to the commit being cherry picked, since there is no other.

Show 6 quoted lines
> That is, unless you are making "git cherry-pick --allow-empty" not to stop
> and leave it to "git commit" to clean it up.  If that were the case (which
> is not, after applying this patch alone), then this message will be issued
> only when a conflict resolution resulted in an empty commit, so "If the
> commit you were trying to cherry-pick was empty to begin with" would not
> apply, either.

Ok, I see what you're saying. This advice makes sense if you issue git cherry-pick, but not if you issue git cherry-pick --allow-empty. I think the additional advice provided when you add in the keep-redundant-commits patch though, assuming I re-add the possibly language above, yes? Neil

Show 5 quoted lines
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
Junio C Hamano· Apr 10, 2012, 19:32 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
Show 7 quoted lines
> On Tue, Apr 10, 2012 at 09:45:46AM -0700, Junio C Hamano wrote:
> ...
>>  (2) The message is given by the "git commit" command.  "If the commit was
>>      created empty" looks confusing.  Even though I can understand that
> Its coded within the git commit command code, but is only ever displayed if
> whence is GIT_CHERRY_PICK, so as far as I can see, from a users perspective,
> this will only be seen if they type git cherry-pick on the command line.
Here is what I tried, and I think you are wrong.
	$ git cherry-pick $some_commit
        ... conflicts ...
        $ edit so that the working tree matches HEAD
        $ git commit -a
        ... message from status ...
        THE ADVICE IN QUEWSTION COMES HERE!!!
Neil Horman· Apr 10, 2012, 20:00 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Tue, Apr 10, 2012 at 12:32:18PM -0700, Junio C Hamano wrote:
Show 20 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > On Tue, Apr 10, 2012 at 09:45:46AM -0700, Junio C Hamano wrote:
> > ...
> >>  (2) The message is given by the "git commit" command.  "If the commit was
> >>      created empty" looks confusing.  Even though I can understand that
> > Its coded within the git commit command code, but is only ever displayed if
> > whence is GIT_CHERRY_PICK, so as far as I can see, from a users perspective,
> > this will only be seen if they type git cherry-pick on the command line.
> 
> Here is what I tried, and I think you are wrong.
> 
> 	$ git cherry-pick $some_commit
>         ... conflicts ...
>         $ edit so that the working tree matches HEAD
>         $ git commit -a
>         ... message from status ...
>         THE ADVICE IN QUEWSTION COMES HERE!!!
> 
> 

Ok, I admit I didn't really think of that case, but that seems to me to be the trivial case, which is unlikely to be encountered. If you do a git cherry-pick and have conflicts, you by definition don't have a commit that is resolved to empty (at least not without manual intervention), nor do you have a commit which was initially empty. You really have to go out of your way to take a commit that conflicts, make it empty, and then commit it without realizing that its empty.

I agree that seeing advice regarding git cherry-pick when you run git commit is awkward, but its no worse than seeing advice indicating you should run git commit when you run git cherry-pick (i.e. the case in which you run git cherry-pick <C>, where <C> is empty an not fast-forward-able.

Perhaps whats called for here is and advice differentiator? I.e if git commit is run directly from the command line, issue advice relating to using git commit --allow-empty, otherwise issue advice relating to git cherry-pick.

I think we could do that by looking at the parent process at run time, unless theres a good way to differentiate by the sate information in .git

Thoughts?
Neil
 
Junio C Hamano· Apr 10, 2012, 20:32 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
Show 24 quoted lines
> On Tue, Apr 10, 2012 at 12:32:18PM -0700, Junio C Hamano wrote:
>> Neil Horman <nhorman@tuxdriver.com> writes:
>> 
>> > On Tue, Apr 10, 2012 at 09:45:46AM -0700, Junio C Hamano wrote:
>> > ...
>> >>  (2) The message is given by the "git commit" command.  "If the commit was
>> >>      created empty" looks confusing.  Even though I can understand that
>> > Its coded within the git commit command code, but is only ever displayed if
>> > whence is GIT_CHERRY_PICK, so as far as I can see, from a users perspective,
>> > this will only be seen if they type git cherry-pick on the command line.
>> 
>> Here is what I tried, and I think you are wrong.
>> 
>> 	$ git cherry-pick $some_commit
>>         ... conflicts ...
>>         $ edit so that the working tree matches HEAD
>>         $ git commit -a
>>         ... message from status ...
>>         THE ADVICE IN QUEWSTION COMES HERE!!!
>> 
>> 
> Ok, I admit I didn't really think of that case, but that seems to me to be the
> trivial case, which is unlikely to be encountered.  If you do a git cherry-pick and
> have conflicts, you by definition don't have a commit that is resolved to empty...
Not at all unlikely, especially in a distributed world.

You may have seen two patches but they make sense as one so that you apply to one branch as one change, while the branch you are cherry-picking from may have these two changes as individual commits. Neither of them will apply cleanly to your tree, and your resolution will be "keep mine---I already have this".

Neil Horman· Apr 10, 2012, 20:39 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Tue, Apr 10, 2012 at 01:32:44PM -0700, Junio C Hamano wrote:
Show 35 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > On Tue, Apr 10, 2012 at 12:32:18PM -0700, Junio C Hamano wrote:
> >> Neil Horman <nhorman@tuxdriver.com> writes:
> >> 
> >> > On Tue, Apr 10, 2012 at 09:45:46AM -0700, Junio C Hamano wrote:
> >> > ...
> >> >>  (2) The message is given by the "git commit" command.  "If the commit was
> >> >>      created empty" looks confusing.  Even though I can understand that
> >> > Its coded within the git commit command code, but is only ever displayed if
> >> > whence is GIT_CHERRY_PICK, so as far as I can see, from a users perspective,
> >> > this will only be seen if they type git cherry-pick on the command line.
> >> 
> >> Here is what I tried, and I think you are wrong.
> >> 
> >> 	$ git cherry-pick $some_commit
> >>         ... conflicts ...
> >>         $ edit so that the working tree matches HEAD
> >>         $ git commit -a
> >>         ... message from status ...
> >>         THE ADVICE IN QUEWSTION COMES HERE!!!
> >> 
> >> 
> > Ok, I admit I didn't really think of that case, but that seems to me to be the
> > trivial case, which is unlikely to be encountered.  If you do a git cherry-pick and
> > have conflicts, you by definition don't have a commit that is resolved to empty...
> 
> Not at all unlikely, especially in a distributed world.
> 
> You may have seen two patches but they make sense as one so that you apply
> to one branch as one change, while the branch you are cherry-picking from
> may have these two changes as individual commits.  Neither of them will
> apply cleanly to your tree, and your resolution will be "keep mine---I
> already have this".
> 

Ok, fine. What do you say to my proposal regarding the splitting of the device dependent on how we were executed? It seems we can't use a single advice string in this case, as no matter which we choose there is a use case in which it fails to make sense. Neil

Junio C Hamano· Apr 10, 2012, 21:09 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
> ...  What do you say to my proposal regarding the splitting of the device
> dependent on how we were executed?  It seems we can't use a single advice string
> in this case, as no matter which we choose there is a use case in which it fails
> to make sense.

When your cherry-pick got --allow-empty, it should pass --allow-empty to its inner invocation of commit if it is picking an originally empty commit, and this advice will not trigger because commit will happily commit the no-change change.

When your cherry-pick got --allow-empty but not --keep-unnecessary-commit, its inner invocation of commit must not pass --allow-empty if it is _not_ picking an originally empty commit. Then the inner commit will fail if it auto resolves to no change, and the user sees the advice. The current advice text is appropriate for this case.

When your cherry-pick did not get either of these flags, its inner invocation of commit must not pass --allow-empty. The user sees the advice when the auto resolved result matches HEAD from the commit invoked by cherry-pick. The current advice text is fine for this case, as we say "possibly", not "we definitely know it was due to conflict resolution".

Or your cherry-pick may have failed due to a conflict, regardless of the options like --allow-empty or --keep-unnecessary-commit given to it, and the user may have run commit after resolving the conflict. The current advice text is fine for this case, too, as we say "possibly", and it indeed is what just happened.

So I do not think you need to change anything with respect to the advice message.

Am I missing some other cases?
Neil Horman· Apr 11, 2012, 00:44 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Tue, Apr 10, 2012 at 02:09:17PM -0700, Junio C Hamano wrote:
Show 35 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > ...  What do you say to my proposal regarding the splitting of the device
> > dependent on how we were executed?  It seems we can't use a single advice string
> > in this case, as no matter which we choose there is a use case in which it fails
> > to make sense.
> 
> When your cherry-pick got --allow-empty, it should pass --allow-empty to
> its inner invocation of commit if it is picking an originally empty
> commit, and this advice will not trigger because commit will happily
> commit the no-change change.
> 
> When your cherry-pick got --allow-empty but not --keep-unnecessary-commit,
> its inner invocation of commit must not pass --allow-empty if it is _not_
> picking an originally empty commit.  Then the inner commit will fail if it
> auto resolves to no change, and the user sees the advice.  The current
> advice text is appropriate for this case.
> 
> When your cherry-pick did not get either of these flags, its inner
> invocation of commit must not pass --allow-empty.  The user sees the
> advice when the auto resolved result matches HEAD from the commit invoked
> by cherry-pick.  The current advice text is fine for this case, as we say
> "possibly", not "we definitely know it was due to conflict resolution".
> 
> Or your cherry-pick may have failed due to a conflict, regardless of the
> options like --allow-empty or --keep-unnecessary-commit given to it, and
> the user may have run commit after resolving the conflict.  The current
> advice text is fine for this case, too, as we say "possibly", and it
> indeed is what just happened.
> 
> So I do not think you need to change anything with respect to the advice
> message.
> 
> Am I missing some other cases?
> 

No, you covered all the cases, but I disagree with your assertion that the advice is correct (or at least optimal) in any of these cases. If a cherry-pick without any options is preformed and the commit is empty (regardless of the reason), the advice given is that git commit --allow-empty should be used. With the addition of these new options, thats not true any longer. Instead of using git commit --allow-empty, you can use git cherry-pick --allow-empty.

Given your description above though, I'm ok rolling this back. Regardless of my disagreement, git commit --allow-empty still works just as well after the addition of these options, so while I still think its awkward to give git commit advice on the result of a git cherry-pick operation, its not any more problematic than the issues you've pointed out with my change. I'll roll this back in my next version and will look for a way to provide better advice in the future.

Regards Neil

> 
Junio C Hamano· Apr 11, 2012, 16:52 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
Show 6 quoted lines
> No, you covered all the cases, but I disagree with your assertion that the advice
> is correct (or at least optimal) in any of these cases. If a cherry-pick without
> any options is preformed and the commit is empty (regardless of the reason), the
> advice given is that git commit --allow-empty should be used.  With the addition
> of these new options, thats not true any longer.  Instead of using git commit
> --allow-empty, you can use git cherry-pick --allow-empty.

Sorry, I am confused. Do you mean that the sequence goes like this (with concrete examples of command line args)?

	$ git cherry-pick nh/empty-rebase
        ... stops because "git show nh/empty-rebase" is empty
        $ git cherry-pick --allow-empty
But that cannot be correct, without --continue [*1*], i.e.
	$ git cherry-pick --allow-empty --continue

no? I didn't check, but if the command without --continue in the above sequence does not error out, I think it is a bug.

I am actually OK with suggesting "git cherry-pick --continue", but then "cherry-pick --allow-empty" (or "--keep-unnecessary-commits") that punts and gives the control back to the user should leave enough clue for a later invocation of itself so that it can realize that the original invocation was made with "--allow-empty". In other words, I am OK if the interaction goes like this:

	$ git cherry-pick --keep-unnecessary-commits nh/empty-rebase
        ... stops due to a conflict
        $ edit builtin/revert.c
        ... the result ends up being empty
        $ git add -u ;# resolved
        $ git cherry-pick --continue
[Side note]

*1* It was an original UI mistake to make the users conclude a "git merge" that asked the user to help resolving the conflict with "git commit", which was inherited by "git cherry-pick" and "git revert", especially when these three commands are merely a special "possibly zero or one stoppage" case of more general sequencing commands like "am" and "rebase" that can stop zero or more times to ask the user for help and the way to resume them is to re-run the same command with "--continue" option (and without any other arguments), e.g.

	$ git am -3 ./+nh.mbox
        ... stops due to conflict and asks to resolve them
        $ edit builtin/revert.c
        $ git add builtin/revert.c
        $ git am --continue

and also discussed that in the longer-term it would be nice to teach the oddball commands to honor "--continue". "am" originally took "--resolved" (and it still does, and it will do so in the future) for the same purpose, and we taught it and "cherry-pick" and "revert" to honor "--continue". Probably we should start teaching "merge" to honor it as well to complete the vision.

Neil Horman· Apr 11, 2012, 18:29 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Wed, Apr 11, 2012 at 09:52:21AM -0700, Junio C Hamano wrote:
Show 16 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > No, you covered all the cases, but I disagree with your assertion that the advice
> > is correct (or at least optimal) in any of these cases. If a cherry-pick without
> > any options is preformed and the commit is empty (regardless of the reason), the
> > advice given is that git commit --allow-empty should be used.  With the addition
> > of these new options, thats not true any longer.  Instead of using git commit
> > --allow-empty, you can use git cherry-pick --allow-empty.
> 
> Sorry, I am confused.  Do you mean that the sequence goes like this (with
> concrete examples of command line args)?
> 
> 	$ git cherry-pick nh/empty-rebase
>         ... stops because "git show nh/empty-rebase" is empty
>         $ git cherry-pick --allow-empty
> 
No, currently what happens is the following:
$ git cherry-pick nh/empty-rebase
       ... either gets accepted as empty if the cherry-pick qualifies for
fast-forward, or stops if it doesn not, indicating the empty_cherry_pick advice
that git commit --allow-empty should be used.

While that advice is accurate, in that a git commit --allow-empty will accept the empty commit, it would seem (at least to me) preferable to offer guidance that git cherry-pick should be used instead in this case, because that is the command the user was issuing.

Show 7 quoted lines
> But that cannot be correct, without --continue [*1*], i.e.
> 
> 	$ git cherry-pick --allow-empty --continue
> 
> no?  I didn't check, but if the command without --continue in the above
> sequence does not error out, I think it is a bug.
> 

No, it errors out. I'm sorry to have confused you. The only point that I was trying to make here is that, when running git cherry-pick, its seems awkward to a user to get advice indicating that git commit --allow-empty should be run. My change was intended to resolve that so that advice no how to use cherry-pick options to avoid the error was issued instead. Thats all. I hadn't considered the fact that manual resolution of a cherry-pick (where getting advice about git commit makes more sense) was also a factor here

Show 14 quoted lines
> I am actually OK with suggesting "git cherry-pick --continue", but then
> "cherry-pick --allow-empty" (or "--keep-unnecessary-commits") that punts
> and gives the control back to the user should leave enough clue for a
> later invocation of itself so that it can realize that the original
> invocation was made with "--allow-empty".  In other words, I am OK if the
> interaction goes like this:
> 
> 	$ git cherry-pick --keep-unnecessary-commits nh/empty-rebase
>         ... stops due to a conflict
>         $ edit builtin/revert.c
>         ... the result ends up being empty
>         $ git add -u ;# resolved
>         $ git cherry-pick --continue
> 

No, Id rather not do that thanks. The intent of these options we really to automate the rebase process, so rebasing doesn't stop on empty commits. Using the model above seems to disagree with that.

Show 25 quoted lines
> 
> [Side note]
> 
> *1* It was an original UI mistake to make the users conclude a "git merge"
> that asked the user to help resolving the conflict with "git commit",
> which was inherited by "git cherry-pick" and "git revert", especially when
> these three commands are merely a special "possibly zero or one stoppage"
> case of more general sequencing commands like "am" and "rebase" that can
> stop zero or more times to ask the user for help and the way to resume
> them is to re-run the same command with "--continue" option (and without
> any other arguments), e.g.
> 
> 	$ git am -3 ./+nh.mbox
>         ... stops due to conflict and asks to resolve them
>         $ edit builtin/revert.c
>         $ git add builtin/revert.c
>         $ git am --continue
> 
> and also discussed that in the longer-term it would be nice to teach the
> oddball commands to honor "--continue".  "am" originally took "--resolved"
> (and it still does, and it will do so in the future) for the same purpose,
> and we taught it and "cherry-pick" and "revert" to honor "--continue".
> Probably we should start teaching "merge" to honor it as well to complete
> the vision.
> 

I won't pretend to fully understand the implications of what you said on your side note here, but yes, from the ways I've used merge in the past, allowing it to continue would be very nice I think. Neil

Junio C Hamano· Apr 11, 2012, 18:50 UTC · re: Neil Horman · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

Neil Horman <nhorman@tuxdriver.com> writes:
Show 10 quoted lines
>> But that cannot be correct, without --continue [*1*], i.e.
>> 
>> 	$ git cherry-pick --allow-empty --continue
>> 
>> no?  I didn't check, but if the command without --continue in the above
>> sequence does not error out, I think it is a bug.
>> 
> No, it errors out.  I'm sorry to have confused you.  The only point that I was
> trying to make here is that, when running git cherry-pick, its seems awkward to
> a user to get advice indicating that git commit --allow-empty should be run.

I was only saying that "git cherry-pick --allow-empty" is a *bad* suggestion because it does *not* work and errors out, and you seem to agree with me on that point. I also said I am OK if the suggestion for this case were to run "git cherry-pick --continue".

But you sound like you are disagreeing with me; I am not sure where you found what I said not agreeable. So I am not sure what to say at this point.

Neil Horman· Apr 11, 2012, 18:56 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/4] git-cherry-pick: add allow-empty option

On Wed, Apr 11, 2012 at 11:50:29AM -0700, Junio C Hamano wrote:
Show 22 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> >> But that cannot be correct, without --continue [*1*], i.e.
> >> 
> >> 	$ git cherry-pick --allow-empty --continue
> >> 
> >> no?  I didn't check, but if the command without --continue in the above
> >> sequence does not error out, I think it is a bug.
> >> 
> > No, it errors out.  I'm sorry to have confused you.  The only point that I was
> > trying to make here is that, when running git cherry-pick, its seems awkward to
> > a user to get advice indicating that git commit --allow-empty should be run.
> 
> I was only saying that "git cherry-pick --allow-empty" is a *bad*
> suggestion because it does *not* work and errors out, and you seem to
> agree with me on that point.  I also said I am OK if the suggestion for
> this case were to run "git cherry-pick --continue".
> 
> But you sound like you are disagreeing with me; I am not sure where you
> found what I said not agreeable.  So I am not sure what to say at this
> point.
> 

I'm sorry, I think I see where our mutual confusion is. git cherry-pick --allow-empty _does_ error out on its own. The advice that I rewrote was meant to imply that the cherry-pick command should have been rerun with the --allow-empty option, i.e.: git cherry-pick --allow-empty <commit>

I can see however, looking at from what I think was your point of view, how the advice would have been bad, because taken strictly as given, it would fail.

Its all moot however, I've reverted the advice in my tree here. As soon as I complete testing of the optimization/rewites to git_run_commit and is_original_commit_empty, I'll have another set for review.

Regards Neil

Neil Horman· Apr 10, 2012, 15:47 UTC · re: Neil Horman · lore

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

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/commit.c                  |    5 ++
 builtin/revert.c                  |    8 +++-
 sequencer.c                       |  106 ++++++++++++++++++++++++++++++++-----
 sequencer.h                       |    1 +
 5 files changed, 117 insertions(+), 15 deletions(-)
Show changes to 5 files +117 −15

Documentation/git-cherry-pick.txt, builtin/commit.c, builtin/revert.c, sequencer.c, sequencer.h

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
+	a previous commit are ignored.  To force the inclusion of those commits
+	use `--keep-redundant-commits`
+
+--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`
 
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
diff --git a/builtin/commit.c b/builtin/commit.c
index 0cd10ab..c386189 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -61,6 +61,11 @@ N_("The previous cherry-pick is empty.\n"
 "\n"
 "    git cherry-pick --allow-empty\n"
 "\n"
+"If the commit was made empty via conflict resolution, and you wish\n"
+"to keep the now-empty commit anyway, use:\n"
+"\n"
+"    git cherry-pick --keep-redundant-commits\n"
+"\n"
 "Otherwise, please use 'git reset'\n");
 
 static const char *use_message_buffer;
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..5d033db 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,102 @@ 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)
+static 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) {
+		const char *argv[] = { "diff-index", "--quiet", "--exit-code",
+					"--cached", "HEAD", NULL };
+
+		/*
+ 		 * If we run git diff-index with the above option and it returns
+ 		 * zero, then there have been no changes made to the index by
+ 		 * this patch, i.e. its empty.  Since our previous empty test
+ 		 * indicated that this patch was not created empty, its been made
+ 		 * redundant.  Since keep_if_made_empty is not set, we just skip
+ 		 * it
+ 		 */
+		if (run_command_v_opt(argv, RUN_GIT_CMD) == 0)
+			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");
 
-	args[i] = NULL;
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+	struct argv_array argv_array;
+	struct child_process cp;
+	char ptree[40], pptree[40];
+	int ret = 0;
+
+	argv_array_init(&argv_array);
+	memset(&cp, 0, sizeof(struct child_process));
+
+	argv_array_push(&argv_array, "rev-parse");
+	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));
+
+	cp.git_cmd = 1;
+	cp.no_stdin = 1;
+	cp.no_stderr = 1;
+	cp.out = -1;
+	cp.argv = argv_array.argv;
+
+	if (start_command(&cp) < 0)
+		goto out;
+
+	if (read_in_full(cp.out, ptree, sizeof(ptree)) < sizeof(ptree)) {
+		close(cp.out);
+		goto out;
+	}
+
+	close(cp.out);
+
+	finish_command(&cp);
+
+	argv_array_clear(&argv_array);
+
+	argv_array_push(&argv_array, "rev-parse");
+	argv_array_pushf(&argv_array, "%s^^{tree}", sha1_to_hex(commit->object.sha1));
+	cp.out = -1;
+	cp.argv = argv_array.argv;
+
+	if (start_command(&cp) < 0)
+		goto out;
+
+	if (read_in_full(cp.out, pptree, sizeof(pptree)) < sizeof(ptree)) {
+		close(cp.out);
+		goto out;
+	}
+
+	close(cp.out);
+
+	finish_command(&cp);
+
+	if (!strncmp(ptree, pptree, sizeof(ptree)))
+		ret = 1;
+out:
+	argv_array_clear(&argv_array);	
+	return ret;
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +366,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +492,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_original_commit_empty(commit);
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -435,7 +515,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		rerere(opts->allow_rerere_auto);
 	} else {
 		if (!opts->no_commit)
-			res = run_git_commit(defmsg, opts);
+			res = run_git_commit(defmsg, opts, empty_commit);
 	}
 
 	free_message(&msg);
diff --git a/sequencer.h b/sequencer.h
index e2cd725..862a79a 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int keep_if_made_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Apr 10, 2012, 17:04 UTC · re: Neil Horman · lore

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

Neil Horman <nhorman@tuxdriver.com> writes:
> +	/* keep_if_made_empty implies allow_empty */
> +	if (opts->keep_if_made_empty)
> +		opts->allow_empty = 1;
> +
 
OK.
Show 39 quoted lines
> diff --git a/sequencer.c b/sequencer.c
> index 71929ba..5d033db 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,102 @@ 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)
> +static 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) {
> +		const char *argv[] = { "diff-index", "--quiet", "--exit-code",
> +					"--cached", "HEAD", NULL };
> +
> +		/*
> + 		 * If we run git diff-index with the above option and it returns
> + 		 * zero, then there have been no changes made to the index by
> + 		 * this patch, i.e. its empty.  Since our previous empty test
> + 		 * indicated that this patch was not created empty, its been made
> + 		 * redundant.  Since keep_if_made_empty is not set, we just skip
> + 		 * it
> + 		 */
> +		if (run_command_v_opt(argv, RUN_GIT_CMD) == 0)
> +			return 0;

Wouldn't it be far simpler to do this without forking diff-index? I haven't followed the codepath leading to this, but it should be very easy to find a commit object that corresponds to the current HEAD and peek the tree object in it to find out the current tree, and because the original function is going to run an as-is "git commit", the tree you are going to commit should be available by calling cache_tree_update() like write-tree does. If they match, you are trying to create an empty commit. E.g. (error checking elided):

	unsigned char head_sha1[20];
	struct commit *head_commit;
	resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
        head_commit = lookup_commit(head_sha1);
        parse_commit(head_commit);
        if (!cache_tree_fully_valid(active_cache_tree))
		cache_tree_update(active_cache_tree, active_cache,
				active_nr, 0);
	return hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
Show 12 quoted lines
> +static int is_original_commit_empty(struct commit *commit)
> +{
> +	struct argv_array argv_array;
> +	struct child_process cp;
> +	char ptree[40], pptree[40];
> +	int ret = 0;
> +
> +	argv_array_init(&argv_array);
> +	memset(&cp, 0, sizeof(struct child_process));
> +
> +	argv_array_push(&argv_array, "rev-parse");
> +	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));

Likewise. You have the commit object, so just make sure it was parsed, and peek its tree object. Also do the same for its parent commit. Now you have two trees, so you can compare their object names. E.g. (error checking elided):

	const unsigned char *ptree_sha1;
	parse_commit(commit);
        if (commit->parents) {
		struct commit *parent = commit->parents->item;
                parse_commit(parent);
                ptree_sha1 = parent->tree->object.sha1;
	} else {
        	ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
        }
	return hashcmp(ptree_sha1, commit->tree->object.sha1);

The higher code structure of this patch looks good, but the lower level implementation details are done way too inefficiently.

Neil Horman· Apr 10, 2012, 18:25 UTC · re: Junio C Hamano · lore

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

On Tue, Apr 10, 2012 at 10:04:32AM -0700, Junio C Hamano wrote:
Show 88 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > +	/* keep_if_made_empty implies allow_empty */
> > +	if (opts->keep_if_made_empty)
> > +		opts->allow_empty = 1;
> > +
>  
> OK.
> 
> > diff --git a/sequencer.c b/sequencer.c
> > index 71929ba..5d033db 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,102 @@ 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)
> > +static 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) {
> > +		const char *argv[] = { "diff-index", "--quiet", "--exit-code",
> > +					"--cached", "HEAD", NULL };
> > +
> > +		/*
> > + 		 * If we run git diff-index with the above option and it returns
> > + 		 * zero, then there have been no changes made to the index by
> > + 		 * this patch, i.e. its empty.  Since our previous empty test
> > + 		 * indicated that this patch was not created empty, its been made
> > + 		 * redundant.  Since keep_if_made_empty is not set, we just skip
> > + 		 * it
> > + 		 */
> > +		if (run_command_v_opt(argv, RUN_GIT_CMD) == 0)
> > +			return 0;
> 
> Wouldn't it be far simpler to do this without forking diff-index?  I
> haven't followed the codepath leading to this, but it should be very easy
> to find a commit object that corresponds to the current HEAD and peek the
> tree object in it to find out the current tree, and because the original
> function is going to run an as-is "git commit", the tree you are going to
> commit should be available by calling cache_tree_update() like write-tree
> does.  If they match, you are trying to create an empty commit.  E.g. (error
> checking elided):
> 
> 	unsigned char head_sha1[20];
> 	struct commit *head_commit;
> 
> 	resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
>         head_commit = lookup_commit(head_sha1);
>         parse_commit(head_commit);
> 
>         if (!cache_tree_fully_valid(active_cache_tree))
> 		cache_tree_update(active_cache_tree, active_cache,
> 				active_nr, 0);
> 	return hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
> 
> > +static int is_original_commit_empty(struct commit *commit)
> > +{
> > +	struct argv_array argv_array;
> > +	struct child_process cp;
> > +	char ptree[40], pptree[40];
> > +	int ret = 0;
> > +
> > +	argv_array_init(&argv_array);
> > +	memset(&cp, 0, sizeof(struct child_process));
> > +
> > +	argv_array_push(&argv_array, "rev-parse");
> > +	argv_array_pushf(&argv_array, "%s^{tree}", sha1_to_hex(commit->object.sha1));
> 
> Likewise.  You have the commit object, so just make sure it was parsed,
> and peek its tree object.  Also do the same for its parent commit.  Now
> you have two trees, so you can compare their object names.  E.g. (error
> checking elided):
> 

Honestly, I wasn't aware there was a faster way. In my last version you suggested using git diff-index and git rev-parse (allbeit the latter was for use in the git-rebase script, and I just reused it). Regardless, I was going with your previous suggestions. I can re-do these to use these new suggestions.

Neil
Neil Horman· Apr 10, 2012, 15:47 UTC · re: Neil Horman · lore

[PATCH v3 3/4] git-cherry-pick: Add test to validate new options

Since we've added the --allow-empty and --keep-redundant-commits options to git cherry-pick we should also add a test to ensure that its working properly

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 t/t3505-cherry-pick-empty.sh |   31 ++++++++++++++++++++++++++++++-
 1 files changed, 30 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +30 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..9d419ae 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,28 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fourth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" && {
+		git cherry-pick empty-branch2
+		test "$?" = 1 
+	}
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master && {
+		git cherry-pick --allow-empty empty-branch2
+		test "$?" = 0
+	}
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+	git checkout master && {
+		git cherry-pick --keep-redundant-commits HEAD^
+		test "$?" = 0
+	}
+'
+
 test_done
-- 
1.7.7.6
Neil Horman· Apr 10, 2012, 15:47 UTC · re: Neil Horman · lore

[PATCH v3 4/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   35 ++++++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 55 insertions(+), 8 deletions(-)
Show changes to 4 files +55 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..040289c 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty" 
+then
+	# we have to do this the hard way.  git format-patch completely squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..597d60a 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,12 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	ptree=$(git rev-parse "$1"^{tree})
+	pptree=$(git rev-parse "$1"^^{tree})
+	return $(test "$ptree" = "$pptree")
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +197,18 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
+	if is_empty_commit $@ 
+	then
+		empty_args="--allow-empty"
+	fi
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +792,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	then
+		comment_out="# pick"
+	else
+		comment_out="pick"
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +821,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +871,15 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	cat >> "$todo" << EOF
+	# Note that commits which are empty at the time of rebasing are 
+	# commented out. 
+EOF
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Neil Horman· Apr 13, 2012, 18:45 UTC · re: Neil Horman · lore

[PATCH v5 0/4]Enhance git-rebases flexibiilty in handling empty commits

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --keep-redundant-commits, which allows the user to also keep commits that were made empty via conflict resolution. It also enhances git-rebase to add a --keep-empty option which enables rebases to preserve empty commits.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
V3)
	* reversed the --ignore-if-empty-logic to by default only keep initially
	empty commits
	* replaced --ignore-if-empty with --keep-redundant-commits, to allow
	empty commits that are made empty via conflict resolution, in addition
	to commits that were created as empty
	* reworked is_original_commit_empty to be more efficient and portable
	* Misc sylistic and spelling cleanups
V4)
	* Reverted the cherry-pick advice changes in V3 based on in-thread
	discussion
	* Rewrote my changes to is_original_commit_empty and run_git_commit to
	not have to fork, making them more efficient.
v5)
	* Additional help text clean up
	* Additional error checking added to run_git_commit code
	* Whitespace cleanup
	* Removed needed cache_tree freeing
	* Test case cleanup
	* Fixed regression in t3404 and t3416 - this turned out to be 
        a problem with the note that git rebase -i adds at the bottom
	of the rebase text.  It was inadvertently indented and caused the
	test fake editor to misread the commit template.  The indentation 
	has been corrected, and these two tests, as well as all the other
	expected tests pass now

-- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Neil Horman· Apr 13, 2012, 18:45 UTC · re: Neil Horman · lore

[PATCH v5 1/4] git-cherry-pick: add allow-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 17 insertions(+), 2 deletions(-)
Show changes to 4 files +17 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..730237a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty::
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Neil Horman· Apr 13, 2012, 18:45 UTC · re: Neil Horman · lore

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

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                       |   90 +++++++++++++++++++++++++++++++------
 sequencer.h                       |    1 +
 4 files changed, 95 insertions(+), 16 deletions(-)
Show changes to 4 files +95 −16

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 730237a..0c004e9 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. the commit recorded the
+	same tree as its parent).  Commits which are made empty due to a
+	previous commit are ignored.  To force the inclusion of those commits
+	use `--keep-redundant-commits`.
+
+--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`.
 
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
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..aefea66 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,82 @@ 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)
-{
-	/* 7 is max possible length of our args array including NULL */
-	const char *args[7];
-	int i = 0;
+static int run_git_commit(const char *defmsg, struct replay_opts *opts, int empty)
+{
+	struct argv_array array;
+	int rc;
+
+	if (!empty && !opts->keep_if_made_empty) {
+		unsigned char head_sha1[20];
+		struct commit *head_commit;
+
+		if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+			return error(_("Could not resolve HEAD commit\n"));
+
+		head_commit = lookup_commit(head_sha1);
+		if (!head_commit || parse_commit(head_commit))
+			return error(_("could not parse commit %s\n"),
+				     sha1_to_hex(head_commit->object.sha1));
+
+		if (!active_cache_tree)
+			active_cache_tree = cache_tree();
+
+		if (!cache_tree_fully_valid(active_cache_tree))
+			if (cache_tree_update(active_cache_tree, active_cache,
+					  active_nr, 0))
+				return error(_("Unable to update cache tree\n"));
+
+		rc = !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+
+		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");
+
+
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+	const unsigned char *ptree_sha1;
 
-	args[i] = NULL;
+	if (parse_commit(commit))
+		return error(_("Could not parse commit %s\n"),
+			     sha1_to_hex(commit->object.sha1));
+	if (commit->parents) {
+		struct commit *parent = commit->parents->item;
+		if (parse_commit(parent))
+			return error(_("Could not parse parent commit %s\n"),
+				sha1_to_hex(parent->object.sha1));
+		ptree_sha1 = parent->tree->object.sha1;
+	} else {
+		ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+	}
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	return !hashcmp(ptree_sha1, commit->tree->object.sha1);
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +346,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +472,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_original_commit_empty(commit);
+	if (empty_commit < 0)
+		return empty_commit;
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -435,7 +497,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		rerere(opts->allow_rerere_auto);
 	} else {
 		if (!opts->no_commit)
-			res = run_git_commit(defmsg, opts);
+			res = run_git_commit(defmsg, opts, empty_commit);
 	}
 
 	free_message(&msg);
diff --git a/sequencer.h b/sequencer.h
index e2cd725..862a79a 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int keep_if_made_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Clemens Buchacher· Apr 15, 2012, 10:42 UTC · re: Neil Horman · lore

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

On Fri, Apr 13, 2012 at 02:45:05PM -0400, Neil Horman wrote:
>
> +                     OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_if_made_empty, "keep redundant, empty commits"),

For consistency, I'd prefer if the variable name were the same as the option. Then I wouldn't have to keep the option<->variable translation in mind.

>
> +     if (!empty && !opts->keep_if_made_empty) {
[...]
> +                     return 0;
[...]
>       if (opts->allow_empty)
> +             argv_array_push(&array, "--allow-empty");
> +
> +     rc = run_command_v_opt(array.argv, RUN_GIT_CMD);

I find it a bit strange, that if we cherry-pick a commit that was already empty, we _do_ call git commit (and error out), but if we find a commit that is made empty, we do _not_ call git commit and quietly succeed (in not doing anything). But I suppose that is the legacy behavior?

> +
> +		rc = !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
> +
> +		if (rc)

rc is short for run_command, for which it stores the return value, no? Let's not abuse the variable like this and instead use the result directly:

 if (!hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1))

If you make the entire paragraph leading up to this a separate function, say index_is_empty(), then the above would read more naturally like this:

 if (!opts->keep_if_made_empty && !empty && index_is_empty())
> +			/*
> +			 * The head tree and the parent tree match
> +			 * meaning the commit is empty.  Since it wasn't created
Don't you mean the head tree and the index?
Show 28 quoted lines
> +			 * 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");
> +
> +
Why two newlines?
> +	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
> +	argv_array_clear(&array);
> +	return rc;
> +}
Looks good to me otherwise.
Neil Horman· Apr 16, 2012, 15:38 UTC · re: Clemens Buchacher · lore

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

On Sun, Apr 15, 2012 at 12:42:12PM +0200, Clemens Buchacher wrote:
Show 8 quoted lines
> On Fri, Apr 13, 2012 at 02:45:05PM -0400, Neil Horman wrote:
> >
> > +                     OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_if_made_empty, "keep redundant, empty commits"),
> 
> For consistency, I'd prefer if the variable name were the same as the
> option. Then I wouldn't have to keep the option<->variable translation
> in mind.
> 
Ok
Show 16 quoted lines
> >
> > +     if (!empty && !opts->keep_if_made_empty) {
> [...]
> > +                     return 0;
> [...]
> >       if (opts->allow_empty)
> > +             argv_array_push(&array, "--allow-empty");
> > +
> > +     rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
> 
> I find it a bit strange, that if we cherry-pick a commit that was
> already empty, we _do_ call git commit (and error out), but if we find a
> commit that is made empty, we do _not_ call git commit and quietly
> succeed (in not doing anything). But I suppose that is the legacy
> behavior?
> 

Correct, more or less. The legacy behavior is to call git commit unilaterally. With this change, we still do that, but we pass --allow-empty to the commit command, allowing it to preserve the empty commit. If we specify keep-redundant-commits, we skip the check to see if the new commit is empty and create the new (now empty) commit. The only change is that if we do not specify keep-redundant-commits, we check to see if the commit is made empty in git-cherry-pick and skip it if it is. We could, instead of returning prior to calling git-commit, use that test to override the keep_empty option below, so that we don't pass --allow-empty to git-commit instead. That would preserve the prior code path, but for no real advatage, as the outcome is the same, and this way saves us having to fork the git-commit command, which I think is adventageous.

Show 9 quoted lines
> > +
> > +		rc = !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
> > +
> > +		if (rc)
> 
> rc is short for run_command, for which it stores the return value, no?
> Let's not abuse the variable like this and instead use the result
> directly:
> 

No. rc is short for return code, generically used to represent the value to be returned from a function. Its used in this fashion in any number of places, both in git and any other project.

Show 14 quoted lines
>  if (!hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1))
> 
> If you make the entire paragraph leading up to this a separate function,
> say index_is_empty(), then the above would read more naturally like
> this:
> 
>  if (!opts->keep_if_made_empty && !empty && index_is_empty())
> 
> > +			/*
> > +			 * The head tree and the parent tree match
> > +			 * meaning the commit is empty.  Since it wasn't created
> 
> Don't you mean the head tree and the index?
> 
yeah.
Show 38 quoted lines
> > +			 * 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");
> > +
> > +
> 
> Why two newlines?
> 
> > +	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
> > +	argv_array_clear(&array);
> > +	return rc;
> > +}
> 
> Looks good to me otherwise.
> 
Clemens Buchacher· Apr 16, 2012, 22:10 UTC · re: Neil Horman · lore

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

On Mon, Apr 16, 2012 at 11:38:27AM -0400, Neil Horman wrote:
Show 16 quoted lines
> > 
> > I find it a bit strange, that if we cherry-pick a commit that was
> > already empty, we _do_ call git commit (and error out), but if we find a
> > commit that is made empty, we do _not_ call git commit and quietly
> > succeed (in not doing anything). But I suppose that is the legacy
> > behavior?
>
> Correct, more or less.  The legacy behavior is to call git commit unilaterally.
> [...] The only change is that if we do not specify
> keep-redundant-commits, we check to see if the commit is made empty in
> git-cherry-pick and skip it if it is.  We could, instead of returning prior to
> calling git-commit, use that test to override the keep_empty option below, so
> that we don't pass --allow-empty to git-commit instead.  That would preserve the
> prior code path, but for no real advatage, as the outcome is the same, and this
> way saves us having to fork the git-commit command, which I think is
> adventageous.

Except that the outcome is not the same. With and without your changes, git cherry-pick <empty-commit> fails. But with your changes, git cherry-pick <commit-will-become-empty> will succeed and do nothing, while before it would have failed exactly like git cherry-pick <empty-commit>.

So I am not arguing whether failing or skipping is the better default behavior. But the legacy behavior is consistent between the empty-commit and commit-will-become-empty cases. And if we change the behavior for one, why not also for the other?

Neil Horman· Apr 17, 2012, 10:43 UTC · re: Clemens Buchacher · lore

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

On Tue, Apr 17, 2012 at 12:10:18AM +0200, Clemens Buchacher wrote:
Show 29 quoted lines
> On Mon, Apr 16, 2012 at 11:38:27AM -0400, Neil Horman wrote:
> > > 
> > > I find it a bit strange, that if we cherry-pick a commit that was
> > > already empty, we _do_ call git commit (and error out), but if we find a
> > > commit that is made empty, we do _not_ call git commit and quietly
> > > succeed (in not doing anything). But I suppose that is the legacy
> > > behavior?
> >
> > Correct, more or less.  The legacy behavior is to call git commit unilaterally.
> > [...] The only change is that if we do not specify
> > keep-redundant-commits, we check to see if the commit is made empty in
> > git-cherry-pick and skip it if it is.  We could, instead of returning prior to
> > calling git-commit, use that test to override the keep_empty option below, so
> > that we don't pass --allow-empty to git-commit instead.  That would preserve the
> > prior code path, but for no real advatage, as the outcome is the same, and this
> > way saves us having to fork the git-commit command, which I think is
> > adventageous.
> 
> Except that the outcome is not the same. With and without your changes,
> git cherry-pick <empty-commit> fails. But with your changes, git
> cherry-pick <commit-will-become-empty> will succeed and do nothing,
> while before it would have failed exactly like git cherry-pick
> <empty-commit>.
> 
> So I am not arguing whether failing or skipping is the better default
> behavior. But the legacy behavior is consistent between the empty-commit
> and commit-will-become-empty cases.  And if we change the behavior for
> one, why not also for the other?
> 

Ah, I see what you're saying. Yes, hadn't thought of that, Ok, I'll change the logic to just toggle the addition of the --allow-empty flag to git commit. Neil

Junio C Hamano· Apr 17, 2012, 15:42 UTC · re: Clemens Buchacher · lore

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

Clemens Buchacher <drizzd@aon.at> writes:
Show 11 quoted lines
> On Mon, Apr 16, 2012 at 11:38:27AM -0400, Neil Horman wrote:
> ...
> Except that the outcome is not the same. With and without your changes,
> git cherry-pick <empty-commit> fails. But with your changes, git
> cherry-pick <commit-will-become-empty> will succeed and do nothing,
> while before it would have failed exactly like git cherry-pick
> <empty-commit>.
>
> So I am not arguing whether failing or skipping is the better default
> behavior. But the legacy behavior is consistent between the empty-commit
> and commit-will-become-empty cases.

Is that particular "consistency" a good one, though? If you had an empty commit in the original range, it is a lot more likely that it was an error that you would want to know about. You may be the kind of person who value an empty commit in your history, using it as some kind of a mark in the history, and in that case you would want to know that it is being discarded. On the other hand, if a commit that did something in the original context turns out to be unnecessary in the replayed context, that is not something you would ever want to keep in the replayed context, and erroring out and forcing you to say "yeah, I admit I do not want it" would just be annoying.

So "consistency" between the two would actually be a mistake that we may want to "break", I would think.

Clemens Buchacher· Apr 17, 2012, 21:37 UTC · re: Junio C Hamano · lore

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

On Tue, Apr 17, 2012 at 08:42:52AM -0700, Junio C Hamano wrote:
Show 24 quoted lines
> Clemens Buchacher <drizzd@aon.at> writes:
> 
> > On Mon, Apr 16, 2012 at 11:38:27AM -0400, Neil Horman wrote:
> > ...
> > Except that the outcome is not the same. With and without your changes,
> > git cherry-pick <empty-commit> fails. But with your changes, git
> > cherry-pick <commit-will-become-empty> will succeed and do nothing,
> > while before it would have failed exactly like git cherry-pick
> > <empty-commit>.
> >
> > So I am not arguing whether failing or skipping is the better default
> > behavior. But the legacy behavior is consistent between the empty-commit
> > and commit-will-become-empty cases.
> 
> Is that particular "consistency" a good one, though?  If you had an empty
> commit in the original range, it is a lot more likely that it was an error
> that you would want to know about.  You may be the kind of person who
> value an empty commit in your history, using it as some kind of a mark in
> the history, and in that case you would want to know that it is being
> discarded.  On the other hand, if a commit that did something in the
> original context turns out to be unnecessary in the replayed context, that
> is not something you would ever want to keep in the replayed context, and
> erroring out and forcing you to say "yeah, I admit I do not want it" would
> just be annoying.
Yeah, that makes sense.
> So "consistency" between the two would actually be a mistake that we may
> want to "break", I would think.

Agreed. But we should document the change in the commit message and maybe add a comment, because it is really strange to read

 if (!empty && index_empty)
 	return "it's all good"
 if (empty)
 	return "oh noes!"

without an explanation as to why empty and index_empty are so different. Neil, what do you think?

Neil Horman· Apr 18, 2012, 10:41 UTC · re: Clemens Buchacher · lore

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

On Tue, Apr 17, 2012 at 11:37:23PM +0200, Clemens Buchacher wrote:
Show 43 quoted lines
> On Tue, Apr 17, 2012 at 08:42:52AM -0700, Junio C Hamano wrote:
> > Clemens Buchacher <drizzd@aon.at> writes:
> > 
> > > On Mon, Apr 16, 2012 at 11:38:27AM -0400, Neil Horman wrote:
> > > ...
> > > Except that the outcome is not the same. With and without your changes,
> > > git cherry-pick <empty-commit> fails. But with your changes, git
> > > cherry-pick <commit-will-become-empty> will succeed and do nothing,
> > > while before it would have failed exactly like git cherry-pick
> > > <empty-commit>.
> > >
> > > So I am not arguing whether failing or skipping is the better default
> > > behavior. But the legacy behavior is consistent between the empty-commit
> > > and commit-will-become-empty cases.
> > 
> > Is that particular "consistency" a good one, though?  If you had an empty
> > commit in the original range, it is a lot more likely that it was an error
> > that you would want to know about.  You may be the kind of person who
> > value an empty commit in your history, using it as some kind of a mark in
> > the history, and in that case you would want to know that it is being
> > discarded.  On the other hand, if a commit that did something in the
> > original context turns out to be unnecessary in the replayed context, that
> > is not something you would ever want to keep in the replayed context, and
> > erroring out and forcing you to say "yeah, I admit I do not want it" would
> > just be annoying.
> 
> Yeah, that makes sense.
> 
> > So "consistency" between the two would actually be a mistake that we may
> > want to "break", I would think.
> 
> Agreed. But we should document the change in the commit message and
> maybe add a comment, because it is really strange to read
> 
>  if (!empty && index_empty)
>  	return "it's all good"
> 
>  if (empty)
>  	return "oh noes!"
> 
> without an explanation as to why empty and index_empty are so different.
> Neil, what do you think?
> 

Well, I've got the comment in the code indicating what were doing, but sure, I can be more vebose in the commit message about whats going on. I can probably rename the empty variable to indicate its meaning a bit more clearly and make that code more readable. Neil

Neil Horman· Apr 13, 2012, 18:45 UTC · re: Neil Horman · lore

[PATCH v5 3/4] git-cherry-pick: Add test to validate new options

Since we've added the --allow-empty and --keep-redundant-commits options to git cherry-pick we should also add a test to ensure that its working properly.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 t/t3505-cherry-pick-empty.sh |   28 +++++++++++++++++++++++++++-
 1 files changed, 27 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +27 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..5e3ad65 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,25 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fourth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" && {
+		test_must_fail git cherry-pick empty-branch2
+	}
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master && {
+		git cherry-pick --allow-empty empty-branch2
+	}
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+	git checkout master && {
+		git cherry-pick --keep-redundant-commits HEAD^
+	}
+'
+
 test_done
-- 
1.7.7.6
Clemens Buchacher· Apr 15, 2012, 09:39 UTC · re: Neil Horman · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Fri, Apr 13, 2012 at 02:45:06PM -0400, Neil Horman wrote:
Show 9 quoted lines
>  
> +test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
> +	git checkout master &&
> +	echo fourth >> file2 &&
> +	git add file2 &&
> +	git commit -m "fourth" && {
> +		test_must_fail git cherry-pick empty-branch2
> +	}
> +'
You don't need the braces. The same below.
Show 12 quoted lines
> +
> +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> +	git checkout master && {
> +		git cherry-pick --allow-empty empty-branch2
> +	}
> +'
> +
> +test_expect_success 'cherry pick with --keep-redundant-commits' '
> +	git checkout master && {
> +		git cherry-pick --keep-redundant-commits HEAD^
> +	}
> +'

And the expected result is that the HEAD commit is not removed, right? You should check for that as well.

Also, please checkout empty-branch2^0 first, in order to make the test independent of its predecessor.

Neil Horman· Apr 16, 2012, 11:04 UTC · re: Clemens Buchacher · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
Show 32 quoted lines
> On Fri, Apr 13, 2012 at 02:45:06PM -0400, Neil Horman wrote:
> >  
> > +test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
> > +	git checkout master &&
> > +	echo fourth >> file2 &&
> > +	git add file2 &&
> > +	git commit -m "fourth" && {
> > +		test_must_fail git cherry-pick empty-branch2
> > +	}
> > +'
> 
> You don't need the braces. The same below.
> 
> > +
> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> > +	git checkout master && {
> > +		git cherry-pick --allow-empty empty-branch2
> > +	}
> > +'
> > +
> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
> > +	git checkout master && {
> > +		git cherry-pick --keep-redundant-commits HEAD^
> > +	}
> > +'
> 
> And the expected result is that the HEAD commit is not removed, right?
> You should check for that as well.
> 
> Also, please checkout empty-branch2^0 first, in order to make the test
> independent of its predecessor.
> 

ACK, I'll fix these up, thanks Neil

Neil Horman· Apr 16, 2012, 16:14 UTC · re: Clemens Buchacher · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
Show 13 quoted lines
> On Fri, Apr 13, 2012 at 02:45:06PM -0400, Neil Horman wrote:
> >  
> > +test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
> > +	git checkout master &&
> > +	echo fourth >> file2 &&
> > +	git add file2 &&
> > +	git commit -m "fourth" && {
> > +		test_must_fail git cherry-pick empty-branch2
> > +	}
> > +'
> 
> You don't need the braces. The same below.
> 
Ack
Show 19 quoted lines
> > +
> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> > +	git checkout master && {
> > +		git cherry-pick --allow-empty empty-branch2
> > +	}
> > +'
> > +
> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
> > +	git checkout master && {
> > +		git cherry-pick --keep-redundant-commits HEAD^
> > +	}
> > +'
> 
> And the expected result is that the HEAD commit is not removed, right?
> You should check for that as well.
> 
> Also, please checkout empty-branch2^0 first, in order to make the test
> independent of its predecessor.
> 

Not sure I follow what your saying here. The expected result with both of these tests is that a new commit is created, referencing the current HEAD as the new HEAD's parent. We could check that the current HEAD is note removed (ostensibly by recoding the value of the current head and comparing it to HEAD^ after the cherry pick, but that seems like expected behavior for any command that creates a new commit, yet we don't check that anywhere else. Why is here different? Or do you mean something else?

As for the checkout of empty-branch2^0, whats the purpose? I'm just going to checkout master right after that, making it moot. The purpose of the test is to apply a commit that is already in the working tree's history, ensuring that it resolves to an empty commit. Theres nothing really dependent on the prior test there. If we edit the tests significantly, the contents of what that commit is may change, but thats not overly relevant, as the result will still be the same (an empty commit).

Neil
 
Junio C Hamano· Apr 16, 2012, 16:35 UTC · re: Neil Horman · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

Neil Horman <nhorman@tuxdriver.com> writes:
Show 23 quoted lines
> On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
> ...
>> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
>> > +	git checkout master && {
>> > +		git cherry-pick --allow-empty empty-branch2
>> > +	}
>> > +'
>> > +
>> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
>> > +	git checkout master && {
>> > +		git cherry-pick --keep-redundant-commits HEAD^
>> > +	}
>> > +'
>> 
>> And the expected result is that the HEAD commit is not removed, right?
>> You should check for that as well.
>> 
>> Also, please checkout empty-branch2^0 first, in order to make the test
>> independent of its predecessor.
>
> Not sure I follow what your saying here.  The expected result with both of these
> tests is that a new commit is created, referencing the current HEAD as the new
> HEAD's parent.

If the request were "checkout master^0 first" I would understand. The precondition for the second test will be different depending on the first one succeeds or not. Perhaps that is what Clemens meant?

Neil Horman· Apr 16, 2012, 16:50 UTC · re: Junio C Hamano · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Mon, Apr 16, 2012 at 09:35:12AM -0700, Junio C Hamano wrote:
Show 30 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
> > ...
> >> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> >> > +	git checkout master && {
> >> > +		git cherry-pick --allow-empty empty-branch2
> >> > +	}
> >> > +'
> >> > +
> >> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
> >> > +	git checkout master && {
> >> > +		git cherry-pick --keep-redundant-commits HEAD^
> >> > +	}
> >> > +'
> >> 
> >> And the expected result is that the HEAD commit is not removed, right?
> >> You should check for that as well.
> >> 
> >> Also, please checkout empty-branch2^0 first, in order to make the test
> >> independent of its predecessor.
> >
> > Not sure I follow what your saying here.  The expected result with both of these
> > tests is that a new commit is created, referencing the current HEAD as the new
> > HEAD's parent.
> 
> If the request were "checkout master^0 first" I would understand.  The
> precondition for the second test will be different depending on the first
> one succeeds or not.  Perhaps that is what Clemens meant?
> 

Perhaps, but if so, I'm still not sure how a checkout of empty-branch2^0 affects these tests at all, nor do I grok the relevance to ensuring that the HEAD commit wasn't removed (as AIUI, cherry pick never does that anyway). Clement, can you clarify your thoughts here please? Neil

Clemens Buchacher· Apr 16, 2012, 21:42 UTC · re: Neil Horman · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Mon, Apr 16, 2012 at 12:50:24PM -0400, Neil Horman wrote:
Show 35 quoted lines
> On Mon, Apr 16, 2012 at 09:35:12AM -0700, Junio C Hamano wrote:
> > Neil Horman <nhorman@tuxdriver.com> writes:
> > 
> > > On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
> > > ...
> > >> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> > >> > +	git checkout master && {
> > >> > +		git cherry-pick --allow-empty empty-branch2
> > >> > +	}
> > >> > +'
> > >> > +
> > >> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
> > >> > +	git checkout master && {
> > >> > +		git cherry-pick --keep-redundant-commits HEAD^
> > >> > +	}
> > >> > +'
> > >> 
> > >> And the expected result is that the HEAD commit is not removed, right?
> > >> You should check for that as well.
> > >> 
> > >> Also, please checkout empty-branch2^0 first, in order to make the test
> > >> independent of its predecessor.
> > >
> > > Not sure I follow what your saying here.  The expected result with both of these
> > > tests is that a new commit is created, referencing the current HEAD as the new
> > > HEAD's parent.
> > 
> > If the request were "checkout master^0 first" I would understand.  The
> > precondition for the second test will be different depending on the first
> > one succeeds or not.  Perhaps that is what Clemens meant?
> > 
> Perhaps, but if so, I'm still not sure how a checkout of empty-branch2^0 affects
> these tests at all, nor do I grok the relevance to ensuring that the HEAD commit
> wasn't removed (as AIUI, cherry pick never does that anyway).  Clement, can you
> clarify your thoughts here please?

It seems that I was implying a lot more than I realized. What I meant was that master and empty-branch2 are equivalent for the purposes of that test (empty-branch2^ also is a non-empty commit [*1*]), but while master is a moving target, empty-branch2 is untouched.

However, I just notice that empty-branch2 is also the root commit, so maybe this will not work after all. But that should be easy to fix.

And now I am also wondering why we have two tests for cherry picking an empty commit without --allow-empty (the one that you added and the one that was there before). Is the non-ff part significant and if so, how? And why don't we need to test fast-forward cherry-pick with --allow-empty?

Neil Horman· Apr 17, 2012, 10:56 UTC · re: Clemens Buchacher · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Mon, Apr 16, 2012 at 11:42:49PM +0200, Clemens Buchacher wrote:
Show 42 quoted lines
> On Mon, Apr 16, 2012 at 12:50:24PM -0400, Neil Horman wrote:
> > On Mon, Apr 16, 2012 at 09:35:12AM -0700, Junio C Hamano wrote:
> > > Neil Horman <nhorman@tuxdriver.com> writes:
> > > 
> > > > On Sun, Apr 15, 2012 at 11:39:35AM +0200, Clemens Buchacher wrote:
> > > > ...
> > > >> > +test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
> > > >> > +	git checkout master && {
> > > >> > +		git cherry-pick --allow-empty empty-branch2
> > > >> > +	}
> > > >> > +'
> > > >> > +
> > > >> > +test_expect_success 'cherry pick with --keep-redundant-commits' '
> > > >> > +	git checkout master && {
> > > >> > +		git cherry-pick --keep-redundant-commits HEAD^
> > > >> > +	}
> > > >> > +'
> > > >> 
> > > >> And the expected result is that the HEAD commit is not removed, right?
> > > >> You should check for that as well.
> > > >> 
> > > >> Also, please checkout empty-branch2^0 first, in order to make the test
> > > >> independent of its predecessor.
> > > >
> > > > Not sure I follow what your saying here.  The expected result with both of these
> > > > tests is that a new commit is created, referencing the current HEAD as the new
> > > > HEAD's parent.
> > > 
> > > If the request were "checkout master^0 first" I would understand.  The
> > > precondition for the second test will be different depending on the first
> > > one succeeds or not.  Perhaps that is what Clemens meant?
> > > 
> > Perhaps, but if so, I'm still not sure how a checkout of empty-branch2^0 affects
> > these tests at all, nor do I grok the relevance to ensuring that the HEAD commit
> > wasn't removed (as AIUI, cherry pick never does that anyway).  Clement, can you
> > clarify your thoughts here please?
> 
> It seems that I was implying a lot more than I realized. What I meant
> was that master and empty-branch2 are equivalent for the purposes of
> that test (empty-branch2^ also is a non-empty commit [*1*]), but while
> master is a moving target, empty-branch2 is untouched. 
> 

for the purposes of the --keep-redundant-commits however, the target is irrelevant. The only requirement is that we cherry-pick a commit that is guaranteed to become empty when applied. We certainly could do that on empty branch2, but theres no advantage to doing so, and given that every other test attempts to cherry-pick to master, I rather like the consistency.

> However, I just notice that empty-branch2 is also the root commit, so
> maybe this will not work after all. But that should be easy to fix.

It is easy to fix, given your clarified description above, its just that IMO, its not broken.

Show 6 quoted lines
> 
> And now I am also wondering why we have two tests for cherry picking an
> empty commit without --allow-empty (the one that you added and the one
> that was there before). Is the non-ff part significant and if so, how?
> And why don't we need to test fast-forward cherry-pick with
> --allow-empty?

the difference is that the initial empty-branch doesn't just hold an empty commit, but also contains no commit log entry on that empty commit, which git-cherry-pick errors out on in the 'cherry-pick a commit with an empty message' test, and the 'cherry-pick an empty commit' test. I could modify the --allow-empty switch to include commits with no log message, but I didn't want to open that can of worms.

The test separation beyond that is the difference between a failing and a successful test with an empty commit that still has a changelog entry.

As far as the ff logic goes. If a cherry pick qualifies for fast forwarding, then empty commits are automatically are allowed already. Its only if a cherry pick cannot be fast forwarded that its empty status is considered, hence the creation of the 'fourth' commit in the new tests to ensure that the cherry pick doesn't qualify as a fast forward.

Neil
 
> 
Clemens Buchacher· Apr 17, 2012, 21:38 UTC · re: Neil Horman · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Tue, Apr 17, 2012 at 06:56:04AM -0400, Neil Horman wrote:
Show 10 quoted lines
> On Mon, Apr 16, 2012 at 11:42:49PM +0200, Clemens Buchacher wrote:
> > 
> > It seems that I was implying a lot more than I realized. What I meant
> > was that master and empty-branch2 are equivalent for the purposes of
> > that test (empty-branch2^ also is a non-empty commit [*1*]), but while
> > master is a moving target, empty-branch2 is untouched. 
> > 
> for the purposes of the --keep-redundant-commits however, the target is
> irrelevant.  The only requirement is that we cherry-pick a commit that is
> guaranteed to become empty when applied.
That we agree on.
> We certainly could do that on empty branch2, but theres no advantage
> to doing so,

The advantage is that I do not have to read the other tests in order to understand what this test does, because contrary to the master branch, they do not modify empty-branch2.

> and given that every other test attempts to cherry-pick to master, I
> rather like the consistency.
We could also consistently not use the master branch.
Show 5 quoted lines
> > However, I just notice that empty-branch2 is also the root commit, so
> > maybe this will not work after all. But that should be easy to fix.
>
> It is easy to fix, given your clarified description above, its just that IMO,
> its not broken.

Well, I don't mind too badly if this doesn't go may way. But I hope that I managed at least to explain my point.

Neil Horman· Apr 18, 2012, 10:48 UTC · re: Clemens Buchacher · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Tue, Apr 17, 2012 at 11:38:52PM +0200, Clemens Buchacher wrote:
Show 21 quoted lines
> On Tue, Apr 17, 2012 at 06:56:04AM -0400, Neil Horman wrote:
> > On Mon, Apr 16, 2012 at 11:42:49PM +0200, Clemens Buchacher wrote:
> > > 
> > > It seems that I was implying a lot more than I realized. What I meant
> > > was that master and empty-branch2 are equivalent for the purposes of
> > > that test (empty-branch2^ also is a non-empty commit [*1*]), but while
> > > master is a moving target, empty-branch2 is untouched. 
> > > 
> > for the purposes of the --keep-redundant-commits however, the target is
> > irrelevant.  The only requirement is that we cherry-pick a commit that is
> > guaranteed to become empty when applied.
> 
> That we agree on.
> 
> > We certainly could do that on empty branch2, but theres no advantage
> > to doing so,
> 
> The advantage is that I do not have to read the other tests in order to
> understand what this test does, because contrary to the master branch,
> they do not modify empty-branch2.
> 

But empty-branch2, as the name implies, doesn't have a non-empty commit on it yet, and so theres nothing to cherry-pick on that branch that would 'become' empty. We could fix that of course, but I'd worry that that would look odd against the other tests.

Show 5 quoted lines
> > and given that every other test attempts to cherry-pick to master, I
> > rather like the consistency.
> 
> We could also consistently not use the master branch.
> 

Yes, This would be my perferred solution I think. That way we clarify the test to your satisfaction and keep the consistency of the test methodology.

Show 9 quoted lines
> > > However, I just notice that empty-branch2 is also the root commit, so
> > > maybe this will not work after all. But that should be easy to fix.
> >
> > It is easy to fix, given your clarified description above, its just that IMO,
> > its not broken.
> 
> Well, I don't mind too badly if this doesn't go may way. But I hope that
> I managed at least to explain my point.
> 

Yes, absolutely. How about this: Given that we agree on our ability to consistently not use master above, what if we table this discussion for now, leave the tests as they are, and fix them all up in a separate patch set? I don't mind making the change your requesting at all, but I'd rather do it as part of modifying all the tests not to use master, and doing it in a separate changeset, so we don't convolute what this series is doing. Does that sound reasonable? Neil

Clemens Buchacher· Apr 18, 2012, 18:34 UTC · re: Neil Horman · lore

Re: [PATCH v5 3/4] git-cherry-pick: Add test to validate new options

On Wed, Apr 18, 2012 at 06:48:59AM -0400, Neil Horman wrote:
Show 5 quoted lines
>
> Given that we agree on our ability to consistently not use master
> above, what if we table this discussion for now, leave the tests as
> they are, and fix them all up in a separate patch set? Does that sound
> reasonable?
Absolutely.
Neil Horman· Apr 13, 2012, 18:45 UTC · re: Neil Horman · lore

[PATCH v5 4/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   32 +++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 52 insertions(+), 8 deletions(-)
Show changes to 4 files +52 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..04d8941 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+	# we have to do this the hard way.  git format-patch completely squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..e3940be 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,12 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	ptree=$(git rev-parse "$1"^{tree})
+	pptree=$(git rev-parse "$1"^^{tree})
+	return $(test "$ptree" = "$pptree")
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +197,18 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
+	if is_empty_commit $@
+	then
+		empty_args="--allow-empty"
+	fi
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +792,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	then
+		comment_out="# pick"
+	else
+		comment_out="pick"
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +821,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +871,12 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	echo "# Note that empty commits are commented out" >> "$todo"
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Clemens Buchacher· Apr 15, 2012, 09:33 UTC · re: Neil Horman · lore

Re: [PATCH v5 4/4] git-rebase: add keep_empty flag

On Fri, Apr 13, 2012 at 02:45:07PM -0400, Neil Horman wrote:
>
> Add a command line switch to git-rebase to allow a user the ability to specify
> that they want to keep any commits in a series that are empty.
Thanks. That should be useful.
Show 6 quoted lines
> +is_empty_commit() {
> +     ptree=$(git rev-parse "$1"^{tree})
> +     pptree=$(git rev-parse "$1"^^{tree})
> +     return $(test "$ptree" = "$pptree")
> +}
> +

What's the extra leading 'p' for? Any reason not to use 'tree' and 'ptree'?

Show 9 quoted lines
>  pick_one () {
>  	ff=--ff
> +
> +	if is_empty_commit $@
> +	then
> +		empty_args="--allow-empty"
> +	fi
> +
>  	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac

You do not handle the case where pick_one is called with -n. I think you need to move the case statement in front and then call is_empty_commit $sha1.

Not that it matters after this change, but in general using $@ without quotes looks wrong to my eyes.

Show 16 quoted lines
> @@ -780,9 +792,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
>  	sed -n "s/^>//p" |
>  while read -r shortsha1 rest
>  do
> +
> +	if test -z "$keep_empty" && is_empty_commit $shortsha1
> +	then
> +		comment_out="# pick"
> +	else
> +		comment_out="pick"
> +	fi
> +
>  	if test t != "$preserve_merges"
>  	then
> -		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
> +		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
How about setting comment_out="# " or comment_out="" instead and then
 printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
That would read more natural to me.
Neil Horman· Apr 16, 2012, 16:46 UTC · re: Clemens Buchacher · lore

Re: [PATCH v5 4/4] git-rebase: add keep_empty flag

On Sun, Apr 15, 2012 at 11:33:03AM +0200, Clemens Buchacher wrote:
Show 17 quoted lines
> On Fri, Apr 13, 2012 at 02:45:07PM -0400, Neil Horman wrote:
> >
> > Add a command line switch to git-rebase to allow a user the ability to specify
> > that they want to keep any commits in a series that are empty.
> 
> Thanks. That should be useful.
> 
> > +is_empty_commit() {
> > +     ptree=$(git rev-parse "$1"^{tree})
> > +     pptree=$(git rev-parse "$1"^^{tree})
> > +     return $(test "$ptree" = "$pptree")
> > +}
> > +
> 
> What's the extra leading 'p' for? Any reason not to use 'tree' and
> 'ptree'?
> 

Hold-over from when I did this in C (pointer to tree vs. pointer to parent tree). I can remove the p though, as you right, it makes less sense in a shell

Show 17 quoted lines
> >  pick_one () {
> >  	ff=--ff
> > +
> > +	if is_empty_commit $@
> > +	then
> > +		empty_args="--allow-empty"
> > +	fi
> > +
> >  	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
> 
> You do not handle the case where pick_one is called with -n. I think you
> need to move the case statement in front and then call is_empty_commit
> $sha1.
> 
> Not that it matters after this change, but in general using $@ without
> quotes looks wrong to my eyes.
> 
Ack, to both points.
Show 22 quoted lines
> > @@ -780,9 +792,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
> >  	sed -n "s/^>//p" |
> >  while read -r shortsha1 rest
> >  do
> > +
> > +	if test -z "$keep_empty" && is_empty_commit $shortsha1
> > +	then
> > +		comment_out="# pick"
> > +	else
> > +		comment_out="pick"
> > +	fi
> > +
> >  	if test t != "$preserve_merges"
> >  	then
> > -		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
> > +		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
> 
> How about setting comment_out="# " or comment_out="" instead and then
> 
>  printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
> 
> That would read more natural to me.
Yeah, I can do that
> 
Neil Horman· Apr 17, 2012, 18:20 UTC · re: Neil Horman · lore

[PATCH v6 0/4] Enhance git-rebases flexibiilty in handling empty commits

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --keep-redundant-commits, which allows the user to also keep commits that were made empty via conflict resolution. It also enhances git-rebase to add a --keep-empty option which enables rebases to preserve empty commits.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
V3)
	* reversed the --ignore-if-empty-logic to by default only keep initially
	empty commits
	* replaced --ignore-if-empty with --keep-redundant-commits, to allow
	empty commits that are made empty via conflict resolution, in addition
	to commits that were created as empty
	* reworked is_original_commit_empty to be more efficient and portable
	* Misc sylistic and spelling cleanups
V4)
	* Reverted the cherry-pick advice changes in V3 based on in-thread
	discussion
	* Rewrote my changes to is_original_commit_empty and run_git_commit to
	not have to fork, making them more efficient.
v5)
	* Additional help text clean up
	* Additional error checking added to run_git_commit code
	* Whitespace cleanup
	* Removed needed cache_tree freeing
	* Test case cleanup
	* Fixed regression in t3404 and t3416 - this turned out to be 
        a problem with the note that git rebase -i adds at the bottom
	of the rebase text.  It was inadvertently indented and caused the
	test fake editor to misread the commit template.  The indentation 
	has been corrected, and these two tests, as well as all the other
	expected tests pass now
v6)
	* synced keeep-redundant-commits option and variable name
	* fixed up some comment terminology
	* minor newline cleanup
	* Removed some unneeded braces from test code
	* minor syntatic cleanup in rebase scripts
	* Refactored empty index checking to make run_git_commit more readable
	I was also going to change the logic so that it operated more like git
	cherry-pick did before the patchset, but Junio's comments made me think
	the new logic was preferable.
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Neil Horman· Apr 17, 2012, 18:20 UTC · re: Neil Horman · lore

[PATCH v6 1/4] git-cherry-pick: add allow-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 17 insertions(+), 2 deletions(-)
Show changes to 4 files +17 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..730237a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty::
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Neil Horman· Apr 17, 2012, 18:20 UTC · re: Neil Horman · lore

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

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                       |   97 ++++++++++++++++++++++++++++++++-----
 sequencer.h                       |    1 +
 4 files changed, 103 insertions(+), 15 deletions(-)
Show changes to 4 files +103 −15

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 730237a..0c004e9 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. the commit recorded the
+	same tree as its parent).  Commits which are made empty due to a
+	previous commit are ignored.  To force the inclusion of those commits
+	use `--keep-redundant-commits`.
+
+--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`.
 
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
diff --git a/builtin/revert.c b/builtin/revert.c
index 06b00e6..f135502 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_redundant_commits, "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_redundant_commits)
+		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..acc9c6d 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"
 
@@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	return !clean;
 }
 
+static int is_index_unchanged()
+{
+	unsigned char head_sha1[20];
+	struct commit *head_commit;
+
+	if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+		return error(_("Could not resolve HEAD commit\n"));
+
+	head_commit = lookup_commit(head_sha1);
+	if (!head_commit || parse_commit(head_commit))
+		return error(_("could not parse commit %s\n"),
+			     sha1_to_hex(head_commit->object.sha1));
+
+	if (!active_cache_tree)
+		active_cache_tree = cache_tree();
+
+	if (!cache_tree_fully_valid(active_cache_tree))
+		if (cache_tree_update(active_cache_tree, active_cache,
+				  active_nr, 0))
+			return error(_("Unable to update cache tree\n"));
+
+	return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
 /*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
@@ -258,26 +283,67 @@ 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)
+static 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;
+	int index_unchanged = is_index_unchanged();
+
+	/*
+	 * if index_unchanged is < 0, then we encountered an error
+	 * trying to parse HEAD or the active_cache_tree, so bail out
+	 */
+	if (index_unchanged < 0)
+		return index_unchanged;
+
+	if (!empty && !opts->keep_redundant_commits && index_unchanged)
+			/*
+			 * The head tree and the index 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, we can just return
+			 * here, skipping this commit
+			 */
+			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");
+
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
 
-	args[i] = NULL;
+static int is_original_commit_empty(struct commit *commit)
+{
+	const unsigned char *ptree_sha1;
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	if (parse_commit(commit))
+		return error(_("Could not parse commit %s\n"),
+			     sha1_to_hex(commit->object.sha1));
+	if (commit->parents) {
+		struct commit *parent = commit->parents->item;
+		if (parse_commit(parent))
+			return error(_("Could not parse parent commit %s\n"),
+				sha1_to_hex(parent->object.sha1));
+		ptree_sha1 = parent->tree->object.sha1;
+	} else {
+		ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+	}
+
+	return !hashcmp(ptree_sha1, commit->tree->object.sha1);
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +355,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +481,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_original_commit_empty(commit);
+	if (empty_commit < 0)
+		return empty_commit;
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -435,7 +506,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		rerere(opts->allow_rerere_auto);
 	} else {
 		if (!opts->no_commit)
-			res = run_git_commit(defmsg, opts);
+			res = run_git_commit(defmsg, opts, empty_commit);
 	}
 
 	free_message(&msg);
diff --git a/sequencer.h b/sequencer.h
index e2cd725..aa5f17c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int keep_redundant_commits;
 
 	int mainline;
 
-- 
1.7.7.6
Clemens Buchacher· Apr 17, 2012, 21:45 UTC · re: Neil Horman · lore

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

On Tue, Apr 17, 2012 at 02:20:07PM -0400, Neil Horman wrote:
Show 11 quoted lines
>
> +	if (!empty && !opts->keep_redundant_commits && index_unchanged)
> +			/*
> +			 * The head tree and the index 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, we can just return
> +			 * here, skipping this commit
> +			 */
> +			return 0;
You can remove one level of indentation (yay!).
Neil Horman· Apr 18, 2012, 10:49 UTC · re: Clemens Buchacher · lore

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

On Tue, Apr 17, 2012 at 11:45:22PM +0200, Clemens Buchacher wrote:
Show 15 quoted lines
> On Tue, Apr 17, 2012 at 02:20:07PM -0400, Neil Horman wrote:
> >
> > +	if (!empty && !opts->keep_redundant_commits && index_unchanged)
> > +			/*
> > +			 * The head tree and the index 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, we can just return
> > +			 * here, skipping this commit
> > +			 */
> > +			return 0;
> 
> You can remove one level of indentation (yay!).
>

Doh! Thanks. Neil

Neil Horman· Apr 17, 2012, 18:20 UTC · re: Neil Horman · lore

[PATCH v6 3/4] git-cherry-pick: Add test to validate new options

Since we've added the --allow-empty and --keep-redundant-commits options to git cherry-pick we should also add a test to ensure that its working properly.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 t/t3505-cherry-pick-empty.sh |   25 ++++++++++++++++++++++++-
 1 files changed, 24 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +24 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..d513127 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,22 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fourth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" &&
+	test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master &&
+	git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+	git checkout master &&
+	git cherry-pick --keep-redundant-commits HEAD^
+'
+
 test_done
-- 
1.7.7.6
Neil Horman· Apr 17, 2012, 18:20 UTC · re: Neil Horman · lore

[PATCH v6 4/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   33 ++++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 53 insertions(+), 8 deletions(-)
Show changes to 4 files +53 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..04d8941 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+	# we have to do this the hard way.  git format-patch completely squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..cef290b 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,12 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	tree=$(git rev-parse "$1"^{tree})
+	ptree=$(git rev-parse "$1"^^{tree})
+	return $(test "$tree" = "$ptree")
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +197,19 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+	if is_empty_commit "$sha1"
+	then
+		empty_args="--allow-empty"
+	fi
+
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +793,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	then
+		comment_out="# pick"
+	else
+		comment_out="pick"
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +822,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s\n' "$comment_out $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +872,12 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	echo "# Note that empty commits are commented out" >> "$todo"
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Clemens Buchacher· Apr 17, 2012, 21:47 UTC · re: Neil Horman · lore

Re: [PATCH v6 4/4] git-rebase: add keep_empty flag

On Tue, Apr 17, 2012 at 02:20:09PM -0400, Neil Horman wrote:
Show 12 quoted lines
>
> @@ -780,9 +793,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
>  	sed -n "s/^>//p" |
>  while read -r shortsha1 rest
>  do
> +
> +	if test -z "$keep_empty" && is_empty_commit $shortsha1
> +	then
> +		comment_out="# pick"
> +	else
> +		comment_out="pick"
> +	fi
You forgot to change this to comment_out="# " and comment_out="".
Neil Horman· Apr 18, 2012, 10:50 UTC · re: Clemens Buchacher · lore

Re: [PATCH v6 4/4] git-rebase: add keep_empty flag

On Tue, Apr 17, 2012 at 11:47:01PM +0200, Clemens Buchacher wrote:
Show 16 quoted lines
> On Tue, Apr 17, 2012 at 02:20:09PM -0400, Neil Horman wrote:
> >
> > @@ -780,9 +793,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
> >  	sed -n "s/^>//p" |
> >  while read -r shortsha1 rest
> >  do
> > +
> > +	if test -z "$keep_empty" && is_empty_commit $shortsha1
> > +	then
> > +		comment_out="# pick"
> > +	else
> > +		comment_out="pick"
> > +	fi
> 
> You forgot to change this to comment_out="# " and comment_out="".
> 

Thanks, I'll fix that up when I modify the keep-redundant-commits commit message Neil

Junio C Hamano· Apr 18, 2012, 22:58 UTC · re: Neil Horman · lore

Re: [PATCH v6 4/4] git-rebase: add keep_empty flag

Neil Horman <nhorman@tuxdriver.com> writes:
Show 15 quoted lines
> Add a command line switch to git-rebase to allow a user the ability to specify
> that they want to keep any commits in a series that are empty.
>
> When git-rebase's type is am, then this option will automatically keep any
> commit that has a tree object identical to its parent.
>
> This patch changes the default behavior of interactive rebases as well.  With
> this patch, git-rebase -i will produce a revision set passed to
> git-revision-editor, in which empty commits are commented out.  Empty commits
> may be kept manually by uncommenting them.  If the new --keep-empty option is
> used in an interactive rebase the empty commits will automatically all be
> uncommented in the editor.
>
> Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> ---
The earlier one in the series seem to be getting solid enough.  Nice.
Show 13 quoted lines
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index 5812222..cef290b 100644
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -167,6 +167,12 @@ has_action () {
>  	sane_grep '^[^#]' "$1" >/dev/null
>  }
>  
> +is_empty_commit() {
> +	tree=$(git rev-parse "$1"^{tree})
> +	ptree=$(git rev-parse "$1"^^{tree})
> +	return $(test "$tree" = "$ptree")
> +}
Could "$1" ever be a root commit without a parent?
Neil Horman· Apr 19, 2012, 13:08 UTC · re: Junio C Hamano · lore

Re: [PATCH v6 4/4] git-rebase: add keep_empty flag

On Wed, Apr 18, 2012 at 03:58:53PM -0700, Junio C Hamano wrote:
Show 20 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Add a command line switch to git-rebase to allow a user the ability to specify
> > that they want to keep any commits in a series that are empty.
> >
> > When git-rebase's type is am, then this option will automatically keep any
> > commit that has a tree object identical to its parent.
> >
> > This patch changes the default behavior of interactive rebases as well.  With
> > this patch, git-rebase -i will produce a revision set passed to
> > git-revision-editor, in which empty commits are commented out.  Empty commits
> > may be kept manually by uncommenting them.  If the new --keep-empty option is
> > used in an interactive rebase the empty commits will automatically all be
> > uncommented in the editor.
> >
> > Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
> > ---
> 
> The earlier one in the series seem to be getting solid enough.  Nice.
> 
Thanks!
Show 16 quoted lines
> > diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> > index 5812222..cef290b 100644
> > --- a/git-rebase--interactive.sh
> > +++ b/git-rebase--interactive.sh
> > @@ -167,6 +167,12 @@ has_action () {
> >  	sane_grep '^[^#]' "$1" >/dev/null
> >  }
> >  
> > +is_empty_commit() {
> > +	tree=$(git rev-parse "$1"^{tree})
> > +	ptree=$(git rev-parse "$1"^^{tree})
> > +	return $(test "$tree" = "$ptree")
> > +}
> 
> Could "$1" ever be a root commit without a parent?
> 

Strictly speaking, yes. If that happens, however, the output of git rev-parse will be an error message that includes the passed in revision. since tree passes '^' while ptree passes '^^' the two revisions will always differ, and as a result is_empty_commit will return false, and the existing behavior of git will be followed. So, yes, rebasing an empty tree would cause odd output in the rev-parse calls in is_empty_comit, but the behavior of git overall would be unaffected (which is not to say that rebasing an empty tree won't show odd corner-case behavior, only that this changeset won't introduce any new odd corner case behavior :) ).

If you like we can add additional checking to ensure that we explicitly catch rev-parse errors and abort the rebase imediately, but I don't think thats strictly necessecary. Would you prefer that?

Regards
Neil
 
Junio C Hamano· Apr 19, 2012, 17:53 UTC · re: Neil Horman · lore

Re: [PATCH v6 4/4] git-rebase: add keep_empty flag

Neil Horman <nhorman@tuxdriver.com> writes:
Show 9 quoted lines
>> > +is_empty_commit() {
>> > +	tree=$(git rev-parse "$1"^{tree})
>> > +	ptree=$(git rev-parse "$1"^^{tree})
>> > +	return $(test "$tree" = "$ptree")
>> > +}
>> 
>> Could "$1" ever be a root commit without a parent?
>> 
> Strictly speaking, yes.  If that happens, however, the output of git rev-parse

You do not have to speak strictly to see that bug that surfaces internal error message leaking to the end user.

Neil Horman· Apr 18, 2012, 19:17 UTC · re: Neil Horman · lore

[PATCH v7 0/4] Enhance git-rebases flexibiilty in handling empty commits

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --keep-redundant-commits, which allows the user to also keep commits that were made empty via conflict resolution. It also enhances git-rebase to add a --keep-empty option which enables rebases to preserve empty commits.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
V3)
	* reversed the --ignore-if-empty-logic to by default only keep initially
	empty commits
	* replaced --ignore-if-empty with --keep-redundant-commits, to allow
	empty commits that are made empty via conflict resolution, in addition
	to commits that were created as empty
	* reworked is_original_commit_empty to be more efficient and portable
	* Misc sylistic and spelling cleanups
V4)
	* Reverted the cherry-pick advice changes in V3 based on in-thread
	discussion
	* Rewrote my changes to is_original_commit_empty and run_git_commit to
	not have to fork, making them more efficient.
v5)
	* Additional help text clean up
	* Additional error checking added to run_git_commit code
	* Whitespace cleanup
	* Removed needed cache_tree freeing
	* Test case cleanup
	* Fixed regression in t3404 and t3416 - this turned out to be 
        a problem with the note that git rebase -i adds at the bottom
	of the rebase text.  It was inadvertently indented and caused the
	test fake editor to misread the commit template.  The indentation 
	has been corrected, and these two tests, as well as all the other
	expected tests pass now
v6)
	* synced keeep-redundant-commits option and variable name
	* fixed up some comment terminology
	* minor newline cleanup
	* Removed some unneeded braces from test code
	* minor syntatic cleanup in rebase scripts
	* Refactored empty index checking to make run_git_commit more readable
	I was also going to change the logic so that it operated more like git
	cherry-pick did before the patchset, but Junio's comments made me think
	the new logic was preferable.
v7)
	* Fixed up forgotten comment_out changes requested in
	git-rebase--interactive
	* Made changelog comment for keep-redundant-commits patch more verbose
	so that it was clear that default behavior was changing
	* further refinement of detection of redundant commits, making
	do_pick_commit and run_git_commit more readable

-- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

-- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Neil Horman· Apr 18, 2012, 19:17 UTC · re: Neil Horman · lore

[PATCH v7 1/4] git-cherry-pick: add allow-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 17 insertions(+), 2 deletions(-)
Show changes to 4 files +17 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..730237a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty::
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Neil Horman· Apr 18, 2012, 19:17 UTC · re: Neil Horman · lore

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

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.

Note that this patch changes the default behavior of git cherry-pick slightly. Prior to this patch all commits in a cherry-pick sequence were applied and git commit was run. The implication here was that, if a commit was redundant, and the commit did not trigger the fast forward logic, the git commit operation, and therefore the git cherry-pick operation would fail, displaying the cherry pick advice (i.e. run git commit --allow-empty). With this patch however, such redundant commits are automatically skipped without stopping, unless --keep-redundant-commits is specified, in which case, they are automatically applied as empty commits.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |   12 ++++-
 builtin/revert.c                  |    8 +++-
 sequencer.c                       |   94 ++++++++++++++++++++++++++++++++----
 sequencer.h                       |    1 +
 4 files changed, 102 insertions(+), 13 deletions(-)
Show changes to 4 files +102 −13

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 730237a..0c004e9 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. the commit recorded the
+	same tree as its parent).  Commits which are made empty due to a
+	previous commit are ignored.  To force the inclusion of those commits
+	use `--keep-redundant-commits`.
+
+--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`.
 
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
diff --git a/builtin/revert.c b/builtin/revert.c
index 06b00e6..f135502 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_redundant_commits, "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_redundant_commits)
+		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..e33dfbb 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"
 
@@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	return !clean;
 }
 
+static int is_index_unchanged()
+{
+	unsigned char head_sha1[20];
+	struct commit *head_commit;
+
+	if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+		return error(_("Could not resolve HEAD commit\n"));
+
+	head_commit = lookup_commit(head_sha1);
+	if (!head_commit || parse_commit(head_commit))
+		return error(_("could not parse commit %s\n"),
+			     sha1_to_hex(head_commit->object.sha1));
+
+	if (!active_cache_tree)
+		active_cache_tree = cache_tree();
+
+	if (!cache_tree_fully_valid(active_cache_tree))
+		if (cache_tree_update(active_cache_tree, active_cache,
+				  active_nr, 0))
+			return error(_("Unable to update cache tree\n"));
+
+	return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
 /*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
@@ -260,24 +285,46 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 7 is max possible length of our args array including NULL */
-	const char *args[7];
-	int i = 0;
+	struct argv_array array;
+	int rc;
+
+	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");
 
-	args[i] = NULL;
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+	const unsigned char *ptree_sha1;
+
+	if (parse_commit(commit))
+		return error(_("Could not parse commit %s\n"),
+			     sha1_to_hex(commit->object.sha1));
+	if (commit->parents) {
+		struct commit *parent = commit->parents->item;
+		if (parse_commit(parent))
+			return error(_("Could not parse parent commit %s\n"),
+				sha1_to_hex(parent->object.sha1));
+		ptree_sha1 = parent->tree->object.sha1;
+	} else {
+		ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+	}
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	return !hashcmp(ptree_sha1, commit->tree->object.sha1);
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +336,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
+	int index_unchanged;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +463,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_original_commit_empty(commit);
+	if (empty_commit < 0)
+		return empty_commit;
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -434,6 +487,25 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		print_advice(res == 1, opts);
 		rerere(opts->allow_rerere_auto);
 	} else {
+		index_unchanged = is_index_unchanged();
+		/*
+		 * If index_unchanged is less than 0, that indicates we either
+		 * couldn't parse HEAD or the index, so error out here.
+		 */
+		if (index_unchanged < 0)
+			return index_unchanged;
+
+		if (!empty_commit && !opts->keep_redundant_commits && index_unchanged)
+			/*
+			 * The head tree and the index 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, we can just return
+			 * here, skipping this commit
+			 */
+			return 0;
+
 		if (!opts->no_commit)
 			res = run_git_commit(defmsg, opts);
 	}
diff --git a/sequencer.h b/sequencer.h
index e2cd725..aa5f17c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int keep_redundant_commits;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Apr 18, 2012, 22:59 UTC · re: Neil Horman · lore

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

Neil Horman <nhorman@tuxdriver.com> writes:
Show 18 quoted lines
> diff --git a/sequencer.c b/sequencer.c
> index 71929ba..e33dfbb 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"
>  
> @@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
>  	return !clean;
>  }
>  
> +static int is_index_unchanged()
> +{

Hmm... I am reasonably sure I fixed this when I queued the previous one to 'pu'.

	static int is_index_unchanged(void)
        {
Neil Horman· Apr 18, 2012, 19:17 UTC · re: Neil Horman · lore

[PATCH v7 3/4] git-cherry-pick: Add test to validate new options

Since we've added the --allow-empty and --keep-redundant-commits options to git cherry-pick we should also add a test to ensure that its working properly.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 t/t3505-cherry-pick-empty.sh |   25 ++++++++++++++++++++++++-
 1 files changed, 24 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +24 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..d513127 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,22 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fourth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" &&
+	test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master &&
+	git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+	git checkout master &&
+	git cherry-pick --keep-redundant-commits HEAD^
+'
+
 test_done
-- 
1.7.7.6
Neil Horman· Apr 18, 2012, 19:18 UTC · re: Neil Horman · lore

[PATCH v7 4/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   33 ++++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 53 insertions(+), 8 deletions(-)
Show changes to 4 files +53 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..04d8941 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+	# we have to do this the hard way.  git format-patch completely squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..b7abe1e 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,12 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	tree=$(git rev-parse "$1"^{tree})
+	ptree=$(git rev-parse "$1"^^{tree})
+	return $(test "$tree" = "$ptree")
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +197,19 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+	if is_empty_commit "$sha1"
+	then
+		empty_args="--allow-empty"
+	fi
+
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +793,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	then
+		comment_out="#"
+	else
+		comment_out=""
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +822,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +872,12 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	echo "# Note that empty commits are commented out" >> "$todo"
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Zbigniew Jędrzejewski-Szmek· Apr 19, 2012, 10:02 UTC · re: Neil Horman · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

On 04/18/2012 09:18 PM, Neil Horman wrote:
Show 14 quoted lines
> Add a command line switch to git-rebase to allow a user the ability to specify
> that they want to keep any commits in a series that are empty.
>
> When git-rebase's type is am, then this option will automatically keep any
> commit that has a tree object identical to its parent.
>
> This patch changes the default behavior of interactive rebases as well.  With
> this patch, git-rebase -i will produce a revision set passed to
> git-revision-editor, in which empty commits are commented out.  Empty commits
> may be kept manually by uncommenting them.  If the new --keep-empty option is
> used in an interactive rebase the empty commits will automatically all be
> uncommented in the editor.
>
> Signed-off-by: Neil Horman<nhorman@tuxdriver.com>

Hi, this one seems to breaks many tests when /bin/sh=dash. (Both v6 in pu and this v7).

E.g. ./t3404-rebase-interactive.sh:

ok 1 - setup not ok - 2 rebase -i with the exec command not ok - 3 rebase -i with the exec command runs from tree root not ok - 4 rebase -i with the exec command checks tree cleanness not ok - 5 no changes are a nop not ok - 6 test the [branch] option not ok - 7 test --onto <branch> not ok - 8 rebase on top of a non-conflicting commit not ok - 9 reflog for the branch shows state before rebase not ok - 10 exchange two commits not ok - 11 stop on conflicting pick not ok - 12 abort not ok - 13 abort with error when new base cannot be checked out not ok - 14 retain authorship not ok - 15 squash not ok - 16 retain authorship when squashing not ok - 17 -p handles "no changes" gracefully not ok 18 - exchange two commits with -p # TODO known breakage not ok - 19 preserve merges with -p not ok - 20 edit ancestor with -p not ok - 21 --continue tries to commit not ok - 22 verbose flag is heeded, even after --continue not ok - 23 multi-squash only fires up editor once not ok - 24 multi-fixup does not fire up editor not ok - 25 commit message used after conflict not ok - 26 commit message retained after conflict not ok - 27 squash and fixup generate correct log messages not ok - 28 squash ignores comments not ok - 29 squash ignores blank lines not ok - 30 squash works as expected not ok - 31 interrupted squash works as expected not ok - 32 interrupted squash works as expected (case 2) not ok - 33 ignore patch if in upstream not ok - 34 --continue tries to commit, even for "edit" not ok - 35 aborted --continue does not squash commits after "edit" not ok - 36 auto-amend only edited commits after "edit" not ok - 37 clean error after failed "exec" not ok - 38 rebase a detached HEAD not ok - 39 rebase a commit violating pre-commit not ok - 40 rebase with a file named HEAD in worktree ok 41 - do "noop" when there is nothing to cherry-pick ok 42 - submodule rebase setup not ok - 43 submodule rebase -i ok 44 - submodule conflict setup not ok - 45 rebase -i continue with only submodule staged not ok - 46 rebase -i continue with unstaged submodule not ok - 47 avoid unnecessary reset not ok - 48 reword ok 49 - rebase -i can copy notes not ok - 50 rebase -i can copy notes over a fixup not ok - 51 rebase while detaching HEAD not ok - 52 always cherry-pick with --no-ff ok 53 - set up commits with funny messages not ok - 54 rebase-i history with funny messages

The problem seems to be that git-rebase says "Nothing to do" and returns 1.

- Zbyszek

Thomas Rast· Apr 19, 2012, 11:49 UTC · re: Zbigniew Jędrzejewski-Szmek · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> writes:
Show 19 quoted lines
> On 04/18/2012 09:18 PM, Neil Horman wrote:
>> Add a command line switch to git-rebase to allow a user the ability to specify
>> that they want to keep any commits in a series that are empty.
>>
>> When git-rebase's type is am, then this option will automatically keep any
>> commit that has a tree object identical to its parent.
>>
>> This patch changes the default behavior of interactive rebases as well.  With
>> this patch, git-rebase -i will produce a revision set passed to
>> git-revision-editor, in which empty commits are commented out.  Empty commits
>> may be kept manually by uncommenting them.  If the new --keep-empty option is
>> used in an interactive rebase the empty commits will automatically all be
>> uncommented in the editor.
>>
>> Signed-off-by: Neil Horman<nhorman@tuxdriver.com>
>
> Hi,
> this one seems to breaks many tests when /bin/sh=dash. (Both v6 in pu
> and this v7).
Probably because of the strange return in this function:
Show 5 quoted lines
>> is_empty_commit() {
>> 	tree=$(git rev-parse "$1"^{tree})
>> 	ptree=$(git rev-parse "$1"^^{tree})
>> 	return $(test "$tree" = "$ptree")
>> }

bash seems to pass on the exit status from $() to the caller, while dash doesn't. It seems bash is actually more correct here, because POSIX says about 'return [n]':

    EXIT STATUS
       The value of the special parameter '?' shall be set to n, an
       unsigned decimal integer, or to the exit status of the last
       command executed if n is not specified.
Either way, it should simply be spelled as
is_empty_commit() {
	tree=$(git rev-parse "$1"^{tree})
	ptree=$(git rev-parse "$1"^^{tree})
	test "$tree" = "$ptree"
}
-- 
Thomas Rast
trast@{inf,student}.ethz.ch
Zbigniew Jędrzejewski-Szmek· Apr 19, 2012, 12:19 UTC · re: Thomas Rast · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

On 04/19/2012 01:49 PM, Thomas Rast wrote:
Show 46 quoted lines
> Zbigniew Jędrzejewski-Szmek<zbyszek@in.waw.pl>  writes:
>
>> On 04/18/2012 09:18 PM, Neil Horman wrote:
>>> Add a command line switch to git-rebase to allow a user the ability to specify
>>> that they want to keep any commits in a series that are empty.
>>>
>>> When git-rebase's type is am, then this option will automatically keep any
>>> commit that has a tree object identical to its parent.
>>>
>>> This patch changes the default behavior of interactive rebases as well.  With
>>> this patch, git-rebase -i will produce a revision set passed to
>>> git-revision-editor, in which empty commits are commented out.  Empty commits
>>> may be kept manually by uncommenting them.  If the new --keep-empty option is
>>> used in an interactive rebase the empty commits will automatically all be
>>> uncommented in the editor.
>>>
>>> Signed-off-by: Neil Horman<nhorman@tuxdriver.com>
>>
>> Hi,
>> this one seems to breaks many tests when /bin/sh=dash. (Both v6 in pu
>> and this v7).
>
> Probably because of the strange return in this function:
>
>>> is_empty_commit() {
>>> 	tree=$(git rev-parse "$1"^{tree})
>>> 	ptree=$(git rev-parse "$1"^^{tree})
>>> 	return $(test "$tree" = "$ptree")
>>> }
>
> bash seems to pass on the exit status from $() to the caller, while dash
> doesn't.  It seems bash is actually more correct here, because POSIX
> says about 'return [n]':
>
>      EXIT STATUS
>         The value of the special parameter '?' shall be set to n, an
>         unsigned decimal integer, or to the exit status of the last
>         command executed if n is not specified.
>
> Either way, it should simply be spelled as
>
> is_empty_commit() {
> 	tree=$(git rev-parse "$1"^{tree})
> 	ptree=$(git rev-parse "$1"^^{tree})
> 	test "$tree" = "$ptree"
> }
Yes, this change fixes the problem (all tests pass).
Thanks!
Zbyszek
Neil Horman· Apr 19, 2012, 13:12 UTC · re: Zbigniew Jędrzejewski-Szmek · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

On Thu, Apr 19, 2012 at 02:19:22PM +0200, Zbigniew Jędrzejewski-Szmek wrote:
Show 53 quoted lines
> On 04/19/2012 01:49 PM, Thomas Rast wrote:
> >Zbigniew Jędrzejewski-Szmek<zbyszek@in.waw.pl>  writes:
> >
> >>On 04/18/2012 09:18 PM, Neil Horman wrote:
> >>>Add a command line switch to git-rebase to allow a user the ability to specify
> >>>that they want to keep any commits in a series that are empty.
> >>>
> >>>When git-rebase's type is am, then this option will automatically keep any
> >>>commit that has a tree object identical to its parent.
> >>>
> >>>This patch changes the default behavior of interactive rebases as well.  With
> >>>this patch, git-rebase -i will produce a revision set passed to
> >>>git-revision-editor, in which empty commits are commented out.  Empty commits
> >>>may be kept manually by uncommenting them.  If the new --keep-empty option is
> >>>used in an interactive rebase the empty commits will automatically all be
> >>>uncommented in the editor.
> >>>
> >>>Signed-off-by: Neil Horman<nhorman@tuxdriver.com>
> >>
> >>Hi,
> >>this one seems to breaks many tests when /bin/sh=dash. (Both v6 in pu
> >>and this v7).
> >
> >Probably because of the strange return in this function:
> >
> >>>is_empty_commit() {
> >>>	tree=$(git rev-parse "$1"^{tree})
> >>>	ptree=$(git rev-parse "$1"^^{tree})
> >>>	return $(test "$tree" = "$ptree")
> >>>}
> >
> >bash seems to pass on the exit status from $() to the caller, while dash
> >doesn't.  It seems bash is actually more correct here, because POSIX
> >says about 'return [n]':
> >
> >     EXIT STATUS
> >        The value of the special parameter '?' shall be set to n, an
> >        unsigned decimal integer, or to the exit status of the last
> >        command executed if n is not specified.
> >
> >Either way, it should simply be spelled as
> >
> >is_empty_commit() {
> >	tree=$(git rev-parse "$1"^{tree})
> >	ptree=$(git rev-parse "$1"^^{tree})
> >	test "$tree" = "$ptree"
> >}
> Yes, this change fixes the problem (all tests pass).
> 
> Thanks!
> 
> Zbyszek
> 

Ok, I'll update it. Neil

Junio C Hamano· Apr 19, 2012, 18:59 UTC · re: Thomas Rast · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

Thomas Rast <trast@student.ethz.ch> writes:
Show 7 quoted lines
> Either way, it should simply be spelled as
>
> is_empty_commit() {
> 	tree=$(git rev-parse "$1"^{tree})
> 	ptree=$(git rev-parse "$1"^^{tree})
> 	test "$tree" = "$ptree"
> }
Thanks; will squash in something like this:
Show changes to git-rebase--interactive.sh +4 −3
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 82042b1..8fe304f 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -168,9 +168,10 @@ has_action () {
 }
 
 is_empty_commit() {
-	tree=$(git rev-parse "$1"^{tree})
-	ptree=$(git rev-parse "$1"^^{tree})
-	return $(test "$tree" = "$ptree")
+	tree=$(git rev-parse "$1"^{tree} 2>/dev/null) &&
+	ptree=$(git rev-parse "$1"^^{tree} 2>/dev/null) ||
+		die "$1: not a commit that can be picked"
+	test "$tree" = "$ptree"
 }
 
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
Junio C Hamano· Apr 19, 2012, 19:05 UTC · re: Junio C Hamano · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

Junio C Hamano <jch@google.com> writes:
Show 12 quoted lines
> Thomas Rast <trast@student.ethz.ch> writes:
>
>> Either way, it should simply be spelled as
>>
>> is_empty_commit() {
>> 	tree=$(git rev-parse "$1"^{tree})
>> 	ptree=$(git rev-parse "$1"^^{tree})
>> 	test "$tree" = "$ptree"
>> }
>
> Thanks; will squash in something like this:
> ...

Ehh, not like that. But something like this, as we need to be able to pick "root" (t3412 insists on it).

Show changes to git-rebase--interactive.sh +5 −3
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 82042b1..de71543 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -168,9 +168,11 @@ has_action () {
 }
 
 is_empty_commit() {
-	tree=$(git rev-parse "$1"^{tree})
-	ptree=$(git rev-parse "$1"^^{tree})
-	return $(test "$tree" = "$ptree")
+	tree=$(git rev-parse "$1"^{tree} 2>/dev/null) ||
+		die "$1: not a commit that can be picked"
+	ptree=$(git rev-parse "$1"^^{tree} 2>/dev/null) ||
+		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+	test "$tree" = "$ptree"
 }
 
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
Neil Horman· Apr 20, 2012, 10:35 UTC · re: Junio C Hamano · lore

Re: [PATCH v7 4/4] git-rebase: add keep_empty flag

On Thu, Apr 19, 2012 at 12:05:56PM -0700, Junio C Hamano wrote:
Show 38 quoted lines
> Junio C Hamano <jch@google.com> writes:
> 
> > Thomas Rast <trast@student.ethz.ch> writes:
> >
> >> Either way, it should simply be spelled as
> >>
> >> is_empty_commit() {
> >> 	tree=$(git rev-parse "$1"^{tree})
> >> 	ptree=$(git rev-parse "$1"^^{tree})
> >> 	test "$tree" = "$ptree"
> >> }
> >
> > Thanks; will squash in something like this:
> > ...
> 
> Ehh, not like that.  But something like this, as we need to be able to
> pick "root" (t3412 insists on it).
> 
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index 82042b1..de71543 100644
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -168,9 +168,11 @@ has_action () {
>  }
>  
>  is_empty_commit() {
> -	tree=$(git rev-parse "$1"^{tree})
> -	ptree=$(git rev-parse "$1"^^{tree})
> -	return $(test "$tree" = "$ptree")
> +	tree=$(git rev-parse "$1"^{tree} 2>/dev/null) ||
> +		die "$1: not a commit that can be picked"
> +	ptree=$(git rev-parse "$1"^^{tree} 2>/dev/null) ||
> +		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
> +	test "$tree" = "$ptree"
>  }
>  
>  # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
> 
What about this?
is_empty_commit() {
        tree=$(git rev-parse -q --verify "$1"^{tree})
        ptree=$(git rev-parse -q --verify "$1"^^{tree})
        # Note that if either rev-parse commands fail, the output 
        # of that command will be an empty string.  If that happens
        # We should just return 0, to indicate the commit is non-empty
        # and let the rest of the git rebase logic handle it.
        if test  -z "$tree"  -o  -z "$ptree"
        then
                return 0
        fi
        return test "$tree" = "$ptree"
}
Neil Horman· Apr 20, 2012, 14:36 UTC · re: Neil Horman · lore

[PATCH v8 0/4] Enhance git-rebases flexibiilty in handling empty commits

git's ability to handle empty commits is somewhat lacking, especially when preforming a rebase. Nominally empty commits are undesireable entries, the result of commits that are made empty by prior commits covering the same changs. But occasionally, empty commits are useful to developers (e.g. inserting notes into the development history without changing any code along the way). In these cases its desireable to easily preserve empty commits during operations like rebases.

This patch series enhances git to do just that. It adds two options to the git-cherry-pick command, --allow-empty, which allows git cherry-pick to preserve an empty commit, even if the fast forward logic isn't applicable during the operation, and --keep-redundant-commits, which allows the user to also keep commits that were made empty via conflict resolution. It also enhances git-rebase to add a --keep-empty option which enables rebases to preserve empty commits.

I've tested these operations out myself here and they work well for me
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>

--- Change notes:

Based on version 1 feedback from this list, the following changes have been made
V2)
	* Changed --keep-empty to --allow-empty in the git cherry-pick command
	* Converted run_git_commit to use argv_array
	* Updated cherry-pick --allow-empty description in man page
	
	* added ignore-if-made-empty option to git-cherry-pick
	* Added test to test suite to validate the new cherry-pick options
	* Updated git-rebase man page to be less verbose and more accurate in the
	description of the keep-empty option
	* squashed the addition of the keep-empty flag in git-rebase down to one
	commit from 3
	* fixed up coding style in git-rebase script
	* Optimized detection of empty commits
	* Only augmented git-rebase-editor message if empty commits are
	possible
	
V3)
	* reversed the --ignore-if-empty-logic to by default only keep initially
	empty commits
	* replaced --ignore-if-empty with --keep-redundant-commits, to allow
	empty commits that are made empty via conflict resolution, in addition
	to commits that were created as empty
	* reworked is_original_commit_empty to be more efficient and portable
	* Misc sylistic and spelling cleanups
V4)
	* Reverted the cherry-pick advice changes in V3 based on in-thread
	discussion
	* Rewrote my changes to is_original_commit_empty and run_git_commit to
	not have to fork, making them more efficient.
v5)
	* Additional help text clean up
	* Additional error checking added to run_git_commit code
	* Whitespace cleanup
	* Removed needed cache_tree freeing
	* Test case cleanup
	* Fixed regression in t3404 and t3416 - this turned out to be 
        a problem with the note that git rebase -i adds at the bottom
	of the rebase text.  It was inadvertently indented and caused the
	test fake editor to misread the commit template.  The indentation 
	has been corrected, and these two tests, as well as all the other
	expected tests pass now
v6)
	* synced keeep-redundant-commits option and variable name
	* fixed up some comment terminology
	* minor newline cleanup
	* Removed some unneeded braces from test code
	* minor syntatic cleanup in rebase scripts
	* Refactored empty index checking to make run_git_commit more readable
	I was also going to change the logic so that it operated more like git
	cherry-pick did before the patchset, but Junio's comments made me think
	the new logic was preferable.
v7)
	* Fixed up forgotten comment_out changes requested in
	git-rebase--interactive
	* Made changelog comment for keep-redundant-commits patch more verbose
	so that it was clear that default behavior was changing
	* further refinement of detection of redundant commits, making
	do_pick_commit and run_git_commit more readable
v8)
	* Fixed git-rebase--interactive so that is_empty_commit doesn't choke on
	root commit
	* Minor stylistic change to is_index_unchanged
	* Fixed is_empty_commit to test tree and ptree such that it works with
	shells other than bash
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html

Neil Horman· Apr 20, 2012, 14:36 UTC · re: Neil Horman · lore

[PATCH v8 1/4] git-cherry-pick: add allow-empty option

git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |    9 +++++++++
 builtin/revert.c                  |    2 ++
 sequencer.c                       |    7 +++++--
 sequencer.h                       |    1 +
 4 files changed, 17 insertions(+), 2 deletions(-)
Show changes to 4 files +17 −2

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index fed5097..730237a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -103,6 +103,15 @@ effect to your index in a row.
 	cherry-pick'ed commit, then a fast forward to this commit will
 	be performed.
 
+--allow-empty::
+	By default, cherry-picking an empty commit will fail,
+	indicating that an explicit invocation of `git commit
+	--allow-empty` is required. This option overrides that
+	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.
+
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
 	See the MERGE STRATEGIES section in linkgit:git-merge[1]
diff --git a/builtin/revert.c b/builtin/revert.c
index e6840f2..06b00e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -114,12 +114,14 @@ 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_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
diff --git a/sequencer.c b/sequencer.c
index a37846a..71929ba 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 6 is max possible length of our args array including NULL */
-	const char *args[6];
+	/* 7 is max possible length of our args array including NULL */
+	const char *args[7];
 	int i = 0;
 
 	args[i++] = "commit";
@@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
+	if (opts->allow_empty)
+		args[i++] = "--allow-empty";
+
 	args[i] = NULL;
 
 	return run_command_v_opt(args, RUN_GIT_CMD);
diff --git a/sequencer.h b/sequencer.h
index bb4b138..e2cd725 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -29,6 +29,7 @@ struct replay_opts {
 	int signoff;
 	int allow_ff;
 	int allow_rerere_auto;
+	int allow_empty;
 
 	int mainline;
 
-- 
1.7.7.6
Neil Horman· Apr 20, 2012, 14:36 UTC · re: Neil Horman · lore

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

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.

Note that this patch changes the default behavior of git cherry-pick slightly. Prior to this patch all commits in a cherry-pick sequence were applied and git commit was run. The implication here was that, if a commit was redundant, and the commit did not trigger the fast forward logic, the git commit operation, and therefore the git cherry-pick operation would fail, displaying the cherry pick advice (i.e. run git commit --allow-empty). With this patch however, such redundant commits are automatically skipped without stopping, unless --keep-redundant-commits is specified, in which case, they are automatically applied as empty commits.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-cherry-pick.txt |   12 ++++-
 builtin/revert.c                  |    8 +++-
 sequencer.c                       |   94 ++++++++++++++++++++++++++++++++----
 sequencer.h                       |    1 +
 4 files changed, 102 insertions(+), 13 deletions(-)
Show changes to 4 files +102 −13

Documentation/git-cherry-pick.txt, builtin/revert.c, sequencer.c, sequencer.h

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 730237a..0c004e9 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. the commit recorded the
+	same tree as its parent).  Commits which are made empty due to a
+	previous commit are ignored.  To force the inclusion of those commits
+	use `--keep-redundant-commits`.
+
+--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`.
 
 --strategy=<strategy>::
 	Use the given merge strategy.  Should only be used once.
diff --git a/builtin/revert.c b/builtin/revert.c
index 06b00e6..f135502 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_redundant_commits, "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_redundant_commits)
+		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..4e3af82 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"
 
@@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	return !clean;
 }
 
+static int is_index_unchanged(void)
+{
+	unsigned char head_sha1[20];
+	struct commit *head_commit;
+
+	if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+		return error(_("Could not resolve HEAD commit\n"));
+
+	head_commit = lookup_commit(head_sha1);
+	if (!head_commit || parse_commit(head_commit))
+		return error(_("could not parse commit %s\n"),
+			     sha1_to_hex(head_commit->object.sha1));
+
+	if (!active_cache_tree)
+		active_cache_tree = cache_tree();
+
+	if (!cache_tree_fully_valid(active_cache_tree))
+		if (cache_tree_update(active_cache_tree, active_cache,
+				  active_nr, 0))
+			return error(_("Unable to update cache tree\n"));
+
+	return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
 /*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
@@ -260,24 +285,46 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-	/* 7 is max possible length of our args array including NULL */
-	const char *args[7];
-	int i = 0;
+	struct argv_array array;
+	int rc;
+
+	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");
 
-	args[i] = NULL;
+	rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+	argv_array_clear(&array);
+	return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+	const unsigned char *ptree_sha1;
+
+	if (parse_commit(commit))
+		return error(_("Could not parse commit %s\n"),
+			     sha1_to_hex(commit->object.sha1));
+	if (commit->parents) {
+		struct commit *parent = commit->parents->item;
+		if (parse_commit(parent))
+			return error(_("Could not parse parent commit %s\n"),
+				sha1_to_hex(parent->object.sha1));
+		ptree_sha1 = parent->tree->object.sha1;
+	} else {
+		ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+	}
 
-	return run_command_v_opt(args, RUN_GIT_CMD);
+	return !hashcmp(ptree_sha1, commit->tree->object.sha1);
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -289,6 +336,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 	char *defmsg = NULL;
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
+	int empty_commit;
+	int index_unchanged;
 
 	if (opts->no_commit) {
 		/*
@@ -414,6 +463,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		free_commit_list(remotes);
 	}
 
+	empty_commit = is_original_commit_empty(commit);
+	if (empty_commit < 0)
+		return empty_commit;
+
 	/*
 	 * If the merge was clean or if it failed due to conflict, we write
 	 * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -434,6 +487,25 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 		print_advice(res == 1, opts);
 		rerere(opts->allow_rerere_auto);
 	} else {
+		index_unchanged = is_index_unchanged();
+		/*
+		 * If index_unchanged is less than 0, that indicates we either
+		 * couldn't parse HEAD or the index, so error out here.
+		 */
+		if (index_unchanged < 0)
+			return index_unchanged;
+
+		if (!empty_commit && !opts->keep_redundant_commits && index_unchanged)
+			/*
+			 * The head tree and the index 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, we can just return
+			 * here, skipping this commit
+			 */
+			return 0;
+
 		if (!opts->no_commit)
 			res = run_git_commit(defmsg, opts);
 	}
diff --git a/sequencer.h b/sequencer.h
index e2cd725..aa5f17c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -30,6 +30,7 @@ struct replay_opts {
 	int allow_ff;
 	int allow_rerere_auto;
 	int allow_empty;
+	int keep_redundant_commits;
 
 	int mainline;
 
-- 
1.7.7.6
Junio C Hamano· Apr 20, 2012, 19:21 UTC · re: Neil Horman · lore

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

Looks good, modulo perhaps these fixes on top.
 - It would be better to say "dropped" to stress that we inspect and
   actively discard, instead of saying "ignored".
 - There is no need to use "changeset", the term glossary even
   discourages, to make sense of the sentence.
 - There was still a stray keep_if_made_empty.
 - s/Add keep/add keep/ on the title as well.
No need to resend, unless you have fixes other than the following does.
Show changes to 2 files +3 −3

Documentation/git-cherry-pick.txt, builtin/revert.c

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 0c004e9..3d25a20 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -113,12 +113,12 @@ effect to your index in a row.
 	even without this option.  Note also, that use of this option only
 	keeps commits that were initially empty (i.e. the commit recorded the
 	same tree as its parent).  Commits which are made empty due to a
-	previous commit are ignored.  To force the inclusion of those commits
+	previous commit are dropped.  To force the inclusion of those commits
 	use `--keep-redundant-commits`.
 
 --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
+	current history, it will become empty.  By default these
 	redundant commits are ignored.  This option overrides that behavior and
 	creates an empty commit object.  Implies `--allow-empty`.
 
diff --git a/builtin/revert.c b/builtin/revert.c
index f135502..b0b9b1a 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -141,7 +141,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 				"--abort", rollback,
 				NULL);
 
-	/* keep_if_made_empty implies allow_empty */
+	/* implies allow_empty */
 	if (opts->keep_redundant_commits)
 		opts->allow_empty = 1;
 
Neil Horman· Apr 20, 2012, 19:56 UTC · re: Junio C Hamano · lore

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

On Fri, Apr 20, 2012 at 12:21:41PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> Looks good, modulo perhaps these fixes on top.
> 
>  - It would be better to say "dropped" to stress that we inspect and
>    actively discard, instead of saying "ignored".
> 
>  - There is no need to use "changeset", the term glossary even
>    discourages, to make sense of the sentence.
> 
>  - There was still a stray keep_if_made_empty.
> 
>  - s/Add keep/add keep/ on the title as well.
> 
> No need to resend, unless you have fixes other than the following does.
> 

Nope I'm good, if you can work those changes in. Thanks! Neil

> 
Neil Horman· Apr 20, 2012, 14:36 UTC · re: Neil Horman · lore

[PATCH v8 3/4] git-cherry-pick: Add test to validate new options

Since we've added the --allow-empty and --keep-redundant-commits options to git cherry-pick we should also add a test to ensure that its working properly.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 t/t3505-cherry-pick-empty.sh |   25 ++++++++++++++++++++++++-
 1 files changed, 24 insertions(+), 1 deletions(-)
Show changes to t/t3505-cherry-pick-empty.sh +24 −1
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index c10b28c..d513127 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -18,7 +18,12 @@ test_expect_success setup '
 	echo third >> file1 &&
 	git add file1 &&
 	test_tick &&
-	git commit --allow-empty-message -m ""
+	git commit --allow-empty-message -m "" &&
+
+	git checkout master &&
+	git checkout -b empty-branch2 &&
+	test_tick &&
+	git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,22 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+	git checkout master &&
+	echo fourth >> file2 &&
+	git add file2 &&
+	git commit -m "fourth" &&
+	test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+	git checkout master &&
+	git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+	git checkout master &&
+	git cherry-pick --keep-redundant-commits HEAD^
+'
+
 test_done
-- 
1.7.7.6
Neil Horman· Apr 20, 2012, 14:36 UTC · re: Neil Horman · lore

[PATCH v8 4/4] git-rebase: add keep_empty flag

Add a command line switch to git-rebase to allow a user the ability to specify that they want to keep any commits in a series that are empty.

When git-rebase's type is am, then this option will automatically keep any commit that has a tree object identical to its parent.

This patch changes the default behavior of interactive rebases as well. With this patch, git-rebase -i will produce a revision set passed to git-revision-editor, in which empty commits are commented out. Empty commits may be kept manually by uncommenting them. If the new --keep-empty option is used in an interactive rebase the empty commits will automatically all be uncommented in the editor.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
---
 Documentation/git-rebase.txt |    4 ++++
 git-rebase--am.sh            |   19 ++++++++++++++-----
 git-rebase--interactive.sh   |   36 +++++++++++++++++++++++++++++++++---
 git-rebase.sh                |    5 +++++
 4 files changed, 56 insertions(+), 8 deletions(-)
Show changes to 4 files +56 −8

Documentation/git-rebase.txt, git-rebase--am.sh, git-rebase--interactive.sh, git-rebase.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 504945c..131c35d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
 	will be reset to where it was when the rebase operation was
 	started.
 
+--keep-empty::
+	Keep the commits that do not change anything from its
+	parents in the result.
+
 --skip::
 	Restart the rebasing process by skipping the current patch.
 
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index c815a24..04d8941 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-	--src-prefix=a/ --dst-prefix=b/ \
-	--no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+	# we have to do this the hard way.  git format-patch completely squashes
+	# empty commits and even if it didn't the format doesn't really lend
+	# itself well to recording empty patches.  fortunately, cherry-pick
+	# makes this easy
+	git cherry-pick --allow-empty "$revisions"
+else
+	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+		--src-prefix=a/ --dst-prefix=b/ \
+		--no-renames $root_flag "$revisions" |
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5812222..ef263e0 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -167,6 +167,15 @@ has_action () {
 	sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
+		die "$1: not a commit that can be picked")
+	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
+		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+
+	return test "$tree" = "$ptree"
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +200,19 @@ git_sequence_editor () {
 
 pick_one () {
 	ff=--ff
+
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
 	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+	if is_empty_commit "$sha1"
+	then
+		empty_args="--allow-empty"
+	fi
+
 	test -d "$rewritten" &&
 		pick_one_preserving_merges "$@" && return
-	output git cherry-pick $ff "$@"
+	output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +796,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 	sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	then
+		comment_out="#"
+	else
+		comment_out=""
+	fi
+
 	if test t != "$preserve_merges"
 	then
-		printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+		printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
 	else
 		sha1=$(git rev-parse $shortsha1)
 		if test -z "$rebase_root"
@@ -801,7 +825,7 @@ do
 		if test f = "$preserve"
 		then
 			touch "$rewritten"/$sha1
-			printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+			printf '%s%s\n' "$comment_out" "pick $shortsha1 $rest" >> "$todo"
 		fi
 	fi
 done
@@ -851,6 +875,12 @@ cat >> "$todo" << EOF
 #
 EOF
 
+if test -z "$keep_empty"
+then
+	echo "# Note that empty commits are commented out" >> "$todo"
+fi
+
+
 has_action "$todo" ||
 	die_abort "Nothing to do"
 
diff --git a/git-rebase.sh b/git-rebase.sh
index 69c1374..24a2840 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty	   preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
 	-i)
 		interactive_rebase=explicit
 		;;
+	-k)
+		keep_empty=yes
+		;;
 	-p)
 		preserve_merges=t
 		test -z "$interactive_rebase" && interactive_rebase=implied
-- 
1.7.7.6
Junio C Hamano· Apr 25, 2012, 01:50 UTC · re: Neil Horman · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

Neil Horman <nhorman@tuxdriver.com> writes:
Show 19 quoted lines
> Add a command line switch to git-rebase to allow a user the ability to specify
> that they want to keep any commits in a series that are empty.
> ...
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index 5812222..ef263e0 100644
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -167,6 +167,15 @@ has_action () {
>  	sane_grep '^[^#]' "$1" >/dev/null
>  }
>  
> +is_empty_commit() {
> +	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
> +		die "$1: not a commit that can be picked")
> +	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
> +		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
> +
> +	return test "$tree" = "$ptree"
> +}
I've amended the above and removed "return " from the last line.

The series is now in 'next', so if we need further enhancement or fixup, they need to come as incremental updates, not as replacements.

Thanks.
Neil Horman· Apr 25, 2012, 10:38 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

On Tue, Apr 24, 2012 at 06:50:26PM -0700, Junio C Hamano wrote:
Show 29 quoted lines
> Neil Horman <nhorman@tuxdriver.com> writes:
> 
> > Add a command line switch to git-rebase to allow a user the ability to specify
> > that they want to keep any commits in a series that are empty.
> > ...
> > diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> > index 5812222..ef263e0 100644
> > --- a/git-rebase--interactive.sh
> > +++ b/git-rebase--interactive.sh
> > @@ -167,6 +167,15 @@ has_action () {
> >  	sane_grep '^[^#]' "$1" >/dev/null
> >  }
> >  
> > +is_empty_commit() {
> > +	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
> > +		die "$1: not a commit that can be picked")
> > +	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
> > +		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
> > +
> > +	return test "$tree" = "$ptree"
> > +}
> 
> I've amended the above and removed "return " from the last line.
> 
> The series is now in 'next', so if we need further enhancement or fixup,
> they need to come as incremental updates, not as replacements.
> 
> Thanks.
> 

Fastastic, thank you! Neil

Martin von Zweigbergk· Jul 18, 2012, 06:20 UTC · re: Neil Horman · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

On Fri, Apr 20, 2012 at 7:36 AM, Neil Horman <nhorman@tuxdriver.com> wrote:
Show 16 quoted lines
>  pick_one () {
>         ff=--ff
> +
>         case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
>         case "$force_rebase" in '') ;; ?*) ff= ;; esac
>         output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
> +
> +       if is_empty_commit "$sha1"
> +       then
> +               empty_args="--allow-empty"
> +       fi
> +
>         test -d "$rewritten" &&
>                 pick_one_preserving_merges "$@" && return
> -       output git cherry-pick $ff "$@"
> +       output git cherry-pick $empty_args $ff "$@"

The is_empty_commit check seems to mean that if $sha1 is an "empty" commit, we pass the --allow-empty option to cherry-pick. If it's not empty, we don't. The word "allow" in "allow-empty" suggests that even if the commit is not empty, cherry-pick would not mind. So, can we always pass "allow-empty" to cherry-pick (i.e. even if the commit to pick is not empty)?

Sorry I'm commenting so late; I didn't have time to look at your patches when you sent them, but I'm currently working on the code touched by this patch.

Johannes Sixt· Jul 18, 2012, 07:10 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

Am 7/18/2012 8:20, schrieb Martin von Zweigbergk:
Show 24 quoted lines
> On Fri, Apr 20, 2012 at 7:36 AM, Neil Horman <nhorman@tuxdriver.com> wrote:
>>  pick_one () {
>>         ff=--ff
>> +
>>         case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
>>         case "$force_rebase" in '') ;; ?*) ff= ;; esac
>>         output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
>> +
>> +       if is_empty_commit "$sha1"
>> +       then
>> +               empty_args="--allow-empty"
>> +       fi
>> +
>>         test -d "$rewritten" &&
>>                 pick_one_preserving_merges "$@" && return
>> -       output git cherry-pick $ff "$@"
>> +       output git cherry-pick $empty_args $ff "$@"
> 
> The is_empty_commit check seems to mean that if $sha1 is an "empty"
> commit, we pass the --allow-empty option to cherry-pick. If it's not
> empty, we don't. The word "allow" in "allow-empty" suggests that even
> if the commit is not empty, cherry-pick would not mind. So, can we
> always pass "allow-empty" to cherry-pick (i.e. even if the commit to
> pick is not empty)?

I don't think so. If the commit is not empty, but all its changes are already in HEAD, then it will become "empty" when cherry-picked to HEAD. In such a case, we usually do not want to record an empty commit, but stop rebase to give to user a chance to deal with the situation.

-- Hannes
Martin von Zweigbergk· Jul 18, 2012, 07:16 UTC · re: Johannes Sixt · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

On Wed, Jul 18, 2012 at 12:10 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:
Show 30 quoted lines
> Am 7/18/2012 8:20, schrieb Martin von Zweigbergk:
>> On Fri, Apr 20, 2012 at 7:36 AM, Neil Horman <nhorman@tuxdriver.com> wrote:
>>>  pick_one () {
>>>         ff=--ff
>>> +
>>>         case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
>>>         case "$force_rebase" in '') ;; ?*) ff= ;; esac
>>>         output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
>>> +
>>> +       if is_empty_commit "$sha1"
>>> +       then
>>> +               empty_args="--allow-empty"
>>> +       fi
>>> +
>>>         test -d "$rewritten" &&
>>>                 pick_one_preserving_merges "$@" && return
>>> -       output git cherry-pick $ff "$@"
>>> +       output git cherry-pick $empty_args $ff "$@"
>>
>> The is_empty_commit check seems to mean that if $sha1 is an "empty"
>> commit, we pass the --allow-empty option to cherry-pick. If it's not
>> empty, we don't. The word "allow" in "allow-empty" suggests that even
>> if the commit is not empty, cherry-pick would not mind. So, can we
>> always pass "allow-empty" to cherry-pick (i.e. even if the commit to
>> pick is not empty)?
>
> I don't think so. If the commit is not empty, but all its changes are
> already in HEAD, then it will become "empty" when cherry-picked to HEAD.
> In such a case, we usually do not want to record an empty commit, but stop
> rebase to give to user a chance to deal with the situation.
Ah, makes sense. Thanks!
Neil Horman· Jul 18, 2012, 12:17 UTC · re: Johannes Sixt · lore

Re: [PATCH v8 4/4] git-rebase: add keep_empty flag

On Wed, Jul 18, 2012 at 09:10:06AM +0200, Johannes Sixt wrote:
Show 33 quoted lines
> Am 7/18/2012 8:20, schrieb Martin von Zweigbergk:
> > On Fri, Apr 20, 2012 at 7:36 AM, Neil Horman <nhorman@tuxdriver.com> wrote:
> >>  pick_one () {
> >>         ff=--ff
> >> +
> >>         case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
> >>         case "$force_rebase" in '') ;; ?*) ff= ;; esac
> >>         output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
> >> +
> >> +       if is_empty_commit "$sha1"
> >> +       then
> >> +               empty_args="--allow-empty"
> >> +       fi
> >> +
> >>         test -d "$rewritten" &&
> >>                 pick_one_preserving_merges "$@" && return
> >> -       output git cherry-pick $ff "$@"
> >> +       output git cherry-pick $empty_args $ff "$@"
> > 
> > The is_empty_commit check seems to mean that if $sha1 is an "empty"
> > commit, we pass the --allow-empty option to cherry-pick. If it's not
> > empty, we don't. The word "allow" in "allow-empty" suggests that even
> > if the commit is not empty, cherry-pick would not mind. So, can we
> > always pass "allow-empty" to cherry-pick (i.e. even if the commit to
> > pick is not empty)?
> 
> I don't think so. If the commit is not empty, but all its changes are
> already in HEAD, then it will become "empty" when cherry-picked to HEAD.
> In such a case, we usually do not want to record an empty commit, but stop
> rebase to give to user a chance to deal with the situation.
> 
> -- Hannes
> 

Yes, this is the meaning. "Allow" was used in the sense of a filter, in that we are allowing an empty commit to make it into the history, whereas a rebase or cherry-pick would normally exclude it. Neil

← back to recent threads