Skip to content

Commit f604652

Browse files
committed
git-diff --numstat -z: make it machine readable
The "-z" format is all about machine parsability, but showing renamed paths as "common/{a => b}/suffix" makes it impossible. The scripts would never have successfully parsed "--numstat -z -M" in the old format. This fixes the output format in a (hopefully minimally) backward incompatible way. * The output without -z is not changed. This has given a good way for humans to view added and deleted lines separately, and showing the path in combined, shorter way would preserve readability. * The output with -z is unchanged for paths that do not involve renames. Existing scripts that do not pass -M/-C are not affected at all. * The output with -z for a renamed path is shown in a format that can easily be distinguished from an unrenamed path. This is based on Jakub Narebski's patch. Bugs and documentation typos are mine. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 71a9883 commit f604652

2 files changed

Lines changed: 129 additions & 32 deletions

File tree

Documentation/diff-format.txt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,64 @@ all parents.
8484

8585

8686
include::diff-generate-patch.txt[]
87+
88+
89+
other diff formats
90+
------------------
91+
92+
The `--summary` option describes newly added, deleted, renamed and
93+
copied files. The `--stat` option adds diffstat(1) graph to the
94+
output. These options can be combined with other options, such as
95+
`-p`, and are meant for human consumption.
96+
97+
When showing a change that involves a rename or a copy, `--stat` output
98+
formats the pathnames compactly by combining common prefix and suffix of
99+
the pathnames. For example, a change that moves `arch/i386/Makefile` to
100+
`arch/x86/Makefile` while modifying 4 lines will be shown like this:
101+
102+
------------------------------------
103+
arch/{i386 => x86}/Makefile | 4 +--
104+
------------------------------------
105+
106+
The `--numstat` option gives the diffstat(1) information but is designed
107+
for easier machine consumption. An entry in `--numstat` output looks
108+
like this:
109+
110+
----------------------------------------
111+
1 2 README
112+
3 1 arch/{i386 => x86}/Makefile
113+
----------------------------------------
114+
115+
That is, from left to right:
116+
117+
. the number of added lines;
118+
. a tab;
119+
. the number of deleted lines;
120+
. a tab;
121+
. pathname (possibly with rename/copy information);
122+
. a newline.
123+
124+
When `-z` output option is in effect, the output is formatted this way:
125+
126+
----------------------------------------
127+
1 2 README NUL
128+
3 1 NUL arch/i386/Makefile NUL arch/x86/Makefile NUL
129+
----------------------------------------
130+
131+
That is:
132+
133+
. the number of added lines;
134+
. a tab;
135+
. the number of deleted lines;
136+
. a tab;
137+
. a NUL (only exists if renamed/copied);
138+
. pathname in preimage;
139+
. a NUL (only exists if renamed/copied);
140+
. pathname in postimage (only exists if renamed/copied);
141+
. a NUL.
142+
143+
The extra `NUL` before the preimage path in renamed case is to allow
144+
scripts that read the output to tell if the current record being read is
145+
a single-path record or a rename/copy record without reading ahead.
146+
After reading added and deleted lines, reading up to `NUL` would yield
147+
the pathname, but if that is `NUL`, the record will show two paths.

diff.c

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,9 @@ struct diffstat_t {
734734
int nr;
735735
int alloc;
736736
struct diffstat_file {
737+
char *from_name;
737738
char *name;
739+
char *print_name;
738740
unsigned is_unmerged:1;
739741
unsigned is_binary:1;
740742
unsigned is_renamed:1;
@@ -755,11 +757,14 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
755757
}
756758
diffstat->files[diffstat->nr++] = x;
757759
if (name_b) {
758-
x->name = pprint_rename(name_a, name_b);
760+
x->from_name = xstrdup(name_a);
761+
x->name = xstrdup(name_b);
759762
x->is_renamed = 1;
760763
}
761-
else
764+
else {
765+
x->from_name = NULL;
762766
x->name = xstrdup(name_a);
767+
}
763768
return x;
764769
}
765770

@@ -803,6 +808,28 @@ static void show_graph(char ch, int cnt, const char *set, const char *reset)
803808
printf("%s", reset);
804809
}
805810

811+
static void fill_print_name(struct diffstat_file *file)
812+
{
813+
char *pname;
814+
815+
if (file->print_name)
816+
return;
817+
818+
if (!file->is_renamed) {
819+
struct strbuf buf;
820+
strbuf_init(&buf, 0);
821+
if (quote_c_style(file->name, &buf, NULL, 0)) {
822+
pname = strbuf_detach(&buf, NULL);
823+
} else {
824+
pname = file->name;
825+
strbuf_release(&buf);
826+
}
827+
} else {
828+
pname = pprint_rename(file->from_name, file->name);
829+
}
830+
file->print_name = pname;
831+
}
832+
806833
static void show_stats(struct diffstat_t* data, struct diff_options *options)
807834
{
808835
int i, len, add, del, total, adds = 0, dels = 0;
@@ -836,19 +863,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
836863
for (i = 0; i < data->nr; i++) {
837864
struct diffstat_file *file = data->files[i];
838865
int change = file->added + file->deleted;
839-
840-
if (!file->is_renamed) { /* renames are already quoted by pprint_rename */
841-
struct strbuf buf;
842-
strbuf_init(&buf, 0);
843-
if (quote_c_style(file->name, &buf, NULL, 0)) {
844-
free(file->name);
845-
file->name = strbuf_detach(&buf, NULL);
846-
} else {
847-
strbuf_release(&buf);
848-
}
849-
}
850-
851-
len = strlen(file->name);
866+
fill_print_name(file);
867+
len = strlen(file->print_name);
852868
if (max_len < len)
853869
max_len = len;
854870

@@ -873,7 +889,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
873889

874890
for (i = 0; i < data->nr; i++) {
875891
const char *prefix = "";
876-
char *name = data->files[i]->name;
892+
char *name = data->files[i]->print_name;
877893
int added = data->files[i]->added;
878894
int deleted = data->files[i]->deleted;
879895
int name_len;
@@ -901,17 +917,17 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
901917
printf("%s%d%s", add_c, added, reset);
902918
printf(" bytes");
903919
printf("\n");
904-
goto free_diffstat_file;
920+
continue;
905921
}
906922
else if (data->files[i]->is_unmerged) {
907923
show_name(prefix, name, len, reset, set);
908924
printf(" Unmerged\n");
909-
goto free_diffstat_file;
925+
continue;
910926
}
911927
else if (!data->files[i]->is_renamed &&
912928
(added + deleted == 0)) {
913929
total_files--;
914-
goto free_diffstat_file;
930+
continue;
915931
}
916932

917933
/*
@@ -933,11 +949,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
933949
show_graph('+', add, add_c, reset);
934950
show_graph('-', del, del_c, reset);
935951
putchar('\n');
936-
free_diffstat_file:
937-
free(data->files[i]->name);
938-
free(data->files[i]);
939952
}
940-
free(data->files);
941953
printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
942954
set, total_files, adds, dels, reset);
943955
}
@@ -962,11 +974,7 @@ static void show_shortstats(struct diffstat_t* data)
962974
dels += deleted;
963975
}
964976
}
965-
free(data->files[i]->name);
966-
free(data->files[i]);
967977
}
968-
free(data->files);
969-
970978
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
971979
total_files, adds, dels);
972980
}
@@ -975,22 +983,49 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
975983
{
976984
int i;
977985

986+
if (data->nr == 0)
987+
return;
988+
978989
for (i = 0; i < data->nr; i++) {
979990
struct diffstat_file *file = data->files[i];
980991

981992
if (file->is_binary)
982993
printf("-\t-\t");
983994
else
984995
printf("%d\t%d\t", file->added, file->deleted);
985-
if (!file->is_renamed) {
986-
write_name_quoted(file->name, stdout, options->line_termination);
996+
if (options->line_termination) {
997+
fill_print_name(file);
998+
if (!file->is_renamed)
999+
write_name_quoted(file->name, stdout,
1000+
options->line_termination);
1001+
else {
1002+
fputs(file->print_name, stdout);
1003+
putchar(options->line_termination);
1004+
}
9871005
} else {
988-
fputs(file->name, stdout);
989-
putchar(options->line_termination);
1006+
if (file->is_renamed) {
1007+
putchar('\0');
1008+
write_name_quoted(file->from_name, stdout, '\0');
1009+
}
1010+
write_name_quoted(file->name, stdout, '\0');
9901011
}
9911012
}
9921013
}
9931014

1015+
static void free_diffstat_info(struct diffstat_t *diffstat)
1016+
{
1017+
int i;
1018+
for (i = 0; i < diffstat->nr; i++) {
1019+
struct diffstat_file *f = diffstat->files[i];
1020+
if (f->name != f->print_name)
1021+
free(f->print_name);
1022+
free(f->name);
1023+
free(f->from_name);
1024+
free(f);
1025+
}
1026+
free(diffstat->files);
1027+
}
1028+
9941029
struct checkdiff_t {
9951030
struct xdiff_emit_state xm;
9961031
const char *filename;
@@ -2943,8 +2978,9 @@ void diff_flush(struct diff_options *options)
29432978
show_numstat(&diffstat, options);
29442979
if (output_format & DIFF_FORMAT_DIFFSTAT)
29452980
show_stats(&diffstat, options);
2946-
else if (output_format & DIFF_FORMAT_SHORTSTAT)
2981+
if (output_format & DIFF_FORMAT_SHORTSTAT)
29472982
show_shortstats(&diffstat);
2983+
free_diffstat_info(&diffstat);
29482984
separator++;
29492985
}
29502986

0 commit comments

Comments
 (0)