Diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/shared.c | 79 | |
| -rw-r--r-- | source/shared.h | 24 | |
| -rw-r--r-- | source/ui-diff.c | 11 | |
| -rw-r--r-- | source/ui-patch.c | 13 |
4 files changed, 116 insertions, 11 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; } diff --git a/source/shared.h b/source/shared.h index f182962..b24c690 100644 --- a/source/shared.h +++ b/source/shared.h @@ -25,8 +25,9 @@ extern struct cgit_repo *cgit_get_repoinfo(const char *url); /* * A diff format callback that hands each filepair to the filepair_fn data - * points at. ISO C has no conversion between a function pointer and void *, - * so the function travels by address. + * points at, or only the pair landing on single_follow when that is set. ISO + * C has no conversion between a function pointer and void *, so the function + * travels by address. */ extern void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options, void *data); @@ -45,6 +46,25 @@ extern int cgit_diff_files(const struct object_id *old_oid, const struct object_ */ extern void cgit_diff_limit_path(struct diff_options *opt, const char *prefix); +/* + * Limit a diff to path and to source, the name it was renamed from, both + * taken literally, with rename detection on so that git pairs the two. + */ +extern void cgit_diff_limit_rename(struct diff_options *opt, const char *path, const char *source); + +/* + * The name path had in the old tree when the change between the two trees + * renamed it, or NULL when path is no renamed file's new name. The result is + * the caller's to free. + */ +extern char *cgit_rename_source(const struct object_id *old_oid, const struct object_id *new_oid, + const char *path); + +/* + * Hand fn each filepair between the two trees, limited to the paths below + * prefix when one is given. A prefix naming a file the old tree lacks still + * comes back as the rename that produced it. + */ extern void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new_oid, filepair_fn fn, const char *prefix, int ignorews); extern void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix); diff --git a/source/ui-diff.c b/source/ui-diff.c index 315a8f0..0e2e75e 100644 --- a/source/ui-diff.c +++ b/source/ui-diff.c @@ -510,8 +510,15 @@ static void print_raw_patch(const struct object_id *old_tree_oid, const struct o repo_diff_setup(the_repository, &diffopt); diffopt.output_format = DIFF_FORMAT_PATCH; diffopt.flags.recursive = 1; - if (prefix) - cgit_diff_limit_path(&diffopt, prefix); + if (prefix) { + char *source = cgit_rename_source(old_tree_oid, new_tree_oid, prefix); + + if (source) + cgit_diff_limit_rename(&diffopt, prefix, source); + else + cgit_diff_limit_path(&diffopt, prefix); + free(source); + } diff_setup_done(&diffopt); ctx.page.mimetype = "text/plain"; diff --git a/source/ui-patch.c b/source/ui-patch.c index 0fa9a2e..a60e7cc 100644 --- a/source/ui-patch.c +++ b/source/ui-patch.c @@ -12,6 +12,7 @@ #include "html.h" #include "ui-patch.h" #include "ui-shared.h" +#include "shared.h" // Room for two hex object ids, the two dots between them, and the null byte. #define REV_RANGE_LEN (2 * GIT_MAX_HEXSZ + 3) @@ -24,6 +25,7 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref struct strvec rev_argv = STRVEC_INIT; char rev_range[REV_RANGE_LEN]; const char *version; + char *source = NULL; if (!new_rev) new_rev = ctx.qry.head; @@ -47,9 +49,20 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref else strvec_push(&rev_argv, "--format=format:From %H Mon Sep 17 00:00:00 2001%n" "From: %an%nDate: %aD%n%w(78,0,1)Subject: %s%n%n%w(0)%b"); + // A file the change renamed is limited by its old name as well, with + // rename detection on, so that git pairs the two into the rename. That + // name comes from the tree rather than the request, so it is matched + // literally. + if (prefix) + source = cgit_rename_source(&old_oid, &new_oid, prefix); + if (source) + strvec_push(&rev_argv, "-M"); strvec_pushl(&rev_argv, rev_range, "--", NULL); if (prefix) strvec_push(&rev_argv, prefix); + if (source) + strvec_pushf(&rev_argv, ":(literal)%s", source); + free(source); // Git inlines a blob up to its own big-file threshold, far past what // any other view inflates, so max-blob-size is applied through it. |
