Skip to content

Commit b27cfb0

Browse files
nhormangitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent df478b7 commit b27cfb0

File tree

4 files changed

+102
-13
lines changed

4 files changed

+102
-13
lines changed

Documentation/git-cherry-pick.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,17 @@ effect to your index in a row.
110110
behavior, allowing empty commits to be preserved automatically
111111
in a cherry-pick. Note that when "--ff" is in effect, empty
112112
commits that meet the "fast-forward" requirement will be kept
113-
even without this option.
113+
even without this option. Note also, that use of this option only
114+
keeps commits that were initially empty (i.e. the commit recorded the
115+
same tree as its parent). Commits which are made empty due to a
116+
previous commit are dropped. To force the inclusion of those commits
117+
use `--keep-redundant-commits`.
118+
119+
--keep-redundant-commits::
120+
If a commit being cherry picked duplicates a commit already in the
121+
current history, it will become empty. By default these
122+
redundant commits are ignored. This option overrides that behavior and
123+
creates an empty commit object. Implies `--allow-empty`.
114124

115125
--strategy=<strategy>::
116126
Use the given merge strategy. Should only be used once.

builtin/revert.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
115115
OPT_END(),
116116
OPT_END(),
117117
OPT_END(),
118+
OPT_END(),
118119
};
119120

120121
if (opts->action == REPLAY_PICK) {
121122
struct option cp_extra[] = {
122123
OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
123124
OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
124-
OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve empty commits"),
125+
OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
126+
OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"),
125127
OPT_END(),
126128
};
127129
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)
139141
"--abort", rollback,
140142
NULL);
141143

144+
/* implies allow_empty */
145+
if (opts->keep_redundant_commits)
146+
opts->allow_empty = 1;
147+
142148
/* Set the subcommand */
143149
if (remove_state)
144150
opts->subcommand = REPLAY_REMOVE_STATE;

sequencer.c

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "rerere.h"
1414
#include "merge-recursive.h"
1515
#include "refs.h"
16+
#include "argv-array.h"
1617

1718
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
1819

@@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
251252
return !clean;
252253
}
253254

255+
static int is_index_unchanged(void)
256+
{
257+
unsigned char head_sha1[20];
258+
struct commit *head_commit;
259+
260+
if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
261+
return error(_("Could not resolve HEAD commit\n"));
262+
263+
head_commit = lookup_commit(head_sha1);
264+
if (!head_commit || parse_commit(head_commit))
265+
return error(_("could not parse commit %s\n"),
266+
sha1_to_hex(head_commit->object.sha1));
267+
268+
if (!active_cache_tree)
269+
active_cache_tree = cache_tree();
270+
271+
if (!cache_tree_fully_valid(active_cache_tree))
272+
if (cache_tree_update(active_cache_tree, active_cache,
273+
active_nr, 0))
274+
return error(_("Unable to update cache tree\n"));
275+
276+
return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
277+
}
278+
254279
/*
255280
* If we are cherry-pick, and if the merge did not result in
256281
* 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,
260285
*/
261286
static int run_git_commit(const char *defmsg, struct replay_opts *opts)
262287
{
263-
/* 7 is max possible length of our args array including NULL */
264-
const char *args[7];
265-
int i = 0;
288+
struct argv_array array;
289+
int rc;
290+
291+
argv_array_init(&array);
292+
argv_array_push(&array, "commit");
293+
argv_array_push(&array, "-n");
266294

267-
args[i++] = "commit";
268-
args[i++] = "-n";
269295
if (opts->signoff)
270-
args[i++] = "-s";
296+
argv_array_push(&array, "-s");
271297
if (!opts->edit) {
272-
args[i++] = "-F";
273-
args[i++] = defmsg;
298+
argv_array_push(&array, "-F");
299+
argv_array_push(&array, defmsg);
274300
}
301+
275302
if (opts->allow_empty)
276-
args[i++] = "--allow-empty";
303+
argv_array_push(&array, "--allow-empty");
277304

278-
args[i] = NULL;
305+
rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
306+
argv_array_clear(&array);
307+
return rc;
308+
}
309+
310+
static int is_original_commit_empty(struct commit *commit)
311+
{
312+
const unsigned char *ptree_sha1;
313+
314+
if (parse_commit(commit))
315+
return error(_("Could not parse commit %s\n"),
316+
sha1_to_hex(commit->object.sha1));
317+
if (commit->parents) {
318+
struct commit *parent = commit->parents->item;
319+
if (parse_commit(parent))
320+
return error(_("Could not parse parent commit %s\n"),
321+
sha1_to_hex(parent->object.sha1));
322+
ptree_sha1 = parent->tree->object.sha1;
323+
} else {
324+
ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
325+
}
279326

280-
return run_command_v_opt(args, RUN_GIT_CMD);
327+
return !hashcmp(ptree_sha1, commit->tree->object.sha1);
281328
}
282329

283330
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)
289336
char *defmsg = NULL;
290337
struct strbuf msgbuf = STRBUF_INIT;
291338
int res;
339+
int empty_commit;
340+
int index_unchanged;
292341

293342
if (opts->no_commit) {
294343
/*
@@ -414,6 +463,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
414463
free_commit_list(remotes);
415464
}
416465

466+
empty_commit = is_original_commit_empty(commit);
467+
if (empty_commit < 0)
468+
return empty_commit;
469+
417470
/*
418471
* If the merge was clean or if it failed due to conflict, we write
419472
* 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)
434487
print_advice(res == 1, opts);
435488
rerere(opts->allow_rerere_auto);
436489
} else {
490+
index_unchanged = is_index_unchanged();
491+
/*
492+
* If index_unchanged is less than 0, that indicates we either
493+
* couldn't parse HEAD or the index, so error out here.
494+
*/
495+
if (index_unchanged < 0)
496+
return index_unchanged;
497+
498+
if (!empty_commit && !opts->keep_redundant_commits && index_unchanged)
499+
/*
500+
* The head tree and the index match
501+
* meaning the commit is empty. Since it wasn't created
502+
* empty (based on the previous test), we can conclude
503+
* the commit has been made redundant. Since we don't
504+
* want to keep redundant commits, we can just return
505+
* here, skipping this commit
506+
*/
507+
return 0;
508+
437509
if (!opts->no_commit)
438510
res = run_git_commit(defmsg, opts);
439511
}

sequencer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct replay_opts {
3030
int allow_ff;
3131
int allow_rerere_auto;
3232
int allow_empty;
33+
int keep_redundant_commits;
3334

3435
int mainline;
3536

0 commit comments

Comments
 (0)