diff --git a/Makefile b/Makefile index 6910448ef2..2aa2385b6b 100644 --- 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 index 0000000000..669002349a --- /dev/null +++ b/combine-diff.c @@ -0,0 +1,469 @@ +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "diffcore.h" +#include "quote.h" + +struct path_list { + struct path_list *next; + int len; + char *path; + unsigned char sha1[20]; + unsigned char parent_sha1[FLEX_ARRAY][20]; +}; + +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 path_list *intersect_paths(struct path_list *curr, + int n, int num_parent) +{ + struct diff_queue_struct *q = &diff_queued_diff; + struct path_list *p; + int i; + + if (!n) { + struct path_list *list = NULL, *tail = NULL; + 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); + if (!tail) + list = tail = p; + else { + tail->next = p; + p = tail; + } + } + 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<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; + if (sline->lost_head) + *(sline->lost_tail) = lline; + else + sline->lost_head = 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 void make_hunks(struct sline *sline, unsigned long cnt, int num_parent) +{ + unsigned long all_mask = (1UL<lost_head; + while (ll) { + for (j = 0; j < num_parent; j++) { + if (ll->parent_map & (1UL<line); + ll = ll->next; + } + for (j = 0; j < num_parent; j++) { + if ((1UL<flag) + putchar(' '); + else + putchar('+'); + } + printf(" %.*s\n", sl->len, sl->bol); + } + } +} + +static void show_combined_diff(struct path_list *elem, int num_parent) +{ + unsigned long size, cnt, lno; + char *result, *cp, *ep; + struct sline *sline; /* survived lines */ + int i; + char ourtmp[TMPPATHLEN]; + + /* Read the result of merge first */ + result = grab_blob(elem->sha1, &size); + write_to_temp_file(ourtmp, result, size); + + 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].len = cp - sline[lno].bol; + sline[lno].flag = (1UL<parent_sha1[i], ourtmp, sline, cnt, i); + + make_hunks(sline, cnt, num_parent); + + dump_sline(sline, cnt, num_parent); + 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); +} + +int diff_tree_combined_merge(const unsigned char *sha1, + const char *header, int show_empty_merge) +{ + struct commit *commit = lookup_commit(sha1); + struct diff_options diffopts; + struct commit_list *parents; + struct path_list *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) { + puts(header); + for (p = paths; p; p = p->next) { + if (!p->len) + continue; + printf("diff --combined "); + if (quote_c_style(p->path, NULL, NULL, 0)) + quote_c_style(p->path, NULL, stdout, 0); + else + printf("%s", p->path); + putchar('\n'); + show_combined_diff(p, num_parent); + } + } + + /* Clean things up */ + while (paths) { + struct path_list *tmp = paths; + paths = paths->next; + free(tmp); + } + return 0; +} diff --git a/diff-tree.c b/diff-tree.c index efa2b9476e..0c689360ee 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -6,6 +6,8 @@ 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 read_stdin = 0; static const char *header = NULL; @@ -79,9 +81,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, msg, len, this_header + offset, sizeof(this_header) - offset); @@ -108,8 +114,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->buffer); + return diff_tree_combined_merge(sha1, header, + show_empty_combined); + } + } for (parents = commit->parents; parents; parents = parents->next) { struct commit *parent = parents->item; @@ -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] [-s] [-v] [--pretty] [-t] [-r] [--root] " "[] [] [...]\n" " -r diff recursively\n" " --root include the initial commit as diff against /dev/null\n" @@ -217,6 +231,10 @@ int main(int argc, const char **argv) ignore_merges = 0; continue; } + if (!strcmp(arg, "-c")) { + combine_merges = 1; + continue; + } if (!strcmp(arg, "-v")) { verbose_header = 1; header_prefix = "diff-tree "; @@ -245,6 +263,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 5696f2aff0..081234cb97 100644 --- a/diff.h +++ b/diff.h @@ -56,6 +56,8 @@ 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); +extern int diff_tree_combined_merge(const unsigned char *sha1, const char *, int); + extern void diff_addremove(struct diff_options *, int addremove, unsigned mode,