From af3feefa1d057713c2277cc1543d438e60c65306 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Jan 2006 01:22:04 -0800 Subject: diff-tree -c: show a merge commit a bit more sensibly. A new option '-c' to diff-tree changes the way a merge commit is displayed when generating a patch output. It shows a "combined diff" (hence the option letter 'c'), which looks like this: $ git-diff-tree --pretty -c -p fec9ebf1 | head -n 18 diff-tree fec9ebf... (from parents) Merge: 0620db3... 8a263ae... Author: Junio C Hamano Date: Sun Jan 15 22:25:35 2006 -0800 Merge fixes up to GIT 1.1.3 diff --combined describe.c @@@ +98,7 @@@ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; } - static void describe(char *arg) - static void describe(struct commit *cmit, int last_one) ++ static void describe(char *arg, int last_one) { + unsigned char sha1[20]; + struct commit *cmit; There are a few things to note about this feature: - The '-c' option implies '-p'. It also implies '-m' halfway in the sense that "interesting" merges are shown, but not all merges. - When a blob matches one of the parents, we do not show a diff for that path at all. For a merge commit, this option shows paths with real file-level merge (aka "interesting things"). - As a concequence of the above, an "uninteresting" merge is not shown at all. You can use '-m' in addition to '-c' to show the commit log for such a merge, but there will be no combined diff output. - Unlike "gitk", the output is monochrome. A '-' character in the nth column means the line is from the nth parent and does not appear in the merge result (i.e. removed from that parent's version). A '+' character in the nth column means the line appears in the merge result, and the nth parent does not have that line (i.e. added by the merge itself or inherited from another parent). The above example output shows that the function signature was changed from either parents (hence two "-" lines and a "++" line), and "unsigned char sha1[20]", prefixed by a " +", was inherited from the first parent. The code as sent to the list was buggy in few corner cases, which I have fixed since then. It does not bother to keep track of and show the line numbers from parent commits, which it probably should. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 6910448..2aa2385 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 0000000..6690023 --- /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 efa2b94..0c68936 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 5696f2a..081234c 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, -- cgit v0.10.2-6-g49f6 From d8f4790e6fe7a9d94468ea83d1370643604d43e5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Jan 2006 01:22:04 -0800 Subject: diff-tree --cc: denser combined diff output for a merge commit. Building on the previous '-c' (combined) option, '--cc' option squelches the output further by omitting hunks that consist of difference with solely one parent. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index 6690023..062ed8a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -278,7 +278,25 @@ 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) +static unsigned long line_diff_parents(struct sline *sline, unsigned long all_mask) +{ + /* + * Look at the line and see from which parents we have 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 parents = (sline->flag ^ all_mask); + if (sline->lost_head) { + struct lline *ll; + for (ll = sline->lost_head; ll; ll = ll->next) + parents |= ll->parent_map; + } + return parents & all_mask; +} + +static void make_hunks(struct sline *sline, unsigned long cnt, + int num_parent, int dense) { unsigned long all_mask = (1UL<parent_sha1[i], ourtmp, sline, cnt, i); - make_hunks(sline, cnt, num_parent); + make_hunks(sline, cnt, num_parent, dense); dump_sline(sline, cnt, num_parent); unlink(ourtmp); @@ -410,7 +468,8 @@ static void show_combined_diff(struct path_list *elem, int num_parent) } int diff_tree_combined_merge(const unsigned char *sha1, - const char *header, int show_empty_merge) + const char *header, + int show_empty_merge, int dense) { struct commit *commit = lookup_commit(sha1); struct diff_options diffopts; @@ -455,7 +514,7 @@ int diff_tree_combined_merge(const unsigned char *sha1, else printf("%s", p->path); putchar('\n'); - show_combined_diff(p, num_parent); + show_combined_diff(p, num_parent, dense); } } diff --git a/diff-tree.c b/diff-tree.c index 0c68936..99c580c 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -8,6 +8,7 @@ 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; @@ -121,7 +122,8 @@ static int diff_tree_commit(const unsigned char *commit_sha1) header = generate_header(sha1, sha1, commit->buffer); return diff_tree_combined_merge(sha1, header, - show_empty_combined); + show_empty_combined, + dense_combined_merges); } } @@ -168,7 +170,7 @@ static int diff_tree_stdin(char *line) } static const char diff_tree_usage[] = -"git-diff-tree [--stdin] [-m] [-c] [-s] [-v] [--pretty] [-t] [-r] [--root] " +"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " "[] [] [...]\n" " -r diff recursively\n" " --root include the initial commit as diff against /dev/null\n" @@ -235,6 +237,10 @@ int main(int argc, const char **argv) 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 "; diff --git a/diff.h b/diff.h index 081234c..ab0d47b 100644 --- a/diff.h +++ b/diff.h @@ -56,7 +56,7 @@ 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 int diff_tree_combined_merge(const unsigned char *sha1, const char *, int, int); extern void diff_addremove(struct diff_options *, int addremove, -- cgit v0.10.2-6-g49f6 From 5290a0f8129cc5830390bad3cc6c8034cb23b41c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Jan 2006 03:34:10 -0800 Subject: combine-diff: fix appending at the tail of a list. ... and use the established pattern of tail initialized to point at the head pointer for an empty list, and updated to point at the next pointer field of the item at the tail when appending. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index 062ed8a..eb763e1 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -29,7 +29,7 @@ static struct path_list *intersect_paths(struct path_list *curr, int i; if (!n) { - struct path_list *list = NULL, *tail = NULL; + struct path_list *list = NULL, **tail = &list; for (i = 0; i < q->nr; i++) { int len; const char *path; @@ -46,12 +46,8 @@ static struct path_list *intersect_paths(struct path_list *curr, 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; - } + *tail = p; + tail = &p->next; } return list; } @@ -212,10 +208,7 @@ static void append_lost(struct sline *sline, int n, const char *line) 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; sline->lost_tail = &lline->next; } @@ -433,6 +426,7 @@ static void show_combined_diff(struct path_list *elem, int num_parent, 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< Date: Wed, 25 Jan 2006 11:55:40 -0800 Subject: combine-diff: minor output changes. Remove extra whitespace between the change indicators and the body text. That is more in line with the uncombined unified diff output (pointed out by Santi Bejar). When showing --cc, say so instead of saying just --combined. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index eb763e1..b0846bc 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -386,7 +386,6 @@ static void dump_sline(struct sline *sline, int cnt, int num_parent) else putchar(' '); } - putchar(' '); puts(ll->line); ll = ll->next; } @@ -396,7 +395,7 @@ static void dump_sline(struct sline *sline, int cnt, int num_parent) else putchar('+'); } - printf(" %.*s\n", sl->len, sl->bol); + printf("%.*s\n", sl->len, sl->bol); } } } @@ -503,7 +502,7 @@ int diff_tree_combined_merge(const unsigned char *sha1, for (p = paths; p; p = p->next) { if (!p->len) continue; - printf("diff --combined "); + printf("diff --%s ", dense ? "cc" : "combined"); if (quote_c_style(p->path, NULL, NULL, 0)) quote_c_style(p->path, NULL, stdout, 0); else -- cgit v0.10.2-6-g49f6 From 263eee29e94de935d62c7c4066fcea0ccfbe60b8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Jan 2006 13:11:38 -0800 Subject: combine-diff: extend --cc logic to Octopus. Santi Bejar points out that a hunk that changes from all the same common parents except one is uninteresting. The earlier round marked changes from only one parent uninteresting, but this also marks hunks that have the same change from all but one parent uninteresting, which is a natural extension of the original idea to Octopus merges. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index b0846bc..a91cafb 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -271,21 +271,56 @@ static int interesting(struct sline *sline, unsigned long all_mask) return ((sline->flag & all_mask) != all_mask || sline->lost_head); } -static unsigned long line_diff_parents(struct sline *sline, unsigned long all_mask) +static unsigned long line_common_diff(struct sline *sline, unsigned long all_mask) { /* - * Look at the line and see from which parents we have 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. + * Look at the line and see from which parents we have the + * same difference. */ - unsigned long parents = (sline->flag ^ all_mask); + + /* 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) - parents |= ll->parent_map; + for (ll = sline->lost_head; ll; ll = ll->next) { + different |= ll->parent_map; + } } - return parents & all_mask; + return different; } static void make_hunks(struct sline *sline, unsigned long cnt, @@ -316,13 +351,14 @@ static void make_hunks(struct sline *sline, unsigned long cnt, if (!dense) return; - /* Look at each hunk, and if it contains changes from only - * one parent, mark that uninteresting. + /* 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) { - int j, hunk_end, diffs; - unsigned long parents; + int j, hunk_end, same, diff; + unsigned long same_diff, all_diff, this_diff; while (i < cnt && !(sline[i].flag & mark)) i++; if (cnt <= i) @@ -330,22 +366,23 @@ static void make_hunks(struct sline *sline, unsigned long cnt, for (hunk_end = i + 1; hunk_end < cnt; hunk_end++) if (!(sline[hunk_end].flag & mark)) break; - /* [i..hunk_end) are interesting. Now is it from - * only one parent? - * If lost lines are only from one parent and - * remaining lines existed in parents other than - * that parent, then the hunk is not that interesting. + /* [i..hunk_end) are interesting. Now does it have + * the same change with all but one parent? */ - parents = 0; - diffs = 0; - for (j = i; j < hunk_end; j++) - parents |= line_diff_parents(sline + j, all_mask); - /* Now, how many bits from [0..num_parent) are on? */ + 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 (parents & (1UL< Date: Wed, 25 Jan 2006 14:26:22 -0800 Subject: diff-tree --cc: squelch header generation on empty patch. Earlier round showed the commit log header and "diff --combined" header even for paths that had no interesting hunk under --cc flag. Move the header display logic around to squelch them. With this, a merge that does not have any interesting merges will not be shown with --cc option, unless -m is used at the same time. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index a91cafb..3b219a0 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -323,12 +323,13 @@ static unsigned long line_all_diff(struct sline *sline, unsigned long all_mask) return different; } -static void make_hunks(struct sline *sline, unsigned long cnt, +static int make_hunks(struct sline *sline, unsigned long cnt, int num_parent, int dense) { unsigned long all_mask = (1UL<parent_sha1[i], ourtmp, sline, cnt, i); - make_hunks(sline, cnt, num_parent, dense); + show_hunks = make_hunks(sline, cnt, num_parent, dense); - dump_sline(sline, cnt, num_parent); + 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); + } unlink(ourtmp); free(result); @@ -496,6 +514,7 @@ static void show_combined_diff(struct path_list *elem, int num_parent, } } free(sline); + return shown_header; } int diff_tree_combined_merge(const unsigned char *sha1, @@ -535,17 +554,12 @@ int diff_tree_combined_merge(const unsigned char *sha1, num_paths++; } if (num_paths || show_empty_merge) { - puts(header); for (p = paths; p; p = p->next) { if (!p->len) continue; - printf("diff --%s ", dense ? "cc" : "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, dense); + if (show_combined_diff(p, num_parent, dense, header, + show_empty_merge)) + header = NULL; } } -- cgit v0.10.2-6-g49f6 From 3ec1909fdaa1189a8f9d23f84e33b45d9d7600cd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Jan 2006 03:53:01 -0800 Subject: combine-diff: better hunk splitting. It considered an otherwise unchanged line that had line removals in front of it an interesting line, which caused hunks to have one extra the trailing context line. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index 3b219a0..df52fa2 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -323,52 +323,141 @@ static unsigned long line_all_diff(struct sline *sline, unsigned long all_mask) return different; } -static int make_hunks(struct sline *sline, unsigned long cnt, - int num_parent, int dense) +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< Date: Sat, 28 Jan 2006 00:03:38 -0800 Subject: diff-files: -c and --cc options. This ports the "combined diff" to diff-files so that differences to the working tree files since stage 2 and stage 3 are shown the same way as combined diff output from diff-tree for the merge commit would be shown if the current working tree files are committed. Signed-off-by: Junio C Hamano diff --git a/combine-diff.c b/combine-diff.c index df52fa2..243f967 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -4,14 +4,6 @@ #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)) @@ -21,15 +13,14 @@ static int uninteresting(struct diff_filepair *p) return 0; } -static struct path_list *intersect_paths(struct path_list *curr, - int n, int num_parent) +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 path_list *p; + struct combine_diff_path *p; int i; if (!n) { - struct path_list *list = NULL, **tail = &list; + struct combine_diff_path *list = NULL, **tail = &list; for (i = 0; i < q->nr; i++) { int len; const char *path; @@ -532,18 +523,52 @@ static void dump_sline(struct sline *sline, int cnt, int num_parent) } } -static int show_combined_diff(struct path_list *elem, int num_parent, - int dense, const char *header, int show_empty) +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[TMPPATHLEN]; + char ourtmp_buf[TMPPATHLEN]; + char *ourtmp = ourtmp_buf; /* Read the result of merge first */ - result = grab_blob(elem->sha1, &size); - write_to_temp_file(ourtmp, result, size); + 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') @@ -589,7 +614,8 @@ static int show_combined_diff(struct path_list *elem, int num_parent, putchar('\n'); dump_sline(sline, cnt, num_parent); } - unlink(ourtmp); + if (ourtmp == ourtmp_buf) + unlink(ourtmp); free(result); for (i = 0; i < cnt; i++) { @@ -613,7 +639,7 @@ int diff_tree_combined_merge(const unsigned char *sha1, struct commit *commit = lookup_commit(sha1); struct diff_options diffopts; struct commit_list *parents; - struct path_list *p, *paths = NULL; + struct combine_diff_path *p, *paths = NULL; int num_parent, i, num_paths; diff_setup(&diffopts); @@ -654,7 +680,7 @@ int diff_tree_combined_merge(const unsigned char *sha1, /* Clean things up */ while (paths) { - struct path_list *tmp = paths; + struct combine_diff_path *tmp = paths; paths = paths->next; free(tmp); } diff --git a/diff-files.c b/diff-files.c index 6c0696c..4a30c56 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] [] [...]" +"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [] [...]" 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.h b/diff.h index ab0d47b..539bd2f 100644 --- a/diff.h +++ b/diff.h @@ -56,6 +56,17 @@ 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 *, -- cgit v0.10.2-6-g49f6 From 461cf59f8924f174d7a0dcc3d77f576d93ed29a4 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 18 Jan 2006 14:47:30 -0800 Subject: rev-list: stop when the file disappears The one thing I've considered doing (I really should) is to add a "stop when you don't find the file" option to "git-rev-list". This patch does some of the work towards that: it removes the "parent" thing when the file disappears, so a "git annotate" could do do something like git-rev-list --remove-empty --parents HEAD -- "$filename" and it would get a good graph that stops when the filename disappears (it's not perfect though: it won't remove all the unintersting commits). It also simplifies the logic of finding tree differences a bit, at the cost of making it a tad less efficient. The old logic was two-phase: it would first simplify _only_ merges tree as it traversed the tree, and then simplify the linear parts of the remainder independently. That was pretty optimal from an efficiency standpoint because it avoids doing any comparisons that we can see are unnecessary, but it made it much harder to understand than it really needed to be. The new logic is a lot more straightforward, and compares the trees as it traverses the graph (ie everything is a single phase). That makes it much easier to stop graph traversal at any point where a file disappears. As an example, let's say that you have a git repository that has had a file called "A" some time in the past. That file gets renamed to B, and then gets renamed back again to A. The old "git-rev-list" would show two commits: the commit that renames B to A (because it changes A) _and_ as its parent the commit that renames A to B (because it changes A). With the new --remove-empty flag, git-rev-list will show just the commit that renames B to A as the "root" commit, and stop traversal there (because that's what you want for "annotate" - you want to stop there, and for every "root" commit you then separately see if it really is a new file, or if the paths history disappeared because it was renamed from some other file). With this patch, you should be able to basically do a "poor mans 'git annotate'" with a fairly simple loop: push("HEAD", "$filename") while (revision,filename = pop()) { for each i in $(git-rev-list --parents --remove-empty $revision -- "$filename") pseudo-parents($i) = git-rev-list parents for that line if (pseudo-parents($i) is non-empty) { show diff of $i against pseudo-parents continue } /* See if the _real_ parents of $i had a rename */ parent($i) = real-parent($i) if (find-rename in $parent($i)->$i) push $parent($i), "old-name" } which should be doable in perl or something (doing stacks in shell is just too painful to be worth it, so I'm not going to do this). Anybody want to try? Linus diff --git a/rev-list.c b/rev-list.c index e00e6fc..7d3ddc6 100644 --- a/rev-list.c +++ b/rev-list.c @@ -54,6 +54,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 +425,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 +460,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 +469,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 +498,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 +582,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 +603,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 +632,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 +824,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; -- cgit v0.10.2-6-g49f6 From 93b74bca86f59b8df410b6fd4803b88ee0f304bf Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jan 2006 01:39:24 -0800 Subject: rev-list --remove-empty: add minimum help and doc entry. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index f9146f1..1c6146c 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 . diff --git a/rev-list.c b/rev-list.c index 7d3ddc6..5bc38fe 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" -- cgit v0.10.2-6-g49f6 From 46a6c2620ba421397eec627b8eb18eb530e694fc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Jan 2006 01:03:18 -0800 Subject: abbrev cleanup: use symbolic constants The minimum length of abbreviated object name was hardcoded in different places to be 4, risking inconsistencies in the future. Also there were three different "default abbreviation precision". Use two C preprocessor symbols to clean up this mess. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 1e8e27f..bdbe2d6 100644 --- a/cache.h +++ b/cache.h @@ -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/describe.c b/describe.c index 4866510..ff65742 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.c b/diff.c index 17d68fa..69767b9 100644 --- a/diff.c +++ b/diff.c @@ -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,7 +846,7 @@ 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; + options->abbrev = DEFAULT_ABBREV; else if (!strncmp(arg, "--abbrev=", 9)) options->abbrev = strtoul(arg + 9, NULL, 10); else diff --git a/diff.h b/diff.h index 5696f2a..122c814 100644 --- a/diff.h +++ b/diff.h @@ -88,9 +88,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/sha1_name.c b/sha1_name.c index e18a96d..ba0747c 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); -- cgit v0.10.2-6-g49f6 From d50125085a6c4775a77d45a6d294d723f6de8869 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Jan 2006 01:35:38 -0800 Subject: rev-parse: --abbrev option. The new option behaves just like --verify, but outputs an abbreviated object name that is unique within the repository. Signed-off-by: Junio C Hamano diff --git a/rev-parse.c b/rev-parse.c index 7abad35..42969a6 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,17 @@ int main(int argc, char **argv) verify = 1; continue; } + if (!strcmp(arg, "--abbrev") || + !strncmp(arg, "--abbrev=", 9)) { + filter &= ~(DO_FLAGS|DO_NOREV); + verify = 1; + abbrev = DEFAULT_ABBREV; + if (arg[8] == '=') + abbrev = strtoul(arg + 9, NULL, 10); + if (abbrev < 0 || 40 <= abbrev) + abbrev = DEFAULT_ABBREV; + continue; + } if (!strcmp(arg, "--sq")) { output_sq = 1; continue; -- cgit v0.10.2-6-g49f6 From b2d4c56f2f370481c80e478ac323ebb13eece807 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 25 Jan 2006 02:37:40 -0800 Subject: diff-tree: abbreviate merge parent object names with --abbrev --pretty. When --abbrev is in effect, abbreviate the merge parent names in prettyprinted output. Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index b8bf35e..244104f 100644 --- a/commit.c +++ b/commit.c @@ -426,9 +426,10 @@ 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_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int parents, int abbrev) { int offset = 0; + unsigned char sha1[20]; if (fmt == CMIT_FMT_ONELINE) return offset; @@ -437,17 +438,25 @@ static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int p 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); + if (abbrev && !get_sha1_hex(line-41, sha1)) + offset = sprintf(buf, "Merge: %s\n", + find_unique_abbrev(sha1, abbrev)); + else + 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 (abbrev && !get_sha1_hex(line+7, sha1)) + offset += sprintf(buf + offset, "%s\n", + find_unique_abbrev(sha1, abbrev)); + else + offset += sprintf(buf + offset, "%.40s\n", line+7); } 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 char *msg, unsigned long len, char *buf, unsigned long space, int abbrev) { int hdr = 1, body = 0; unsigned long offset = 0; @@ -488,7 +497,7 @@ 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); + offset += add_parent_info(fmt, buf + offset, line, ++parents, abbrev); } /* diff --git a/commit.h b/commit.h index 9c4a244..a8c2096 100644 --- a/commit.h +++ b/commit.h @@ -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 char *msg, 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/diff-tree.c b/diff-tree.c index efa2b94..efa17d1 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -84,7 +84,7 @@ static const char *generate_header(const unsigned char *commit_sha1, diff_unique_abbrev(parent_sha1, abbrev) : "root"); offset += pretty_print_commit(commit_format, msg, len, this_header + offset, - sizeof(this_header) - offset); + sizeof(this_header) - offset, abbrev); return this_header; } diff --git a/rev-list.c b/rev-list.c index e00e6fc..5fad300 100644 --- a/rev-list.c +++ b/rev-list.c @@ -81,7 +81,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->buffer, ~0, pretty_header, sizeof(pretty_header), 0); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); diff --git a/show-branch.c b/show-branch.c index 7a0dcc6..d06e577 100644 --- a/show-branch.c +++ b/show-branch.c @@ -259,7 +259,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->object.util; if (commit->object.parsed) pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0, - pretty, sizeof(pretty)); + pretty, sizeof(pretty), 0); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) -- cgit v0.10.2-6-g49f6 From 1dc4fb84b5914621cf59b6b508ad7c9c86c61fa4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Jan 2006 00:48:19 -0800 Subject: rev-parse --abbrev: do not try abbrev shorter than minimum. We do not allow abbreviation shorter than 4 letters in other parts of the system so do not attempt to generate such. Noticed by Uwe Zeisberger. Signed-off-by: Junio C Hamano diff --git a/rev-parse.c b/rev-parse.c index 42969a6..8bf316e 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -206,8 +206,10 @@ int main(int argc, char **argv) abbrev = DEFAULT_ABBREV; if (arg[8] == '=') abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < 0 || 40 <= abbrev) - abbrev = DEFAULT_ABBREV; + if (abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (40 <= abbrev) + abbrev = 40; continue; } if (!strcmp(arg, "--sq")) { -- cgit v0.10.2-6-g49f6 From 62a604ba1c8f1ebcb135039ab04c9ca6c96b67f4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Jan 2006 17:02:07 -0800 Subject: Rename rev-parse --abbrev to --short. The usage of rev-parse to serve as a flag/option parser for git-whatchanged and other commands have serious limitation that the flags cannot be something that is supported by rev-parse itself, and it cannot worked around easily. Since this is rarely used "poor-man's describe", rename the option for now as an easier workaround. Signed-off-by: Junio C Hamano diff --git a/rev-parse.c b/rev-parse.c index 8bf316e..a1aa863 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -199,8 +199,8 @@ int main(int argc, char **argv) verify = 1; continue; } - if (!strcmp(arg, "--abbrev") || - !strncmp(arg, "--abbrev=", 9)) { + if (!strcmp(arg, "--short") || + !strncmp(arg, "--short=", 9)) { filter &= ~(DO_FLAGS|DO_NOREV); verify = 1; abbrev = DEFAULT_ABBREV; -- cgit v0.10.2-6-g49f6 From 3815f423ae39bf774de3c268c6d3e3b72128a4e5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jan 2006 01:54:59 -0800 Subject: pretty_print_commit(): pass commit object instead of commit->buffer. Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index 244104f..e8f53e8 100644 --- a/commit.c +++ b/commit.c @@ -456,12 +456,13 @@ static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int p return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space, int abbrev) +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; + const char *msg = commit->buffer; for (;;) { const char *line = msg; diff --git a/commit.h b/commit.h index a8c2096..986b22d 100644 --- a/commit.h +++ b/commit.h @@ -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, int abbrev); +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/diff-tree.c b/diff-tree.c index efa17d1..44bc238 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -64,12 +64,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); @@ -82,7 +83,7 @@ static const char *generate_header(const unsigned char *commit_sha1, 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, + offset += pretty_print_commit(commit_format, commit, len, this_header + offset, sizeof(this_header) - offset, abbrev); return this_header; @@ -103,7 +104,7 @@ 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, ""); } @@ -113,9 +114,7 @@ static int diff_tree_commit(const unsigned char *commit_sha1) 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 "; diff --git a/rev-list.c b/rev-list.c index 5fad300..334713a 100644 --- a/rev-list.c +++ b/rev-list.c @@ -81,7 +81,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), 0); + pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), 0); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); diff --git a/show-branch.c b/show-branch.c index d06e577..ffe7456 100644 --- a/show-branch.c +++ b/show-branch.c @@ -258,7 +258,7 @@ 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_print_commit(CMIT_FMT_ONELINE, commit, ~0, pretty, sizeof(pretty), 0); else strcpy(pretty, "(unavailable)"); -- cgit v0.10.2-6-g49f6 From f2d4227530499db3e273ae84f30adfd4b70791c6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jan 2006 02:17:19 -0800 Subject: pretty_print_commit: honor grafts. When displaying Merge: lines, we used to take the real commit parents from the commit objects. Use the parsed parents from the commit object instead, so that we honor fake parent information from info/grafts. Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index e8f53e8..97205bf 100644 --- a/commit.c +++ b/commit.c @@ -426,33 +426,27 @@ 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, int abbrev) +static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev) { - int offset = 0; - unsigned char sha1[20]; + 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' */ - if (abbrev && !get_sha1_hex(line-41, sha1)) - offset = sprintf(buf, "Merge: %s\n", - find_unique_abbrev(sha1, abbrev)); - else - offset = sprintf(buf, "Merge: %.40s\n", line-41); - /* Fallthrough */ - default: - /* Replace the previous '\n' with a space */ - buf[offset-1] = ' '; - if (abbrev && !get_sha1_hex(line+7, sha1)) - offset += sprintf(buf + offset, "%s\n", - find_unique_abbrev(sha1, abbrev)); - else - 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; } @@ -460,8 +454,8 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit { 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 (;;) { @@ -498,9 +492,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (!memcmp(line, "parent ", 7)) { if (linelen != 48) die("bad parent line in commit"); - offset += add_parent_info(fmt, buf + offset, line, ++parents, abbrev); + 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. -- cgit v0.10.2-6-g49f6 From 6b1ddbdd6e02719ae2be55dc141a176187e5027e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 27 Jan 2006 02:19:51 -0800 Subject: diff --abbrev= option fix. Earier specifying an abbreviation shorter than minimum fell back to full 40 letters, which was nonsense. Make it to fall back to the minimum number (currently 4). Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 69767b9..8ae6dbc 100644 --- a/diff.c +++ b/diff.c @@ -847,8 +847,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->find_copies_harder = 1; else if (!strcmp(arg, "--abbrev")) options->abbrev = DEFAULT_ABBREV; - else if (!strncmp(arg, "--abbrev=", 9)) + 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; -- cgit v0.10.2-6-g49f6 From b33aba518456bee97bde1fef4fe17ab6bf401bbe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 26 Jan 2006 17:24:52 -0800 Subject: rev-parse: make "whatchanged -- git-fetch-script" work again. The latest update to avoid misspelled revs interfered when we were not interested in parsing non flags or arguments not meant for rev-list. This makes these two forms work again: git whatchanged -- git-fetch-script We could enable "!def" in the part this change touches to make the above work without '--', but then it would cause misspelled v2.6.14..v2.6.16 to be given to diff-tree and defeats the whole point of the previous fix. Signed-off-by: Junio C Hamano diff --git a/rev-parse.c b/rev-parse.c index 7abad35..9567b0f 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -294,7 +294,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); -- cgit v0.10.2-6-g49f6