From: "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Philippe Blain <levraiphilippeblain@gmail.com>,
Johannes Sixt <j6t@kdbg.org>,
Johannes Schindelin <johannes.schindelin@gmx.de>,
Johannes Schindelin <johannes.schindelin@gmx.de>
Subject: [PATCH v2] range-diff: optionally include merge commits' diffs in the analysis
Date: Fri, 08 Nov 2024 13:43:03 +0000 [thread overview]
Message-ID: <pull.1734.v2.git.1731073383564.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.1734.git.1731000007391.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The `git log` command already offers support for including diffs for
merges, via the `--diff-merges=<format>` option.
Let's add corresponding support for `git range-diff`, too. This makes it
more convenient to spot differences between iterations of non-linear
contributions, where so-called "evil merges" are sometimes necessary and
need to be reviewed, too.
In my code reviews, I found the `--diff-merges=first-parent` option
particularly useful.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
Support diff merges option in range diff
The git range-diff command does the same with merge commits as git
rebase: It ignores them.
However, when comparing branch thickets it can be quite illuminating to
watch out for inadvertent changes in merge commits, in particular when
some "evil" merges have been replayed, i.e. merges that needed to
introduce changes outside of the merge conflicts (e.g. when one branch
changed a function's signature and another branch introduced a caller of
said function), in case the replayed merge is no longer "evil" and
therefore potentially incorrect.
Let's introduce support for the --diff-merges option that is passed
through to those git log commands.
I had a need for this earlier this year and got it working, leaving the
GitGitGadget PR in a draft mode. Phil Blain found it and kindly
nerd-sniped me into readying it for submitting, so say thanks to Phil!
Changes since v1:
* Changed the documentation to recommend first-parent mode instead of
vaguely talking about various modes' merits.
* Disallowed the no-arg --diff-merges option (because --diff-merges
requires an argument).
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1734%2Fdscho%2Fsupport-diff-merges-option-in-range-diff-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1734/dscho/support-diff-merges-option-in-range-diff-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1734
Range-diff vs v1:
1: 11361e07af8 ! 1: d01a395900b range-diff: optionally include merge commits' diffs in the analysis
@@ Documentation/git-range-diff.txt: to revert to color all lines according to the
+ corresponding `--diff-merges=<format>` option of linkgit:git-log[1],
+ and include them in the comparison.
++
-+Note: Some of the formats supported by linkgit:git-log[1] make less sense in
-+the context of the `range-diff` command than other formats, so choose wisely!
++Note: In the common case, the `first-parent` mode will be the most natural one
++to use, as it is consistent with the idea that a merge is kind of a "meta
++patch", comprising all the merged commits' patches into a single one.
+
--[no-]notes[=<ref>]::
This flag is passed to the `git log` program
@@ builtin/range-diff.c: int cmd_range_diff(int argc,
N_("notes"), N_("passed to 'git log'"),
PARSE_OPT_OPTARG),
+ OPT_PASSTHRU_ARGV(0, "diff-merges", &diff_merges_arg,
-+ N_("style"), N_("passed to 'git log'"),
-+ PARSE_OPT_OPTARG),
++ N_("style"), N_("passed to 'git log'"), 0),
OPT_BOOL(0, "left-only", &left_only,
N_("only emit output related to the first range")),
OPT_BOOL(0, "right-only", &right_only,
Documentation/git-range-diff.txt | 11 ++++++++++-
builtin/range-diff.c | 10 ++++++++++
range-diff.c | 15 +++++++++++----
range-diff.h | 1 +
t/t3206-range-diff.sh | 16 ++++++++++++++++
5 files changed, 48 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt
index fbdbe0befeb..17a85957877 100644
--- a/Documentation/git-range-diff.txt
+++ b/Documentation/git-range-diff.txt
@@ -10,7 +10,7 @@ SYNOPSIS
[verse]
'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
[--no-dual-color] [--creation-factor=<factor>]
- [--left-only | --right-only]
+ [--left-only | --right-only] [--diff-merges=<format>]
( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
[[--] <path>...]
@@ -81,6 +81,15 @@ to revert to color all lines according to the outer diff markers
Suppress commits that are missing from the second specified range
(or the "right range" when using the `<rev1>...<rev2>` format).
+--diff-merges=<format>::
+ Instead of ignoring merge commits, generate diffs for them using the
+ corresponding `--diff-merges=<format>` option of linkgit:git-log[1],
+ and include them in the comparison.
++
+Note: In the common case, the `first-parent` mode will be the most natural one
+to use, as it is consistent with the idea that a merge is kind of a "meta
+patch", comprising all the merged commits' patches into a single one.
+
--[no-]notes[=<ref>]::
This flag is passed to the `git log` program
(see linkgit:git-log[1]) that generates the patches.
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 1b33ab66a7b..901de5d133d 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -21,6 +21,7 @@ int cmd_range_diff(int argc,
{
struct diff_options diffopt = { NULL };
struct strvec other_arg = STRVEC_INIT;
+ struct strvec diff_merges_arg = STRVEC_INIT;
struct range_diff_options range_diff_opts = {
.creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT,
.diffopt = &diffopt,
@@ -36,6 +37,8 @@ int cmd_range_diff(int argc,
OPT_PASSTHRU_ARGV(0, "notes", &other_arg,
N_("notes"), N_("passed to 'git log'"),
PARSE_OPT_OPTARG),
+ OPT_PASSTHRU_ARGV(0, "diff-merges", &diff_merges_arg,
+ N_("style"), N_("passed to 'git log'"), 0),
OPT_BOOL(0, "left-only", &left_only,
N_("only emit output related to the first range")),
OPT_BOOL(0, "right-only", &right_only,
@@ -62,6 +65,12 @@ int cmd_range_diff(int argc,
if (!simple_color)
diffopt.use_color = 1;
+ /* If `--diff-merges` was specified, imply `--merges` */
+ if (diff_merges_arg.nr) {
+ range_diff_opts.include_merges = 1;
+ strvec_pushv(&other_arg, diff_merges_arg.v);
+ }
+
for (i = 0; i < argc; i++)
if (!strcmp(argv[i], "--")) {
dash_dash = i;
@@ -155,6 +164,7 @@ int cmd_range_diff(int argc,
res = show_range_diff(range1.buf, range2.buf, &range_diff_opts);
strvec_clear(&other_arg);
+ strvec_clear(&diff_merges_arg);
strbuf_release(&range1);
strbuf_release(&range2);
diff --git a/range-diff.c b/range-diff.c
index bbb0952264b..9e59733059b 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -38,7 +38,8 @@ struct patch_util {
* as struct object_id (will need to be free()d).
*/
static int read_patches(const char *range, struct string_list *list,
- const struct strvec *other_arg)
+ const struct strvec *other_arg,
+ unsigned int include_merges)
{
struct child_process cp = CHILD_PROCESS_INIT;
struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT;
@@ -49,7 +50,7 @@ static int read_patches(const char *range, struct string_list *list,
size_t size;
int ret = -1;
- strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
+ strvec_pushl(&cp.args, "log", "--no-color", "-p",
"--reverse", "--date-order", "--decorate=no",
"--no-prefix", "--submodule=short",
/*
@@ -64,6 +65,8 @@ static int read_patches(const char *range, struct string_list *list,
"--pretty=medium",
"--show-notes-by-default",
NULL);
+ if (!include_merges)
+ strvec_push(&cp.args, "--no-merges");
strvec_push(&cp.args, range);
if (other_arg)
strvec_pushv(&cp.args, other_arg->v);
@@ -96,11 +99,14 @@ static int read_patches(const char *range, struct string_list *list,
}
if (skip_prefix(line, "commit ", &p)) {
+ char *q;
if (util) {
string_list_append(list, buf.buf)->util = util;
strbuf_reset(&buf);
}
CALLOC_ARRAY(util, 1);
+ if (include_merges && (q = strstr(p, " (from ")))
+ *q = '\0';
if (repo_get_oid(the_repository, p, &util->oid)) {
error(_("could not parse commit '%s'"), p);
FREE_AND_NULL(util);
@@ -571,13 +577,14 @@ int show_range_diff(const char *range1, const char *range2,
struct string_list branch1 = STRING_LIST_INIT_DUP;
struct string_list branch2 = STRING_LIST_INIT_DUP;
+ unsigned int include_merges = range_diff_opts->include_merges;
if (range_diff_opts->left_only && range_diff_opts->right_only)
res = error(_("options '%s' and '%s' cannot be used together"), "--left-only", "--right-only");
- if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg))
+ if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg, include_merges))
res = error(_("could not parse log for '%s'"), range1);
- if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg))
+ if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg, include_merges))
res = error(_("could not parse log for '%s'"), range2);
if (!res) {
diff --git a/range-diff.h b/range-diff.h
index 2f69f6a434d..cd85000b5a0 100644
--- a/range-diff.h
+++ b/range-diff.h
@@ -16,6 +16,7 @@ struct range_diff_options {
int creation_factor;
unsigned dual_color:1;
unsigned left_only:1, right_only:1;
+ unsigned include_merges:1;
const struct diff_options *diffopt; /* may be NULL */
const struct strvec *other_arg; /* may be NULL */
};
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index 86010931ab6..c18a3fdab83 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -909,4 +909,20 @@ test_expect_success 'submodule changes are shown irrespective of diff.submodule'
test_cmp expect actual
'
+test_expect_success '--diff-merges' '
+ renamed_oid=$(git rev-parse --short renamed-file) &&
+ tree=$(git merge-tree unmodified renamed-file) &&
+ clean=$(git commit-tree -m merge -p unmodified -p renamed-file $tree) &&
+ clean_oid=$(git rev-parse --short $clean) &&
+ conflict=$(git commit-tree -m merge -p unmodified -p renamed-file^ $tree) &&
+ conflict_oid=$(git rev-parse --short $conflict) &&
+
+ git range-diff --diff-merges=1 $clean...$conflict >actual &&
+ cat >expect <<-EOF &&
+ 1: $renamed_oid < -: ------- s/12/B/
+ 2: $clean_oid = 1: $conflict_oid merge
+ EOF
+ test_cmp expect actual
+'
+
test_done
base-commit: 777489f9e09c8d0dd6b12f9d90de6376330577a2
--
gitgitgadget
next prev parent reply other threads:[~2024-11-08 13:43 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-11-07 17:20 [PATCH] range-diff: optionally include merge commits' diffs in the analysis Johannes Schindelin via GitGitGadget
2024-11-08 3:46 ` Junio C Hamano
2024-11-08 6:53 ` Johannes Sixt
2024-11-08 10:53 ` Johannes Schindelin
2024-11-09 8:49 ` Elijah Newren
2024-11-11 0:20 ` Junio C Hamano
2024-11-08 11:56 ` Junio C Hamano
2024-11-08 15:53 ` Elijah Newren
2024-11-08 13:43 ` Johannes Schindelin via GitGitGadget [this message]
2024-11-08 17:04 ` [PATCH v2] " Elijah Newren
2024-11-11 19:55 ` Johannes Schindelin
2024-11-10 20:30 ` Philippe Blain
2024-11-11 20:07 ` Johannes Schindelin
2024-11-26 7:58 ` Junio C Hamano
2024-11-11 0:37 ` Junio C Hamano
2024-11-11 16:51 ` Elijah Newren
2024-11-12 0:29 ` Junio C Hamano
2024-12-16 14:11 ` [PATCH v3 0/2] Support diff merges option in range diff Johannes Schindelin via GitGitGadget
2024-12-16 14:11 ` [PATCH v3 1/2] range-diff: optionally include merge commits' diffs in the analysis Johannes Schindelin via GitGitGadget
2024-12-16 14:11 ` [PATCH v3 2/2] range-diff: introduce the convenience option `--remerge-diff` Johannes Schindelin via GitGitGadget
2024-11-08 15:33 ` [PATCH] range-diff: optionally include merge commits' diffs in the analysis Elijah Newren
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.1734.v2.git.1731073383564.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=j6t@kdbg.org \
--cc=johannes.schindelin@gmx.de \
--cc=levraiphilippeblain@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).