[PATCH] Make 'diff C^!' show the same diff as 'show C'

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Ideally, we'd like 'git diff C^!' to show the same diff that 'git show C'
does (with log.showroot enabled).  This gives easy access to a readable
diff for the commit, irrespective of how many parents it has and without
any trickery to remove the commit message from the git-show output.

cmd_diff relied on telling the various diff invocations apart from
only the number of revisions parsed by setup_revision() (with a twist
for A...B).  In the case of C^! this failed on two counts:

* If C has no parents, setup_revision() turns C^! into simply C.  This
  meant that 'git diff C^!' compared the current worktree to C, which
  is certainly not what the user asked for.

* Otherwise setup_revision puts C itself last, i.e., the rev.pending
  are ^C^1 ... ^C^N C.  So the first revision is uninteresting and in
  the case of exactly two parents, the symmetric difference revspec
  (diff A...B) case fired, and compared C only to C^1 (instead of
  showing a combined diff).

Detect the presence of A...B or C^! style arguments before running
setup_revisions(), so that we know in which case we are in.  We can
then dispatch to the right case without peeking at UNINTERESTING
flags.

There's still some complication in builtin_diff_combined() because
0fe7c1d (built-in diff: assorted updates., 2006-04-29) advertises that
'git diff T0 T1 ... Tn' does a combined diff of arbitrary trees where T0
is the merge result, so we have to handle both this case and C^!.

Note that UNINTERESTING is not a good criterion at all, as it is
tacked onto *trees*; if any of the involved revisions share the same
trees, the flags will overwrite each other.

Thanks to Abhijit "crab" Menon-Sen for noticing that 'diff C^!' didn't
work as expected on root commits, and Björn "doener" Steinbrink for
helpful discussions.
---

Error checking is still iffy, but I'm not sure that can be fixed
without throwing out the whole "argument parsing through
setup_revisions" code and handrolling it.


 Documentation/git-diff.txt  |   10 +++++++-
 builtin-diff.c              |   54 +++++++++++++++++++++++++++++++++++-------
 t/t4013-diff-various.sh     |    3 ++
 t/t4013/diff.diff_initial^! |   28 ++++++++++++++++++++++
 t/t4013/diff.diff_master^!  |   29 +++++++++++++++++++++++
 t/t4013/diff.diff_side^!    |   32 +++++++++++++++++++++++++
 6 files changed, 146 insertions(+), 10 deletions(-)
 create mode 100644 t/t4013/diff.diff_initial^!
 create mode 100644 t/t4013/diff.diff_master^!
 create mode 100644 t/t4013/diff.diff_side^!

diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index 0ac7112..5d0f2a6 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -62,9 +62,17 @@ forced by --no-index.
 	"git diff $(git-merge-base A B) B".  You can omit any one
 	of <commit>, which has the same effect as using HEAD instead.
 
+'git diff' [--options] <commit>^{caret}! [--] [<path>...]::
+
+	This shows the changes that <commit> made relative to its
+	parents.  For an ordinary commit it is the same as `git diff
+	<commit>{caret} <commit>`.  For a root commit it shows a
+	creation patch and for a merge commit it shows a combined
+	diff.
+
 Just in case if you are doing something exotic, it should be
 noted that all of the <commit> in the above description, except
-for the last two forms that use ".." notations, can be any
+for the two forms that use ".." notations, can be any
 <tree-ish>.
 
 For a more complete list of ways to spell <commit>, see
diff --git a/builtin-diff.c b/builtin-diff.c
index ffcdd05..285bf29 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -163,10 +163,17 @@ static int builtin_diff_tree(struct rev_info *revs,
 	return 0;
 }
 
+enum diff_mode {
+	DIFF_MODE_NORMAL,
+	DIFF_MODE_SYMMETRIC,
+	DIFF_MODE_SHOW
+};
+
 static int builtin_diff_combined(struct rev_info *revs,
 				 int argc, const char **argv,
 				 struct object_array_entry *ent,
-				 int ents)
+				 int ents,
+				 enum diff_mode mode)
 {
 	const unsigned char (*parent)[20];
 	int i;
@@ -177,8 +184,18 @@ static int builtin_diff_combined(struct rev_info *revs,
 	if (!revs->dense_combined_merges && !revs->combine_merges)
 		revs->dense_combined_merges = revs->combine_merges = 1;
 	parent = xmalloc(ents * sizeof(*parent));
-	for (i = 0; i < ents; i++)
-		hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
+
+	if (mode == DIFF_MODE_SHOW) {
+		/* diff C^!, we exploit knowledge that C is last */
+		for (i = 1; i < ents; i++)
+			hashcpy((unsigned char *)(parent + i),
+				ent[i-1].item->sha1);
+		hashcpy((unsigned char *)(parent),
+			ent[ents-1].item->sha1);
+	} else {
+		for (i = 0; i < ents; i++)
+			hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
+	}
 	diff_tree_combined(parent[0], parent + 1, ents - 1,
 			   revs->dense_combined_merges, revs);
 	return 0;
@@ -254,6 +271,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	struct blobinfo blob[2];
 	int nongit;
 	int result = 0;
+	enum diff_mode mode = DIFF_MODE_NORMAL;
 
 	/*
 	 * We could get N tree-ish in the rev.pending_objects list.
@@ -292,6 +310,17 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	/* Otherwise, we are doing the usual "git" diff */
 	rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
 
+	for (i = 1; i < argc; i++) {
+		if (prefixcmp(argv[i], "--")) {
+			if (strstr(argv[i], "..."))
+				mode = DIFF_MODE_SYMMETRIC;
+			else if (strstr(argv[i], "^!"))
+				mode = DIFF_MODE_SHOW;
+		} else if (!strcmp(argv[i], "--")) {
+			break;
+		}
+	}
+
 	/* Default to let external and textconv be used */
 	DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
 	DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
@@ -403,11 +432,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 	}
 	else if (blobs)
 		usage(builtin_diff_usage);
-	else if (ents == 1)
-		result = builtin_diff_index(&rev, argc, argv);
-	else if (ents == 2)
-		result = builtin_diff_tree(&rev, argc, argv, ent);
-	else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
+	else if (mode == DIFF_MODE_SYMMETRIC) {
 		/* diff A...B where there is one sane merge base between
 		 * A and B.  We have ent[0] == merge-base, ent[1] == A,
 		 * and ent[2] == B.  Show diff between the base and B.
@@ -415,9 +440,20 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 		ent[1] = ent[2];
 		result = builtin_diff_tree(&rev, argc, argv, ent);
 	}
+	else if (ents == 1 && mode == DIFF_MODE_SHOW) {
+		/* diff R^! where R is a root commit: creation patch */
+		diff_tree_sha1((const unsigned char *) EMPTY_TREE_SHA1_BIN,
+			       ent[0].item->sha1, "", &rev.diffopt);
+		log_tree_diff_flush(&rev);
+		result = 0;
+	}
+	else if (ents == 1)
+		result = builtin_diff_index(&rev, argc, argv);
+	else if (ents == 2)
+		result = builtin_diff_tree(&rev, argc, argv, ent);
 	else
 		result = builtin_diff_combined(&rev, argc, argv,
-					     ent, ents);
+					       ent, ents, mode);
 	result = diff_result_code(&rev.diffopt, result);
 	if (1 < rev.diffopt.skip_stat_unmatch)
 		refresh_index_quietly();
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 8b33321..2ce7204 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -273,6 +273,9 @@ diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
 diff master master^ side
 diff --dirstat master~1 master~2
+diff initial^!
+diff side^!
+diff master^!
 EOF
 
 test_done
diff --git a/t/t4013/diff.diff_initial^! b/t/t4013/diff.diff_initial^!
new file mode 100644
index 0000000..22f6bb7
--- /dev/null
+++ b/t/t4013/diff.diff_initial^!
@@ -0,0 +1,28 @@
+$ git diff initial^!
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff_master^! b/t/t4013/diff.diff_master^!
new file mode 100644
index 0000000..ca2eaa1
--- /dev/null
+++ b/t/t4013/diff.diff_master^!
@@ -0,0 +1,29 @@
+$ git diff master^!
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff_side^! b/t/t4013/diff.diff_side^!
new file mode 100644
index 0000000..6d4378a
--- /dev/null
+++ b/t/t4013/diff.diff_side^!
@@ -0,0 +1,32 @@
+$ git diff side^!
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
-- 
1.6.4.363.g2183a

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]