diff: add ability to insert additional headers for paths

When additional headers are provided, we need to * add diff_filepairs to diff_queued_diff for each paths in the additional headers map which, unless that path is part of another diff_filepair already found in diff_queued_diff * format the headers (colorization, line_prefix for --graph) * make sure the various codepaths that attempt to return early if there are "no changes" take into account the headers that need to be shown. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Feb 2, 2022 at 02:37 UTC 95433eeed9eac439eb21eb30105354b15e71302e
3 files changed +123 -6
diff.c
+120 -4
@@ -27,6 +27,7 @@
27 #include "help.h"
28 #include "promisor-remote.h"
29 #include "dir.h"
30 +#include "strmap.h"
31
32 #ifdef NO_FAST_WORKING_DIRECTORY
33 #define FAST_WORKING_DIRECTORY 0
@@ -3406,6 +3407,31 @@ struct userdiff_driver *get_textconv(struct repository *r,
3407 return userdiff_get_textconv(r, one->driver);
3408 }
3409
3410 +static struct strbuf *additional_headers(struct diff_options *o,
3411 + const char *path)
3412 +{
3413 + if (!o->additional_path_headers)
3414 + return NULL;
3415 + return strmap_get(o->additional_path_headers, path);
3416 +}
3417 +
3418 +static void add_formatted_headers(struct strbuf *msg,
3419 + struct strbuf *more_headers,
3420 + const char *line_prefix,
3421 + const char *meta,
3422 + const char *reset)
3423 +{
3424 + char *next, *newline;
3425 +
3426 + for (next = more_headers->buf; *next; next = newline) {
3427 + newline = strchrnul(next, '\n');
3428 + strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
3429 + (int)(newline - next), next, reset);
3430 + if (*newline)
3431 + newline++;
3432 + }
3433 +}
3434 +
3435 static void builtin_diff(const char *name_a,
3436 const char *name_b,
3437 struct diff_filespec *one,
@@ -3464,6 +3490,17 @@ static void builtin_diff(const char *name_a,
3490 b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
3491 lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
3492 lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
3493 + if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
3494 + /*
3495 + * We should only reach this point for pairs from
3496 + * create_filepairs_for_header_only_notifications(). For
3497 + * these, we should avoid the "/dev/null" special casing
3498 + * above, meaning we avoid showing such pairs as either
3499 + * "new file" or "deleted file" below.
3500 + */
3501 + lbl[0] = a_one;
3502 + lbl[1] = b_two;
3503 + }
3504 strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
3505 if (lbl[0][0] == '/') {
3506 /* /dev/null */
@@ -4328,6 +4365,7 @@ static void fill_metainfo(struct strbuf *msg,
4365 const char *set = diff_get_color(use_color, DIFF_METAINFO);
4366 const char *reset = diff_get_color(use_color, DIFF_RESET);
4367 const char *line_prefix = diff_line_prefix(o);
4368 + struct strbuf *more_headers = NULL;
4369
4370 *must_show_header = 1;
4371 strbuf_init(msg, PATH_MAX * 2 + 300);
@@ -4364,6 +4402,11 @@ static void fill_metainfo(struct strbuf *msg,
4402 default:
4403 *must_show_header = 0;
4404 }
4405 + if ((more_headers = additional_headers(o, name))) {
4406 + add_formatted_headers(msg, more_headers,
4407 + line_prefix, set, reset);
4408 + *must_show_header = 1;
4409 + }
4410 if (one && two && !oideq(&one->oid, &two->oid)) {
4411 const unsigned hexsz = the_hash_algo->hexsz;
4412 int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
@@ -5852,12 +5895,27 @@ int diff_unmodified_pair(struct diff_filepair *p)
5895
5896 static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
5897 {
5855 - if (diff_unmodified_pair(p))
5898 + int include_conflict_headers =
5899 + (additional_headers(o, p->one->path) &&
5900 + (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
5901 +
5902 + /*
5903 + * Check if we can return early without showing a diff. Note that
5904 + * diff_filepair only stores {oid, path, mode, is_valid}
5905 + * information for each path, and thus diff_unmodified_pair() only
5906 + * considers those bits of info. However, we do not want pairs
5907 + * created by create_filepairs_for_header_only_notifications()
5908 + * (which always look like unmodified pairs) to be ignored, so
5909 + * return early if both p is unmodified AND we don't want to
5910 + * include_conflict_headers.
5911 + */
5912 + if (diff_unmodified_pair(p) && !include_conflict_headers)
5913 return;
5914
5915 + /* Actually, we can also return early to avoid showing tree diffs */
5916 if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
5917 (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
5860 - return; /* no tree diffs in patch format */
5918 + return;
5919
5920 run_diff(p, o);
5921 }
@@ -5888,10 +5946,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
5946 run_checkdiff(p, o);
5947 }
5948
5891 -int diff_queue_is_empty(void)
5949 +int diff_queue_is_empty(struct diff_options *o)
5950 {
5951 struct diff_queue_struct *q = &diff_queued_diff;
5952 int i;
5953 + int include_conflict_headers =
5954 + (o->additional_path_headers &&
5955 + (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
5956 +
5957 + if (include_conflict_headers)
5958 + return 0;
5959 +
5960 for (i = 0; i < q->nr; i++)
5961 if (!diff_unmodified_pair(q->queue[i]))
5962 return 0;
@@ -6325,6 +6390,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
6390 warning(_(rename_limit_advice), varname, needed);
6391 }
6392
6393 +static void create_filepairs_for_header_only_notifications(struct diff_options *o)
6394 +{
6395 + struct strset present;
6396 + struct diff_queue_struct *q = &diff_queued_diff;
6397 + struct hashmap_iter iter;
6398 + struct strmap_entry *e;
6399 + int i;
6400 +
6401 + strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
6402 +
6403 + /*
6404 + * Find out which paths exist in diff_queued_diff, preferring
6405 + * one->path for any pair that has multiple paths.
6406 + */
6407 + for (i = 0; i < q->nr; i++) {
6408 + struct diff_filepair *p = q->queue[i];
6409 + char *path = p->one->path ? p->one->path : p->two->path;
6410 +
6411 + if (strmap_contains(o->additional_path_headers, path))
6412 + strset_add(&present, path);
6413 + }
6414 +
6415 + /*
6416 + * Loop over paths in additional_path_headers; for each NOT already
6417 + * in diff_queued_diff, create a synthetic filepair and insert that
6418 + * into diff_queued_diff.
6419 + */
6420 + strmap_for_each_entry(o->additional_path_headers, &iter, e) {
6421 + if (!strset_contains(&present, e->key)) {
6422 + struct diff_filespec *one, *two;
6423 + struct diff_filepair *p;
6424 +
6425 + one = alloc_filespec(e->key);
6426 + two = alloc_filespec(e->key);
6427 + fill_filespec(one, null_oid(), 0, 0);
6428 + fill_filespec(two, null_oid(), 0, 0);
6429 + p = diff_queue(q, one, two);
6430 + p->status = DIFF_STATUS_MODIFIED;
6431 + }
6432 + }
6433 +
6434 + /* Re-sort the filepairs */
6435 + diffcore_fix_diff_index();
6436 +
6437 + /* Cleanup */
6438 + strset_clear(&present);
6439 +}
6440 +
6441 static void diff_flush_patch_all_file_pairs(struct diff_options *o)
6442 {
6443 int i;
@@ -6337,6 +6450,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
6450 if (o->color_moved)
6451 o->emitted_symbols = &esm;
6452
6453 + if (o->additional_path_headers)
6454 + create_filepairs_for_header_only_notifications(o);
6455 +
6456 for (i = 0; i < q->nr; i++) {
6457 struct diff_filepair *p = q->queue[i];
6458 if (check_pair_status(p))
@@ -6413,7 +6529,7 @@ void diff_flush(struct diff_options *options)
6529 * Order: raw, stat, summary, patch
6530 * or: name/name-status/checkdiff (other bits clear)
6531 */
6416 - if (!q->nr)
6532 + if (!q->nr && !options->additional_path_headers)
6533 goto free_queue;
6534
6535 if (output_format & (DIFF_FORMAT_RAW |
diff.h
+2 -1
@@ -395,6 +395,7 @@ struct diff_options {
395
396 struct repository *repo;
397 struct option *parseopts;
398 + struct strmap *additional_path_headers;
399
400 int no_free;
401 };
@@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void);
594 " show all files diff when -S is used and hit is found.\n" \
595 " -a --text treat all files as text.\n"
596
596 -int diff_queue_is_empty(void);
597 +int diff_queue_is_empty(struct diff_options *o);
598 void diff_flush(struct diff_options*);
599 void diff_free(struct diff_options*);
600 void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
log-tree.c
+1 -1
@@ -852,7 +852,7 @@ int log_tree_diff_flush(struct rev_info *opt)
852 opt->shown_dashes = 0;
853 diffcore_std(&opt->diffopt);
854
855 - if (diff_queue_is_empty()) {
855 + if (diff_queue_is_empty(&opt->diffopt)) {
856 int saved_fmt = opt->diffopt.output_format;
857 opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
858 diff_flush(&opt->diffopt);