X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=diff.c;h=f745cdd6e8bcd356c8b6f26accdd26b8b78be056;hb=84c1afd7e7c69c6c3c0677d5ee01925d4c70d318;hp=05e52779b286a8cfa25e9062fa7ce7364131766d;hpb=bceafe752c03f4b13b9b1671a55d9e2acd26432d;p=git.git diff --git a/diff.c b/diff.c index 05e52779..f745cdd6 100644 --- a/diff.c +++ b/diff.c @@ -171,8 +171,8 @@ struct diff_filespec *alloc_filespec(const char *path) void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, unsigned short mode) { - if (mode) { /* just playing defensive */ - spec->mode = mode; + if (mode) { + spec->mode = DIFF_FILE_CANON_MODE(mode); memcpy(spec->sha1, sha1, 20); spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); } @@ -390,7 +390,8 @@ static void remove_tempfile_on_signal(int signo) * infile2 infile2-sha1 infile2-mode [ rename-to ] * */ -static void run_external_diff(const char *name, +static void run_external_diff(const char *pgm, + const char *name, const char *other, struct diff_filespec *one, struct diff_filespec *two, @@ -418,7 +419,6 @@ static void run_external_diff(const char *name, if (pid < 0) die("unable to fork"); if (!pid) { - const char *pgm = external_diff(); if (pgm) { if (one && two) { const char *exec_arg[10]; @@ -468,6 +468,30 @@ static void run_external_diff(const char *name, remove_tempfile(); } +static void run_diff(const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg) +{ + const char *pgm = external_diff(); + if (!pgm && + DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && + (S_IFMT & one->mode) != (S_IFMT & two->mode)) { + /* a filepair that changes between file and symlink + * needs to be split into deletion and creation. + */ + struct diff_filespec *null = alloc_filespec(two->path); + run_external_diff(NULL, name, other, one, null, xfrm_msg); + free(null); + null = alloc_filespec(one->path); + run_external_diff(NULL, name, other, null, two, xfrm_msg); + free(null); + } + else + run_external_diff(pgm, name, other, one, two, xfrm_msg); +} + void diff_setup(int reverse_diff_) { reverse_diff = reverse_diff_; @@ -493,8 +517,6 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, dp->one = one; dp->two = two; dp->score = 0; - dp->orig_order = queue->nr; - dp->rename_rank = 0; diff_q(queue, dp); return dp; } @@ -505,10 +527,22 @@ static void diff_flush_raw(struct diff_filepair *p, { int two_paths; char status[10]; + + if (line_termination) { + const char *err = "path %s cannot be expressed without -z"; + if (strchr(p->one->path, line_termination) || + strchr(p->one->path, inter_name_termination)) + die(err, p->one->path); + if (strchr(p->two->path, line_termination) || + strchr(p->two->path, inter_name_termination)) + die(err, p->two->path); + } + switch (p->status) { case 'C': case 'R': two_paths = 1; - sprintf(status, "%c%1d", p->status, p->score); + sprintf(status, "%c%03d", p->status, + (int)(0.5 + p->score * 100.0/MAX_SCORE)); break; default: two_paths = 0; @@ -543,9 +577,11 @@ int diff_unmodified_pair(struct diff_filepair *p) one = p->one; two = p->two; - /* deletion, addition, mode change and renames are all interesting. */ + /* deletion, addition, mode or type change + * and rename are all interesting. + */ if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) || - (one->mode != two->mode) || + DIFF_PAIR_MODE_CHANGED(p) || strcmp(one->path, two->path)) return 0; @@ -598,9 +634,9 @@ static void diff_flush_patch(struct diff_filepair *p) } if (DIFF_PAIR_UNMERGED(p)) - run_external_diff(name, NULL, NULL, NULL, NULL); + run_diff(name, NULL, NULL, NULL, NULL); else - run_external_diff(name, other, p->one, p->two, msg); + run_diff(name, other, p->one, p->two, msg); } int diff_needs_to_stay(struct diff_queue_struct *q, int i, @@ -628,50 +664,127 @@ int diff_needs_to_stay(struct diff_queue_struct *q, int i, int diff_queue_is_empty(void) { struct diff_queue_struct *q = &diff_queued_diff; - return q->nr == 0; + int i; + for (i = 0; i < q->nr; i++) + if (!diff_unmodified_pair(q->queue[i])) + return 0; + return 1; } -static void diff_resolve_rename_copy(void) +#if DIFF_DEBUG +void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) +{ + fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n", + x, one ? : "", + s->path, + DIFF_FILE_VALID(s) ? "valid" : "invalid", + s->mode, + s->sha1_valid ? sha1_to_hex(s->sha1) : ""); + fprintf(stderr, "queue[%d] %s size %lu flags %d\n", + x, one ? : "", + s->size, s->xfrm_flags); +} + +void diff_debug_filepair(const struct diff_filepair *p, int i) +{ + diff_debug_filespec(p->one, i, "one"); + diff_debug_filespec(p->two, i, "two"); + fprintf(stderr, "score %d, status %c\n", + p->score, p->status ? : '?'); +} + +void diff_debug_queue(const char *msg, struct diff_queue_struct *q) { int i; - struct diff_queue_struct *q = &diff_queued_diff; + if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "q->nr = %d\n", q->nr); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - p->status = 0; + diff_debug_filepair(p, i); + } +} +#endif + +static void diff_resolve_rename_copy(void) +{ + int i, j; + struct diff_filepair *p, *pp; + struct diff_queue_struct *q = &diff_queued_diff; + + /* This should not depend on the ordering of things. */ + + diff_debug_queue("resolve-rename-copy", q); + + for (i = 0; i < q->nr; i++) { + p = q->queue[i]; + p->status = 0; /* undecided */ if (DIFF_PAIR_UNMERGED(p)) p->status = 'U'; else if (!DIFF_FILE_VALID((p)->one)) p->status = 'N'; else if (!DIFF_FILE_VALID((p)->two)) { - /* maybe earlier one said 'R', meaning - * it will take it, in which case we do - * not need to keep 'D'. + /* Deletion record should be omitted if there + * are rename/copy entries using this one as + * the source. Then we can say one of them + * is a rename and the rest are copies. */ - int j; - for (j = 0; j < i; j++) { - struct diff_filepair *pp = q->queue[j]; - if (pp->status == 'R' && - !strcmp(pp->one->path, p->one->path)) + p->status = 'D'; + for (j = 0; j < q->nr; j++) { + pp = q->queue[j]; + if (!strcmp(pp->one->path, p->one->path) && + strcmp(pp->one->path, pp->two->path)) { + p->status = 'X'; break; + } } - if (j < i) - continue; - p->status = 'D'; } + else if (DIFF_PAIR_TYPE_CHANGED(p)) + p->status = 'T'; + + /* from this point on, we are dealing with a pair + * whose both sides are valid and of the same type, i.e. + * either in-place edit or rename/copy edit. + */ else if (strcmp(p->one->path, p->two->path)) { - /* This is rename or copy. Which one is it? */ - if (diff_needs_to_stay(q, i+1, p->one)) - p->status = 'C'; - else + /* See if there is somebody else anywhere that + * will keep the path (either modified or + * unmodified). If so, we have to be a copy, + * not a rename. In addition, if there is + * some other rename or copy that comes later + * than us that uses the same source, we + * have to be a copy, not a rename. + */ + for (j = 0; j < q->nr; j++) { + pp = q->queue[j]; + if (strcmp(pp->one->path, p->one->path)) + continue; + if (!strcmp(pp->one->path, pp->two->path)) { + if (DIFF_FILE_VALID(pp->two)) { + /* non-delete */ + p->status = 'C'; + break; + } + continue; + } + /* pp is a rename/copy ... */ + if (i < j) { + /* ... and comes later than us */ + p->status = 'C'; + break; + } + } + if (!p->status) p->status = 'R'; } - else if (memcmp(p->one->sha1, p->two->sha1, 20)) + else if (memcmp(p->one->sha1, p->two->sha1, 20) || + p->one->mode != p->two->mode) p->status = 'M'; - else { - /* we do not need this one */ - p->status = 0; - } + else + /* this is a "no-change" entry */ + p->status = 'X'; } + diff_debug_queue("resolve-rename-copy done", q); } void diff_flush(int diff_output_style, int resolve_rename_copy) @@ -688,8 +801,11 @@ void diff_flush(int diff_output_style, int resolve_rename_copy) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (p->status == 0) + if ((diff_output_style == DIFF_FORMAT_NO_OUTPUT) || + (p->status == 'X')) continue; + if (p->status == 0) + die("internal error in diff-resolve-rename-copy"); switch (diff_output_style) { case DIFF_FORMAT_PATCH: diff_flush_patch(p);