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

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

From
Neil Horman <nhorman@tuxdriver.com>
Date
Apr 17, 2012, 18:20 UTC
Message-ID
<1334686809-17634-3-git-send-email-nhorman@tuxdriver.com>
In-Reply-To
<1334686809-17634-1-git-send-email-nhorman@tuxdriver.com>

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(-)
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
Previous: Neil HormanNext: Clemens Buchacher
Message 86 of 121 in “Enhance git-rebases flexibiilty in handling empty commits”
  1. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Mar 30, 2012
  2. 1/4 git-cherry-pick: add keep-empty optionNeil Horman, Mar 30, 2012
  3. Junio C HamanoMar 30, 2012
  4. Jeff KingMar 30, 2012
  5. Neil HormanMar 31, 2012
  6. 2/4 git-rebase: add keep_empty flagNeil Horman, Mar 30, 2012
  7. Junio C HamanoMar 30, 2012
  8. Neil HormanMar 31, 2012
  9. 3/4 git-commit-am: Allow automatic rebasing to preserve empty commitsNeil Horman, Mar 30, 2012
  10. Junio C HamanoMar 30, 2012
  11. Neil HormanMar 31, 2012
  12. Junio C HamanoMar 30, 2012
  13. Neil HormanMar 31, 2012
  14. 4/4 git-commit-interactive: Allow rebasing to preserve empty commitsNeil Horman, Mar 30, 2012
  15. Junio C HamanoMar 30, 2012
  16. Neil HormanMar 31, 2012
  17. Junio C HamanoMar 30, 2012
  18. 0/5 Enhance git-rebases flexibiilty handling empty commits [v2]Neil Horman, Apr 5, 2012
  19. 1/5 argv-array: Add argv_array_pop function [v2]Neil Horman, Apr 5, 2012
  20. Junio C HamanoApr 5, 2012
  21. Neil HormanApr 5, 2012
  22. Neil HormanApr 6, 2012
  23. Jeff KingApr 6, 2012
  24. Neil HormanApr 6, 2012
  25. Junio C HamanoApr 6, 2012
  26. Jeff KingApr 6, 2012
  27. Cc tags in the commit message (Re: [PATCH 1/5] argv-array: Add argv_array_pop function [v2])Jonathan Nieder, Apr 6, 2012
  28. Junio C HamanoApr 6, 2012
  29. Jeff KingApr 6, 2012
  30. Junio C HamanoApr 6, 2012
  31. Neil HormanApr 6, 2012
  32. 2/5 git-cherry-pick: add allow-empty option [v2]Neil Horman, Apr 5, 2012
  33. 3/5 git-cherry-pick: Add ignore-if-made-empty option [v2]Neil Horman, Apr 5, 2012
  34. Junio C HamanoApr 5, 2012
  35. Neil HormanApr 5, 2012
  36. Junio C HamanoApr 6, 2012
  37. Neil HormanApr 6, 2012
  38. Johannes SixtApr 6, 2012
  39. 4/5 git-cherry-pick: Add test to validate new options [v2]Neil Horman, Apr 5, 2012
  40. 5/5 git-rebase: add keep_empty flag [v2]Neil Horman, Apr 5, 2012
  41. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 10, 2012
  42. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 10, 2012
  43. Junio C HamanoApr 10, 2012
  44. Neil HormanApr 10, 2012
  45. Junio C HamanoApr 10, 2012
  46. Neil HormanApr 10, 2012
  47. Junio C HamanoApr 10, 2012
  48. Neil HormanApr 10, 2012
  49. Junio C HamanoApr 10, 2012
  50. Neil HormanApr 11, 2012
  51. Junio C HamanoApr 11, 2012
  52. Neil HormanApr 11, 2012
  53. Junio C HamanoApr 11, 2012
  54. Neil HormanApr 11, 2012
  55. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 10, 2012
  56. Junio C HamanoApr 10, 2012
  57. Neil HormanApr 10, 2012
  58. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 10, 2012
  59. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 10, 2012
  60. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 13, 2012
  61. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 13, 2012
  62. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 13, 2012
  63. Clemens BuchacherApr 15, 2012
  64. Neil HormanApr 16, 2012
  65. Clemens BuchacherApr 16, 2012
  66. Neil HormanApr 17, 2012
  67. Junio C HamanoApr 17, 2012
  68. Clemens BuchacherApr 17, 2012
  69. Neil HormanApr 18, 2012
  70. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 13, 2012
  71. Clemens BuchacherApr 15, 2012
  72. Neil HormanApr 16, 2012
  73. Neil HormanApr 16, 2012
  74. Junio C HamanoApr 16, 2012
  75. Neil HormanApr 16, 2012
  76. Clemens BuchacherApr 16, 2012
  77. Neil HormanApr 17, 2012
  78. Clemens BuchacherApr 17, 2012
  79. Neil HormanApr 18, 2012
  80. Clemens BuchacherApr 18, 2012
  81. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 13, 2012
  82. Clemens BuchacherApr 15, 2012
  83. Neil HormanApr 16, 2012
  84. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 17, 2012
  85. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 17, 2012
  86. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 17, 2012
  87. Clemens BuchacherApr 17, 2012
  88. Neil HormanApr 18, 2012
  89. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 17, 2012
  90. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 17, 2012
  91. Clemens BuchacherApr 17, 2012
  92. Neil HormanApr 18, 2012
  93. Junio C HamanoApr 18, 2012
  94. Neil HormanApr 19, 2012
  95. Junio C HamanoApr 19, 2012
  96. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 18, 2012
  97. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 18, 2012
  98. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 18, 2012
  99. Junio C HamanoApr 18, 2012
  100. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 18, 2012
  101. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 18, 2012
  102. Zbigniew Jędrzejewski-SzmekApr 19, 2012
  103. Thomas RastApr 19, 2012
  104. Zbigniew Jędrzejewski-SzmekApr 19, 2012
  105. Neil HormanApr 19, 2012
  106. Junio C HamanoApr 19, 2012
  107. Junio C HamanoApr 19, 2012
  108. Neil HormanApr 20, 2012
  109. 0/4 Enhance git-rebases flexibiilty in handling empty commitsNeil Horman, Apr 20, 2012
  110. 1/4 git-cherry-pick: add allow-empty optionNeil Horman, Apr 20, 2012
  111. 2/4 git-cherry-pick: Add keep-redundant-commits optionNeil Horman, Apr 20, 2012
  112. Junio C HamanoApr 20, 2012
  113. Neil HormanApr 20, 2012
  114. 3/4 git-cherry-pick: Add test to validate new optionsNeil Horman, Apr 20, 2012
  115. 4/4 git-rebase: add keep_empty flagNeil Horman, Apr 20, 2012
  116. Junio C HamanoApr 25, 2012
  117. Neil HormanApr 25, 2012
  118. Martin von ZweigbergkJul 18, 2012
  119. Johannes SixtJul 18, 2012
  120. Martin von ZweigbergkJul 18, 2012
  121. Neil HormanJul 18, 2012

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

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