From af2f2e4d0431427d711f8125b80eeb7333694f44 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Sat, 6 Mar 2021 09:06:56 -0800 Subject: [PATCH] show, log: provide --remerge-diff-only capability --remerge-diff-only is similar to --remerge-diff and implies its behavior, but additionally will also change how all single-parent commits are displayed. If a single parent commit cannot be determined to be either a cherry-pick or a revert (based on its commit message), then: (a) for `git log`, the commit will simply be skipped, (b) for `git show`, a warning that we could not find cherry-pick or revert information for the commit will be shown. If a single parent commit can be determined to be a cherry pick or a revert (and the commit it is a cherry pick or revert of can be found in the commit message), then --remerge-diff-only will cause that commit to be automatically reverted or cherry-picked again (but only creating a new tree, not a new commit), and then the commit will be diffed against the automatic revert or cherry-pick tree. Signed-off-by: Elijah Newren --- Documentation/diff-options.txt | 19 ++++++ builtin/log.c | 6 +- diff-merges.c | 13 +++++ log-tree.c | 102 ++++++++++++++++++++++++++++++++- revision.c | 15 +++++ revision.h | 3 +- 6 files changed, 153 insertions(+), 5 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index cd0b81adbb69d1..59f23b46ea01c6 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -59,6 +59,12 @@ ifdef::git-log[] Produce remerge-diff output for merge commits. Shortcut for '--diff-merges=remerge -p'. +--remerge-diff-only::: + Produce remerge-diff output for merge commits, and for + cherry-picks and reverts. Do not show diff output for + single-parent commits that are not cherry-picks or reverts. + Shortcut for '--diff-merges=remerge-only -p'. + --no-diff-merges:: Synonym for '--diff-merges=off'. @@ -109,6 +115,19 @@ remerge, r:: The output emitted when this option is used is subject to change, and so is its interaction with other options (unless explicitly documented). ++ +remerge-only, ro:: + Similar to remerge, but also looks for messages in single + parent commits suggesting the commit is a cherry-pick or a + revert. If it finds such a note, it will create a temporary + tree representing a redo of the cherry-pick or revert, + possibly including conflict markers. It will then show a diff + between that temporary tree and the actual commit. Further, + with this flag, single parent commits which are not + cherry-picks or reverts will be skipped. ++ +Much like remerge, the output emitted with this option and interactions +with other options is subject to change. -- --combined-all-paths:: diff --git a/builtin/log.c b/builtin/log.c index 21a81c72ac76be..6122398b657f8e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -505,7 +505,7 @@ static int cmd_log_walk_no_free(struct rev_info *rev) int saved_nrl = 0; int saved_dcctc = 0; - if (rev->remerge_diff) { + if (rev->remerge_diff || rev->remerge_diff_only) { rev->remerge_objdir = tmp_objdir_create("remerge-diff"); if (!rev->remerge_objdir) die(_("unable to create temporary object directory")); @@ -551,7 +551,7 @@ static int cmd_log_walk_no_free(struct rev_info *rev) rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; - if (rev->remerge_diff) { + if (rev->remerge_diff || rev->remerge_diff_only) { tmp_objdir_destroy(rev->remerge_objdir); rev->remerge_objdir = NULL; } @@ -2248,6 +2248,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die(_("--check does not make sense")); if (rev.remerge_diff) die(_("--remerge-diff does not make sense")); + if (rev.remerge_diff_only) + die(_("--remerge-diff-only does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || diff --git a/diff-merges.c b/diff-merges.c index 45507588a2797b..662f14c81d9587 100644 --- a/diff-merges.c +++ b/diff-merges.c @@ -20,6 +20,7 @@ static void suppress(struct rev_info *revs) revs->merges_imply_patch = 0; revs->merges_need_diff = 0; revs->remerge_diff = 0; + revs->remerge_diff_only = 0; } static void common_setup(struct rev_info *revs) @@ -67,6 +68,13 @@ static void set_remerge_diff(struct rev_info *revs) revs->simplify_history = 0; } +static void set_remerge_diff_only(struct rev_info *revs) +{ + suppress(revs); + revs->remerge_diff_only = 1; + revs->limited = 1; /* needs limit_list() */ +} + static diff_merges_setup_func_t func_by_opt(const char *optarg) { if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) @@ -81,6 +89,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg) return set_dense_combined; if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge")) return set_remerge_diff; + if (!strcmp(optarg, "ro") || !strcmp(optarg, "remerge-only")) + return set_remerge_diff_only; if (!strcmp(optarg, "m") || !strcmp(optarg, "on")) return set_to_default; return NULL; @@ -137,6 +147,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv) } else if (!strcmp(arg, "--remerge-diff")) { set_remerge_diff(revs); revs->merges_imply_patch = 1; + } else if (!strcmp(arg, "--remerge-diff-only")) { + set_remerge_diff_only(revs); + revs->merges_imply_patch = 1; } else if (!strcmp(arg, "--no-diff-merges")) { set_none(revs); } else if (!strcmp(arg, "--combined-all-paths")) { diff --git a/log-tree.c b/log-tree.c index 7de744911e8e06..a8bbd179e0a1e2 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "approximate-picks.h" #include "commit-reach.h" #include "config.h" #include "diff.h" @@ -1065,6 +1066,94 @@ static int do_remerge_diff(struct rev_info *opt, return !opt->loginfo; } +static int do_repicked_remerge_diff(struct rev_info *opt, + struct commit_list *parents, + struct object_id *oid, + struct commit *commit) +{ + struct merge_options o; + struct merge_result res; + struct commit *base, *side1, *side2; + struct tree *base_tree, *side1_tree, *side2_tree; + struct strbuf base_label = STRBUF_INIT; + struct strbuf side1_label = STRBUF_INIT; + struct strbuf side2_label = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + int is_revert; + + /* side1 is the commit on which the cherry-pick or revert was built */ + side1 = parents->item; + parse_commit_or_die(side1); + /* get side2 and base */ + get_message_pick(commit, &is_revert, &side2, &base); + if (!side2) { + show_log(opt); + fprintf(opt->diffopt.file, + "diff: warning: Could not find cherry-pick or revert " + "information for\n commit %s .\n", + oid_to_hex(&commit->object.oid)); + return !opt->loginfo; + } + + /* Convert commits to trees */ + base_tree = base ? repo_get_commit_tree(the_repository, base) : + lookup_tree(the_repository, the_hash_algo->empty_tree); + side1_tree = repo_get_commit_tree(the_repository, side1); + side2_tree = repo_get_commit_tree(the_repository, side2); + + /* Revert implemented as cherry-pick with base and side2 swapped */ + if (is_revert) { + SWAP(base, side2); + SWAP(base_tree, side2_tree); + } + + /* Setup merge options */ + init_merge_options(&o, the_repository); + memset(&res, 0, sizeof(res)); + o.show_rename_progress = 0; + o.record_conflict_msgs_as_headers = 1; + o.msg_header_prefix = "remerge"; + ctx.abbrev = DEFAULT_ABBREV; + if (base) + repo_format_commit_message(the_repository, base, "%h (%s)", + &base_label, &ctx); + else + strbuf_addstr(&base_label, "empty tree"); + repo_format_commit_message(the_repository, side1, "%h (%s)", + &side1_label, &ctx); + if (side2) + repo_format_commit_message(the_repository, side2, "%h (%s)", + &side2_label, &ctx); + else + strbuf_addstr(&side2_label, "empty tree"); + o.ancestor = base_label.buf; + o.branch1 = side1_label.buf; + o.branch2 = side2_label.buf; + + /* Redo the merge */ + merge_incore_nonrecursive(&o, base_tree, side1_tree, side2_tree, &res); + + /* Diff against the merge */ + setup_additional_headers(&opt->diffopt, res.path_messages); + diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt); + log_tree_diff_flush(opt); + + /* Release resources */ + cleanup_additional_headers(&opt->diffopt); + merge_finalize(&o, &res); + strbuf_release(&base_label); + strbuf_release(&side1_label); + strbuf_release(&side2_label); + + /* Clean up the contents of the temporary object directory */ + if (opt->remerge_objdir) + tmp_objdir_discard_objects(opt->remerge_objdir); + else + BUG("did a remerge diff without remerge_objdir?!?"); + + return !opt->loginfo; +} + /* * Show the diff of a commit. * @@ -1077,6 +1166,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log struct object_id *oid; int is_merge; int all_need_diff = opt->diff || opt->diffopt.flags.exit_with_status; + int remerge_wanted = (opt->remerge_diff || opt->remerge_diff_only); if (!all_need_diff && !opt->merges_need_diff) return 0; @@ -1091,6 +1181,13 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log /* Root commit? */ if (!parents) { + if (opt->remerge_diff_only) { + show_log(opt); + fprintf(opt->diffopt.file, + "diff: warning: No remerge-diffs for root " + "commits.\n"); + return 1; + } if (opt->show_root_diff) { diff_root_tree_oid(oid, "", &opt->diffopt); log_tree_diff_flush(opt); @@ -1101,7 +1198,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log if (is_merge) { int octopus = (parents->next->next != NULL); - if (opt->remerge_diff) { + if (remerge_wanted) { if (octopus) { show_log(opt); fprintf(opt->diffopt.file, @@ -1120,7 +1217,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log } } else return 0; - } + } else if (opt->remerge_diff_only) + return do_repicked_remerge_diff(opt, parents, oid, commit); showed_log = 0; for (;;) { diff --git a/revision.c b/revision.c index 2ee6886078cf1d..03975845e490a7 100644 --- a/revision.c +++ b/revision.c @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "approximate-picks.h" #include "config.h" #include "environment.h" #include "gettext.h" @@ -1468,6 +1469,20 @@ static int limit_list(struct rev_info *revs) obj->flags |= UNINTERESTING; if (process_parents(revs, commit, &original_list, NULL) < 0) return -1; + if (revs->remerge_diff_only) { + int revert; + struct commit *pick, *base; + struct commit_list *parents; + + parents = get_saved_parents(revs, commit); + if (!parents) + continue; + if (parents && !parents->next) { + get_message_pick(commit, &revert, &pick, &base); + if (!pick) + continue; + } + } if (obj->flags & UNINTERESTING) { mark_parents_uninteresting(revs, commit); slop = still_interesting(original_list, date, slop, &interesting_cache); diff --git a/revision.h b/revision.h index 0e470d1df19f69..5b98362ae6fcb5 100644 --- a/revision.h +++ b/revision.h @@ -248,7 +248,8 @@ struct rev_info { combined_all_paths:1, dense_combined_merges:1, first_parent_merges:1, - remerge_diff:1; + remerge_diff:1, + remerge_diff_only:1; /* Format info */ int show_notes;