git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH v6 0/3] Add support for displaying submodules as a proper diff
@ 2016-08-15 23:06 Jacob Keller
  2016-08-15 23:07 ` [PATCH v6 1/3] diff.c: remove output_prefix_length field Jacob Keller
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Jacob Keller @ 2016-08-15 23:06 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Stefan Beller, Jeff King, Johannes Sixt, Jacob Keller

From: Jacob Keller <jacob.keller@gmail.com>

This patch series adds support for displaying a submodule as a
difference between the pre and post commits. This allows projects who
frequently update submodule contents to view the submodule in the log as
if it were just one squashed commit that updates all the files in the
submodule together, even if you mix-match the submodule part with a
regular part you just see a complete diff that shows all the changes.

To make this work, I also extended the graph-aware output with a
--line-prefix option. This option extends both diff and log to show a
prefix. It's a bit of a hack in someways, but it works for showing a
prefix both when graph is enabled and when its not. I think it works
quite well.

I added several tests to the line-prefix, and also a few tests for the
new submodule format.

I welcome comments on how to improve the graph line-prefix code, as well
as the actual submodule diff format.

Jacob Keller (2):
  graph: add support for --line-prefix on all graph-aware output
  diff: add SUBMODULE_DIFF format to display submodule diff

Junio C Hamano (1):
  diff.c: remove output_prefix_length field

 Documentation/diff-config.txt                      |   3 +-
 Documentation/diff-options.txt                     |  10 +-
 builtin/rev-list.c                                 |  70 +-
 diff.c                                             |  49 +-
 diff.h                                             |  11 +-
 graph.c                                            | 105 +--
 graph.h                                            |  22 +-
 log-tree.c                                         |   5 +-
 submodule.c                                        | 130 ++++
 submodule.h                                        |   6 +
 t/t4013-diff-various.sh                            |   6 +
 ...diff.diff_--line-prefix=abc_master_master^_side |  29 +
 t/t4013/diff.diff_--line-prefix_--cached_--_file0  |  15 +
 t/t4059-diff-submodule-option-diff-format.sh       | 738 +++++++++++++++++++++
 t/t4202-log.sh                                     | 323 +++++++++
 15 files changed, 1419 insertions(+), 103 deletions(-)
 create mode 100644 t/t4013/diff.diff_--line-prefix=abc_master_master^_side
 create mode 100644 t/t4013/diff.diff_--line-prefix_--cached_--_file0
 create mode 100755 t/t4059-diff-submodule-option-diff-format.sh

-- 
2.9.2.873.g47c31b4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v6 1/3] diff.c: remove output_prefix_length field
  2016-08-15 23:06 [PATCH v6 0/3] Add support for displaying submodules as a proper diff Jacob Keller
@ 2016-08-15 23:07 ` Jacob Keller
  2016-08-16 18:03   ` Junio C Hamano
  2016-08-15 23:07 ` [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output Jacob Keller
  2016-08-15 23:07 ` [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff Jacob Keller
  2 siblings, 1 reply; 13+ messages in thread
From: Jacob Keller @ 2016-08-15 23:07 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Stefan Beller, Jeff King, Johannes Sixt

From: Junio C Hamano <gitster@pobox.com>

"diff/log --stat" has a logic that determines the display columns
available for the diffstat part of the output and apportions it for
pathnames and diffstat graph automatically.

5e71a84a (Add output_prefix_length to diff_options, 2012-04-16)
added the output_prefix_length field to diff_options structure to
allow this logic subtract the display columns used for display the
history graph part from the total "terminal width"; this matters
when the "git log --graph -p" option is in use.

The field be set to the number of display columns needed to show the
output from the output_prefix() callback.  Any new output_prefix()
callback must also update the field accordingly, which is error
prone.  As there is only one user of the field, and the user has the
actual value of the prefix string, let's get rid of the field and
have the user count the display width itself.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c  | 2 +-
 diff.h  | 1 -
 graph.c | 2 --
 3 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/diff.c b/diff.c
index b43d3dd2ecb7..ae069c303077 100644
--- a/diff.c
+++ b/diff.c
@@ -1625,7 +1625,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 	 */
 
 	if (options->stat_width == -1)
-		width = term_columns() - options->output_prefix_length;
+		width = term_columns() - strlen(line_prefix);
 	else
 		width = options->stat_width ? options->stat_width : 80;
 	number_width = decimal_width(max_change) > number_width ?
diff --git a/diff.h b/diff.h
index 125447be09eb..49e4aaafb2da 100644
--- a/diff.h
+++ b/diff.h
@@ -174,7 +174,6 @@ struct diff_options {
 	diff_format_fn_t format_callback;
 	void *format_callback_data;
 	diff_prefix_fn_t output_prefix;
-	int output_prefix_length;
 	void *output_prefix_data;
 
 	int diff_path_counter;
diff --git a/graph.c b/graph.c
index dd1720148dc5..a46803840511 100644
--- a/graph.c
+++ b/graph.c
@@ -197,7 +197,6 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
 	assert(opt);
 	assert(graph);
 
-	opt->output_prefix_length = graph->width;
 	strbuf_reset(&msgbuf);
 	graph_padding_line(graph, &msgbuf);
 	return &msgbuf;
@@ -245,7 +244,6 @@ struct git_graph *graph_init(struct rev_info *opt)
 	 */
 	opt->diffopt.output_prefix = diff_output_prefix_callback;
 	opt->diffopt.output_prefix_data = graph;
-	opt->diffopt.output_prefix_length = 0;
 
 	return graph;
 }
-- 
2.9.2.873.g47c31b4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output
  2016-08-15 23:06 [PATCH v6 0/3] Add support for displaying submodules as a proper diff Jacob Keller
  2016-08-15 23:07 ` [PATCH v6 1/3] diff.c: remove output_prefix_length field Jacob Keller
@ 2016-08-15 23:07 ` Jacob Keller
  2016-08-16 18:22   ` Junio C Hamano
  2016-08-15 23:07 ` [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff Jacob Keller
  2 siblings, 1 reply; 13+ messages in thread
From: Jacob Keller @ 2016-08-15 23:07 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Stefan Beller, Jeff King, Johannes Sixt, Jacob Keller

From: Jacob Keller <jacob.keller@gmail.com>

Add an extension to git-diff and git-log (and any other graph-aware
displayable output) such that "--line-prefix=<string>" will print the
additional line-prefix on every line of output.

To make this work, we have to fix a few bugs in the graph API that force
graph_show_commit_msg to be used only when you have a valid graph.
Additionally, we extend the default_diff_output_prefix handler to work
even when no graph is enabled.

This is somewhat of a hack on top of the graph API, but I think it
should be acceptable here.

This will be used by a future extension of submodule display which
displays the submodule diff as the actual diff between the pre and post
commit in the submodule project.

Add some tests for both git-log and git-diff to ensure that the prefix
is honored correctly.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 Documentation/diff-options.txt                     |   3 +
 builtin/rev-list.c                                 |  70 ++---
 diff.c                                             |   6 +
 diff.h                                             |   1 +
 graph.c                                            | 103 ++++---
 graph.h                                            |  22 +-
 log-tree.c                                         |   5 +-
 t/t4013-diff-various.sh                            |   6 +
 ...diff.diff_--line-prefix=abc_master_master^_side |  29 ++
 t/t4013/diff.diff_--line-prefix_--cached_--_file0  |  15 +
 t/t4202-log.sh                                     | 323 +++++++++++++++++++++
 11 files changed, 503 insertions(+), 80 deletions(-)
 create mode 100644 t/t4013/diff.diff_--line-prefix=abc_master_master^_side
 create mode 100644 t/t4013/diff.diff_--line-prefix_--cached_--_file0

diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 705a87394200..cc4342e2034d 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -569,5 +569,8 @@ endif::git-format-patch[]
 --no-prefix::
 	Do not show any source or destination prefix.
 
+--line-prefix=<prefix>::
+	Prepend an additional prefix to every line of output.
+
 For more detailed explanation on these common options, see also
 linkgit:gitdiffcore[7].
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 0ba82b1635b6..1a75a83538f4 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -122,48 +122,40 @@ static void show_commit(struct commit *commit, void *data)
 		ctx.fmt = revs->commit_format;
 		ctx.output_encoding = get_log_output_encoding();
 		pretty_print_commit(&ctx, commit, &buf);
-		if (revs->graph) {
-			if (buf.len) {
-				if (revs->commit_format != CMIT_FMT_ONELINE)
-					graph_show_oneline(revs->graph);
+		if (buf.len) {
+			if (revs->commit_format != CMIT_FMT_ONELINE)
+				graph_show_oneline(revs->graph);
 
-				graph_show_commit_msg(revs->graph, &buf);
+			graph_show_commit_msg(revs->graph, stdout, &buf);
 
-				/*
-				 * Add a newline after the commit message.
-				 *
-				 * Usually, this newline produces a blank
-				 * padding line between entries, in which case
-				 * we need to add graph padding on this line.
-				 *
-				 * However, the commit message may not end in a
-				 * newline.  In this case the newline simply
-				 * ends the last line of the commit message,
-				 * and we don't need any graph output.  (This
-				 * always happens with CMIT_FMT_ONELINE, and it
-				 * happens with CMIT_FMT_USERFORMAT when the
-				 * format doesn't explicitly end in a newline.)
-				 */
-				if (buf.len && buf.buf[buf.len - 1] == '\n')
-					graph_show_padding(revs->graph);
-				putchar('\n');
-			} else {
-				/*
-				 * If the message buffer is empty, just show
-				 * the rest of the graph output for this
-				 * commit.
-				 */
-				if (graph_show_remainder(revs->graph))
-					putchar('\n');
-				if (revs->commit_format == CMIT_FMT_ONELINE)
-					putchar('\n');
-			}
+			/*
+				* Add a newline after the commit message.
+				*
+				* Usually, this newline produces a blank
+				* padding line between entries, in which case
+				* we need to add graph padding on this line.
+				*
+				* However, the commit message may not end in a
+				* newline.  In this case the newline simply
+				* ends the last line of the commit message,
+				* and we don't need any graph output.  (This
+				* always happens with CMIT_FMT_ONELINE, and it
+				* happens with CMIT_FMT_USERFORMAT when the
+				* format doesn't explicitly end in a newline.)
+				*/
+			if (buf.len && buf.buf[buf.len - 1] == '\n')
+				graph_show_padding(revs->graph);
+			putchar('\n');
 		} else {
-			if (revs->commit_format != CMIT_FMT_USERFORMAT ||
-			    buf.len) {
-				fwrite(buf.buf, 1, buf.len, stdout);
-				putchar(info->hdr_termination);
-			}
+			/*
+				* If the message buffer is empty, just show
+				* the rest of the graph output for this
+				* commit.
+				*/
+			if (graph_show_remainder(revs->graph))
+				putchar('\n');
+			if (revs->commit_format == CMIT_FMT_ONELINE)
+				putchar('\n');
 		}
 		strbuf_release(&buf);
 	} else {
diff --git a/diff.c b/diff.c
index ae069c303077..e286897b51e6 100644
--- a/diff.c
+++ b/diff.c
@@ -18,6 +18,7 @@
 #include "ll-merge.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "graph.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -3966,6 +3967,11 @@ int diff_opt_parse(struct diff_options *options,
 		options->a_prefix = optarg;
 		return argcount;
 	}
+	else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
+		options->line_prefix = optarg;
+		graph_setup_line_prefix(options);
+		return argcount;
+	}
 	else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
 		options->b_prefix = optarg;
 		return argcount;
diff --git a/diff.h b/diff.h
index 49e4aaafb2da..83d0b1ae8580 100644
--- a/diff.h
+++ b/diff.h
@@ -115,6 +115,7 @@ struct diff_options {
 	const char *pickaxe;
 	const char *single_follow;
 	const char *a_prefix, *b_prefix;
+	const char *line_prefix;
 	unsigned flags;
 	unsigned touched_flags;
 
diff --git a/graph.c b/graph.c
index a46803840511..3ab71456262c 100644
--- a/graph.c
+++ b/graph.c
@@ -2,7 +2,6 @@
 #include "commit.h"
 #include "color.h"
 #include "graph.h"
-#include "diff.h"
 #include "revision.h"
 
 /* Internal API */
@@ -28,8 +27,15 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
  * responsible for printing this line's graph (perhaps via
  * graph_show_commit() or graph_show_oneline()) before calling
  * graph_show_strbuf().
+ *
+ * Note that unlike some other graph display functions, you must pass the file
+ * handle directly. It is assumed that this is the same file handle as the
+ * file specified by the graph diff options. This is necessary so that
+ * graph_show_strbuf can be called even with a NULL graph.
  */
-static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
+static void graph_show_strbuf(struct git_graph *graph,
+			      FILE *file,
+			      struct strbuf const *sb);
 
 /*
  * TODO:
@@ -59,6 +65,17 @@ enum graph_state {
 	GRAPH_COLLAPSING
 };
 
+static void graph_show_line_prefix(const struct diff_options *diffopt)
+{
+	if (!diffopt || !diffopt->line_prefix)
+		return;
+
+	fwrite(diffopt->line_prefix,
+	       sizeof(char),
+	       strlen(diffopt->line_prefix),
+	       diffopt->file);
+}
+
 static const char **column_colors;
 static unsigned short column_colors_max;
 
@@ -195,13 +212,27 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
 	static struct strbuf msgbuf = STRBUF_INIT;
 
 	assert(opt);
-	assert(graph);
 
 	strbuf_reset(&msgbuf);
-	graph_padding_line(graph, &msgbuf);
+	if (opt->line_prefix)
+		strbuf_addstr(&msgbuf, opt->line_prefix);
+	if (graph)
+		graph_padding_line(graph, &msgbuf);
 	return &msgbuf;
 }
 
+static const struct diff_options *default_diffopt = NULL;
+
+void graph_setup_line_prefix(struct diff_options *diffopt)
+{
+	default_diffopt = diffopt;
+
+	/* setup an output prefix callback if necessary */
+	if (diffopt && !diffopt->output_prefix)
+		diffopt->output_prefix = diff_output_prefix_callback;
+}
+
+
 struct git_graph *graph_init(struct rev_info *opt)
 {
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
@@ -1183,8 +1214,10 @@ void graph_show_commit(struct git_graph *graph)
 	struct strbuf msgbuf = STRBUF_INIT;
 	int shown_commit_line = 0;
 
-	if (!graph)
+	if (!graph) {
+		graph_show_line_prefix(default_diffopt);
 		return;
+	}
 
 	/*
 	 * When showing a diff of a merge against each of its parents, we
@@ -1198,6 +1231,7 @@ void graph_show_commit(struct git_graph *graph)
 
 	while (!shown_commit_line && !graph_is_commit_finished(graph)) {
 		shown_commit_line = graph_next_line(graph, &msgbuf);
+		graph_show_line_prefix(&graph->revs->diffopt);
 		fwrite(msgbuf.buf, sizeof(char), msgbuf.len,
 			graph->revs->diffopt.file);
 		if (!shown_commit_line)
@@ -1212,10 +1246,13 @@ void graph_show_oneline(struct git_graph *graph)
 {
 	struct strbuf msgbuf = STRBUF_INIT;
 
-	if (!graph)
+	if (!graph) {
+		graph_show_line_prefix(default_diffopt);
 		return;
+	}
 
 	graph_next_line(graph, &msgbuf);
+	graph_show_line_prefix(&graph->revs->diffopt);
 	fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file);
 	strbuf_release(&msgbuf);
 }
@@ -1224,10 +1261,13 @@ void graph_show_padding(struct git_graph *graph)
 {
 	struct strbuf msgbuf = STRBUF_INIT;
 
-	if (!graph)
+	if (!graph) {
+		graph_show_line_prefix(default_diffopt);
 		return;
+	}
 
 	graph_padding_line(graph, &msgbuf);
+	graph_show_line_prefix(&graph->revs->diffopt);
 	fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file);
 	strbuf_release(&msgbuf);
 }
@@ -1237,14 +1277,19 @@ int graph_show_remainder(struct git_graph *graph)
 	struct strbuf msgbuf = STRBUF_INIT;
 	int shown = 0;
 
-	if (!graph)
+	if (!graph) {
+		graph_show_line_prefix(default_diffopt);
 		return 0;
+	}
 
-	if (graph_is_commit_finished(graph))
+	if (graph_is_commit_finished(graph)) {
+		graph_show_line_prefix(&graph->revs->diffopt);
 		return 0;
+	}
 
 	for (;;) {
 		graph_next_line(graph, &msgbuf);
+		graph_show_line_prefix(&graph->revs->diffopt);
 		fwrite(msgbuf.buf, sizeof(char), msgbuf.len,
 			graph->revs->diffopt.file);
 		strbuf_setlen(&msgbuf, 0);
@@ -1260,17 +1305,12 @@ int graph_show_remainder(struct git_graph *graph)
 	return shown;
 }
 
-
-static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
+static void graph_show_strbuf(struct git_graph *graph,
+			      FILE *file,
+			      struct strbuf const *sb)
 {
 	char *p;
 
-	if (!graph) {
-		fwrite(sb->buf, sizeof(char), sb->len,
-			graph->revs->diffopt.file);
-		return;
-	}
-
 	/*
 	 * Print the strbuf line by line,
 	 * and display the graph info before each line but the first.
@@ -1285,7 +1325,7 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
 		} else {
 			len = (sb->buf + sb->len) - p;
 		}
-		fwrite(p, sizeof(char), len, graph->revs->diffopt.file);
+		fwrite(p, sizeof(char), len, file);
 		if (next_p && *next_p != '\0')
 			graph_show_oneline(graph);
 		p = next_p;
@@ -1293,29 +1333,20 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
 }
 
 void graph_show_commit_msg(struct git_graph *graph,
+			   FILE *file,
 			   struct strbuf const *sb)
 {
 	int newline_terminated;
 
-	if (!graph) {
-		/*
-		 * If there's no graph, just print the message buffer.
-		 *
-		 * The message buffer for CMIT_FMT_ONELINE and
-		 * CMIT_FMT_USERFORMAT are already missing a terminating
-		 * newline.  All of the other formats should have it.
-		 */
-		fwrite(sb->buf, sizeof(char), sb->len,
-			graph->revs->diffopt.file);
-		return;
-	}
-
-	newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
-
 	/*
 	 * Show the commit message
 	 */
-	graph_show_strbuf(graph, sb);
+	graph_show_strbuf(graph, file, sb);
+
+	if (!graph)
+		return;
+
+	newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
 
 	/*
 	 * If there is more output needed for this commit, show it now
@@ -1327,7 +1358,7 @@ void graph_show_commit_msg(struct git_graph *graph,
 		 * new line.
 		 */
 		if (!newline_terminated)
-			putc('\n', graph->revs->diffopt.file);
+			putc('\n', file);
 
 		graph_show_remainder(graph);
 
@@ -1335,6 +1366,6 @@ void graph_show_commit_msg(struct git_graph *graph,
 		 * If sb ends with a newline, our output should too.
 		 */
 		if (newline_terminated)
-			putc('\n', graph->revs->diffopt.file);
+			putc('\n', file);
 	}
 }
diff --git a/graph.h b/graph.h
index 3f48c19b6208..af623390b605 100644
--- a/graph.h
+++ b/graph.h
@@ -1,9 +1,22 @@
 #ifndef GRAPH_H
 #define GRAPH_H
+#include "diff.h"
 
 /* A graph is a pointer to this opaque structure */
 struct git_graph;
 
+/*
+ * Called to setup global display of line_prefix diff option.
+ *
+ * Passed a diff_options structure which indicates the line_prefix and the
+ * file to output the prefix to. This is sort of a hack used so that the
+ * line_prefix will be honored by all flows which also honor "--graph"
+ * regardless of whether a graph has actually been setup. The normal graph
+ * flow will honor the exact diff_options passed, but a NULL graph will cause
+ * display of a line_prefix to stdout.
+ */
+void graph_setup_line_prefix(struct diff_options *diffopt);
+
 /*
  * Set up a custom scheme for column colors.
  *
@@ -113,7 +126,14 @@ int graph_show_remainder(struct git_graph *graph);
  * missing a terminating newline (including if it is empty), the output
  * printed by graph_show_commit_msg() will also be missing a terminating
  * newline.
+ *
+ * Note that unlike some other graph display functions, you must pass the file
+ * handle directly. It is assumed that this is the same file handle as the
+ * file specified by the graph diff options. This is necessary so that
+ * graph_show_commit_msg can be called even with a NULL graph.
  */
-void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb);
+void graph_show_commit_msg(struct git_graph *graph,
+			   FILE *file,
+			   struct strbuf const *sb);
 
 #endif /* GRAPH_H */
diff --git a/log-tree.c b/log-tree.c
index bfb735c84556..8c2415747a26 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -715,10 +715,7 @@ void show_log(struct rev_info *opt)
 	else
 		opt->missing_newline = 0;
 
-	if (opt->graph)
-		graph_show_commit_msg(opt->graph, &msgbuf);
-	else
-		fwrite(msgbuf.buf, sizeof(char), msgbuf.len, opt->diffopt.file);
+	graph_show_commit_msg(opt->graph, opt->diffopt.file, &msgbuf);
 	if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) {
 		if (!opt->missing_newline)
 			graph_show_padding(opt->graph);
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 94ef5000e787..566817e2efdc 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -306,6 +306,8 @@ diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
 diff master master^ side
+# Can't use spaces...
+diff --line-prefix=abc master master^ side
 diff --dirstat master~1 master~2
 diff --dirstat initial rearrange
 diff --dirstat-by-file initial rearrange
@@ -325,6 +327,10 @@ test_expect_success 'diff --cached -- file on unborn branch' '
 	git diff --cached -- file0 >result &&
 	test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result
 '
+test_expect_success 'diff --line-prefix with spaces' '
+	git diff --line-prefix="| | | " --cached -- file0 >result &&
+	test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" result
+'
 
 test_expect_success 'diff-tree --stdin with log formatting' '
 	cat >expect <<-\EOF &&
diff --git a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side
new file mode 100644
index 000000000000..99f91e7f0e32
--- /dev/null
+++ b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side
@@ -0,0 +1,29 @@
+$ git diff --line-prefix=abc master master^ side
+abcdiff --cc dir/sub
+abcindex cead32e,7289e35..992913c
+abc--- a/dir/sub
+abc+++ b/dir/sub
+abc@@@ -1,6 -1,4 +1,8 @@@
+abc  A
+abc  B
+abc +C
+abc +D
+abc +E
+abc +F
+abc+ 1
+abc+ 2
+abcdiff --cc file0
+abcindex b414108,f4615da..10a8a9f
+abc--- a/file0
+abc+++ b/file0
+abc@@@ -1,6 -1,6 +1,9 @@@
+abc  1
+abc  2
+abc  3
+abc +4
+abc +5
+abc +6
+abc+ A
+abc+ B
+abc+ C
+$
diff --git a/t/t4013/diff.diff_--line-prefix_--cached_--_file0 b/t/t4013/diff.diff_--line-prefix_--cached_--_file0
new file mode 100644
index 000000000000..f41ba4d36aa1
--- /dev/null
+++ b/t/t4013/diff.diff_--line-prefix_--cached_--_file0
@@ -0,0 +1,15 @@
+| | | diff --git a/file0 b/file0
+| | | new file mode 100644
+| | | index 0000000..10a8a9f
+| | | --- /dev/null
+| | | +++ b/file0
+| | | @@ -0,0 +1,9 @@
+| | | +1
+| | | +2
+| | | +3
+| | | +4
+| | | +5
+| | | +6
+| | | +A
+| | | +B
+| | | +C
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e2db47c36e09..1ccbd5948a73 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -187,6 +187,16 @@ test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' '
 	test_cmp expect actual
 '
 
+cat > expect << EOF
+=== 804a787 sixth
+=== 394ef78 fifth
+=== 5d31159 fourth
+EOF
+test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' '
+	git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
+	test_cmp expect actual
+'
+
 cat > expect << EOF
 5d31159 fourth
 804a787 sixth
@@ -284,6 +294,21 @@ test_expect_success 'simple log --graph' '
 	test_cmp expect actual
 '
 
+cat > expect <<EOF
+123 * Second
+123 * sixth
+123 * fifth
+123 * fourth
+123 * third
+123 * second
+123 * initial
+EOF
+
+test_expect_success 'simple log --graph --line-prefix="123 "' '
+	git log --graph --line-prefix="123 " --pretty=tformat:%s >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'set up merge history' '
 	git checkout -b side HEAD~4 &&
 	test_commit side-1 1 1 &&
@@ -313,6 +338,27 @@ test_expect_success 'log --graph with merge' '
 	test_cmp expect actual
 '
 
+cat > expect <<\EOF
+| | | *   Merge branch 'side'
+| | | |\
+| | | | * side-2
+| | | | * side-1
+| | | * | Second
+| | | * | sixth
+| | | * | fifth
+| | | * | fourth
+| | | |/
+| | | * third
+| | | * second
+| | | * initial
+EOF
+
+test_expect_success 'log --graph --line-prefix="| | | " with merge' '
+	git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s |
+		sed "s/ *\$//" >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
 	git log --raw --graph --oneline -m master | head -n 500 >actual &&
 	grep "initial" actual
@@ -867,6 +913,283 @@ test_expect_success 'log --graph with diff and stats' '
 	test_i18ncmp expect actual.sanitized
 '
 
+cat >expect <<\EOF
+*** *   commit COMMIT_OBJECT_NAME
+*** |\  Merge: MERGE_PARENTS
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     Merge HEADS DESCRIPTION
+*** | |
+*** | * commit COMMIT_OBJECT_NAME
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     reach
+*** | | ---
+*** | |  reach.t | 1 +
+*** | |  1 file changed, 1 insertion(+)
+*** | |
+*** | | diff --git a/reach.t b/reach.t
+*** | | new file mode 100644
+*** | | index 0000000..10c9591
+*** | | --- /dev/null
+*** | | +++ b/reach.t
+*** | | @@ -0,0 +1 @@
+*** | | +reach
+*** | |
+*** |  \
+*** *-. \   commit COMMIT_OBJECT_NAME
+*** |\ \ \  Merge: MERGE_PARENTS
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Merge HEADS DESCRIPTION
+*** | | | |
+*** | | * | commit COMMIT_OBJECT_NAME
+*** | | |/  Author: A U Thor <author@example.com>
+*** | | |
+*** | | |       octopus-b
+*** | | |   ---
+*** | | |    octopus-b.t | 1 +
+*** | | |    1 file changed, 1 insertion(+)
+*** | | |
+*** | | |   diff --git a/octopus-b.t b/octopus-b.t
+*** | | |   new file mode 100644
+*** | | |   index 0000000..d5fcad0
+*** | | |   --- /dev/null
+*** | | |   +++ b/octopus-b.t
+*** | | |   @@ -0,0 +1 @@
+*** | | |   +octopus-b
+*** | | |
+*** | * | commit COMMIT_OBJECT_NAME
+*** | |/  Author: A U Thor <author@example.com>
+*** | |
+*** | |       octopus-a
+*** | |   ---
+*** | |    octopus-a.t | 1 +
+*** | |    1 file changed, 1 insertion(+)
+*** | |
+*** | |   diff --git a/octopus-a.t b/octopus-a.t
+*** | |   new file mode 100644
+*** | |   index 0000000..11ee015
+*** | |   --- /dev/null
+*** | |   +++ b/octopus-a.t
+*** | |   @@ -0,0 +1 @@
+*** | |   +octopus-a
+*** | |
+*** * | commit COMMIT_OBJECT_NAME
+*** |/  Author: A U Thor <author@example.com>
+*** |
+*** |       seventh
+*** |   ---
+*** |    seventh.t | 1 +
+*** |    1 file changed, 1 insertion(+)
+*** |
+*** |   diff --git a/seventh.t b/seventh.t
+*** |   new file mode 100644
+*** |   index 0000000..9744ffc
+*** |   --- /dev/null
+*** |   +++ b/seventh.t
+*** |   @@ -0,0 +1 @@
+*** |   +seventh
+*** |
+*** *   commit COMMIT_OBJECT_NAME
+*** |\  Merge: MERGE_PARENTS
+*** | | Author: A U Thor <author@example.com>
+*** | |
+*** | |     Merge branch 'tangle'
+*** | |
+*** | *   commit COMMIT_OBJECT_NAME
+*** | |\  Merge: MERGE_PARENTS
+*** | | | Author: A U Thor <author@example.com>
+*** | | |
+*** | | |     Merge branch 'side' (early part) into tangle
+*** | | |
+*** | * |   commit COMMIT_OBJECT_NAME
+*** | |\ \  Merge: MERGE_PARENTS
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Merge branch 'master' (early part) into tangle
+*** | | | |
+*** | * | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     tangle-a
+*** | | | | ---
+*** | | | |  tangle-a | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/tangle-a b/tangle-a
+*** | | | | new file mode 100644
+*** | | | | index 0000000..7898192
+*** | | | | --- /dev/null
+*** | | | | +++ b/tangle-a
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +a
+*** | | | |
+*** * | | |   commit COMMIT_OBJECT_NAME
+*** |\ \ \ \  Merge: MERGE_PARENTS
+*** | | | | | Author: A U Thor <author@example.com>
+*** | | | | |
+*** | | | | |     Merge branch 'side'
+*** | | | | |
+*** | * | | | commit COMMIT_OBJECT_NAME
+*** | | |_|/  Author: A U Thor <author@example.com>
+*** | |/| |
+*** | | | |       side-2
+*** | | | |   ---
+*** | | | |    2 | 1 +
+*** | | | |    1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | |   diff --git a/2 b/2
+*** | | | |   new file mode 100644
+*** | | | |   index 0000000..0cfbf08
+*** | | | |   --- /dev/null
+*** | | | |   +++ b/2
+*** | | | |   @@ -0,0 +1 @@
+*** | | | |   +2
+*** | | | |
+*** | * | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     side-1
+*** | | | | ---
+*** | | | |  1 | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/1 b/1
+*** | | | | new file mode 100644
+*** | | | | index 0000000..d00491f
+*** | | | | --- /dev/null
+*** | | | | +++ b/1
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +1
+*** | | | |
+*** * | | | commit COMMIT_OBJECT_NAME
+*** | | | | Author: A U Thor <author@example.com>
+*** | | | |
+*** | | | |     Second
+*** | | | | ---
+*** | | | |  one | 1 +
+*** | | | |  1 file changed, 1 insertion(+)
+*** | | | |
+*** | | | | diff --git a/one b/one
+*** | | | | new file mode 100644
+*** | | | | index 0000000..9a33383
+*** | | | | --- /dev/null
+*** | | | | +++ b/one
+*** | | | | @@ -0,0 +1 @@
+*** | | | | +case
+*** | | | |
+*** * | | | commit COMMIT_OBJECT_NAME
+*** | |_|/  Author: A U Thor <author@example.com>
+*** |/| |
+*** | | |       sixth
+*** | | |   ---
+*** | | |    a/two | 1 -
+*** | | |    1 file changed, 1 deletion(-)
+*** | | |
+*** | | |   diff --git a/a/two b/a/two
+*** | | |   deleted file mode 100644
+*** | | |   index 9245af5..0000000
+*** | | |   --- a/a/two
+*** | | |   +++ /dev/null
+*** | | |   @@ -1 +0,0 @@
+*** | | |   -ni
+*** | | |
+*** * | | commit COMMIT_OBJECT_NAME
+*** | | | Author: A U Thor <author@example.com>
+*** | | |
+*** | | |     fifth
+*** | | | ---
+*** | | |  a/two | 1 +
+*** | | |  1 file changed, 1 insertion(+)
+*** | | |
+*** | | | diff --git a/a/two b/a/two
+*** | | | new file mode 100644
+*** | | | index 0000000..9245af5
+*** | | | --- /dev/null
+*** | | | +++ b/a/two
+*** | | | @@ -0,0 +1 @@
+*** | | | +ni
+*** | | |
+*** * | | commit COMMIT_OBJECT_NAME
+*** |/ /  Author: A U Thor <author@example.com>
+*** | |
+*** | |       fourth
+*** | |   ---
+*** | |    ein | 1 +
+*** | |    1 file changed, 1 insertion(+)
+*** | |
+*** | |   diff --git a/ein b/ein
+*** | |   new file mode 100644
+*** | |   index 0000000..9d7e69f
+*** | |   --- /dev/null
+*** | |   +++ b/ein
+*** | |   @@ -0,0 +1 @@
+*** | |   +ichi
+*** | |
+*** * | commit COMMIT_OBJECT_NAME
+*** |/  Author: A U Thor <author@example.com>
+*** |
+*** |       third
+*** |   ---
+*** |    ichi | 1 +
+*** |    one  | 1 -
+*** |    2 files changed, 1 insertion(+), 1 deletion(-)
+*** |
+*** |   diff --git a/ichi b/ichi
+*** |   new file mode 100644
+*** |   index 0000000..9d7e69f
+*** |   --- /dev/null
+*** |   +++ b/ichi
+*** |   @@ -0,0 +1 @@
+*** |   +ichi
+*** |   diff --git a/one b/one
+*** |   deleted file mode 100644
+*** |   index 9d7e69f..0000000
+*** |   --- a/one
+*** |   +++ /dev/null
+*** |   @@ -1 +0,0 @@
+*** |   -ichi
+*** |
+*** * commit COMMIT_OBJECT_NAME
+*** | Author: A U Thor <author@example.com>
+*** |
+*** |     second
+*** | ---
+*** |  one | 2 +-
+*** |  1 file changed, 1 insertion(+), 1 deletion(-)
+*** |
+*** | diff --git a/one b/one
+*** | index 5626abf..9d7e69f 100644
+*** | --- a/one
+*** | +++ b/one
+*** | @@ -1 +1 @@
+*** | -one
+*** | +ichi
+*** |
+*** * commit COMMIT_OBJECT_NAME
+***   Author: A U Thor <author@example.com>
+***
+***       initial
+***   ---
+***    one | 1 +
+***    1 file changed, 1 insertion(+)
+***
+***   diff --git a/one b/one
+***   new file mode 100644
+***   index 0000000..5626abf
+***   --- /dev/null
+***   +++ b/one
+***   @@ -0,0 +1 @@
+***   +one
+EOF
+
+test_expect_success 'log --line-prefix="*** " --graph with diff and stats' '
+	git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual &&
+	sanitize_output >actual.sanitized <actual &&
+	test_i18ncmp expect actual.sanitized
+'
+
 test_expect_success 'dotdot is a parent directory' '
 	mkdir -p a/b &&
 	( echo sixth && echo fifth ) >expect &&
-- 
2.9.2.873.g47c31b4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-15 23:06 [PATCH v6 0/3] Add support for displaying submodules as a proper diff Jacob Keller
  2016-08-15 23:07 ` [PATCH v6 1/3] diff.c: remove output_prefix_length field Jacob Keller
  2016-08-15 23:07 ` [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output Jacob Keller
@ 2016-08-15 23:07 ` Jacob Keller
  2016-08-16 18:48   ` Junio C Hamano
  2 siblings, 1 reply; 13+ messages in thread
From: Jacob Keller @ 2016-08-15 23:07 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Stefan Beller, Jeff King, Johannes Sixt, Jacob Keller

From: Jacob Keller <jacob.keller@gmail.com>

Teach git-diff and friends a new format for displaying the difference of
a submodule using git-diff inside the submodule project. This allows
users to easily see exactly what source changed in a given commit that
updates the submodule pointer. To do this, remove DIFF_SUBMODULE_LOG bit
from the diff options, and instead add a new enum type for these
formats.

Add some tests for the new format and option.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 Documentation/diff-config.txt                |   3 +-
 Documentation/diff-options.txt               |   7 +-
 diff.c                                       |  41 +-
 diff.h                                       |   9 +-
 submodule.c                                  | 130 +++++
 submodule.h                                  |   6 +
 t/t4059-diff-submodule-option-diff-format.sh | 738 +++++++++++++++++++++++++++
 7 files changed, 915 insertions(+), 19 deletions(-)
 create mode 100755 t/t4059-diff-submodule-option-diff-format.sh

diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
index d5a5b17d5088..f5d693afad6c 100644
--- a/Documentation/diff-config.txt
+++ b/Documentation/diff-config.txt
@@ -123,7 +123,8 @@ diff.suppressBlankEmpty::
 diff.submodule::
 	Specify the format in which differences in submodules are
 	shown.  The "log" format lists the commits in the range like
-	linkgit:git-submodule[1] `summary` does.  The "short" format
+	linkgit:git-submodule[1] `summary` does.  The "diff" format shows the
+	diff between the beginning and end of the range. The "short" format
 	format just shows the names of the commits at the beginning
 	and end of the range.  Defaults to short.
 
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index cc4342e2034d..d3ca4ad2c2c5 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -215,8 +215,11 @@ any of those replacements occurred.
 	the commits in the range like linkgit:git-submodule[1] `summary` does.
 	Omitting the `--submodule` option or specifying `--submodule=short`,
 	uses the 'short' format. This format just shows the names of the commits
-	at the beginning and end of the range.  Can be tweaked via the
-	`diff.submodule` configuration variable.
+	at the beginning and end of the range. When `--submodule=diff` is
+	given, the 'diff' format is used. This format shows the diff between
+	the old and new submodule commmit from the perspective of the
+	submodule.  Defaults to `diff.submodule` or 'short' if the config
+	option is unset.
 
 --color[=<when>]::
 	Show colored diff.
diff --git a/diff.c b/diff.c
index e286897b51e6..232fbe17680f 100644
--- a/diff.c
+++ b/diff.c
@@ -132,9 +132,11 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
 static int parse_submodule_params(struct diff_options *options, const char *value)
 {
 	if (!strcmp(value, "log"))
-		DIFF_OPT_SET(options, SUBMODULE_LOG);
+		options->submodule_format = DIFF_SUBMODULE_LOG;
+	else if (!strcmp(value, "diff"))
+		options->submodule_format = DIFF_SUBMODULE_DIFF;
 	else if (!strcmp(value, "short"))
-		DIFF_OPT_CLR(options, SUBMODULE_LOG);
+		options->submodule_format = DIFF_SUBMODULE_SHORT;
 	else
 		return -1;
 	return 0;
@@ -2300,9 +2302,18 @@ static void builtin_diff(const char *name_a,
 	struct strbuf header = STRBUF_INIT;
 	const char *line_prefix = diff_line_prefix(o);
 
-	if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
-			(!one->mode || S_ISGITLINK(one->mode)) &&
-			(!two->mode || S_ISGITLINK(two->mode))) {
+	diff_set_mnemonic_prefix(o, "a/", "b/");
+	if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+		a_prefix = o->b_prefix;
+		b_prefix = o->a_prefix;
+	} else {
+		a_prefix = o->a_prefix;
+		b_prefix = o->b_prefix;
+	}
+
+	if (o->submodule_format == DIFF_SUBMODULE_LOG &&
+	    (!one->mode || S_ISGITLINK(one->mode)) &&
+	    (!two->mode || S_ISGITLINK(two->mode))) {
 		const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
 		const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
 		show_submodule_summary(o->file, one->path ? one->path : two->path,
@@ -2311,6 +2322,15 @@ static void builtin_diff(const char *name_a,
 				two->dirty_submodule,
 				meta, del, add, reset);
 		return;
+	} else if (o->submodule_format == DIFF_SUBMODULE_DIFF &&
+		   (!one->mode || S_ISGITLINK(one->mode)) &&
+		   (!two->mode || S_ISGITLINK(two->mode))) {
+		show_submodule_diff(o->file, one->path ? one->path : two->path,
+				line_prefix,
+				one->oid.hash, two->oid.hash,
+				two->dirty_submodule,
+				meta, a_prefix, b_prefix, reset);
+		return;
 	}
 
 	if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
@@ -2318,15 +2338,6 @@ static void builtin_diff(const char *name_a,
 		textconv_two = get_textconv(two);
 	}
 
-	diff_set_mnemonic_prefix(o, "a/", "b/");
-	if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
-		a_prefix = o->b_prefix;
-		b_prefix = o->a_prefix;
-	} else {
-		a_prefix = o->a_prefix;
-		b_prefix = o->b_prefix;
-	}
-
 	/* Never use a non-valid filename anywhere if at all possible */
 	name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
 	name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
@@ -3916,7 +3927,7 @@ int diff_opt_parse(struct diff_options *options,
 		DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
 		handle_ignore_submodules_arg(options, arg);
 	} else if (!strcmp(arg, "--submodule"))
-		DIFF_OPT_SET(options, SUBMODULE_LOG);
+		options->submodule_format = DIFF_SUBMODULE_LOG;
 	else if (skip_prefix(arg, "--submodule=", &arg))
 		return parse_submodule_opt(options, arg);
 	else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
diff --git a/diff.h b/diff.h
index 83d0b1ae8580..abaa9a49c036 100644
--- a/diff.h
+++ b/diff.h
@@ -83,7 +83,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
 #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
-#define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
+/* (1 << 23) unused */
 #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
 #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
@@ -110,6 +110,12 @@ enum diff_words_type {
 	DIFF_WORDS_COLOR
 };
 
+enum diff_submodule_format {
+	DIFF_SUBMODULE_SHORT = 0,
+	DIFF_SUBMODULE_LOG,
+	DIFF_SUBMODULE_DIFF
+};
+
 struct diff_options {
 	const char *orderfile;
 	const char *pickaxe;
@@ -156,6 +162,7 @@ struct diff_options {
 	int stat_count;
 	const char *word_regex;
 	enum diff_words_type word_diff;
+	enum diff_submodule_format submodule_format;
 
 	/* this is set by diffcore for DIFF_FORMAT_PATCH */
 	int found_changes;
diff --git a/submodule.c b/submodule.c
index 1b5cdfb7e784..b1da68dd49c9 100644
--- a/submodule.c
+++ b/submodule.c
@@ -333,6 +333,136 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
 	strbuf_release(&sb);
 }
 
+void show_submodule_diff(FILE *f, const char *path,
+		const char *line_prefix,
+		unsigned char one[20], unsigned char two[20],
+		unsigned dirty_submodule, const char *meta,
+		const char *a_prefix, const char *b_prefix,
+		const char *reset)
+{
+	struct strbuf submodule_git_dir = STRBUF_INIT, sb = STRBUF_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
+	const char *git_dir;
+
+	if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) {
+		fprintf(f, "%sSubmodule %s contains untracked content\n",
+			line_prefix, path);
+	}
+	if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
+		fprintf(f, "%sSubmodule %s contains modified content\n",
+			line_prefix, path);
+	}
+
+	strbuf_addf(&sb, "%s%sSubmodule %s %s..",
+		    line_prefix, meta, path,
+		    find_unique_abbrev(one, DEFAULT_ABBREV));
+	strbuf_addf(&sb, "%s:%s",
+		    find_unique_abbrev(two, DEFAULT_ABBREV),
+		    reset);
+	fwrite(sb.buf, sb.len, 1, f);
+
+	if (is_null_sha1(one))
+		fprintf(f, " (new submodule)");
+	if (is_null_sha1(two))
+		fprintf(f, " (submodule deleted)");
+
+	/*
+	 * We need to determine the most accurate location to call the sub
+	 * command, and handle the various corner cases involved. We'll first
+	 * attempt to use the path directly if the submodule is checked out.
+	 * Then, if that fails, we'll check the standard module location in
+	 * the git directory. If even this fails, it means we can't do the
+	 * lookup because the module has not been initialized.
+	 */
+	strbuf_addf(&submodule_git_dir, "%s/.git", path);
+	git_dir = resolve_gitdir(submodule_git_dir.buf);
+	if (git_dir) {
+		/*
+		 * If we can resolve a git dir, this means that the submodule
+		 * has been checked out. In this case, just use the original
+		 * path since we want access to the work tree
+		 */
+		git_dir = path;
+	} else {
+		/*
+		 * If we can't resolve a git dir, this means that the
+		 * submodule has not been checked out. In this case, try the
+		 * standard location for modules
+		 */
+		strbuf_reset(&submodule_git_dir);
+		strbuf_addf(&submodule_git_dir, "%s/modules/%s", get_git_dir(), path);
+		git_dir = resolve_gitdir(submodule_git_dir.buf);
+		if (!git_dir) {
+			/*
+			 * If we failed to find a git directory here, then the
+			 * submodule must not have been initialized. Without
+			 * the initialized contents of the submodule, we won't
+			 * be able to perform the difference.
+			 */
+			fprintf(f, " (submodule not initialized)\n");
+			goto out;
+		}
+	}
+
+	/*
+	 * print a newline and flush the file so that the diff output appears
+	 * starting on its own line
+	 */
+	fprintf(f, "\n");
+	fflush(f);
+
+	cp.git_cmd = 1;
+	cp.dir = git_dir;
+	cp.out = dup(fileno(f));
+	cp.no_stdin = 1;
+
+	argv_array_push(&cp.args, "diff");
+	argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
+	argv_array_pushf(&cp.args, "--src-prefix=%s%s/", a_prefix, path);
+	argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", b_prefix, path);
+
+	if (is_null_sha1(one)) {
+		/*
+		 * If the origin commit is null, we want to use the empty tree
+		 * so that we see a diff of all the new contents added.
+		 */
+		argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
+	} else {
+		/* Use the old commit as the diff base */
+		argv_array_push(&cp.args, sha1_to_hex(one));
+	}
+
+	if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
+		/*
+		 * If the submodule has modified contents we want to diff
+		 * against the work tree, so don't add a second parameter.
+		 * This is essentially equivalent of using diff-index instead.
+		 * Note that we can't (easily) show the diff of any untracked
+		 * files.
+		 */
+	} else if (is_null_sha1(two)) {
+		/*
+		 * If new commit is null, we should diff against the empty
+		 * tree, so that we see a diff of all submodule contents
+		 * removed.
+		 */
+		argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
+	} else {
+		/*
+		 * Finally, in other cases use the new commit as the end
+		 * result.
+		 */
+		argv_array_push(&cp.args, sha1_to_hex(two));
+	}
+
+	if (run_command(&cp))
+		fprintf(f, "(diff failed)\n");
+
+out:
+	strbuf_release(&submodule_git_dir);
+	strbuf_release(&sb);
+}
+
 void show_submodule_summary(FILE *f, const char *path,
 		const char *line_prefix,
 		unsigned char one[20], unsigned char two[20],
diff --git a/submodule.h b/submodule.h
index 2af939099819..f32a25c667ab 100644
--- a/submodule.h
+++ b/submodule.h
@@ -41,6 +41,12 @@ int parse_submodule_update_strategy(const char *value,
 		struct submodule_update_strategy *dst);
 const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
 void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
+void show_submodule_diff(FILE *f, const char *path,
+		const char *line_prefix,
+		unsigned char one[20], unsigned char two[20],
+		unsigned dirty_submodule, const char *meta,
+		const char *a_prefix, const char *b_prefix,
+		const char *reset);
 void show_submodule_summary(FILE *f, const char *path,
 		const char *line_prefix,
 		unsigned char one[20], unsigned char two[20],
diff --git a/t/t4059-diff-submodule-option-diff-format.sh b/t/t4059-diff-submodule-option-diff-format.sh
new file mode 100755
index 000000000000..d1306b65986c
--- /dev/null
+++ b/t/t4059-diff-submodule-option-diff-format.sh
@@ -0,0 +1,738 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin
+# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests)
+# Copyright (c) 2016 Jacob Keller (copy + convert to --submodule=diff)
+#
+
+test_description='Support for diff format verbose submodule difference in git diff
+
+This test tries to verify the sanity of --submodule=diff option of git diff.
+'
+
+. ./test-lib.sh
+
+# Tested non-UTF-8 encoding
+test_encoding="ISO8859-1"
+
+# String "added" in German (translated with Google Translate), encoded in UTF-8,
+# used in sample commit log messages in add_file() function below.
+added=$(printf "hinzugef\303\274gt")
+add_file () {
+	(
+		cd "$1" &&
+		shift &&
+		for name
+		do
+			echo "$name" >"$name" &&
+			git add "$name" &&
+			test_tick &&
+			# "git commit -m" would break MinGW, as Windows refuse to pass
+			# $test_encoding encoded parameter to git.
+			echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding |
+			git -c "i18n.commitEncoding=$test_encoding" commit -F -
+		done >/dev/null &&
+		git rev-parse --short --verify HEAD
+	)
+}
+commit_file () {
+	test_tick &&
+	git commit "$@" -m "Commit $*" >/dev/null
+}
+
+test_create_repo sm1 &&
+add_file . foo >/dev/null
+
+head1=$(add_file sm1 foo1 foo2)
+fullhead1=$(cd sm1; git rev-parse --verify HEAD)
+
+test_expect_success 'added submodule' '
+	git add sm1 &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 0000000..1beffeb: (new submodule)
+	diff --git a/sm1/foo1 b/sm1/foo1
+	new file mode 100644
+	index 0000000..1715acd
+	--- /dev/null
+	+++ b/sm1/foo1
+	@@ -0,0 +1 @@
+	+foo1
+	diff --git a/sm1/foo2 b/sm1/foo2
+	new file mode 100644
+	index 0000000..54b060e
+	--- /dev/null
+	+++ b/sm1/foo2
+	@@ -0,0 +1 @@
+	+foo2
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'added submodule, set diff.submodule' '
+	git config diff.submodule log &&
+	git add sm1 &&
+	git diff --cached >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 0000000...$head1 (new submodule)
+	EOF
+	git config --unset diff.submodule &&
+	test_cmp expected actual
+'
+
+test_expect_success '--submodule=short overrides diff.submodule' '
+	test_config diff.submodule log &&
+	git add sm1 &&
+	git diff --submodule=short --cached >actual &&
+	cat >expected <<-EOF &&
+	diff --git a/sm1 b/sm1
+	new file mode 160000
+	index 0000000..$head1
+	--- /dev/null
+	+++ b/sm1
+	@@ -0,0 +1 @@
+	+Subproject commit $fullhead1
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'diff.submodule does not affect plumbing' '
+	test_config diff.submodule log &&
+	git diff-index -p HEAD >actual &&
+	cat >expected <<-EOF &&
+	diff --git a/sm1 b/sm1
+	new file mode 160000
+	index 0000000..$head1
+	--- /dev/null
+	+++ b/sm1
+	@@ -0,0 +1 @@
+	+Subproject commit $fullhead1
+	EOF
+	test_cmp expected actual
+'
+
+commit_file sm1 &&
+head2=$(add_file sm1 foo3)
+
+test_expect_success 'modified submodule(forward)' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 1beffeb..30b9670:
+	diff --git a/sm1/foo3 b/sm1/foo3
+	new file mode 100644
+	index 0000000..c1ec6c6
+	--- /dev/null
+	+++ b/sm1/foo3
+	@@ -0,0 +1 @@
+	+foo3
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule(forward)' '
+	git diff --submodule=diff >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 1beffeb..30b9670:
+	diff --git a/sm1/foo3 b/sm1/foo3
+	new file mode 100644
+	index 0000000..c1ec6c6
+	--- /dev/null
+	+++ b/sm1/foo3
+	@@ -0,0 +1 @@
+	+foo3
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule(forward) --submodule' '
+	git diff --submodule >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 $head1..$head2:
+	  > Add foo3 ($added foo3)
+	EOF
+	test_cmp expected actual
+'
+
+fullhead2=$(cd sm1; git rev-parse --verify HEAD)
+test_expect_success 'modified submodule(forward) --submodule=short' '
+	git diff --submodule=short >actual &&
+	cat >expected <<-EOF &&
+	diff --git a/sm1 b/sm1
+	index $head1..$head2 160000
+	--- a/sm1
+	+++ b/sm1
+	@@ -1 +1 @@
+	-Subproject commit $fullhead1
+	+Subproject commit $fullhead2
+	EOF
+	test_cmp expected actual
+'
+
+commit_file sm1 &&
+head3=$(
+	cd sm1 &&
+	git reset --hard HEAD~2 >/dev/null &&
+	git rev-parse --short --verify HEAD
+)
+
+test_expect_success 'modified submodule(backward)' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 30b9670..dafb207:
+	diff --git a/sm1/foo2 b/sm1/foo2
+	deleted file mode 100644
+	index 54b060e..0000000
+	--- a/sm1/foo2
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo2
+	diff --git a/sm1/foo3 b/sm1/foo3
+	deleted file mode 100644
+	index c1ec6c6..0000000
+	--- a/sm1/foo3
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo3
+	EOF
+	test_cmp expected actual
+'
+
+head4=$(add_file sm1 foo4 foo5)
+test_expect_success 'modified submodule(backward and forward)' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 30b9670..d176589:
+	diff --git a/sm1/foo2 b/sm1/foo2
+	deleted file mode 100644
+	index 54b060e..0000000
+	--- a/sm1/foo2
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo2
+	diff --git a/sm1/foo3 b/sm1/foo3
+	deleted file mode 100644
+	index c1ec6c6..0000000
+	--- a/sm1/foo3
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo3
+	diff --git a/sm1/foo4 b/sm1/foo4
+	new file mode 100644
+	index 0000000..a0016db
+	--- /dev/null
+	+++ b/sm1/foo4
+	@@ -0,0 +1 @@
+	+foo4
+	diff --git a/sm1/foo5 b/sm1/foo5
+	new file mode 100644
+	index 0000000..d6f2413
+	--- /dev/null
+	+++ b/sm1/foo5
+	@@ -0,0 +1 @@
+	+foo5
+	EOF
+	test_cmp expected actual
+'
+
+commit_file sm1 &&
+mv sm1 sm1-bak &&
+echo sm1 >sm1 &&
+head5=$(git hash-object sm1 | cut -c1-7) &&
+git add sm1 &&
+rm -f sm1 &&
+mv sm1-bak sm1
+
+test_expect_success 'typechanged submodule(submodule->blob), --cached' '
+	git diff --submodule=diff --cached >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 d176589..0000000: (submodule deleted)
+	diff --git a/sm1/foo1 b/sm1/foo1
+	deleted file mode 100644
+	index 1715acd..0000000
+	--- a/sm1/foo1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo1
+	diff --git a/sm1/foo4 b/sm1/foo4
+	deleted file mode 100644
+	index a0016db..0000000
+	--- a/sm1/foo4
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo4
+	diff --git a/sm1/foo5 b/sm1/foo5
+	deleted file mode 100644
+	index d6f2413..0000000
+	--- a/sm1/foo5
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-foo5
+	diff --git a/sm1 b/sm1
+	new file mode 100644
+	index 0000000..9da5fb8
+	--- /dev/null
+	+++ b/sm1
+	@@ -0,0 +1 @@
+	+sm1
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'typechanged submodule(submodule->blob)' '
+	git diff --submodule=diff >actual &&
+	cat >expected <<-EOF &&
+	diff --git a/sm1 b/sm1
+	deleted file mode 100644
+	index 9da5fb8..0000000
+	--- a/sm1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-sm1
+	Submodule sm1 0000000..d176589: (new submodule)
+	diff --git a/sm1/foo1 b/sm1/foo1
+	new file mode 100644
+	index 0000000..1715acd
+	--- /dev/null
+	+++ b/sm1/foo1
+	@@ -0,0 +1 @@
+	+foo1
+	diff --git a/sm1/foo4 b/sm1/foo4
+	new file mode 100644
+	index 0000000..a0016db
+	--- /dev/null
+	+++ b/sm1/foo4
+	@@ -0,0 +1 @@
+	+foo4
+	diff --git a/sm1/foo5 b/sm1/foo5
+	new file mode 100644
+	index 0000000..d6f2413
+	--- /dev/null
+	+++ b/sm1/foo5
+	@@ -0,0 +1 @@
+	+foo5
+	EOF
+	test_cmp expected actual
+'
+
+rm -rf sm1 &&
+git checkout-index sm1
+test_expect_success 'typechanged submodule(submodule->blob)' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 $head4..0000000: (submodule deleted) (submodule not initialized)
+	diff --git a/sm1 b/sm1
+	new file mode 100644
+	index 0000000..$head5
+	--- /dev/null
+	+++ b/sm1
+	@@ -0,0 +1 @@
+	+sm1
+	EOF
+	test_cmp expected actual
+'
+
+rm -f sm1 &&
+test_create_repo sm1 &&
+head6=$(add_file sm1 foo6 foo7)
+fullhead6=$(cd sm1; git rev-parse --verify HEAD)
+test_expect_success 'nonexistent commit' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 d176589..17243c9:
+	(diff failed)
+	EOF
+	test_cmp expected actual
+'
+
+commit_file
+test_expect_success 'typechanged submodule(blob->submodule)' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	diff --git a/sm1 b/sm1
+	deleted file mode 100644
+	index 9da5fb8..0000000
+	--- a/sm1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-sm1
+	Submodule sm1 0000000..17243c9: (new submodule)
+	diff --git a/sm1/foo6 b/sm1/foo6
+	new file mode 100644
+	index 0000000..462398b
+	--- /dev/null
+	+++ b/sm1/foo6
+	@@ -0,0 +1 @@
+	+foo6
+	diff --git a/sm1/foo7 b/sm1/foo7
+	new file mode 100644
+	index 0000000..6e9262c
+	--- /dev/null
+	+++ b/sm1/foo7
+	@@ -0,0 +1 @@
+	+foo7
+	EOF
+	test_cmp expected actual
+'
+
+commit_file sm1 &&
+test_expect_success 'submodule is up to date' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked content' '
+	echo new > sm1/new-file &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains untracked content
+	Submodule sm1 17243c9..17243c9:
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked content (untracked ignored)' '
+	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'submodule contains untracked content (dirty ignored)' '
+	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'submodule contains untracked content (all ignored)' '
+	git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'submodule contains untracked and modified content' '
+	echo new > sm1/foo6 &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains untracked content
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..17243c9:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+# NOT OK
+test_expect_success 'submodule contains untracked and modified content (untracked ignored)' '
+	echo new > sm1/foo6 &&
+	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..17243c9:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'submodule contains untracked and modified content (dirty ignored)' '
+	echo new > sm1/foo6 &&
+	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'submodule contains untracked and modified content (all ignored)' '
+	echo new > sm1/foo6 &&
+	git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'submodule contains modified content' '
+	rm -f sm1/new-file &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..17243c9:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+(cd sm1; git commit -mchange foo6 >/dev/null) &&
+head8=$(cd sm1; git rev-parse --short --verify HEAD) &&
+test_expect_success 'submodule is modified' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content' '
+	echo new > sm1/new-file &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains untracked content
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (untracked ignored)' '
+	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (dirty ignored)' '
+	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked content (all ignored)' '
+	git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content' '
+	echo modification >> sm1/foo6 &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains untracked content
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..dfda541 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1,2 @@
+	-foo6
+	+new
+	+modification
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' '
+	echo modification >> sm1/foo6 &&
+	git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..e20e2d9 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1,3 @@
+	-foo6
+	+new
+	+modification
+	+modification
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' '
+	echo modification >> sm1/foo6 &&
+	git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..3e75765 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1 @@
+	-foo6
+	+new
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'modified submodule contains untracked and modified content (all ignored)' '
+	echo modification >> sm1/foo6 &&
+	git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
+	! test -s actual
+'
+
+# NOT OK
+test_expect_success 'modified submodule contains modified content' '
+	rm -f sm1/new-file &&
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 contains modified content
+	Submodule sm1 17243c9..cfce562:
+	diff --git a/sm1/foo6 b/sm1/foo6
+	index 462398b..ac466ca 100644
+	--- a/sm1/foo6
+	+++ b/sm1/foo6
+	@@ -1 +1,5 @@
+	-foo6
+	+new
+	+modification
+	+modification
+	+modification
+	+modification
+	EOF
+	test_cmp expected actual
+'
+
+rm -rf sm1
+test_expect_success 'deleted submodule' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..0000000: (submodule deleted) (submodule not initialized)
+	EOF
+	test_cmp expected actual
+'
+
+test_create_repo sm2 &&
+head7=$(add_file sm2 foo8 foo9) &&
+git add sm2
+
+test_expect_success 'multiple submodules' '
+	git diff-index -p --submodule=diff HEAD >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..0000000: (submodule deleted) (submodule not initialized)
+	Submodule sm2 0000000..a5a65c9: (new submodule)
+	diff --git a/sm2/foo8 b/sm2/foo8
+	new file mode 100644
+	index 0000000..db9916b
+	--- /dev/null
+	+++ b/sm2/foo8
+	@@ -0,0 +1 @@
+	+foo8
+	diff --git a/sm2/foo9 b/sm2/foo9
+	new file mode 100644
+	index 0000000..9c3b4f6
+	--- /dev/null
+	+++ b/sm2/foo9
+	@@ -0,0 +1 @@
+	+foo9
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'path filter' '
+	git diff-index -p --submodule=diff HEAD sm2 >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm2 0000000..a5a65c9: (new submodule)
+	diff --git a/sm2/foo8 b/sm2/foo8
+	new file mode 100644
+	index 0000000..db9916b
+	--- /dev/null
+	+++ b/sm2/foo8
+	@@ -0,0 +1 @@
+	+foo8
+	diff --git a/sm2/foo9 b/sm2/foo9
+	new file mode 100644
+	index 0000000..9c3b4f6
+	--- /dev/null
+	+++ b/sm2/foo9
+	@@ -0,0 +1 @@
+	+foo9
+	EOF
+	test_cmp expected actual
+'
+
+commit_file sm2
+test_expect_success 'given commit' '
+	git diff-index -p --submodule=diff HEAD^ >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..0000000: (submodule deleted) (submodule not initialized)
+	Submodule sm2 0000000..a5a65c9: (new submodule)
+	diff --git a/sm2/foo8 b/sm2/foo8
+	new file mode 100644
+	index 0000000..db9916b
+	--- /dev/null
+	+++ b/sm2/foo8
+	@@ -0,0 +1 @@
+	+foo8
+	diff --git a/sm2/foo9 b/sm2/foo9
+	new file mode 100644
+	index 0000000..9c3b4f6
+	--- /dev/null
+	+++ b/sm2/foo9
+	@@ -0,0 +1 @@
+	+foo9
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'setup .git file for sm2' '
+	(cd sm2 &&
+	 REAL="$(pwd)/../.real" &&
+	 mv .git "$REAL"
+	 echo "gitdir: $REAL" >.git)
+'
+
+test_expect_success 'diff --submodule=diff with .git file' '
+	git diff --submodule=diff HEAD^ >actual &&
+	cat >expected <<-EOF &&
+	Submodule sm1 17243c9..0000000: (submodule deleted) (submodule not initialized)
+	Submodule sm2 0000000..a5a65c9: (new submodule)
+	diff --git a/sm2/foo8 b/sm2/foo8
+	new file mode 100644
+	index 0000000..db9916b
+	--- /dev/null
+	+++ b/sm2/foo8
+	@@ -0,0 +1 @@
+	+foo8
+	diff --git a/sm2/foo9 b/sm2/foo9
+	new file mode 100644
+	index 0000000..9c3b4f6
+	--- /dev/null
+	+++ b/sm2/foo9
+	@@ -0,0 +1 @@
+	+foo9
+	EOF
+	test_cmp expected actual
+'
+
+test_done
-- 
2.9.2.873.g47c31b4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 1/3] diff.c: remove output_prefix_length field
  2016-08-15 23:07 ` [PATCH v6 1/3] diff.c: remove output_prefix_length field Jacob Keller
@ 2016-08-16 18:03   ` Junio C Hamano
  2016-08-16 18:08     ` Jacob Keller
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2016-08-16 18:03 UTC (permalink / raw)
  To: Jacob Keller; +Cc: git, Stefan Beller, Jeff King, Johannes Sixt

Jacob Keller <jacob.e.keller@intel.com> writes:

> From: Junio C Hamano <gitster@pobox.com>

Thanks.  I had quite a many typoes in this one.

> "diff/log --stat" has a logic that determines the display columns
> available for the diffstat part of the output and apportions it for
> pathnames and diffstat graph automatically.
>
> 5e71a84a (Add output_prefix_length to diff_options, 2012-04-16)
> added the output_prefix_length field to diff_options structure to
> allow this logic subtract the display columns used for display the

s/^.*$/allow this logic to subtract the display columns used for the/

> history graph part from the total "terminal width"; this matters
> when the "git log --graph -p" option is in use.
>
> The field be set to the number of display columns needed to show the

s/field be/field must be/

> output from the output_prefix() callback.  Any new output_prefix()
> callback must also update the field accordingly, which is error

"output from the output_prefix() callback, which is error"

> prone.

No need to resend; I had it with the above fix in 'pu' for a few
days already ;-)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 1/3] diff.c: remove output_prefix_length field
  2016-08-16 18:03   ` Junio C Hamano
@ 2016-08-16 18:08     ` Jacob Keller
  0 siblings, 0 replies; 13+ messages in thread
From: Jacob Keller @ 2016-08-16 18:08 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

On Tue, Aug 16, 2016 at 11:03 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Jacob Keller <jacob.e.keller@intel.com> writes:
>
>> From: Junio C Hamano <gitster@pobox.com>
>
> Thanks.  I had quite a many typoes in this one.
>
>
> No need to resend; I had it with the above fix in 'pu' for a few
> days already ;-)

Thanks,
Jake

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output
  2016-08-15 23:07 ` [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output Jacob Keller
@ 2016-08-16 18:22   ` Junio C Hamano
  2016-08-16 20:19     ` Jacob Keller
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2016-08-16 18:22 UTC (permalink / raw)
  To: Jacob Keller; +Cc: git, Stefan Beller, Jeff King, Johannes Sixt, Jacob Keller

Jacob Keller <jacob.e.keller@intel.com> writes:

> To make this work, we have to fix a few bugs in the graph API that force
> graph_show_commit_msg to be used only when you have a valid graph.
> Additionally, we extend the default_diff_output_prefix handler to work
> even when no graph is enabled.
>
> This is somewhat of a hack on top of the graph API, but I think it
> should be acceptable here.

Unlike the opt-prefix-length I removed in 1/3, the length of the
line-prefix will never change during the lifetime of a single
diff_options struct, so it might turn out that repeated strlen()
on it for each and every line output is wasteful.

Other than that, I didn't spot anything immediately questionable.

Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-15 23:07 ` [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff Jacob Keller
@ 2016-08-16 18:48   ` Junio C Hamano
  2016-08-16 20:25     ` Jacob Keller
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2016-08-16 18:48 UTC (permalink / raw)
  To: Jacob Keller; +Cc: git, Stefan Beller, Jeff King, Johannes Sixt, Jacob Keller

Jacob Keller <jacob.e.keller@intel.com> writes:

> diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
> index d5a5b17d5088..f5d693afad6c 100644
> --- a/Documentation/diff-config.txt
> +++ b/Documentation/diff-config.txt
> @@ -123,7 +123,8 @@ diff.suppressBlankEmpty::
>  diff.submodule::
>  	Specify the format in which differences in submodules are
>  	shown.  The "log" format lists the commits in the range like
> -	linkgit:git-submodule[1] `summary` does.  The "short" format
> +	linkgit:git-submodule[1] `summary` does.  The "diff" format shows the
> +	diff between the beginning and end of the range. The "short" format
>  	format just shows the names of the commits at the beginning
>  	and end of the range.  Defaults to short.

It would be much better to describe the default one first and then
more involved one next, no?  That would also match what the change
to "diff-options" in this patch does (can be seen below).

> diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
> index cc4342e2034d..d3ca4ad2c2c5 100644
> --- a/Documentation/diff-options.txt
> +++ b/Documentation/diff-options.txt
> @@ -215,8 +215,11 @@ any of those replacements occurred.
>  	the commits in the range like linkgit:git-submodule[1] `summary` does.
>  	Omitting the `--submodule` option or specifying `--submodule=short`,
>  	uses the 'short' format. This format just shows the names of the commits
> -	at the beginning and end of the range.  Can be tweaked via the
> -	`diff.submodule` configuration variable.
> +	at the beginning and end of the range. When `--submodule=diff` is
> +	given, the 'diff' format is used. This format shows the diff between
> +	the old and new submodule commmit from the perspective of the
> +	submodule.  Defaults to `diff.submodule` or 'short' if the config
> +	option is unset.

> @@ -2311,6 +2322,15 @@ static void builtin_diff(const char *name_a,
>  				two->dirty_submodule,
>  				meta, del, add, reset);
>  		return;
> +	} else if (o->submodule_format == DIFF_SUBMODULE_DIFF &&
> +		   (!one->mode || S_ISGITLINK(one->mode)) &&
> +		   (!two->mode || S_ISGITLINK(two->mode))) {
> +		show_submodule_diff(o->file, one->path ? one->path : two->path,
> +				line_prefix,
> +				one->oid.hash, two->oid.hash,
> +				two->dirty_submodule,
> +				meta, a_prefix, b_prefix, reset);
> +		return;
>  	}

The "either missing or is submodule" check used here is being
consistent with the existing "submodule=log" case.  Good.

> diff --git a/submodule.c b/submodule.c
> index 1b5cdfb7e784..b1da68dd49c9 100644
> --- a/submodule.c
> +++ b/submodule.c
> @@ -333,6 +333,136 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
>  	strbuf_release(&sb);
>  }
>  
> +void show_submodule_diff(FILE *f, const char *path,
> +		const char *line_prefix,
> +		unsigned char one[20], unsigned char two[20],
> +		unsigned dirty_submodule, const char *meta,
> +		const char *a_prefix, const char *b_prefix,
> +		const char *reset)
> +{
> +	struct strbuf submodule_git_dir = STRBUF_INIT, sb = STRBUF_INIT;
> +	struct child_process cp = CHILD_PROCESS_INIT;
> +	const char *git_dir;
> +
> +	if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) {
> +		fprintf(f, "%sSubmodule %s contains untracked content\n",
> +			line_prefix, path);
> +	}
> +	if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
> +		fprintf(f, "%sSubmodule %s contains modified content\n",
> +			line_prefix, path);
> +	}
> +
> +	strbuf_addf(&sb, "%s%sSubmodule %s %s..",
> +		    line_prefix, meta, path,
> +		    find_unique_abbrev(one, DEFAULT_ABBREV));
> +	strbuf_addf(&sb, "%s:%s",
> +		    find_unique_abbrev(two, DEFAULT_ABBREV),
> +		    reset);
> +	fwrite(sb.buf, sb.len, 1, f);
> +
> +	if (is_null_sha1(one))
> +		fprintf(f, " (new submodule)");
> +	if (is_null_sha1(two))
> +		fprintf(f, " (submodule deleted)");

These messages are in sync with show_submodule_summary() that is
used in --submodule=log codepath.  Good.

> +	/*
> +	 * We need to determine the most accurate location to call the sub
> +	 * command, and handle the various corner cases involved. We'll first
> +	 * attempt to use the path directly if the submodule is checked out.
> +	 * Then, if that fails, we'll check the standard module location in
> +	 * the git directory. If even this fails, it means we can't do the
> +	 * lookup because the module has not been initialized.
> +	 */

This is more elaborate than what show_submodule_summary() does,
isn't it?  Would it make the patch series (and the resulting code)
more understandable if you used the same code by refactoring these
two?  If so, I wonder if it makes sense to split 3/3 into a few
separate steps:

 * Update the internal "--submodule=<type>" handling without adding
   the "--submodule=diff" and show_submodule_diff() function.

 * Refactor the determination of the submodule status (i.e. does it
   even have a clone?  where is its repository? etc.) from existing
   show_submodule_summary() into a separate helper function.

 * Make that helper function more elaborate like what you do here,
   and update show_submodule_summary().  I think the state
   show_submodule_summary() calls "not checked out" corresponds to
   what you say "not initialized" below, and they should share the
   same logic to determine that the submodule is in that state, and
   share the same message, for example.

 * Introduce "--submodule=diff", and show_submodule_diff() function;
   the latter would use the helper function prepared in the previous
   step.

perhaps?

> +	strbuf_addf(&submodule_git_dir, "%s/.git", path);
> +	git_dir = resolve_gitdir(submodule_git_dir.buf);
> +	if (git_dir) {
> +		/*
> +		 * If we can resolve a git dir, this means that the submodule
> +		 * has been checked out. In this case, just use the original
> +		 * path since we want access to the work tree
> +		 */
> +		git_dir = path;
> +	} else {
> +		/*
> +		 * If we can't resolve a git dir, this means that the
> +		 * submodule has not been checked out. In this case, try the
> +		 * standard location for modules
> +		 */
> +		strbuf_reset(&submodule_git_dir);
> +		strbuf_addf(&submodule_git_dir, "%s/modules/%s", get_git_dir(), path);
> +		git_dir = resolve_gitdir(submodule_git_dir.buf);
> +		if (!git_dir) {
> +			/*
> +			 * If we failed to find a git directory here, then the
> +			 * submodule must not have been initialized. Without
> +			 * the initialized contents of the submodule, we won't
> +			 * be able to perform the difference.
> +			 */
> +			fprintf(f, " (submodule not initialized)\n");
> +			goto out;
> +		}
> +	}
> +
> +	/*
> +	 * print a newline and flush the file so that the diff output appears
> +	 * starting on its own line
> +	 */
> +	fprintf(f, "\n");
> +	fflush(f);
> +
> +	cp.git_cmd = 1;
> +	cp.dir = git_dir;
> +	cp.out = dup(fileno(f));
> +	cp.no_stdin = 1;
> +
> +	argv_array_push(&cp.args, "diff");
> +	argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
> +	argv_array_pushf(&cp.args, "--src-prefix=%s%s/", a_prefix, path);
> +	argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", b_prefix, path);
> +
> +	if (is_null_sha1(one)) {
> +		/*
> +		 * If the origin commit is null, we want to use the empty tree
> +		 * so that we see a diff of all the new contents added.
> +		 */
> +		argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
> +	} else {
> +		/* Use the old commit as the diff base */
> +		argv_array_push(&cp.args, sha1_to_hex(one));
> +	}
> +
> +	if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
> +		/*
> +		 * If the submodule has modified contents we want to diff
> +		 * against the work tree, so don't add a second parameter.
> +		 * This is essentially equivalent of using diff-index instead.
> +		 * Note that we can't (easily) show the diff of any untracked
> +		 * files.
> +		 */
> +	} else if (is_null_sha1(two)) {

It is safer to have ';' inside the empty if(){} block to make sure
that one empty statement exists there.  It makes the intention of
the code clearer, too.

I am debating myself if this is a good thing to do, though.  The
submodule is a separate project for a reason, and there is a reason
why the changes haven't been committed.  When asking "what's different
between these two superproject states?", should the user really see
these uncommitted changes?

Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output
  2016-08-16 18:22   ` Junio C Hamano
@ 2016-08-16 20:19     ` Jacob Keller
  0 siblings, 0 replies; 13+ messages in thread
From: Jacob Keller @ 2016-08-16 20:19 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

On Tue, Aug 16, 2016 at 11:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Jacob Keller <jacob.e.keller@intel.com> writes:
>
>> To make this work, we have to fix a few bugs in the graph API that force
>> graph_show_commit_msg to be used only when you have a valid graph.
>> Additionally, we extend the default_diff_output_prefix handler to work
>> even when no graph is enabled.
>>
>> This is somewhat of a hack on top of the graph API, but I think it
>> should be acceptable here.
>
> Unlike the opt-prefix-length I removed in 1/3, the length of the
> line-prefix will never change during the lifetime of a single
> diff_options struct, so it might turn out that repeated strlen()
> on it for each and every line output is wasteful.
>

I could change this to store the length at option initialization,
probably a good idea. Would it be better to just store it as a strbuf,
since these know their length already? Or just add a
line_prefix_length field? I agree that calling strlen a lot is
probably wasteful.

> Other than that, I didn't spot anything immediately questionable.
>

Thanks. I definitely feel like this is somewhat abusing the graph API
because we're adding something not exactly graph-related. But in some
ways it really is graph related as well, so I think it makes some
sense.

Regards,
Jake

> Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-16 18:48   ` Junio C Hamano
@ 2016-08-16 20:25     ` Jacob Keller
  2016-08-16 21:14       ` Junio C Hamano
  0 siblings, 1 reply; 13+ messages in thread
From: Jacob Keller @ 2016-08-16 20:25 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

On Tue, Aug 16, 2016 at 11:48 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Jacob Keller <jacob.e.keller@intel.com> writes:
>
>> diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
>> index d5a5b17d5088..f5d693afad6c 100644
>> --- a/Documentation/diff-config.txt
>> +++ b/Documentation/diff-config.txt
>> @@ -123,7 +123,8 @@ diff.suppressBlankEmpty::
>>  diff.submodule::
>>       Specify the format in which differences in submodules are
>>       shown.  The "log" format lists the commits in the range like
>> -     linkgit:git-submodule[1] `summary` does.  The "short" format
>> +     linkgit:git-submodule[1] `summary` does.  The "diff" format shows the
>> +     diff between the beginning and end of the range. The "short" format
>>       format just shows the names of the commits at the beginning
>>       and end of the range.  Defaults to short.
>
> It would be much better to describe the default one first and then
> more involved one next, no?  That would also match what the change
> to "diff-options" in this patch does (can be seen below).
>

The main thing is that "--submodule" alone means "use the log format"
so I think that's why it went first. I can reword this to make it more
clear how this works.

Thanks,
Jake

>> @@ -2311,6 +2322,15 @@ static void builtin_diff(const char *name_a,
>>                               two->dirty_submodule,
>>                               meta, del, add, reset);
>>               return;
>> +     } else if (o->submodule_format == DIFF_SUBMODULE_DIFF &&
>> +                (!one->mode || S_ISGITLINK(one->mode)) &&
>> +                (!two->mode || S_ISGITLINK(two->mode))) {
>> +             show_submodule_diff(o->file, one->path ? one->path : two->path,
>> +                             line_prefix,
>> +                             one->oid.hash, two->oid.hash,
>> +                             two->dirty_submodule,
>> +                             meta, a_prefix, b_prefix, reset);
>> +             return;
>>       }
>
> The "either missing or is submodule" check used here is being
> consistent with the existing "submodule=log" case.  Good.
>
>> diff --git a/submodule.c b/submodule.c
>> index 1b5cdfb7e784..b1da68dd49c9 100644
>> --- a/submodule.c
>> +++ b/submodule.c
>> @@ -333,6 +333,136 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
>>       strbuf_release(&sb);
>>  }
>>
>> +void show_submodule_diff(FILE *f, const char *path,
>> +             const char *line_prefix,
>> +             unsigned char one[20], unsigned char two[20],
>> +             unsigned dirty_submodule, const char *meta,
>> +             const char *a_prefix, const char *b_prefix,
>> +             const char *reset)
>> +{
>> +     struct strbuf submodule_git_dir = STRBUF_INIT, sb = STRBUF_INIT;
>> +     struct child_process cp = CHILD_PROCESS_INIT;
>> +     const char *git_dir;
>> +
>> +     if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) {
>> +             fprintf(f, "%sSubmodule %s contains untracked content\n",
>> +                     line_prefix, path);
>> +     }
>> +     if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
>> +             fprintf(f, "%sSubmodule %s contains modified content\n",
>> +                     line_prefix, path);
>> +     }
>> +
>> +     strbuf_addf(&sb, "%s%sSubmodule %s %s..",
>> +                 line_prefix, meta, path,
>> +                 find_unique_abbrev(one, DEFAULT_ABBREV));
>> +     strbuf_addf(&sb, "%s:%s",
>> +                 find_unique_abbrev(two, DEFAULT_ABBREV),
>> +                 reset);
>> +     fwrite(sb.buf, sb.len, 1, f);
>> +
>> +     if (is_null_sha1(one))
>> +             fprintf(f, " (new submodule)");
>> +     if (is_null_sha1(two))
>> +             fprintf(f, " (submodule deleted)");
>
> These messages are in sync with show_submodule_summary() that is
> used in --submodule=log codepath.  Good.
>

They're not exactly the same due to some ways of splitting up new lines.

>> +     /*
>> +      * We need to determine the most accurate location to call the sub
>> +      * command, and handle the various corner cases involved. We'll first
>> +      * attempt to use the path directly if the submodule is checked out.
>> +      * Then, if that fails, we'll check the standard module location in
>> +      * the git directory. If even this fails, it means we can't do the
>> +      * lookup because the module has not been initialized.
>> +      */
>
> This is more elaborate than what show_submodule_summary() does,
> isn't it?  Would it make the patch series (and the resulting code)
> more understandable if you used the same code by refactoring these
> two?  If so, I wonder if it makes sense to split 3/3 into a few
> separate steps:

The show_submodule_summary just uses "add_submodule_odb" which adds
the submodule as an alternate source of objects, if I understand
correctly.

>
>  * Update the internal "--submodule=<type>" handling without adding
>    the "--submodule=diff" and show_submodule_diff() function.
>
>  * Refactor the determination of the submodule status (i.e. does it
>    even have a clone?  where is its repository? etc.) from existing
>    show_submodule_summary() into a separate helper function.
>
>  * Make that helper function more elaborate like what you do here,
>    and update show_submodule_summary().  I think the state
>    show_submodule_summary() calls "not checked out" corresponds to
>    what you say "not initialized" below, and they should share the
>    same logic to determine that the submodule is in that state, and
>    share the same message, for example.

Makes sense, I might squash that with the above if it's easier.

>
>  * Introduce "--submodule=diff", and show_submodule_diff() function;
>    the latter would use the helper function prepared in the previous
>    step.
>
> perhaps?
>

That makes more sense. I'll rework the series some more.

>> +
>> +     if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
>> +             /*
>> +              * If the submodule has modified contents we want to diff
>> +              * against the work tree, so don't add a second parameter.
>> +              * This is essentially equivalent of using diff-index instead.
>> +              * Note that we can't (easily) show the diff of any untracked
>> +              * files.
>> +              */
>> +     } else if (is_null_sha1(two)) {
>
> It is safer to have ';' inside the empty if(){} block to make sure
> that one empty statement exists there.  It makes the intention of
> the code clearer, too.
>

Will do.

> I am debating myself if this is a good thing to do, though.  The
> submodule is a separate project for a reason, and there is a reason
> why the changes haven't been committed.  When asking "what's different
> between these two superproject states?", should the user really see
> these uncommitted changes?
>
> Thanks.

Well, the previous submodule code for "log" prints out "submodule has
untracked content" and "submodule has modified content" so I figured
the diff might want to show that as a diff too? Or maybe we just stick
with the messages and only show the difference of what's actually been
committed.. I think that's probably ok too.

Regards,
Jake

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-16 20:25     ` Jacob Keller
@ 2016-08-16 21:14       ` Junio C Hamano
  2016-08-16 21:20         ` Jacob Keller
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2016-08-16 21:14 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

Jacob Keller <jacob.keller@gmail.com> writes:

>>> +
>>> +     if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
>>> +             /*
>>> +              * If the submodule has modified contents we want to diff
>>> +              * against the work tree, so don't add a second parameter.
>>> +              * This is essentially equivalent of using diff-index instead.
>>> +              * Note that we can't (easily) show the diff of any untracked
>>> +              * files.
>>> +              */
>> ...
>> I am debating myself if this is a good thing to do, though.  The
>> submodule is a separate project for a reason, and there is a reason
>> why the changes haven't been committed.  When asking "what's different
>> between these two superproject states?", should the user really see
>> these uncommitted changes?
>
> Well, the previous submodule code for "log" prints out "submodule has
> untracked content" and "submodule has modified content" so I figured
> the diff might want to show that as a diff too? Or maybe we just stick
> with the messages and only show the difference of what's actually been
> committed.. I think that's probably ok too.

I do not have a strong opinion.  We only see DIRTY when we are
looking at the working tree at the top-level diff (i.e. "git diff
HEAD~ HEAD" at the top-level, or "git diff --cached" will not show
DIRTY_SUBMODULE_MODIFIED when a submodule has local modifications in
its working tree), so in that sense, it probably makes sense to show
the more detailed picture of the working tree like your patch does.
After all, choosing to use --submodule=diff is a strong sign that
the user who says top-level "git diff [<tree>]" wants to see the
details of her work-in-progress.

Do you need to handle "git diff -R [<tree>]" at the top-level a bit
differently, by the way?  If this function gets the full diff_options
that is driving the top-level diff, the DIFF_OPT_REVERSE_DIFF bit
would tell you that.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-16 21:14       ` Junio C Hamano
@ 2016-08-16 21:20         ` Jacob Keller
  2016-08-16 21:31           ` Junio C Hamano
  0 siblings, 1 reply; 13+ messages in thread
From: Jacob Keller @ 2016-08-16 21:20 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

On Tue, Aug 16, 2016 at 2:14 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Jacob Keller <jacob.keller@gmail.com> writes:
>
>>>> +
>>>> +     if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {
>>>> +             /*
>>>> +              * If the submodule has modified contents we want to diff
>>>> +              * against the work tree, so don't add a second parameter.
>>>> +              * This is essentially equivalent of using diff-index instead.
>>>> +              * Note that we can't (easily) show the diff of any untracked
>>>> +              * files.
>>>> +              */
>>> ...
>>> I am debating myself if this is a good thing to do, though.  The
>>> submodule is a separate project for a reason, and there is a reason
>>> why the changes haven't been committed.  When asking "what's different
>>> between these two superproject states?", should the user really see
>>> these uncommitted changes?
>>
>> Well, the previous submodule code for "log" prints out "submodule has
>> untracked content" and "submodule has modified content" so I figured
>> the diff might want to show that as a diff too? Or maybe we just stick
>> with the messages and only show the difference of what's actually been
>> committed.. I think that's probably ok too.
>
> I do not have a strong opinion.  We only see DIRTY when we are
> looking at the working tree at the top-level diff (i.e. "git diff
> HEAD~ HEAD" at the top-level, or "git diff --cached" will not show
> DIRTY_SUBMODULE_MODIFIED when a submodule has local modifications in
> its working tree), so in that sense, it probably makes sense to show
> the more detailed picture of the working tree like your patch does.
> After all, choosing to use --submodule=diff is a strong sign that
> the user who says top-level "git diff [<tree>]" wants to see the
> details of her work-in-progress.
>
> Do you need to handle "git diff -R [<tree>]" at the top-level a bit
> differently, by the way?  If this function gets the full diff_options
> that is driving the top-level diff, the DIFF_OPT_REVERSE_DIFF bit
> would tell you that.
>

Probably. Unfortunately what I really need is to be able to convert
(almost) all diff options from the diff_options structure back into
command line flags. This is the primary reason I would prefer to not
use a sub-command, but I'm not really sure what's the best way to
actually DO that in a safe way.

The sub command brings nice separation between the superproject and
its submodules... but it has problem because we can't use C calls
directly, so I can't pass the options along myself.

Thoughts on that? Or should we just limit ourselves to only some
options get propagated to the submodule?

Thanks,
Jake

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff
  2016-08-16 21:20         ` Jacob Keller
@ 2016-08-16 21:31           ` Junio C Hamano
  0 siblings, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2016-08-16 21:31 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Jacob Keller, Git mailing list, Stefan Beller, Jeff King, Johannes Sixt

Jacob Keller <jacob.keller@gmail.com> writes:

> Thoughts on that? Or should we just limit ourselves to only some
> options get propagated to the submodule?

I think you have to be selective either way.  You do not want
pathspecs used to limit the top-level paths propagated down when you
run a diff or a log in the submodule, for example, and that does not
change even if you start using the code in diff-lib.c (after adding
the submodule odb as an alternate).


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2016-08-16 21:31 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-15 23:06 [PATCH v6 0/3] Add support for displaying submodules as a proper diff Jacob Keller
2016-08-15 23:07 ` [PATCH v6 1/3] diff.c: remove output_prefix_length field Jacob Keller
2016-08-16 18:03   ` Junio C Hamano
2016-08-16 18:08     ` Jacob Keller
2016-08-15 23:07 ` [PATCH v6 2/3] graph: add support for --line-prefix on all graph-aware output Jacob Keller
2016-08-16 18:22   ` Junio C Hamano
2016-08-16 20:19     ` Jacob Keller
2016-08-15 23:07 ` [PATCH v6 3/3] diff: add SUBMODULE_DIFF format to display submodule diff Jacob Keller
2016-08-16 18:48   ` Junio C Hamano
2016-08-16 20:25     ` Jacob Keller
2016-08-16 21:14       ` Junio C Hamano
2016-08-16 21:20         ` Jacob Keller
2016-08-16 21:31           ` Junio C Hamano

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).