threads / patch / 63637

v2, 2 partsgit pull/merge --compact-summary

Subject: [PATCH v2 0/2] git pull/merge --compact-summary

## tl;dr

7 messages between Jun 12, 2025 and Jun 13, 2025. Diffs are folded; open one to read it.

replies: 6people: 2as markdown or json

Junio C Hamano· Jun 12, 2025, 22:25 UTC · lore

By default, "git merge" and "git pull" shows "git diff --stat @{1}" to show the extent of the changes. While it gives a good overview, one thing that is missing in this output is which paths are created and/or deleted.

With these two patches, these commands learn "--compact-summary" command line option and a new value "compact" for the existing merge.stat configuration variable, to change that final output to use "git diff --compact-summary @{1}" instead.

I sent this out with random fix-ups in separate threads but never made a formal patch series. Here are the earlier messages:

 https://lore.kernel.org/git/xmqq5xh6xlpm.fsf@gitster.g/
 https://lore.kernel.org/git/xmqqfrg6i57t.fsf@gitster.g/
Junio C Hamano (2):
  merge/pull: --compact-summary
  merge/pull: extend merge.stat configuration variable to cover
    --compact-summary
 Documentation/config/merge.adoc  | 12 ++++++--
 Documentation/git-merge.adoc     |  2 +-
 Documentation/merge-options.adoc |  3 ++
 builtin/merge.c                  | 53 +++++++++++++++++++++++++++++---
 builtin/pull.c                   |  3 ++
 t/t7600-merge.sh                 | 43 +++++++++++++++++++++++++-
 6 files changed, 108 insertions(+), 8 deletions(-)
-- 
2.50.0-rc2-255-gd84100c98d
Junio C Hamano· Jun 12, 2025, 22:25 UTC · re: Junio C Hamano · lore

[PATCH v2 1/2] merge/pull: --compact-summary

By default, "git merge" and "git pull" shows "git diff --stat @{1}" to show the extent of the changes. While it gives a good overview, one thing that is missing in this output is which paths are created and/or deleted.

Introduce "--compact-summary" option to these two commands that tells it to instead show "git diff --compact-summary @{1}", which gives the same diffstat but notes the created or deleted paths.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-merge.adoc     |  2 +-
 Documentation/merge-options.adoc |  3 +++
 builtin/merge.c                  | 39 ++++++++++++++++++++++++++++----
 builtin/pull.c                   |  3 +++
 t/t7600-merge.sh                 | 28 ++++++++++++++++++++++-
 5 files changed, 69 insertions(+), 6 deletions(-)
Show changes to 5 files +69 −6

Documentation/git-merge.adoc, Documentation/merge-options.adoc, builtin/merge.c, builtin/pull.c, t/t7600-merge.sh

diff --git a/Documentation/git-merge.adoc b/Documentation/git-merge.adoc
index 12aa859d16..d53923c3b7 100644
--- a/Documentation/git-merge.adoc
+++ b/Documentation/git-merge.adoc
@@ -9,7 +9,7 @@ git-merge - Join two or more development histories together
 SYNOPSIS
 --------
 [synopsis]
-git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
+git merge [-n] [--stat] [--compact-summary] [--no-commit] [--squash] [--[no-]edit]
 	[--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
 	[--[no-]allow-unrelated-histories]
 	[--[no-]rerere-autoupdate] [-m <msg>] [-F <file>]
diff --git a/Documentation/merge-options.adoc b/Documentation/merge-options.adoc
index 078f4f6157..95ef491be1 100644
--- a/Documentation/merge-options.adoc
+++ b/Documentation/merge-options.adoc
@@ -113,6 +113,9 @@ include::signoff-option.adoc[]
 With `-n` or `--no-stat` do not show a diffstat at the end of the
 merge.
 
+`--compact-summary`::
+	Show a compact-summary at the end of the merge.
+
 `--squash`::
 `--no-squash`::
 	Produce the working tree and index state as if a real merge
diff --git a/builtin/merge.c b/builtin/merge.c
index ce90e52fe4..736739d3a9 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -69,7 +69,10 @@ static const char * const builtin_merge_usage[] = {
 	NULL
 };
 
-static int show_diffstat = 1, shortlog_len = -1, squash;
+#define MERGE_SHOW_DIFFSTAT 1
+#define MERGE_SHOW_COMPACTSUMMARY 2
+
+static int show_diffstat = MERGE_SHOW_DIFFSTAT, shortlog_len = -1, squash;
 static int option_commit = -1;
 static int option_edit = -1;
 static int allow_trivial = 1, have_message, verify_signatures;
@@ -243,12 +246,28 @@ static int option_parse_strategy(const struct option *opt UNUSED,
 	return 0;
 }
 
+static int option_parse_compact_summary(const struct option *opt,
+					const char *name UNUSED, int unset)
+{
+	int *setting = opt->value;
+
+	if (unset)
+		*setting = 0;
+	else
+		*setting = MERGE_SHOW_COMPACTSUMMARY;
+	return 0;
+}
+
 static struct option builtin_merge_options[] = {
 	OPT_SET_INT('n', NULL, &show_diffstat,
 		N_("do not show a diffstat at the end of the merge"), 0),
 	OPT_BOOL(0, "stat", &show_diffstat,
 		N_("show a diffstat at the end of the merge")),
 	OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
+	OPT_CALLBACK_F(0, "compact-summary", &show_diffstat, N_("compact-summary"),
+		       N_("show a compactstat at the end of the merge"),
+		       PARSE_OPT_NOARG,
+		       option_parse_compact_summary),
 	{
 		.type = OPTION_INTEGER,
 		.long_name = "log",
@@ -494,8 +513,19 @@ static void finish(struct commit *head_commit,
 		struct diff_options opts;
 		repo_diff_setup(the_repository, &opts);
 		init_diffstat_widths(&opts);
-		opts.output_format |=
-			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+
+		switch (show_diffstat) {
+		case MERGE_SHOW_DIFFSTAT: /* 1 */
+			opts.output_format |=
+				DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+			break;
+		case MERGE_SHOW_COMPACTSUMMARY: /* 2 */
+			opts.output_format |= DIFF_FORMAT_DIFFSTAT;
+			opts.flags.stat_with_summary = 1;
+			break;
+		default:
+			break;
+		}
 		opts.detect_rename = DIFF_DETECT_RENAME;
 		diff_setup_done(&opts);
 		diff_tree_oid(head, new_head, "", &opts);
@@ -643,7 +673,8 @@ static int git_merge_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
-		show_diffstat = git_config_bool(k, v);
+		show_diffstat = git_config_bool(k, v)
+			? MERGE_SHOW_DIFFSTAT : 0;
 	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
 	} else if (!strcmp(k, "pull.twohead")) {
diff --git a/builtin/pull.c b/builtin/pull.c
index a1ebc6ad33..6e72a2e9a4 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -143,6 +143,9 @@ static struct option pull_options[] = {
 	OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
 		N_("(synonym to --stat)"),
 		PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
+	OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL,
+		N_("show a compact-summary at the end of the merge"),
+		PARSE_OPT_NOARG),
 	OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
 		N_("add (at most <n>) entries from shortlog to merge commit message"),
 		PARSE_OPT_OPTARG),
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 2a8df29219..2972922b6a 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -185,8 +185,19 @@ test_expect_success 'reject non-strategy with a git-merge-foo name' '
 test_expect_success 'merge c0 with c1' '
 	echo "OBJID HEAD@{0}: merge c1: Fast-forward" >reflog.expected &&
 
+	cat >expect <<-\EOF &&
+	Updating FROM..TO
+	Fast-forward
+	 file  | 2 +-
+	 other | 9 +++++++++
+	 2 files changed, 10 insertions(+), 1 deletion(-)
+	 create mode 100644 other
+	EOF
+
 	git reset --hard c0 &&
-	git merge c1 &&
+	git merge c1 >out &&
+	sed -e "1s/^Updating [0-9a-f.]*/Updating FROM..TO/" out >actual &&
+	test_cmp expect actual &&
 	verify_merge file result.1 &&
 	verify_head "$c1" &&
 
@@ -205,6 +216,21 @@ test_expect_success 'merge c0 with c1 with --ff-only' '
 	verify_head "$c1"
 '
 
+test_expect_success 'the same merge with compact summary' '
+	cat >expect <<-\EOF &&
+	Updating FROM..TO
+	Fast-forward
+	 file        | 2 +-
+	 other (new) | 9 +++++++++
+	 2 files changed, 10 insertions(+), 1 deletion(-)
+	EOF
+
+	git reset --hard c0 &&
+	git merge --compact-summary c1 >out &&
+	sed -e "1s/^Updating [0-9a-f.]*/Updating FROM..TO/" out >actual &&
+	test_cmp expect actual
+'
+
 test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge from unborn branch' '
-- 
2.50.0-rc2-255-gd84100c98d
Kristoffer Haugsbakk· Jun 13, 2025, 12:07 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 1/2] merge/pull: --compact-summary

On Fri, Jun 13, 2025, at 00:25, Junio C Hamano wrote:
Show 10 quoted lines
>  static struct option builtin_merge_options[] = {
>  	OPT_SET_INT('n', NULL, &show_diffstat,
>  		N_("do not show a diffstat at the end of the merge"), 0),
>  	OPT_BOOL(0, "stat", &show_diffstat,
>  		N_("show a diffstat at the end of the merge")),
>  	OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
> +	OPT_CALLBACK_F(0, "compact-summary", &show_diffstat, N_("compact-summary"),
> +		       N_("show a compactstat at the end of the merge"),
> +		       PARSE_OPT_NOARG,
> +		       option_parse_compact_summary),
`compactstat` is very dense.  s/compactstat/compact stat/ ?
Junio C Hamano· Jun 13, 2025, 16:26 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH v2 1/2] merge/pull: --compact-summary

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
Show 13 quoted lines
> On Fri, Jun 13, 2025, at 00:25, Junio C Hamano wrote:
>>  static struct option builtin_merge_options[] = {
>>  	OPT_SET_INT('n', NULL, &show_diffstat,
>>  		N_("do not show a diffstat at the end of the merge"), 0),
>>  	OPT_BOOL(0, "stat", &show_diffstat,
>>  		N_("show a diffstat at the end of the merge")),
>>  	OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
>> +	OPT_CALLBACK_F(0, "compact-summary", &show_diffstat, N_("compact-summary"),
>> +		       N_("show a compactstat at the end of the merge"),
>> +		       PARSE_OPT_NOARG,
>> +		       option_parse_compact_summary),
>
> `compactstat` is very dense.  s/compactstat/compact stat/ ?

Nice spotting. I think "compact summary" (to match the diff option "--compact-summary") may be even better.

Junio C Hamano· Jun 12, 2025, 22:25 UTC · re: Junio C Hamano · lore

[PATCH v2 2/2] merge/pull: extend merge.stat configuration variable to cover --compact-summary

Existing `merge.stat` configuration variable is a Boolean that defaults to `true` to control `git merge --[no-]stat` behaviour.

Extend it to be "Boolean or text", that takes false, true, or "compact", with the last one triggering the --compact-summary option introduced earlier.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config/merge.adoc | 12 ++++++++++--
 builtin/merge.c                 | 18 ++++++++++++++++--
 t/t7600-merge.sh                | 15 +++++++++++++++
 3 files changed, 41 insertions(+), 4 deletions(-)
Show changes to 3 files +41 −4

Documentation/config/merge.adoc, builtin/merge.c, t/t7600-merge.sh

diff --git a/Documentation/config/merge.adoc b/Documentation/config/merge.adoc
index 86359f6dd2..251a48fdf8 100644
--- a/Documentation/config/merge.adoc
+++ b/Documentation/config/merge.adoc
@@ -81,8 +81,16 @@ as `false`. Defaults to `conflict`.
 	attributes" in linkgit:gitattributes[5].
 
 `merge.stat`::
-	Whether to print the diffstat between `ORIG_HEAD` and the merge result
-	at the end of the merge.  True by default.
+	What, if anything, to print between `ORIG_HEAD` and the merge result
+	at the end of the merge.  Possible values are:
++
+--
+`false`;; Show nothing.
+`true`;; Show `git diff --diffstat ORIG_HEAD`.
+`compact`;; Show `git diff --compact-summary ORIG_HEAD`.
+--
++
+If this variable is left unspecified, it defaults to `true`.
 
 `merge.autoStash`::
 	When set to `true`, automatically create a temporary stash entry
diff --git a/builtin/merge.c b/builtin/merge.c
index 736739d3a9..65fed4b687 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -673,8 +673,22 @@ static int git_merge_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
-		show_diffstat = git_config_bool(k, v)
-			? MERGE_SHOW_DIFFSTAT : 0;
+		int val = git_parse_maybe_bool_text(v);
+		switch (val) {
+		case 0:
+			show_diffstat = 0;
+			break;
+		case 1:
+			show_diffstat = MERGE_SHOW_DIFFSTAT;
+			break;
+		default:
+			if (!strcmp(v, "compact"))
+				show_diffstat = MERGE_SHOW_COMPACTSUMMARY;
+			else
+				/* setting from the future -- use the default */
+				show_diffstat = MERGE_SHOW_DIFFSTAT;
+			break;
+		}
 	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
 	} else if (!strcmp(k, "pull.twohead")) {
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 2972922b6a..8046c6bb54 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -231,6 +231,21 @@ test_expect_success 'the same merge with compact summary' '
 	test_cmp expect actual
 '
 
+test_expect_success 'the same merge with merge.stat=compact' '
+	cat >expect <<-\EOF &&
+	Updating FROM..TO
+	Fast-forward
+	 file        | 2 +-
+	 other (new) | 9 +++++++++
+	 2 files changed, 10 insertions(+), 1 deletion(-)
+	EOF
+
+	git reset --hard c0 &&
+	git -c merge.stat=compact merge c1 >out &&
+	sed -e "1s/^Updating [0-9a-f.]*/Updating FROM..TO/" out >actual &&
+	test_cmp expect actual
+'
+
 test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge from unborn branch' '
-- 
2.50.0-rc2-255-gd84100c98d
Kristoffer Haugsbakk· Jun 13, 2025, 12:09 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 2/2] merge/pull: extend merge.stat configuration variable to cover --compact-summary

On Fri, Jun 13, 2025, at 00:25, Junio C Hamano wrote:
> Extend it to be "Boolean or text", that takes false, true, or
> "compact", with the last one triggering the --compact-summary
> option introduced earlier.

Would it make sense to make `diffstat` an alias for `true`? I’m not sure since you need to keep `true` anyway so you still have that historical artifact (?)[1] of `true` meaning `diffstat`.

† 1: In the sense of what was added first
Also
> "compact", with the last one triggering the --compact-summary
> option introduced earlier.

So `merge.stat` can be used instead of `--compact-summary`. But now the option does not mention `merge.stat`?

Show 10 quoted lines
>  `merge.stat`::
> -	Whether to print the diffstat between `ORIG_HEAD` and the merge result
> -	at the end of the merge.  True by default.
> +	What, if anything, to print between `ORIG_HEAD` and the merge result
> +	at the end of the merge.  Possible values are:
> ++
> +--
> +`false`;; Show nothing.
> +`true`;; Show `git diff --diffstat ORIG_HEAD`.
> +`compact`;; Show `git diff --compact-summary ORIG_HEAD`.

I can’t run `git diff --diffstat ORIG_HEAD`. `--diffstat` is not an option.

> +If this variable is left unspecified, it defaults to `true`.
I think this is simpler:
    Defaults to `true`.

There’s also several places in this doc which uses something similar judging by a search for “defaults to”. Only exception is `merge.renameLimit`:

    If not specified, defaults to
Junio C Hamano· Jun 13, 2025, 16:38 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH v2 2/2] merge/pull: extend merge.stat configuration variable to cover --compact-summary

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
Show 8 quoted lines
> On Fri, Jun 13, 2025, at 00:25, Junio C Hamano wrote:
>> Extend it to be "Boolean or text", that takes false, true, or
>> "compact", with the last one triggering the --compact-summary
>> option introduced earlier.
>
> Would it make sense to make `diffstat` an alias for `true`?  I’m not
> sure since you need to keep `true` anyway so you still have that
> historical artifact (?)[1] of `true` meaning `diffstat`.

Correct. But people can bikeshed about adding such synonyms later without breaking existing users, so I would not consider such a tweak for the initial version.

Show 5 quoted lines
> There’s also several places in this doc which uses something similar
> judging by a search for “defaults to”.  Only exception is
> `merge.renameLimit`:
>
>     If not specified, defaults to

Having something before "Defaults to X" (like "if left unspecified" etc.) may not be logically and technically needed, as that is the definion of what the "default" is.

Having said that, I somehow find the resulting sentence easier to read with such an introducer, but perhaps it is just me.

Among the output of
    $ git grep -E -i ', default' Documentation/
we already have quite a few of them.
Thanks.

Documentation/config/commitgraph.adoc:repository, defaulting to 1 if there are none. Documentation/config/merge.adoc: rename detection during a merge. If not specified, defaults Documentation/fetch-options.adoc:is set (see linkgit:git-config[1]), defaulting to 'on-demand' if unset. Documentation/git-init.adoc:The option can have the following values, defaulting to `group` if no value Documentation/git-send-email.adoc:value; if that is unspecified, default to `auto`. Documentation/git-send-email.adoc: value; if that is unspecified, default to --signed-off-by-cc. Documentation/git-send-email.adoc: configuration value; if that is unspecified, default to --no-cc-cover. Documentation/git-send-email.adoc: configuration value; if that is unspecified, default to --no-to-cover. Documentation/git-send-email.adoc:that is unspecified, default to 'self' if --suppress-from is Documentation/git-send-email.adoc: value; if that is unspecified, default to --no-suppress-from. Documentation/git-send-email.adoc:is unspecified, default to 'auto' unless any of the suppress options

← back to recent threads