Merge lt/revlist,jc/diff,jc/revparse,jc/abbrev
authorJunio C Hamano <junkio@cox.net>
Sat, 28 Jan 2006 08:16:09 +0000 (00:16 -0800)
committerJunio C Hamano <junkio@cox.net>
Sat, 28 Jan 2006 08:16:09 +0000 (00:16 -0800)
Documentation/git-rev-list.txt
Makefile
combine-diff.c [new file with mode: 0644]
diff-files.c
diff-tree.c
diff.h
rev-list.c
rev-parse.c

index f9146f1..1c6146c 100644 (file)
@@ -14,6 +14,7 @@ SYNOPSIS
             [ \--min-age=timestamp ]
             [ \--sparse ]
             [ \--no-merges ]
+            [ \--remove-empty ]
             [ \--all ]
             [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
             [ \--parents ]
@@ -80,6 +81,9 @@ OPTIONS
        (still subject to count and age limitation), but apply
        merge simplification nevertheless.
 
+--remove-empty::
+       Stop when a given path disappears from the tree.
+
 --all::
        Pretend as if all the refs in `$GIT_DIR/refs/` are
        listed on the command line as <commit>.
index 6910448..2aa2385 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -182,7 +182,7 @@ LIB_H = \
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o tree-diff.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o
 
 LIB_OBJS = \
        blob.o commit.o connect.o count-delta.o csum-file.o \
diff --git a/combine-diff.c b/combine-diff.c
new file mode 100644 (file)
index 0000000..243f967
--- /dev/null
@@ -0,0 +1,688 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "quote.h"
+
+static int uninteresting(struct diff_filepair *p)
+{
+       if (diff_unmodified_pair(p))
+               return 1;
+       if (!S_ISREG(p->one->mode) || !S_ISREG(p->two->mode))
+               return 1;
+       return 0;
+}
+
+static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct combine_diff_path *p;
+       int i;
+
+       if (!n) {
+               struct combine_diff_path *list = NULL, **tail = &list;
+               for (i = 0; i < q->nr; i++) {
+                       int len;
+                       const char *path;
+                       if (uninteresting(q->queue[i]))
+                               continue;
+                       path = q->queue[i]->two->path;
+                       len = strlen(path);
+
+                       p = xmalloc(sizeof(*p) + len + 1 + num_parent * 20);
+                       p->path = (char*) &(p->parent_sha1[num_parent][0]);
+                       memcpy(p->path, path, len);
+                       p->path[len] = 0;
+                       p->len = len;
+                       p->next = NULL;
+                       memcpy(p->sha1, q->queue[i]->two->sha1, 20);
+                       memcpy(p->parent_sha1[n], q->queue[i]->one->sha1, 20);
+                       *tail = p;
+                       tail = &p->next;
+               }
+               return list;
+       }
+
+       for (p = curr; p; p = p->next) {
+               int found = 0;
+               if (!p->len)
+                       continue;
+               for (i = 0; i < q->nr; i++) {
+                       const char *path;
+                       int len;
+
+                       if (uninteresting(q->queue[i]))
+                               continue;
+                       path = q->queue[i]->two->path;
+                       len = strlen(path);
+                       if (len == p->len && !memcmp(path, p->path, len)) {
+                               found = 1;
+                               memcpy(p->parent_sha1[n],
+                                      q->queue[i]->one->sha1, 20);
+                               break;
+                       }
+               }
+               if (!found)
+                       p->len = 0;
+       }
+       return curr;
+}
+
+struct lline {
+       struct lline *next;
+       int len;
+       unsigned long parent_map;
+       char line[FLEX_ARRAY];
+};
+
+struct sline {
+       struct lline *lost_head, **lost_tail;
+       char *bol;
+       int len;
+       unsigned long flag;
+};
+
+static char *grab_blob(const unsigned char *sha1, unsigned long *size)
+{
+       char *blob;
+       char type[20];
+       if (!memcmp(sha1, null_sha1, 20)) {
+               /* deleted blob */
+               *size = 0;
+               return xcalloc(1, 1);
+       }
+       blob = read_sha1_file(sha1, type, size);
+       if (strcmp(type, "blob"))
+               die("object '%s' is not a blob!", sha1_to_hex(sha1));
+       return blob;
+}
+
+#define TMPPATHLEN 50
+#define MAXLINELEN 10240
+
+static void write_to_temp_file(char *tmpfile, void *blob, unsigned long size)
+{
+       int fd = git_mkstemp(tmpfile, TMPPATHLEN, ".diff_XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write(fd, blob, size) != size)
+               die("unable to write temp-file");
+       close(fd);
+}
+
+static void write_temp_blob(char *tmpfile, const unsigned char *sha1)
+{
+       unsigned long size;
+       void *blob;
+       blob = grab_blob(sha1, &size);
+       write_to_temp_file(tmpfile, blob, size);
+       free(blob);
+}
+
+static int parse_num(char **cp_p, unsigned int *num_p)
+{
+       char *cp = *cp_p;
+       unsigned int num = 0;
+       int read_some;
+
+       while ('0' <= *cp && *cp <= '9')
+               num = num * 10 + *cp++ - '0';
+       if (!(read_some = cp - *cp_p))
+               return -1;
+       *cp_p = cp;
+       *num_p = num;
+       return 0;
+}
+
+static int parse_hunk_header(char *line, int len,
+                            unsigned int *ob, unsigned int *on,
+                            unsigned int *nb, unsigned int *nn)
+{
+       char *cp;
+       cp = line + 4;
+       if (parse_num(&cp, ob)) {
+       bad_line:
+               return error("malformed diff output: %s", line);
+       }
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, on))
+                       goto bad_line;
+       }
+       else
+               *on = 1;
+       if (*cp++ != ' ' || *cp++ != '+')
+               goto bad_line;
+       if (parse_num(&cp, nb))
+               goto bad_line;
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, nn))
+                       goto bad_line;
+       }
+       else
+               *nn = 1;
+       return -!!memcmp(cp, " @@", 3);
+}
+
+static void append_lost(struct sline *sline, int n, const char *line)
+{
+       struct lline *lline;
+       int len = strlen(line);
+       unsigned long this_mask = (1UL<<n);
+       if (line[len-1] == '\n')
+               len--;
+
+       /* Check to see if we can squash things */
+       if (sline->lost_head) {
+               struct lline *last_one = NULL;
+               /* We cannot squash it with earlier one */
+               for (lline = sline->lost_head;
+                    lline;
+                    lline = lline->next)
+                       if (lline->parent_map & this_mask)
+                               last_one = lline;
+               lline = last_one ? last_one->next : sline->lost_head;
+               while (lline) {
+                       if (lline->len == len &&
+                           !memcmp(lline->line, line, len)) {
+                               lline->parent_map |= this_mask;
+                               return;
+                       }
+                       lline = lline->next;
+               }
+       }
+
+       lline = xmalloc(sizeof(*lline) + len + 1);
+       lline->len = len;
+       lline->next = NULL;
+       lline->parent_map = this_mask;
+       memcpy(lline->line, line, len);
+       lline->line[len] = 0;
+       *sline->lost_tail = lline;
+       sline->lost_tail = &lline->next;
+}
+
+static void combine_diff(const unsigned char *parent, const char *ourtmp,
+                        struct sline *sline, int cnt, int n)
+{
+       FILE *in;
+       char parent_tmp[TMPPATHLEN];
+       char cmd[TMPPATHLEN * 2 + 1024];
+       char line[MAXLINELEN];
+       unsigned int lno, ob, on, nb, nn;
+       unsigned long pmask = ~(1UL << n);
+       struct sline *lost_bucket = NULL;
+
+       write_temp_blob(parent_tmp, parent);
+       sprintf(cmd, "diff --unified=0 -La/x -Lb/x '%s' '%s'",
+               parent_tmp, ourtmp);
+       in = popen(cmd, "r");
+       if (!in)
+               return;
+
+       lno = 1;
+       while (fgets(line, sizeof(line), in) != NULL) {
+               int len = strlen(line);
+               if (5 < len && !memcmp("@@ -", line, 4)) {
+                       if (parse_hunk_header(line, len,
+                                             &ob, &on, &nb, &nn))
+                               break;
+                       lno = nb;
+                       if (!nb) {
+                               /* @@ -1,2 +0,0 @@ to remove the
+                                * first two lines...
+                                */
+                               nb = 1;
+                       }
+                       lost_bucket = &sline[nb-1]; /* sline is 0 based */
+                       continue;
+               }
+               if (!lost_bucket)
+                       continue;
+               switch (line[0]) {
+               case '-':
+                       append_lost(lost_bucket, n, line+1);
+                       break;
+               case '+':
+                       sline[lno-1].flag &= pmask;
+                       lno++;
+                       break;
+               }
+       }
+       fclose(in);
+       unlink(parent_tmp);
+}
+
+static unsigned long context = 3;
+static char combine_marker = '@';
+
+static int interesting(struct sline *sline, unsigned long all_mask)
+{
+       return ((sline->flag & all_mask) != all_mask || sline->lost_head);
+}
+
+static unsigned long line_common_diff(struct sline *sline, unsigned long all_mask)
+{
+       /*
+        * Look at the line and see from which parents we have the
+        * same difference.
+        */
+
+       /* Lower bits of sline->flag records if the parent had this
+        * line, so XOR with all_mask gives us on-bits for parents we
+        * have differences with.
+        */
+       unsigned long common_adds = (sline->flag ^ all_mask) & all_mask;
+       unsigned long common_removes = all_mask;
+
+       /* If all the parents have this line, that also counts as
+        * having the same difference.
+        */
+       if (!common_adds)
+               common_adds = all_mask;
+
+       if (sline->lost_head) {
+               /* Lost head list records the lines removed from
+                * the parents, and parent_map records from which
+                * parent the line was removed.
+                */
+               struct lline *ll;
+               for (ll = sline->lost_head; ll; ll = ll->next) {
+                       common_removes &= ll->parent_map;
+               }
+       }
+       return common_adds & common_removes;
+}
+
+static unsigned long line_all_diff(struct sline *sline, unsigned long all_mask)
+{
+       /*
+        * Look at the line and see from which parents we have some difference.
+        */
+       unsigned long different = (sline->flag ^ all_mask) & all_mask;
+       if (sline->lost_head) {
+               /* Lost head list records the lines removed from
+                * the parents, and parent_map records from which
+                * parent the line was removed.
+                */
+               struct lline *ll;
+               for (ll = sline->lost_head; ll; ll = ll->next) {
+                       different |= ll->parent_map;
+               }
+       }
+       return different;
+}
+
+static unsigned long adjust_hunk_tail(struct sline *sline,
+                                     unsigned long all_mask,
+                                     unsigned long hunk_begin,
+                                     unsigned long i)
+{
+       /* i points at the first uninteresting line.
+        * If the last line of the hunk was interesting
+        * only because it has some deletion, then
+        * it is not all that interesting for the
+        * purpose of giving trailing context lines.
+        */
+       if ((hunk_begin + 1 <= i) &&
+           ((sline[i-1].flag & all_mask) == all_mask))
+               i--;
+       return i;
+}
+
+static unsigned long next_interesting(struct sline *sline,
+                                     unsigned long mark,
+                                     unsigned long i,
+                                     unsigned long cnt,
+                                     int uninteresting)
+{
+       while (i < cnt)
+               if (uninteresting ?
+                   !(sline[i].flag & mark) :
+                   (sline[i].flag & mark))
+                       return i;
+               else
+                       i++;
+       return cnt;
+}
+
+static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
+{
+       unsigned long all_mask = (1UL<<num_parent) - 1;
+       unsigned long mark = (1UL<<num_parent);
+       unsigned long i;
+
+       i = next_interesting(sline, mark, 0, cnt, 0);
+       if (cnt <= i)
+               return 0;
+
+       while (i < cnt) {
+               unsigned long j = (context < i) ? (i - context) : 0;
+               unsigned long k;
+               while (j < i)
+                       sline[j++].flag |= mark;
+
+       again:
+               j = next_interesting(sline, mark, i, cnt, 1);
+               if (cnt <= j)
+                       break; /* the rest are all interesting */
+
+               /* lookahead context lines */
+               k = next_interesting(sline, mark, j, cnt, 0);
+               j = adjust_hunk_tail(sline, all_mask, i, j);
+
+               if (k < j + context) {
+                       /* k is interesting and [j,k) are not, but
+                        * paint them interesting because the gap is small.
+                        */
+                       while (j < k)
+                               sline[j++].flag |= mark;
+                       i = k;
+                       goto again;
+               }
+
+               /* j is the first uninteresting line and there is
+                * no overlap beyond it within context lines.
+                */
+               i = k;
+               k = (j + context < cnt) ? j + context : cnt;
+               while (j < k)
+                       sline[j++].flag |= mark;
+       }
+       return 1;
+}
+
+static int make_hunks(struct sline *sline, unsigned long cnt,
+                      int num_parent, int dense)
+{
+       unsigned long all_mask = (1UL<<num_parent) - 1;
+       unsigned long mark = (1UL<<num_parent);
+       unsigned long i;
+       int has_interesting = 0;
+
+       for (i = 0; i < cnt; i++) {
+               if (interesting(&sline[i], all_mask))
+                       sline[i].flag |= mark;
+               else
+                       sline[i].flag &= ~mark;
+       }
+       if (!dense)
+               return give_context(sline, cnt, num_parent);
+
+       /* Look at each hunk, and if we have changes from only one
+        * parent, or the changes are the same from all but one
+        * parent, mark that uninteresting.
+        */
+       i = 0;
+       while (i < cnt) {
+               unsigned long j, hunk_begin, hunk_end;
+               int same, diff;
+               unsigned long same_diff, all_diff;
+               while (i < cnt && !(sline[i].flag & mark))
+                       i++;
+               if (cnt <= i)
+                       break; /* No more interesting hunks */
+               hunk_begin = i;
+               for (j = i + 1; j < cnt; j++) {
+                       if (!(sline[j].flag & mark)) {
+                               /* Look beyond the end to see if there
+                                * is an interesting line after this
+                                * hunk within context span.
+                                */
+                               unsigned long la; /* lookahead */
+                               int contin = 0;
+                               la = adjust_hunk_tail(sline, all_mask,
+                                                    hunk_begin, j);
+                               la = (la + context < cnt) ?
+                                       (la + context) : cnt;
+                               while (j <= --la) {
+                                       if (sline[la].flag & mark) {
+                                               contin = 1;
+                                               break;
+                                       }
+                               }
+                               if (!contin)
+                                       break;
+                               j = la;
+                       }
+               }
+               hunk_end = j;
+
+               /* [i..hunk_end) are interesting.  Now does it have
+                * the same change with all but one parent?
+                */
+               same_diff = all_mask;
+               all_diff = 0;
+               for (j = i; j < hunk_end; j++) {
+                       same_diff &= line_common_diff(sline + j, all_mask);
+                       all_diff |= line_all_diff(sline + j, all_mask);
+               }
+               diff = same = 0;
+               for (j = 0; j < num_parent; j++) {
+                       if (same_diff & (1UL<<j))
+                               same++;
+                       if (all_diff & (1UL<<j))
+                               diff++;
+               }
+               if ((num_parent - 1 <= same) || (diff == 1)) {
+                       /* This hunk is not that interesting after all */
+                       for (j = hunk_begin; j < hunk_end; j++)
+                               sline[j].flag &= ~mark;
+               }
+               i = hunk_end;
+       }
+
+       has_interesting = give_context(sline, cnt, num_parent);
+       return has_interesting;
+}
+
+static void dump_sline(struct sline *sline, int cnt, int num_parent)
+{
+       unsigned long mark = (1UL<<num_parent);
+       int i;
+       int lno = 0;
+
+       while (1) {
+               struct sline *sl = &sline[lno];
+               int hunk_end;
+               while (lno < cnt && !(sline[lno].flag & mark))
+                       lno++;
+               if (cnt <= lno)
+                       break;
+               for (hunk_end = lno + 1; hunk_end < cnt; hunk_end++)
+                       if (!(sline[hunk_end].flag & mark))
+                               break;
+               for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+               printf(" +%d,%d ", lno+1, hunk_end-lno);
+               for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+               putchar('\n');
+               while (lno < hunk_end) {
+                       struct lline *ll;
+                       int j;
+                       sl = &sline[lno++];
+                       ll = sl->lost_head;
+                       while (ll) {
+                               for (j = 0; j < num_parent; j++) {
+                                       if (ll->parent_map & (1UL<<j))
+                                               putchar('-');
+                                       else
+                                               putchar(' ');
+                               }
+                               puts(ll->line);
+                               ll = ll->next;
+                       }
+                       for (j = 0; j < num_parent; j++) {
+                               if ((1UL<<j) & sl->flag)
+                                       putchar(' ');
+                               else
+                                       putchar('+');
+                       }
+                       printf("%.*s\n", sl->len, sl->bol);
+               }
+       }
+}
+
+int show_combined_diff(struct combine_diff_path *elem, int num_parent,
+                      int dense, const char *header, int show_empty)
+{
+       unsigned long size, cnt, lno;
+       char *result, *cp, *ep;
+       struct sline *sline; /* survived lines */
+       int i, show_hunks, shown_header = 0;
+       char ourtmp_buf[TMPPATHLEN];
+       char *ourtmp = ourtmp_buf;
+
+       /* Read the result of merge first */
+       if (memcmp(elem->sha1, null_sha1, 20)) {
+               result = grab_blob(elem->sha1, &size);
+               write_to_temp_file(ourtmp, result, size);
+       }
+       else {
+               struct stat st;
+               int fd;
+               ourtmp = elem->path;
+               if (0 <= (fd = open(ourtmp, O_RDONLY)) &&
+                   !fstat(fd, &st)) {
+                       int len = st.st_size;
+                       int cnt = 0;
+
+                       size = len;
+                       result = xmalloc(len + 1);
+                       while (cnt < len) {
+                               int done = xread(fd, result+cnt, len-cnt);
+                               if (done == 0)
+                                       break;
+                               if (done < 0)
+                                       die("read error '%s'", ourtmp);
+                               cnt += done;
+                       }
+                       result[len] = 0;
+               }
+               else {
+                       /* deleted file */
+                       size = 0;
+                       result = xmalloc(1);
+                       result[0] = 0;
+                       ourtmp = "/dev/null";
+               }
+               if (0 <= fd)
+                       close(fd);
+       }
+
+       for (cnt = 0, cp = result; cp - result < size; cp++) {
+               if (*cp == '\n')
+                       cnt++;
+       }
+       if (result[size-1] != '\n')
+               cnt++; /* incomplete line */
+
+       sline = xcalloc(cnt, sizeof(*sline));
+       ep = result;
+       sline[0].bol = result;
+       for (lno = 0, cp = result; cp - result < size; cp++) {
+               if (*cp == '\n') {
+                       sline[lno].lost_tail = &sline[lno].lost_head;
+                       sline[lno].len = cp - sline[lno].bol;
+                       sline[lno].flag = (1UL<<num_parent) - 1;
+                       lno++;
+                       if (lno < cnt)
+                               sline[lno].bol = cp + 1;
+               }
+       }
+       if (result[size-1] != '\n') {
+               sline[cnt-1].lost_tail = &sline[cnt-1].lost_head;
+               sline[cnt-1].len = size - (sline[cnt-1].bol - result);
+               sline[cnt-1].flag = (1UL<<num_parent) - 1;
+       }
+
+       for (i = 0; i < num_parent; i++)
+               combine_diff(elem->parent_sha1[i], ourtmp, sline, cnt, i);
+
+       show_hunks = make_hunks(sline, cnt, num_parent, dense);
+
+       if (header && (show_hunks || show_empty)) {
+               shown_header++;
+               puts(header);
+       }
+       if (show_hunks) {
+               printf("diff --%s ", dense ? "cc" : "combined");
+               if (quote_c_style(elem->path, NULL, NULL, 0))
+                       quote_c_style(elem->path, NULL, stdout, 0);
+               else
+                       printf("%s", elem->path);
+               putchar('\n');
+               dump_sline(sline, cnt, num_parent);
+       }
+       if (ourtmp == ourtmp_buf)
+               unlink(ourtmp);
+       free(result);
+
+       for (i = 0; i < cnt; i++) {
+               if (sline[i].lost_head) {
+                       struct lline *ll = sline[i].lost_head;
+                       while (ll) {
+                               struct lline *tmp = ll;
+                               ll = ll->next;
+                               free(tmp);
+                       }
+               }
+       }
+       free(sline);
+       return shown_header;
+}
+
+int diff_tree_combined_merge(const unsigned char *sha1,
+                            const char *header,
+                            int show_empty_merge, int dense)
+{
+       struct commit *commit = lookup_commit(sha1);
+       struct diff_options diffopts;
+       struct commit_list *parents;
+       struct combine_diff_path *p, *paths = NULL;
+       int num_parent, i, num_paths;
+
+       diff_setup(&diffopts);
+       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diffopts.recursive = 1;
+
+       /* count parents */
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               ; /* nothing */
+
+       /* find set of paths that everybody touches */
+       for (parents = commit->parents, i = 0;
+            parents;
+            parents = parents->next, i++) {
+               struct commit *parent = parents->item;
+               diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
+                              &diffopts);
+               paths = intersect_paths(paths, i, num_parent);
+               diff_flush(&diffopts);
+       }
+
+       /* find out surviving paths */
+       for (num_paths = 0, p = paths; p; p = p->next) {
+               if (p->len)
+                       num_paths++;
+       }
+       if (num_paths || show_empty_merge) {
+               for (p = paths; p; p = p->next) {
+                       if (!p->len)
+                               continue;
+                       if (show_combined_diff(p, num_parent, dense, header,
+                                              show_empty_merge))
+                               header = NULL;
+               }
+       }
+
+       /* Clean things up */
+       while (paths) {
+               struct combine_diff_path *tmp = paths;
+               paths = paths->next;
+               free(tmp);
+       }
+       return 0;
+}
index 6c0696c..4a30c56 100644 (file)
@@ -7,12 +7,14 @@
 #include "diff.h"
 
 static const char diff_files_usage[] =
-"git-diff-files [-q] [-0/-1/2/3] [<common diff options>] [<path>...]"
+"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
 static struct diff_options diff_options;
 static int silent = 0;
 static int diff_unmerged_stage = 2;
+static int combine_merges = 0;
+static int dense_combined_merges = 0;
 
 static void show_unmerge(const char *path)
 {
@@ -66,6 +68,10 @@ int main(int argc, const char **argv)
                        ; /* no-op */
                else if (!strcmp(argv[1], "-s"))
                        ; /* no-op */
+               else if (!strcmp(argv[1], "-c"))
+                       combine_merges = 1;
+               else if (!strcmp(argv[1], "--cc"))
+                       dense_combined_merges = combine_merges = 1;
                else {
                        int diff_opt_cnt;
                        diff_opt_cnt = diff_opt_parse(&diff_options,
@@ -82,6 +88,9 @@ int main(int argc, const char **argv)
                }
                argv++; argc--;
        }
+       if (combine_merges) {
+               diff_options.output_format = DIFF_FORMAT_PATCH;
+       }
 
        /* Find the directory, and set up the pathspec */
        pathspec = get_pathspec(prefix, argv + 1);
@@ -108,14 +117,35 @@ int main(int argc, const char **argv)
                        continue;
 
                if (ce_stage(ce)) {
-                       show_unmerge(ce->name);
+                       struct {
+                               struct combine_diff_path p;
+                               unsigned char fill[4][20];
+                       } combine;
+
+                       combine.p.next = NULL;
+                       combine.p.len = ce_namelen(ce);
+                       combine.p.path = xmalloc(combine.p.len + 1);
+                       memcpy(combine.p.path, ce->name, combine.p.len);
+                       combine.p.path[combine.p.len] = 0;
+                       memset(combine.p.sha1, 0, 100);
+
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
+                               int stage;
 
                                if (strcmp(ce->name, nce->name))
                                        break;
+
+                               /* Stage #2 (ours) is the first parent,
+                                * stage #3 (theirs) is the second.
+                                */
+                               stage = ce_stage(nce);
+                               if (2 <= stage)
+                                       memcpy(combine.p.parent_sha1[stage-2],
+                                              nce->sha1, 20);
+
                                /* diff against the proper unmerged stage */
-                               if (ce_stage(nce) == diff_unmerged_stage)
+                               if (stage == diff_unmerged_stage)
                                        ce = nce;
                                i++;
                        }
@@ -123,10 +153,19 @@ int main(int argc, const char **argv)
                         * Compensate for loop update
                         */
                        i--;
+
+                       if (combine_merges) {
+                               show_combined_diff(&combine.p, 2,
+                                                  dense_combined_merges,
+                                                  NULL, 0);
+                               continue;
+                       }
+
                        /*
                         * Show the diff for the 'ce' if we found the one
                         * from the desired stage.
                         */
+                       show_unmerge(ce->name);
                        if (ce_stage(ce) != diff_unmerged_stage)
                                continue;
                }
index 44bc238..6593a69 100644 (file)
@@ -6,6 +6,9 @@ static int show_root_diff = 0;
 static int no_commit_id = 0;
 static int verbose_header = 0;
 static int ignore_merges = 1;
+static int show_empty_combined = 0;
+static int combine_merges = 0;
+static int dense_combined_merges = 0;
 static int read_stdin = 0;
 
 static const char *header = NULL;
@@ -80,9 +83,13 @@ static const char *generate_header(const unsigned char *commit_sha1,
        offset = sprintf(this_header, "%s%s ",
                         header_prefix,
                         diff_unique_abbrev(commit_sha1, abbrev));
-       offset += sprintf(this_header + offset, "(from %s)\n",
-                        parent_sha1 ?
-                        diff_unique_abbrev(parent_sha1, abbrev) : "root");
+       if (commit_sha1 != parent_sha1)
+               offset += sprintf(this_header + offset, "(from %s)\n",
+                                 parent_sha1
+                                 ? diff_unique_abbrev(parent_sha1, abbrev)
+                                 : "root");
+       else
+               offset += sprintf(this_header + offset, "(from parents)\n");
        offset += pretty_print_commit(commit_format, commit, len,
                                      this_header + offset,
                                      sizeof(this_header) - offset, abbrev);
@@ -109,8 +116,16 @@ static int diff_tree_commit(const unsigned char *commit_sha1)
        }
 
        /* More than one parent? */
-       if (ignore_merges && commit->parents && commit->parents->next)
-               return 0;
+       if (commit->parents && commit->parents->next) {
+               if (ignore_merges)
+                       return 0;
+               else if (combine_merges) {
+                       header = generate_header(sha1, sha1, commit);
+                       return diff_tree_combined_merge(sha1, header,
+                                                       show_empty_combined,
+                                                       dense_combined_merges);
+               }
+       }
 
        for (parents = commit->parents; parents; parents = parents->next) {
                struct commit *parent = parents->item;
@@ -153,7 +168,7 @@ static int diff_tree_stdin(char *line)
 }
 
 static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
 "[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
 "  -r            diff recursively\n"
 "  --root        include the initial commit as diff against /dev/null\n"
@@ -216,6 +231,14 @@ int main(int argc, const char **argv)
                        ignore_merges = 0;
                        continue;
                }
+               if (!strcmp(arg, "-c")) {
+                       combine_merges = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cc")) {
+                       dense_combined_merges = combine_merges = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-v")) {
                        verbose_header = 1;
                        header_prefix = "diff-tree ";
@@ -244,6 +267,12 @@ int main(int argc, const char **argv)
        if (diff_options.output_format == DIFF_FORMAT_PATCH)
                diff_options.recursive = 1;
 
+       if (combine_merges) {
+               diff_options.output_format = DIFF_FORMAT_PATCH;
+               show_empty_combined = !ignore_merges;
+               ignore_merges = 0;
+       }
+
        diff_tree_setup_paths(get_pathspec(prefix, argv));
        diff_setup_done(&diff_options);
 
diff --git a/diff.h b/diff.h
index 122c814..9a0169c 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -56,6 +56,19 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
 extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
                          const char *base, struct diff_options *opt);
 
+struct combine_diff_path {
+       struct combine_diff_path *next;
+       int len;
+       char *path;
+       unsigned char sha1[20];
+       unsigned char parent_sha1[FLEX_ARRAY][20];
+};
+
+int show_combined_diff(struct combine_diff_path *elem, int num_parent,
+                      int dense, const char *header, int show_empty);
+
+extern int diff_tree_combined_merge(const unsigned char *sha1, const char *, int, int);
+
 extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
index 334713a..0b142c1 100644 (file)
@@ -21,6 +21,7 @@ static const char rev_list_usage[] =
 "    --min-age=epoch\n"
 "    --sparse\n"
 "    --no-merges\n"
+"    --remove-empty\n"
 "    --all\n"
 "  ordering output:\n"
 "    --merge-order [ --show-breaks ]\n"
@@ -54,6 +55,7 @@ static int stop_traversal = 0;
 static int topo_order = 0;
 static int no_merges = 0;
 static const char **paths = NULL;
+static int remove_empty_trees = 0;
 
 static void show_commit(struct commit *commit)
 {
@@ -424,14 +426,33 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
-static int is_different = 0;
+#define TREE_SAME      0
+#define TREE_NEW       1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
 
 static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
                    const char *base, const char *path)
 {
-       is_different = 1;
+       int diff = TREE_DIFFERENT;
+
+       /*
+        * Is it an add of a new file? It means that
+        * the old tree didn't have it at all, so we
+        * will turn "TREE_SAME" -> "TREE_NEW", but
+        * leave any "TREE_DIFFERENT" alone (and if
+        * it already was "TREE_NEW", we'll keep it
+        * "TREE_NEW" of course).
+        */
+       if (addremove == '+') {
+               diff = tree_difference;
+               if (diff != TREE_SAME)
+                       return;
+               diff = TREE_NEW;
+       }
+       tree_difference = diff;
 }
 
 static void file_change(struct diff_options *options,
@@ -440,7 +461,7 @@ static void file_change(struct diff_options *options,
                 const unsigned char *new_sha1,
                 const char *base, const char *path)
 {
-       is_different = 1;
+       tree_difference = TREE_DIFFERENT;
 }
 
 static struct diff_options diff_opt = {
@@ -449,12 +470,16 @@ static struct diff_options diff_opt = {
        .change = file_change,
 };
 
-static int same_tree(struct tree *t1, struct tree *t2)
+static int compare_tree(struct tree *t1, struct tree *t2)
 {
-       is_different = 0;
+       if (!t1)
+               return TREE_NEW;
+       if (!t2)
+               return TREE_DIFFERENT;
+       tree_difference = TREE_SAME;
        if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
-               return 0;
-       return !is_different;
+               return TREE_DIFFERENT;
+       return tree_difference;
 }
 
 static int same_tree_as_empty(struct tree *t1)
@@ -474,28 +499,55 @@ static int same_tree_as_empty(struct tree *t1)
        empty.buf = "";
        empty.size = 0;
 
-       is_different = 0;
+       tree_difference = 0;
        retval = diff_tree(&empty, &real, "", &diff_opt);
        free(tree);
 
-       return retval >= 0 && !is_different;
+       return retval >= 0 && !tree_difference;
 }
 
-static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+static void try_to_simplify_commit(struct commit *commit)
 {
+       struct commit_list **pp, *parent;
+
        if (!commit->tree)
-               return NULL;
+               return;
 
-       while (parent) {
+       if (!commit->parents) {
+               if (!same_tree_as_empty(commit->tree))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
                struct commit *p = parent->item;
-               parent = parent->next;
+
+               if (p->object.flags & UNINTERESTING) {
+                       pp = &parent->next;
+                       continue;
+               }
+
                parse_commit(p);
-               if (!p->tree)
+               switch (compare_tree(p->tree, commit->tree)) {
+               case TREE_SAME:
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       return;
+
+               case TREE_NEW:
+                       if (remove_empty_trees && same_tree_as_empty(p->tree)) {
+                               *pp = parent->next;
+                               continue;
+                       }
+               /* fallthrough */
+               case TREE_DIFFERENT:
+                       pp = &parent->next;
                        continue;
-               if (same_tree(commit->tree, p->tree))
-                       return p;
+               }
+               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
-       return NULL;
+       commit->object.flags |= TREECHANGE;
 }
 
 static void add_parents_to_list(struct commit *commit, struct commit_list **list)
@@ -531,20 +583,14 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
        }
 
        /*
-        * Ok, the commit wasn't uninteresting. If it
-        * is a merge, try to find the parent that has
-        * no differences in the path set if one exists.
+        * Ok, the commit wasn't uninteresting. Try to
+        * simplify the commit history and find the parent
+        * that has no differences in the path set if one exists.
         */
-       if (paths && parent && parent->next) {
-               struct commit *preferred;
-
-               preferred = try_to_simplify_merge(commit, parent);
-               if (preferred) {
-                       parent->item = preferred;
-                       parent->next = NULL;
-               }
-       }
+       if (paths)
+               try_to_simplify_commit(commit);
 
+       parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
 
@@ -558,33 +604,6 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
        }
 }
 
-static void compress_list(struct commit_list *list)
-{
-       while (list) {
-               struct commit *commit = list->item;
-               struct commit_list *parent = commit->parents;
-               list = list->next;
-
-               if (!parent) {
-                       if (!same_tree_as_empty(commit->tree))
-                               commit->object.flags |= TREECHANGE;
-                       continue;
-               }
-
-               /*
-                * Exactly one parent? Check if it leaves the tree
-                * unchanged
-                */
-               if (!parent->next) {
-                       struct tree *t1 = commit->tree;
-                       struct tree *t2 = parent->item->tree;
-                       if (!t1 || !t2 || same_tree(t1, t2))
-                               continue;
-               }
-               commit->object.flags |= TREECHANGE;
-       }
-}
-
 static struct commit_list *limit_list(struct commit_list *list)
 {
        struct commit_list *newlist = NULL;
@@ -614,8 +633,6 @@ static struct commit_list *limit_list(struct commit_list *list)
        }
        if (tree_objects)
                mark_edges_uninteresting(newlist);
-       if (paths && dense)
-               compress_list(newlist);
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
@@ -808,6 +825,10 @@ int main(int argc, const char **argv)
                        dense = 0;
                        continue;
                }
+               if (!strcmp(arg, "--remove-empty")) {
+                       remove_empty_trees = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--")) {
                        i++;
                        break;
index a1aa863..d2f0864 100644 (file)
@@ -310,7 +310,9 @@ int main(int argc, char **argv)
                }
                if (verify)
                        die("Needed a single revision");
-               if (lstat(arg, &st) < 0)
+               if ((filter & DO_REVS) &&
+                   (filter & DO_NONFLAGS) && /* !def && */
+                   lstat(arg, &st) < 0)
                        die("'%s': %s", arg, strerror(errno));
                as_is = 1;
                show_file(arg);