Diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Show a renamed file's diff as the rename
A diff limited to the new name cannot see the old one, so the
diffstat's link for a renamed file, the commit page reached from that
file's log, and the file's rawdiff and patch all showed every line as
added. A path the old tree lacks is now diffed across the whole commit
with rename detection aimed at it alone, the way git blame looks for a
rename, so it is compared with the commit's deleted files and nothing
else. Doing it in cgit_diff_tree keeps the log's counts for that commit
in step with the page they link to.
The rawdiff and patch pages leave the diff to git's own output, so for
them that search finds the old name, and both names go into the limit
with rename detection on.
Diffstat (limited to 'source/shared.c')
| -rw-r--r-- | source/shared.c | 79 |
1 file changed, 72 insertions, 7 deletions
diff --git a/source/shared.c b/source/shared.c index c189b11..fcdcd24 100644 --- a/source/shared.c +++ b/source/shared.c @@ -87,15 +87,19 @@ struct cgit_repo *cgit_get_repoinfo(const char *url) return NULL; } -void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options UNUSED, void *data) +void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) { filepair_fn fn = *(filepair_fn *)data; + const char *wanted = options->single_follow; int i; for (i = 0; i < q->nr; i++) { // An unmerged path has no single pair of blobs to show. if (q->queue[i]->status == 'U') continue; + // A diff aimed at one path ran over the whole commit. + if (wanted && strcmp(q->queue[i]->two->path, wanted)) + continue; fn(q->queue[i]); } } @@ -215,11 +219,48 @@ int cgit_diff_files(const struct object_id *old_oid, const struct object_id *new return 0; } +static void set_literal_item(struct pathspec_item *item, const char *path) +{ + int len = path ? strlen(path) : 0; + + // nowildcard_len matching len makes git treat the path as literal + // rather than as a glob. + *item = (struct pathspec_item){ .match = path, .len = len, .nowildcard_len = len }; +} + void cgit_diff_limit_path(struct diff_options *opt, const char *prefix) { cgit_literal_pathspec(&opt->pathspec, xmalloc(sizeof(struct pathspec_item)), xstrdup(prefix)); } +void cgit_diff_limit_rename(struct diff_options *opt, const char *path, const char *source) +{ + struct pathspec_item *items = xcalloc(2, sizeof(*items)); + + set_literal_item(&items[0], xstrdup(path)); + set_literal_item(&items[1], xstrdup(source)); + opt->pathspec.nr = 2; + opt->pathspec.items = items; + opt->detect_rename = DIFF_DETECT_RENAME; +} + +/* + * Whether path names something other than a directory in the new tree that + * the old tree lacks, which is where a rename lands. + */ +static int path_is_new(const struct object_id *old_oid, const struct object_id *new_oid, + const char *path) +{ + struct object_id oid; + unsigned short mode; + + if (!*path || !old_oid || is_null_oid(old_oid)) + return 0; + if (get_tree_entry(the_repository, new_oid, path, &oid, &mode) || S_ISDIR(mode)) + return 0; + return get_tree_entry(the_repository, old_oid, path, &oid, &mode) != 0; +} + void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new_oid, filepair_fn fn, const char *prefix, int ignorews) { @@ -234,7 +275,12 @@ void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); opt.format_callback = cgit_diff_tree_cb; opt.format_callback_data = &fn; - if (prefix) + // The source of a rename lies outside a limit naming its destination, + // so a new path is diffed across the whole commit instead, with + // rename detection aimed at that path alone. + if (prefix && path_is_new(old_oid, new_oid, prefix)) + opt.single_follow = prefix; + else if (prefix) cgit_diff_limit_path(&opt, prefix); diff_setup_done(&opt); @@ -246,6 +292,29 @@ void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new diff_flush(&opt); } +// The old name cgit_rename_source's diff finds, which a filepair_fn has no +// other way to hand back. +static char *rename_source; + +static void note_rename_source(struct diff_filepair *pair) +{ + if (pair->status == DIFF_STATUS_RENAMED && !rename_source) + rename_source = xstrdup(pair->one->path); +} + +char *cgit_rename_source(const struct object_id *old_oid, const struct object_id *new_oid, + const char *path) +{ + char *source; + + if (!path_is_new(old_oid, new_oid, path)) + return NULL; + cgit_diff_tree(old_oid, new_oid, note_rename_source, path, 0); + source = rename_source; + rename_source = NULL; + return source; +} + void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix) { const struct object_id *old_oid = NULL; @@ -262,11 +331,7 @@ int cgit_blob_too_large(size_t size) void cgit_literal_pathspec(struct pathspec *ps, struct pathspec_item *item, const char *path) { - int len = path ? strlen(path) : 0; - - // nowildcard_len matching len makes git treat the path as literal - // rather than as a glob. - *item = (struct pathspec_item){ .match = path, .len = len, .nowildcard_len = len }; + set_literal_item(item, path); ps->nr = path ? 1 : 0; ps->items = item; } |
