diff options
-rw-r--r-- | Documentation/git-rev-list.txt | 4 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | cache.h | 3 | ||||
-rw-r--r-- | combine-diff.c | 688 | ||||
-rw-r--r-- | commit.c | 46 | ||||
-rw-r--r-- | commit.h | 2 | ||||
-rw-r--r-- | describe.c | 3 | ||||
-rw-r--r-- | diff-files.c | 45 | ||||
-rw-r--r-- | diff-tree.c | 54 | ||||
-rw-r--r-- | diff.c | 11 | ||||
-rw-r--r-- | diff.h | 16 | ||||
-rw-r--r-- | rev-list.c | 139 | ||||
-rw-r--r-- | rev-parse.c | 20 | ||||
-rw-r--r-- | sha1_name.c | 2 | ||||
-rw-r--r-- | show-branch.c | 4 |
15 files changed, 932 insertions, 107 deletions
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index f9146f1900..1c6146c764 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -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>. @@ -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 \ @@ -221,6 +221,9 @@ extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); /* Convert to/from hex/sha1 representation */ +#define MINIMUM_ABBREV 4 +#define DEFAULT_ABBREV 7 + extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ diff --git a/combine-diff.c b/combine-diff.c new file mode 100644 index 0000000000..243f96775a --- /dev/null +++ b/combine-diff.c @@ -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; +} @@ -426,33 +426,37 @@ static int is_empty_line(const char *line, int len) return !len; } -static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int parents) +static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev) { - int offset = 0; + struct commit_list *parent = commit->parents; + int offset; - if (fmt == CMIT_FMT_ONELINE) - return offset; - switch (parents) { - case 1: - break; - case 2: - /* Go back to the previous line: 40 characters of previous parent, and one '\n' */ - offset = sprintf(buf, "Merge: %.40s\n", line-41); - /* Fallthrough */ - default: - /* Replace the previous '\n' with a space */ - buf[offset-1] = ' '; - offset += sprintf(buf + offset, "%.40s\n", line+7); + if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next) + return 0; + + offset = sprintf(buf, "Merge:"); + + while (parent) { + struct commit *p = parent->item; + parent = parent->next; + + offset += sprintf(buf + offset, + abbrev ? " %s..." : " %s", + abbrev + ? find_unique_abbrev(p->object.sha1, abbrev) + : sha1_to_hex(p->object.sha1)); } + buf[offset++] = '\n'; return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev) { int hdr = 1, body = 0; unsigned long offset = 0; - int parents = 0; int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4; + int parents_shown = 0; + const char *msg = commit->buffer; for (;;) { const char *line = msg; @@ -488,9 +492,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned l if (!memcmp(line, "parent ", 7)) { if (linelen != 48) die("bad parent line in commit"); - offset += add_parent_info(fmt, buf + offset, line, ++parents); + continue; } + if (!parents_shown) { + offset += add_merge_info(fmt, buf + offset, + commit, abbrev); + parents_shown = 1; + continue; + } /* * MEDIUM == DEFAULT shows only author with dates. * FULL shows both authors but not dates. @@ -48,7 +48,7 @@ enum cmit_fmt { }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev); /** Removes the first commit from a list sorted by date, and adds all * of its parents. diff --git a/describe.c b/describe.c index 4866510eaf..ff65742615 100644 --- a/describe.c +++ b/describe.c @@ -11,7 +11,6 @@ static const char describe_usage[] = static int all = 0; /* Default to annotated tags only */ static int tags = 0; /* But allow any tags if --tags is specified */ -#define DEFAULT_ABBREV 8 /* maybe too many */ static int abbrev = DEFAULT_ABBREV; static int names = 0, allocs = 0; @@ -155,7 +154,7 @@ int main(int argc, char **argv) tags = 1; else if (!strncmp(arg, "--abbrev=", 9)) { abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < 4 || 40 <= abbrev) + if (abbrev < MINIMUM_ABBREV || 40 <= abbrev) abbrev = DEFAULT_ABBREV; } else diff --git a/diff-files.c b/diff-files.c index 6c0696c34f..4a30c56f84 100644 --- a/diff-files.c +++ b/diff-files.c @@ -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; } diff --git a/diff-tree.c b/diff-tree.c index efa2b9476e..6593a6920a 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -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; @@ -64,12 +67,13 @@ static int diff_root_tree(const unsigned char *new, const char *base) static const char *generate_header(const unsigned char *commit_sha1, const unsigned char *parent_sha1, - const char *msg) + const struct commit *commit) { static char this_header[16384]; int offset; unsigned long len; int abbrev = diff_options.abbrev; + const char *msg = commit->buffer; if (!verbose_header) return sha1_to_hex(commit_sha1); @@ -79,12 +83,16 @@ 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"); - offset += pretty_print_commit(commit_format, msg, len, + 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); + sizeof(this_header) - offset, abbrev); return this_header; } @@ -103,19 +111,25 @@ static int diff_tree_commit(const unsigned char *commit_sha1) /* Root commit? */ if (show_root_diff && !commit->parents) { - header = generate_header(sha1, NULL, commit->buffer); + header = generate_header(sha1, NULL, commit); diff_root_tree(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; - header = generate_header(sha1, - parent->object.sha1, - commit->buffer); + header = generate_header(sha1, parent->object.sha1, commit); diff_tree_sha1_top(parent->object.sha1, commit_sha1, ""); if (!header && verbose_header) { header_prefix = "\ndiff-tree "; @@ -154,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" @@ -217,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 "; @@ -245,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); @@ -723,7 +723,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) if (memcmp(one->sha1, two->sha1, 20)) { char one_sha1[41]; - int abbrev = o->full_index ? 40 : DIFF_DEFAULT_INDEX_ABBREV; + int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; memcpy(one_sha1, sha1_to_hex(one->sha1), 41); len += snprintf(msg + len, sizeof(msg) - len, @@ -846,9 +846,14 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--find-copies-harder")) options->find_copies_harder = 1; else if (!strcmp(arg, "--abbrev")) - options->abbrev = DIFF_DEFAULT_ABBREV; - else if (!strncmp(arg, "--abbrev=", 9)) + options->abbrev = DEFAULT_ABBREV; + else if (!strncmp(arg, "--abbrev=", 9)) { options->abbrev = strtoul(arg + 9, NULL, 10); + if (options->abbrev < MINIMUM_ABBREV) + options->abbrev = MINIMUM_ABBREV; + else if (40 < options->abbrev) + options->abbrev = 40; + } else return 0; return 1; @@ -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, @@ -88,9 +101,6 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_ALL 1 -#define DIFF_DEFAULT_INDEX_ABBREV 7 /* hex digits */ -#define DIFF_DEFAULT_ABBREV 7 /* hex digits */ - extern void diffcore_std(struct diff_options *); extern void diffcore_std_no_resolve(struct diff_options *); diff --git a/rev-list.c b/rev-list.c index e00e6fc76d..0b142c1a6f 100644 --- a/rev-list.c +++ b/rev-list.c @@ -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) { @@ -81,7 +83,7 @@ static void show_commit(struct commit *commit) if (verbose_header) { static char pretty_header[16384]; - pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header)); + pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), 0); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); @@ -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; diff --git a/rev-parse.c b/rev-parse.c index 7abad35de9..d2f086432b 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -20,6 +20,7 @@ static char *def = NULL; #define REVERSED 1 static int show_type = NORMAL; static int symbolic = 0; +static int abbrev = 0; static int output_sq = 0; static int revs_count = 0; @@ -95,6 +96,8 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) putchar('^'); if (symbolic && name) show(name); + else if (abbrev) + show(find_unique_abbrev(sha1, abbrev)); else show(sha1_to_hex(sha1)); } @@ -196,6 +199,19 @@ int main(int argc, char **argv) verify = 1; continue; } + if (!strcmp(arg, "--short") || + !strncmp(arg, "--short=", 9)) { + filter &= ~(DO_FLAGS|DO_NOREV); + verify = 1; + abbrev = DEFAULT_ABBREV; + if (arg[8] == '=') + abbrev = strtoul(arg + 9, NULL, 10); + if (abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (40 <= abbrev) + abbrev = 40; + continue; + } if (!strcmp(arg, "--sq")) { output_sq = 1; continue; @@ -294,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); diff --git a/sha1_name.c b/sha1_name.c index e18a96d772..ba0747c84d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -155,7 +155,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, char canonical[40]; unsigned char res[20]; - if (len < 4) + if (len < MINIMUM_ABBREV) return -1; memset(res, 0, 20); memset(canonical, 'x', 40); diff --git a/show-branch.c b/show-branch.c index 7a0dcc649e..ffe7456a6a 100644 --- a/show-branch.c +++ b/show-branch.c @@ -258,8 +258,8 @@ static void show_one_commit(struct commit *commit, int no_name) char pretty[256], *cp; struct commit_name *name = commit->object.util; if (commit->object.parsed) - pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0, - pretty, sizeof(pretty)); + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + pretty, sizeof(pretty), 0); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) |