Skip to contentSkip to conversation

pretty: add diff-stat log placeholders

PATCH In review Andrey Zarubin via GitGitGadget 3 participants 3 messages Apr 30 – May 4 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 pretty: add diff-stat log placeholders

From: Andrey Zarubin <[email protected]>

Currently, users who want per-commit line/file change counts in a custom log format must post-process git log --shortstat output because the pretty formatter exposes no equivalent placeholders.

Introduce %(diff-stat:files), %(diff-stat:insertions), %(diff-stat:deletions), and %(diff-stat:lines), computed from the same diffstat machinery as --shortstat and cached once per commit during format expansion.

Short aliases are provided as %aF, %aA, and %aR. The requested %aI and %aD forms are unavailable because those names already expand to author dates, so use additions/removals mnemonics instead.

When log output is already walking a diff, the formatter reuses the current diff queue. Otherwise it computes a private summary lazily, so formats without these placeholders still pay no diff cost.

---
pretty: add diff-stat log placeholders

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2284%2Fzarandr%2Fmaster-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2284/zarandr/master-v1
Pull-Request: https://github.com/git/git/pull/2284

 Documentation/pretty-formats.adoc |  12 +++
 builtin/log.c                     |   5 +
 diff.c                            |  32 ++++--
 diff.h                            |   8 ++
 log-tree.c                        |   2 +
 pretty.c                          | 166 ++++++++++++++++++++++++++++++
 pretty.h                          |   3 +
 t/t4205-log-pretty-formats.sh     | 162 +++++++++++++++++++++++++++++
 8 files changed, 381 insertions(+), 9 deletions(-)
Documentation/pretty-formats.adoc +12 -0
index 2ae0eb11a9..d1b574f3ad 100644
@@ -294,6 +294,18 @@ tags are added or removed at the same time.
 `exclude=<pattern>`;; Do not consider tags matching the given
    `glob(7)` _<pattern>_, excluding the `refs/tags/` prefix.
 
+++%(diff-stat:files)++:: show the number of files changed
+++%(diff-stat:insertions)++:: show the number of inserted lines
+++%(diff-stat:deletions)++:: show the number of deleted lines
+++%(diff-stat:lines)++:: show the total number of inserted and deleted lines
++
+  These placeholders are computed like `--shortstat`. By default,
+  merge commits expand to `0` unless a merge diff mode such as `-m`,
+  `-c`, or `--cc` is in effect.
++%aF+:: short alias for `%(diff-stat:files)`
++%aA+:: short alias for `%(diff-stat:insertions)`
++%aR+:: short alias for `%(diff-stat:deletions)`
+
 +%S+:: ref name given on the command line by which the commit was reached
        (like `git log --source`), only works with `git log`
 +%e+:: encoding
builtin/log.c +5 -0
index 8c0939dd42..017face2c0 100644
@@ -321,6 +321,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 	memset(&w, 0, sizeof(w));
 	userformat_find_requirements(NULL, &w);
 
+	if (w.diffstat) {
+		rev->diff = 1;
+		rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT;
+	}
+
 	if (!rev->show_notes_given && (!rev->pretty_given || w.notes))
 		rev->show_notes = 1;
 	if (rev->show_notes)
diff.c +23 -9
index 397e38b41c..2f018e801a 100644
@@ -3195,12 +3195,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 	strbuf_release(&out);
 }
 
-static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
+void summarize_diffstat(struct diffstat_t *data,
+			struct diff_stat_summary *summary)
 {
-	int i, adds = 0, dels = 0, total_files = data->nr;
+	int i;
 
-	if (data->nr == 0)
-		return;
+	summary->files = data->nr;
+	summary->insertions = 0;
+	summary->deletions = 0;
 
 	for (i = 0; i < data->nr; i++) {
 		int added = data->files[i]->added;
@@ -3208,13 +3210,25 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
 
 		if (data->files[i]->is_unmerged ||
 		    (!data->files[i]->is_interesting && (added + deleted == 0))) {
-			total_files--;
-		} else if (!data->files[i]->is_binary) { /* don't count bytes */
-			adds += added;
-			dels += deleted;
+			summary->files--;
+		} else if (!data->files[i]->is_binary) {
+			summary->insertions += added;
+			summary->deletions += deleted;
 		}
 	}
-	print_stat_summary_inserts_deletes(options, total_files, adds, dels);
+}
+
+static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
+{
+	struct diff_stat_summary summary;
+
+	if (data->nr == 0)
+		return;
+
+	summarize_diffstat(data, &summary);
+	print_stat_summary_inserts_deletes(options, summary.files,
+					   summary.insertions,
+					   summary.deletions);
 }
 
 static void show_numstat(struct diffstat_t *data, struct diff_options *options)
diff.h +8 -0
index 7eb84aadf4..798c52138d 100644
@@ -449,6 +449,12 @@ struct diffstat_t {
 	} **files;
 };
 
+struct diff_stat_summary {
+	int files;
+	int insertions;
+	int deletions;
+};
+
 enum color_diff {
 	DIFF_RESET = 0,
 	DIFF_CONTEXT = 1,
@@ -581,6 +587,8 @@ struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
 
 void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
 		      struct diff_queue_struct *q);
+void summarize_diffstat(struct diffstat_t *diffstat,
+			struct diff_stat_summary *summary);
 void free_diffstat_info(struct diffstat_t *diffstat);
 
 #define DIFF_SETUP_REVERSE      	1
log-tree.c +2 -0
index 7e048701d0..aa6f6dd27d 100644
@@ -881,6 +881,8 @@ void show_log(struct rev_info *opt)
 	ctx.expand_tabs_in_log = opt->expand_tabs_in_log;
 	ctx.output_encoding = get_log_output_encoding();
 	ctx.rev = opt;
+	ctx.diff_parent = parent;
+	ctx.diff_queue_present = diff_queued_diff.nr > 0;
 	if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
 		ctx.from_ident = &opt->from_ident;
 	if (opt->graph)
pretty.c +166 -0

208 lines not on this page · raw message

pretty.h +3 -0
index fac699033e..7f0491e512 100644
@@ -58,6 +58,8 @@ struct pretty_print_context {
 	 */
 	struct string_list in_body_headers;
 	int graph_width;
+	const struct commit *diff_parent;
+	unsigned diff_queue_present:1;
 };
 
 /* Check whether commit format is mail. */
@@ -75,6 +77,7 @@ struct userformat_want {
 	unsigned notes:1;
 	unsigned source:1;
 	unsigned decorate:1;
+	unsigned diffstat:1;
 };
 void userformat_find_requirements(const char *fmt, struct userformat_want *w);
 
t/t4205-log-pretty-formats.sh +162 -0

166 lines not on this page · raw message

base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0

Signature · 1 line
gitgitgadget
#1 lore raw
Quoted 17 lines · Andrey Zarubin via GitGitGadget

From: Andrey Zarubin <[email protected]>

Currently, users who want per-commit line/file change counts in a custom log format must post-process git log --shortstat output because the pretty formatter exposes no equivalent placeholders.

Introduce %(diff-stat:files), %(diff-stat:insertions), %(diff-stat:deletions), and %(diff-stat:lines), computed from the same diffstat machinery as --shortstat and cached once per commit during format expansion.

Short aliases are provided as %aF, %aA, and %aR. The requested %aI and %aD forms are unavailable because those names already expand to author dates, so use additions/removals mnemonics instead.

Quoted 8 lines

When log output is already walking a diff, the formatter reuses the current diff queue. Otherwise it computes a private summary lazily, so formats without these placeholders still pay no diff cost.

---
pretty: add diff-stat log placeholders

Personally I find this a bit on the other side of the line between sensible and insanity. Will we next be adding a new placeholder to show the summary (i.e. list of created, deleted, and renamed paths) and another placeholder to show the entire patch text?

#2 lore raw
Quoted 25 lines · Junio C Hamano
Quoted 22 lines · Andrey Zarubin via GitGitGadget

From: Andrey Zarubin <[email protected]>

Currently, users who want per-commit line/file change counts in a custom log format must post-process git log --shortstat output because the pretty formatter exposes no equivalent placeholders.

Introduce %(diff-stat:files), %(diff-stat:insertions), %(diff-stat:deletions), and %(diff-stat:lines), computed from the same diffstat machinery as --shortstat and cached once per commit during format expansion.

Short aliases are provided as %aF, %aA, and %aR. The requested %aI and %aD forms are unavailable because those names already expand to author dates, so use additions/removals mnemonics instead.

When log output is already walking a diff, the formatter reuses the current diff queue. Otherwise it computes a private summary lazily, so formats without these placeholders still pay no diff cost.

Quoted 8 lines
Quoted 3 lines

---
pretty: add diff-stat log placeholders

Personally I find this a bit on the other side of the line between sensible and insanity. Will we next be adding a new placeholder to show the summary (i.e. list of created, deleted, and renamed paths) and another placeholder to show the entire patch text?

I see the concern, and I agree that placeholders for --summary or full patch text would cross that line.

The distinction I had in mind is that these are bounded scalar values, not diff output. They are the same three counters already produced by --shortstat, and the main use case is one-line structured log output where today callers have to run git log --shortstat and parse/correlate the human-oriented output after the fact.

Path summaries and patch text are qualitatively different: they are multi-line, formatting-heavy, affected by quoting/color/output choices, and would effectively embed diff output inside the pretty formatter. I would not want this change to imply support for that direction.

If the short aliases make this feel too much like expanding the kitchen sink, I can drop them and keep only the explicit %(diff-stat:<field>) forms. I think the long forms make the intended scope clearer: numeric shortstat counters only.