Subject: [PATCH/RFC] diff: Make numstat machine friendly also for renames

From: Jakub Narebski <[email-masked]>
Date: Tue, 8 May 2007 02:43:42 +0200

Instead of saving human readable rename information in the 'name'
field when diffstat info is generated, do it when writing --stat
output. Change --numstat output to be machine friendly.

This makes result of git-diff --numstat more suitable for machines
also when renames are involved, by using format similar to the one for
renames in the raw diff format, instead of the format more suited for
humans.

The numstat format for rename is now

  added deleted TAB path for "src" TAB path for "dst" LF

or if -z option is used

  added deleted TAB path for "src" NUL NUL path for "dst" NUL

When -z option is not used, ", TAB, LF, and backslash characters in
pathnames are represented as \", \t, \n, and \\, respectively. If any
character needs to be quoted then pathnames are enclosed in double
quotes.

Signed-off-by: Jakub Narebski <[email-masked]>
---
This change increases memory footprint a bit, as struct diffstat_file
is wider by sizeof(char *) wide field, which is NULL except for
renames.

I have thought about storing it using one pointer to fragment of
memory of the form "dst name \0 src name \0", but this trades a little
memory for CPU time.

The goal of this change is to make it possible to generate HTML
diffstat against first parent for merge commits in gitweb. The current
notation for renames, which looks for example like below:

  t/{t6030-bisect-run.sh => t6030-bisect-porcelain.sh}

is not easy to parse by machines (note that filename may contain
"=>"), but easy to understand _usually_ by humans.

P.S. By the way, what is the difference between quote_one and
quote_c_style, i.e. when one calls one and when the other?

 diff.c |   22 ++++++++++++++++++----
 1 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/diff.c b/diff.c
index 7bbe759..568c59b 100644
--- a/diff.c
+++ b/diff.c
@@ -703,6 +703,7 @@ struct diffstat_t {
 	int alloc;
 	struct diffstat_file {
 		char *name;
+		char *from_name;
 		unsigned is_unmerged:1;
 		unsigned is_binary:1;
 		unsigned is_renamed:1;
@@ -722,12 +723,13 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
 				diffstat->alloc * sizeof(x));
 	}
 	diffstat->files[diffstat->nr++] = x;
+	x->name = xstrdup(name_a);
 	if (name_b) {
-		x->name = pprint_rename(name_a, name_b);
+		x->from_name = xstrdup(name_b);
 		x->is_renamed = 1;
 	}
 	else
-		x->name = xstrdup(name_a);
+		x->from_name = NULL;
 	return x;
 }
 
@@ -805,7 +807,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
 		struct diffstat_file *file = data->files[i];
 		int change = file->added + file->deleted;
 
-		if (!file->is_renamed) {  /* renames are already quoted by pprint_rename */
+		if (!file->is_renamed) {  /* renames are quoted by pprint_rename */
 			len = quote_c_style(file->name, NULL, NULL, 0);
 			if (len) {
 				char *qname = xmalloc(len + 1);
@@ -813,6 +815,10 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
 				free(file->name);
 				file->name = qname;
 			}
+		} else {
+			char *qname = pprint_rename(file->name, file->from_name);
+			free(file->name);
+			file->name = qname;
 		}
 
 		len = strlen(file->name);
@@ -949,11 +955,19 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
 			printf("-\t-\t");
 		else
 			printf("%d\t%d\t", file->added, file->deleted);
-		if (options->line_termination && !file->is_renamed &&
+		if (options->line_termination &&
 		    quote_c_style(file->name, NULL, NULL, 0))
 			quote_c_style(file->name, NULL, stdout, 0);
 		else
 			fputs(file->name, stdout);
+		if (file->is_renamed) {
+			printf("%s", options->line_termination ? "\t" : "\0\0");
+			if (options->line_termination &&
+			    quote_c_style(file->from_name, NULL, NULL, 0))
+				quote_c_style(file->from_name, NULL, stdout, 0);
+			else
+				fputs(file->from_name, stdout);
+		}
 		putchar(options->line_termination);
 	}
 }
-- 
1.5.1.3