{"thread":{"id":"3994","subject":"[PATCH] built-in diff.","startedAt":"2006-04-29T07:19:28Z","lastAt":"2006-04-29T16:59:53Z","messageCount":5,"participants":["Junio C Hamano","Johannes Schindelin","Linus Torvalds"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"19244","messageId":"7virosalxb.fsf@assigned-by-dhcp.cox.net","threadId":"3994","inReplyTo":null,"subject":"[PATCH] built-in diff.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-29T07:19:28Z","receivedAt":"2006-04-29T07:19:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This starts to replace the shell script version of \"git diff\".\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\n * Should apply on top of \"next\" with trivial conflict fixups\n   (hint: \"git am -3\").\n\n   It is not fully compatible with the shell script version yet,\n   so it is called \"git diffn\" for now.  It does not do the\n   fancy option defaulting, other than \"git diffn --cached\" to\n   mean \"git diffn --cached HEAD\", and I do not think I got the\n   defaulting to --cc right either.  Also it does not default to\n   do -M either.  If somebody cares deeply, patches are welcome.\n   When its option defaulting gets compatible with the shell\n   script version, or when its new default options becomes\n   accepted, we will rename it to \"git diff\" and merge it in.\n\n   To ask for the diff-raw output, you can give --raw option.\n\n Makefile       |    2 \n builtin-diff.c |  332 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n builtin.h      |    1 \n git.c          |    1 \n 4 files changed, 335 insertions(+), 1 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 8ce27a6..277c1ac 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -214,7 +214,7 @@ LIB_OBJS = \\\n \t$(DIFF_OBJS)\n \n BUILTIN_OBJS = \\\n-\tbuiltin-log.o builtin-help.o\n+\tbuiltin-log.o builtin-help.o builtin-diff.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n LIBS = $(GITLIBS) -lz\ndiff --git a/builtin-diff.c b/builtin-diff.c\nnew file mode 100644\nindex 0000000..1968212\n--- /dev/null\n+++ b/builtin-diff.c\n@@ -0,0 +1,332 @@\n+/*\n+ * Builtin \"git diff\"\n+ *\n+ * Copyright (c) 2006 Junio C Hamano\n+ */\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n+#include \"revision.h\"\n+#include \"log-tree.h\"\n+#include \"builtin.h\"\n+\n+/* NEEDSWORK: struct object has place for name but we _do_\n+ * know mode when we extracted the blob out of a tree, which\n+ * we currently lose.\n+ */\n+struct blobinfo {\n+\tunsigned char sha1[20];\n+\tconst char *name;\n+};\n+\n+static const char builtin_diff_usage[] =\n+\"diff <options> <rev>{0,2} -- <path>*\";\n+\n+static int builtin_diff_files(struct rev_info *revs,\n+\t\t\t      int argc, const char **argv)\n+{\n+\tint silent = 0;\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--base\"))\n+\t\t\trevs->max_count = 1;\n+\t\telse if (!strcmp(arg, \"--ours\"))\n+\t\t\trevs->max_count = 2;\n+\t\telse if (!strcmp(arg, \"--theirs\"))\n+\t\t\trevs->max_count = 3;\n+\t\telse if (!strcmp(arg, \"-q\"))\n+\t\t\tsilent = 1;\n+\t\telse if (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\t/*\n+\t * Make sure there are NO revision (i.e. pending object) parameter,\n+\t * rev.max_count is reasonable (0 <= n <= 3),\n+\t * there is no other revision filtering parameters.\n+\t */\n+\tif (revs->pending_objects ||\n+\t    revs->min_age != -1 ||\n+\t    revs->max_age != -1)\n+\t\tusage(builtin_diff_usage);\n+\t/*\n+\t * Backward compatibility wart - \"diff-files -s\" used to\n+\t * defeat the common diff option \"-s\" which asked for\n+\t * DIFF_FORMAT_NO_OUTPUT.\n+\t */\n+\tif (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)\n+\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\treturn run_diff_files(revs, silent);\n+}\n+\n+static void stuff_change(struct diff_options *opt,\n+\t\t\t unsigned old_mode, unsigned new_mode,\n+\t\t\t const unsigned char *old_sha1,\n+\t\t\t const unsigned char *new_sha1,\n+\t\t\t const char *old_name,\n+\t\t\t const char *new_name)\n+{\n+\tstruct diff_filespec *one, *two;\n+\n+\tif (memcmp(null_sha1, old_sha1, 20) &&\n+\t    memcmp(null_sha1, new_sha1, 20) &&\n+\t    !memcmp(old_sha1, new_sha1, 20))\n+\t\treturn;\n+\n+\tif (opt->reverse_diff) {\n+\t\tunsigned tmp;\n+\t\tconst\n+\t\t\tconst unsigned char *tmp_u;\n+\t\tconst char *tmp_c;\n+\t\ttmp = old_mode; old_mode = new_mode; new_mode = tmp;\n+\t\ttmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;\n+\t\ttmp_c = old_name; old_name = new_name; new_name = tmp_c;\n+\t}\n+\tone = alloc_filespec(old_name);\n+\ttwo = alloc_filespec(new_name);\n+\tfill_filespec(one, old_sha1, old_mode);\n+\tfill_filespec(two, new_sha1, new_mode);\n+\n+\t/* NEEDSWORK: shouldn't this part of diffopt??? */\n+\tdiff_queue(&diff_queued_diff, one, two);\n+}\n+\n+static int builtin_diff_b_f(struct rev_info *revs,\n+\t\t\t    int argc, const char **argv,\n+\t\t\t    struct blobinfo *blob,\n+\t\t\t    const char *path)\n+{\n+\t/* Blob vs file in the working tree*/\n+\tstruct stat st;\n+\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\tif (lstat(path, &st))\n+\t\tdie(\"'%s': %s\", path, strerror(errno));\n+\tif (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))\n+\t\tdie(\"'%s': not a regular file or symlink\", path);\n+\tstuff_change(&revs->diffopt,\n+\t\t     canon_mode(st.st_mode), canon_mode(st.st_mode),\n+\t\t     blob[0].sha1, null_sha1,\n+\t\t     blob[0].name, path);\n+\tdiffcore_std(&revs->diffopt);\n+\tdiff_flush(&revs->diffopt);\n+\treturn 0;\n+}\n+\n+static int builtin_diff_blobs(struct rev_info *revs,\n+\t\t\t      int argc, const char **argv,\n+\t\t\t      struct blobinfo *blob)\n+{\n+\t/* Blobs */\n+\tunsigned mode = canon_mode(S_IFREG | 0644);\n+\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\tstuff_change(&revs->diffopt,\n+\t\t     mode, mode,\n+\t\t     blob[0].sha1, blob[1].sha1,\n+\t\t     blob[1].name, blob[1].name);\n+\tdiffcore_std(&revs->diffopt);\n+\tdiff_flush(&revs->diffopt);\n+\treturn 0;\n+}\n+\n+static int builtin_diff_index(struct rev_info *revs,\n+\t\t\t      int argc, const char **argv)\n+{\n+\tint cached = 0;\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--cached\"))\n+\t\t\tcached = 1;\n+\t\telse if (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\t/*\n+\t * Make sure there is one revision (i.e. pending object),\n+\t * and there is no revision filtering parameters.\n+\t */\n+\tif (!revs->pending_objects || revs->pending_objects->next ||\n+\t    revs->max_count != -1 || revs->min_age != -1 ||\n+\t    revs->max_age != -1)\n+\t\tusage(builtin_diff_usage);\n+\treturn run_diff_index(revs, cached);\n+}\n+\n+static int builtin_diff_tree(struct rev_info *revs,\n+\t\t\t     int argc, const char **argv,\n+\t\t\t     struct object_list *ent)\n+{\n+\t/* We saw two trees, ent[0] and ent[1].\n+\t * unless ent[0] is unintesting, they are swapped\n+\t */\n+\tconst unsigned char *(sha1[2]);\n+\tint swap = 1;\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\tif (ent[0].item->flags & UNINTERESTING)\n+\t\tswap = 0;\n+\tsha1[swap] = ent[0].item->sha1;\n+\tsha1[1-swap] = ent[1].item->sha1;\n+\tdiff_tree_sha1(sha1[0], sha1[1], \"\", &revs->diffopt);\n+\tlog_tree_diff_flush(revs);\n+\treturn 0;\n+}\n+\n+static void add_head(struct rev_info *revs)\n+{\n+\tunsigned char sha1[20];\n+\tstruct object *obj;\n+\tif (get_sha1(\"HEAD\", sha1))\n+\t\treturn;\n+\tobj = parse_object(sha1);\n+\tif (!obj)\n+\t\treturn;\n+\tadd_object(obj, &revs->pending_objects, NULL, \"HEAD\");\n+}\n+\n+int cmd_diff(int argc, const char **argv, char **envp)\n+{\n+\tstruct rev_info rev;\n+\tstruct object_list *list, ent[2];\n+\tint ents = 0, blobs = 0, paths = 0;\n+\tconst char *path = NULL;\n+\tstruct blobinfo blob[2];\n+\n+\t/*\n+\t * We could get N tree-ish in the rev.pending_objects list.\n+\t * Also there could be M blobs there, and P pathspecs.\n+\t *\n+\t * N=0, M=0:\n+\t *\tcache vs files (diff-files)\n+\t * N=0, M=2:\n+\t *      compare two random blobs.  P must be zero.\n+\t * N=0, M=1, P=1:\n+\t *\tcompare a blob with a working tree file.\n+\t *\n+\t * N=1, M=0:\n+\t *      tree vs cache (diff-index --cached)\n+\t *\n+\t * N=2, M=0:\n+\t *      tree vs tree (diff-tree)\n+\t *\n+\t * Other cases are errors.\n+\t */\n+\t\n+\tgit_config(git_diff_config);\n+\tinit_revisions(&rev);\n+\trev.diffopt.output_format = DIFF_FORMAT_PATCH;\n+\n+\targc = setup_revisions(argc, argv, &rev, NULL);\n+\t/* Do we have --cached and not have a pending object, then\n+\t * default to HEAD by hand.  Eek.\n+\t */\n+\tif (!rev.pending_objects) {\n+\t\tint i;\n+\t\tfor (i = 1; i < argc; i++) {\n+\t\t\tconst char *arg = argv[i];\n+\t\t\tif (!strcmp(arg, \"--\"))\n+\t\t\t\tbreak;\n+\t\t\telse if (!strcmp(arg, \"--cached\")) {\n+\t\t\t\tadd_head(&rev);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tfor (list = rev.pending_objects; list; list = list->next) {\n+\t\tstruct object *obj = list->item;\n+\t\tconst char *name = list->name;\n+\t\tint flags = (obj->flags & UNINTERESTING);\n+\t\tif (!obj->parsed)\n+\t\t\tobj = parse_object(obj->sha1);\n+\t\tobj = deref_tag(obj, NULL, 0);\n+\t\tif (!obj)\n+\t\t\tdie(\"invalid object '%s' given.\", name);\n+\t\tif (!strcmp(obj->type, commit_type))\n+\t\t\tobj = &((struct commit *)obj)->tree->object;\n+\t\tif (!strcmp(obj->type, tree_type)) {\n+\t\t\tif (2 <= ents)\n+\t\t\t\tdie(\"more than two trees given: '%s'\", name);\n+\t\t\tobj->flags |= flags;\n+\t\t\tent[ents].item = obj;\n+\t\t\tent[ents].name = name;\n+\t\t\tents++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(obj->type, blob_type)) {\n+\t\t\tif (2 <= blobs)\n+\t\t\t\tdie(\"more than two blobs given: '%s'\", name);\n+\t\t\tmemcpy(blob[blobs].sha1, obj->sha1, 20);\n+\t\t\tblob[blobs].name = name;\n+\t\t\tblobs++;\n+\t\t\tcontinue;\n+\t\t\t\n+\t\t}\n+\t\tdie(\"unhandled object '%s' given.\", name);\n+\t}\n+\tif (rev.prune_data) {\n+\t\tconst char **pathspec = rev.prune_data;\n+\t\twhile (*pathspec) {\n+\t\t\tif (!path)\n+\t\t\t\tpath = *pathspec;\n+\t\t\tpaths++;\n+\t\t\tpathspec++;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now, do the arguments look reasonable?\n+\t */\n+\tif (!ents) {\n+\t\tswitch (blobs) {\n+\t\tcase 0:\n+\t\t\treturn builtin_diff_files(&rev, argc, argv);\n+\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tif (paths != 1)\n+\t\t\t\tusage(builtin_diff_usage);\n+\t\t\treturn builtin_diff_b_f(&rev, argc, argv, blob, path);\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\treturn builtin_diff_blobs(&rev, argc, argv, blob);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tusage(builtin_diff_usage);\n+\t\t}\n+\t}\n+\telse if (blobs)\n+\t\tusage(builtin_diff_usage);\n+\telse if (ents == 1)\n+\t\treturn builtin_diff_index(&rev, argc, argv);\n+\telse if (ents == 2)\n+\t\treturn builtin_diff_tree(&rev, argc, argv, ent);\n+\tusage(builtin_diff_usage);\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 47408a0..52ffa52 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -19,5 +19,6 @@ extern int cmd_version(int argc, const c\n extern int cmd_whatchanged(int argc, const char **argv, char **envp);\n extern int cmd_show(int argc, const char **argv, char **envp);\n extern int cmd_log(int argc, const char **argv, char **envp);\n+extern int cmd_diff(int argc, const char **argv, char **envp);\n \n #endif\ndiff --git a/git.c b/git.c\nindex 01b7e28..ff9b87a 100644\n--- a/git.c\n+++ b/git.c\n@@ -46,6 +46,7 @@ static void handle_internal_command(int \n \t\t{ \"log\", cmd_log },\n \t\t{ \"whatchanged\", cmd_whatchanged },\n \t\t{ \"show\", cmd_show },\n+\t\t{ \"diffn\", cmd_diff },\n \t};\n \tint i;\n \n-- \n1.3.1.g1d19\n"},{"id":"19247","messageId":"7vodyk91c1.fsf@assigned-by-dhcp.cox.net","threadId":"3994","inReplyTo":"7virosalxb.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH] built-in diff: assorted updates.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-29T09:29:34Z","receivedAt":"2006-04-29T09:29:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"git diff(n)\" without --base, --ours, etc. defaults to --cc,\nwhich usually is the same as -p unless you are in the middle of\na conflicted merge, just like the shell script version.\n\n\"git diff(n) blobA blobB path\" complains and dies.\n\n\"git diff(n) tree0 tree1 tree2...treeN\" does combined diff that\nshows a merge of tree1..treeN to result in tree0.\n\nGiving \"-c\" option to any command that defaults to \"--cc\" turns\noff dense-combined flag.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\n * Comes on top of the previous \"built-in diff\" patch.\n\n   Now I think this is more or less what Linus envisioned when\n   he started converting the rev related option parsing to use\n   setup_revisions(), including the exotic combinations the\n   shell script \"git diff\" does not support:\n\n   git diff(n) HEAD~10:Makefile Makefile\t        ; blob vs any file\n   git diff(n) HEAD~10:Makefile HEAD~20:Makefile        ; two blobs\n   git diff(n) v1.0.0 v1.0.0^1 v1.0.0^2\t; combine 'em\n\n   The normal ones are also supported:\n\n   git diff(n) -- Documentation\t\t\t\t; index vs working\n   git diff(n) --cached \t\t\t\t; tree vs index\n   git diff(n) HEAD\t\t\t\t\t; tree vs working\n   git diff(n) HEAD~10 HEAD~8\t\t\t\t; two trees\n   git diff(n) HEAD:arch/i386 HEAD:arch/x86_64\t\t; two trees\n\n builtin-diff.c |   55 ++++++++++++++++++++++++++++++++++++++++++++++---------\n combine-diff.c |   47 ++++++++++++++++++++++++++++++-----------------\n diff.h         |    2 ++\n revision.c     |    1 +\n 4 files changed, 79 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin-diff.c b/builtin-diff.c\nindex 1968212..c543105 100644\n--- a/builtin-diff.c\n+++ b/builtin-diff.c\n@@ -47,13 +47,17 @@ static int builtin_diff_files(struct rev\n \t}\n \t/*\n \t * Make sure there are NO revision (i.e. pending object) parameter,\n-\t * rev.max_count is reasonable (0 <= n <= 3),\n-\t * there is no other revision filtering parameters.\n+\t * specified rev.max_count is reasonable (0 <= n <= 3), and\n+\t * there is no other revision filtering parameter.\n \t */\n \tif (revs->pending_objects ||\n \t    revs->min_age != -1 ||\n-\t    revs->max_age != -1)\n+\t    revs->max_age != -1 ||\n+\t    3 < revs->max_count)\n \t\tusage(builtin_diff_usage);\n+\tif (revs->max_count < 0 &&\n+\t    (revs->diffopt.output_format == DIFF_FORMAT_PATCH))\n+\t\trevs->combine_merges = revs->dense_combined_merges = 1;\n \t/*\n \t * Backward compatibility wart - \"diff-files -s\" used to\n \t * defeat the common diff option \"-s\" which asked for\n@@ -178,9 +182,6 @@ static int builtin_diff_tree(struct rev_\n \t\t\t     int argc, const char **argv,\n \t\t\t     struct object_list *ent)\n {\n-\t/* We saw two trees, ent[0] and ent[1].\n-\t * unless ent[0] is unintesting, they are swapped\n-\t */\n \tconst unsigned char *(sha1[2]);\n \tint swap = 1;\n \twhile (1 < argc) {\n@@ -191,6 +192,10 @@ static int builtin_diff_tree(struct rev_\n \t\t\tusage(builtin_diff_usage);\n \t\targv++; argc--;\n \t}\n+\n+\t/* We saw two trees, ent[0] and ent[1].\n+\t * unless ent[0] is unintesting, they are swapped\n+\t */\n \tif (ent[0].item->flags & UNINTERESTING)\n \t\tswap = 0;\n \tsha1[swap] = ent[0].item->sha1;\n@@ -200,6 +205,33 @@ static int builtin_diff_tree(struct rev_\n \treturn 0;\n }\n \n+static int builtin_diff_combined(struct rev_info *revs,\n+\t\t\t\t int argc, const char **argv,\n+\t\t\t\t struct object_list *ent,\n+\t\t\t\t int ents)\n+{\n+\tconst unsigned char (*parent)[20];\n+\tint i;\n+\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\tif (!strcmp(arg, \"--raw\"))\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_RAW;\n+\t\telse\n+\t\t\tusage(builtin_diff_usage);\n+\t\targv++; argc--;\n+\t}\n+\tif (!revs->dense_combined_merges && !revs->combine_merges)\n+\t\trevs->dense_combined_merges = revs->combine_merges = 1;\n+\tparent = xmalloc(ents * sizeof(*parent));\n+\t/* Again, the revs are all reverse */\n+\tfor (i = 0; i < ents; i++)\n+\t\tmemcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);\n+\tdiff_tree_combined(parent[0], parent + 1, ents - 1,\n+\t\t\t   revs->dense_combined_merges, revs);\n+\treturn 0;\n+}\n+\n static void add_head(struct rev_info *revs)\n {\n \tunsigned char sha1[20];\n@@ -215,7 +247,7 @@ static void add_head(struct rev_info *re\n int cmd_diff(int argc, const char **argv, char **envp)\n {\n \tstruct rev_info rev;\n-\tstruct object_list *list, ent[2];\n+\tstruct object_list *list, ent[100];\n \tint ents = 0, blobs = 0, paths = 0;\n \tconst char *path = NULL;\n \tstruct blobinfo blob[2];\n@@ -273,8 +305,9 @@ int cmd_diff(int argc, const char **argv\n \t\tif (!strcmp(obj->type, commit_type))\n \t\t\tobj = &((struct commit *)obj)->tree->object;\n \t\tif (!strcmp(obj->type, tree_type)) {\n-\t\t\tif (2 <= ents)\n-\t\t\t\tdie(\"more than two trees given: '%s'\", name);\n+\t\t\tif (ARRAY_SIZE(ent) <= ents)\n+\t\t\t\tdie(\"more than %d trees given: '%s'\",\n+\t\t\t\t    ARRAY_SIZE(ent), name);\n \t\t\tobj->flags |= flags;\n \t\t\tent[ents].item = obj;\n \t\t\tent[ents].name = name;\n@@ -316,6 +349,8 @@ int cmd_diff(int argc, const char **argv\n \t\t\treturn builtin_diff_b_f(&rev, argc, argv, blob, path);\n \t\t\tbreak;\n \t\tcase 2:\n+\t\t\tif (paths)\n+\t\t\t\tusage(builtin_diff_usage);\n \t\t\treturn builtin_diff_blobs(&rev, argc, argv, blob);\n \t\t\tbreak;\n \t\tdefault:\n@@ -328,5 +363,7 @@ int cmd_diff(int argc, const char **argv\n \t\treturn builtin_diff_index(&rev, argc, argv);\n \telse if (ents == 2)\n \t\treturn builtin_diff_tree(&rev, argc, argv, ent);\n+\telse\n+\t\treturn builtin_diff_combined(&rev, argc, argv, ent, ents);\n \tusage(builtin_diff_usage);\n }\ndiff --git a/combine-diff.c b/combine-diff.c\nindex ca36f5d..8a8fe38 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -831,15 +831,16 @@ void show_combined_diff(struct combine_d\n \t}\n }\n \n-void diff_tree_combined_merge(const unsigned char *sha1,\n-\t\t\t     int dense, struct rev_info *rev)\n+void diff_tree_combined(const unsigned char *sha1,\n+\t\t\tconst unsigned char parent[][20],\n+\t\t\tint num_parent,\n+\t\t\tint dense,\n+\t\t\tstruct rev_info *rev)\n {\n \tstruct diff_options *opt = &rev->diffopt;\n-\tstruct commit *commit = lookup_commit(sha1);\n \tstruct diff_options diffopts;\n-\tstruct commit_list *parents;\n \tstruct combine_diff_path *p, *paths = NULL;\n-\tint num_parent, i, num_paths;\n+\tint i, num_paths;\n \tint do_diffstat;\n \n \tdo_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||\n@@ -849,17 +850,8 @@ void diff_tree_combined_merge(const unsi\n \tdiffopts.with_stat = 0;\n \tdiffopts.recursive = 1;\n \n-\t/* count parents */\n-\tfor (parents = commit->parents, num_parent = 0;\n-\t     parents;\n-\t     parents = parents->next, num_parent++)\n-\t\t; /* nothing */\n-\n \t/* find set of paths that everybody touches */\n-\tfor (parents = commit->parents, i = 0;\n-\t     parents;\n-\t     parents = parents->next, i++) {\n-\t\tstruct commit *parent = parents->item;\n+\tfor (i = 0; i < num_parent; i++) {\n \t\t/* show stat against the first parent even\n \t\t * when doing combined diff.\n \t\t */\n@@ -867,8 +859,7 @@ void diff_tree_combined_merge(const unsi\n \t\t\tdiffopts.output_format = DIFF_FORMAT_DIFFSTAT;\n \t\telse\n \t\t\tdiffopts.output_format = DIFF_FORMAT_NO_OUTPUT;\n-\t\tdiff_tree_sha1(parent->object.sha1, commit->object.sha1, \"\",\n-\t\t\t       &diffopts);\n+\t\tdiff_tree_sha1(parent[i], sha1, \"\", &diffopts);\n \t\tdiffcore_std(&diffopts);\n \t\tpaths = intersect_paths(paths, i, num_parent);\n \n@@ -907,3 +898,25 @@ void diff_tree_combined_merge(const unsi\n \t\tfree(tmp);\n \t}\n }\n+\n+void diff_tree_combined_merge(const unsigned char *sha1,\n+\t\t\t     int dense, struct rev_info *rev)\n+{\n+\tint num_parent;\n+\tconst unsigned char (*parent)[20];\n+\tstruct commit *commit = lookup_commit(sha1);\n+\tstruct commit_list *parents;\n+\n+\t/* count parents */\n+\tfor (parents = commit->parents, num_parent = 0;\n+\t     parents;\n+\t     parents = parents->next, num_parent++)\n+\t\t; /* nothing */\n+\n+\tparent = xmalloc(num_parent * sizeof(*parent));\n+\tfor (parents = commit->parents, num_parent = 0;\n+\t     parents;\n+\t     parents = parents->next, num_parent++)\n+\t\tmemcpy(parent + num_parent, parents->item->object.sha1, 20);\n+\tdiff_tree_combined(sha1, parent, num_parent, dense, rev);\n+}\ndiff --git a/diff.h b/diff.h\nindex 7150b90..b3b2c4d 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -75,6 +75,8 @@ #define combine_diff_path_size(n, l) \\\n extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\t      int dense, struct rev_info *);\n \n+extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);\n+\n extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);\n \n extern void diff_addremove(struct diff_options *,\ndiff --git a/revision.c b/revision.c\nindex f2a9f25..4381d45 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -664,6 +664,7 @@ int setup_revisions(int argc, const char\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-c\")) {\n \t\t\t\trevs->diff = 1;\n+\t\t\t\trevs->dense_combined_merges = 0;\n \t\t\t\trevs->combine_merges = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n1.3.1.ga0c5\n"},{"id":"19248","messageId":"7vaca490ln.fsf@assigned-by-dhcp.cox.net","threadId":"3994","inReplyTo":"7virosalxb.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH/RFC] Extended SHA1 -- \"rev^#\" syntax to mean \"all parents\"","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-04-29T09:45:24Z","receivedAt":"2006-04-29T09:45:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A short-hand \"rev^#\" is understood to be \"all parents of the\nnamed commit\" with this patch.  So you can do\n\n\tgit show v1.0.0^#\n\nto view the parents of a merge commit,\n\n\tgitk ^v1.0.0^# v1.0.4\n\nto view the log between two revs (including the bottom one), and\n\n\tgit diff --cc v1.1.0 v1.0.0^#\n\nto inspect what got changed from the merge parents of v1.0.0 to v1.1.0.\n\nThis might be just my shiny new toy that is not very useful in\npractice.  I needed it to do the multi-tree diff on Len's\ninfamous 12-way Octopus; typing \"diff --cc funmerge funmerge^1\nfunmerge^2 funmerge^3 ...\" was too painful.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n\n---\n\n revision.c |   37 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 37 insertions(+), 0 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex f2a9f25..194f35b 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -477,6 +477,36 @@ static void handle_all(struct rev_info *\n \tfor_each_ref(handle_one_ref);\n }\n \n+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)\n+{\n+\tunsigned char sha1[20];\n+\tstruct object *it;\n+\tstruct commit *commit;\n+\tstruct commit_list *parents;\n+\n+\tif (*arg == '^') {\n+\t\tflags ^= UNINTERESTING;\n+\t\targ++;\n+\t}\n+\tif (get_sha1(arg, sha1))\n+\t\treturn 0;\n+\twhile (1) {\n+\t\tit = get_reference(revs, arg, sha1, 0);\n+\t\tif (strcmp(it->type, tag_type))\n+\t\t\tbreak;\n+\t\tmemcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);\n+\t}\n+\tif (strcmp(it->type, commit_type))\n+\t\treturn 0;\n+\tcommit = (struct commit *)it;\n+\tfor (parents = commit->parents; parents; parents = parents->next) {\n+\t\tit = &parents->item->object;\n+\t\tit->flags |= flags;\n+\t\tadd_pending_object(revs, it, arg);\n+\t}\n+\treturn 1;\n+}\n+\n void init_revisions(struct rev_info *revs)\n {\n \tmemset(revs, 0, sizeof(*revs));\n@@ -746,6 +776,13 @@ int setup_revisions(int argc, const char\n \t\t\t}\n \t\t\t*dotdot = '.';\n \t\t}\n+\t\tdotdot = strstr(arg, \"^#\");\n+\t\tif (dotdot && !dotdot[2]) {\n+\t\t\t*dotdot = 0;\n+\t\t\tif (add_parents_only(revs, arg, flags))\n+\t\t\t\tcontinue;\n+\t\t\t*dotdot = '^';\n+\t\t}\n \t\tlocal_flags = 0;\n \t\tif (*arg == '^') {\n \t\t\tlocal_flags = UNINTERESTING;\n-- \n1.3.1.ga0c5\n"},{"id":"19254","messageId":"Pine.LNX.4.63.0604291737390.25093@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"3994","inReplyTo":"7vaca490ln.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH/RFC] Extended SHA1 -- \"rev^#\" syntax to mean \"all parents\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-04-29T15:38:56Z","receivedAt":"2006-04-29T15:38:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 29 Apr 2006, Junio C Hamano wrote:\n\n> A short-hand \"rev^#\" is understood to be \"all parents of the\n> named commit\" with this patch.\n\nJust my 2/100: Why not \"rev^*\"? I could remember that more easily.\n\nCiao,\nDscho\n"},{"id":"19257","messageId":"Pine.LNX.4.64.0604290954370.3701@g5.osdl.org","threadId":"3994","inReplyTo":"Pine.LNX.4.63.0604291737390.25093@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: [PATCH/RFC] Extended SHA1 -- \"rev^#\" syntax to mean \"all parents\"","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-04-29T16:59:53Z","receivedAt":"2006-04-29T16:59:53Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Sat, 29 Apr 2006, Johannes Schindelin wrote:\n> \n> > A short-hand \"rev^#\" is understood to be \"all parents of the\n> > named commit\" with this patch.\n> \n> Just my 2/100: Why not \"rev^*\"? I could remember that more easily.\n\nYeah, that (or ^@ - to match shell \"$1\" \"$2\" .. \"$@\") was my reaction too.\n\nThe \"rev^#\" thing should to my mind return the _number_ of parents, the \nsame way \"$#\" does in shell. I actually pronounce that '#' character \nmentally as \"number\", but maybe that's just because I'm totally mentally \ndamaged by shell programming, and everybody else probably calls it \"hash\" \n(and some people apparently call it \"pound\", for some really sick reason).\n\nSo \"rev^#\" literally reads as \"revision parent number\" to me. Useful? \nMaybe. Maybe not.\n\n\t\tLinus\n"}]}