Diff options
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; } |
