Skip to content

Commit fb13227

Browse files
committed
git-diff: squelch "empty" diffs
After starting to edit a working tree file but later when your edit ends up identical to the original (this can also happen when you ran a wholesale regexp replace with something like "perl -i" that does not actually modify many of the paths), "git diff" between the index and the working tree outputs many "empty" diffs that show "diff --git" headers and nothing else, because these paths are stat-dirty. While it was a way to warn the user that the earlier action of the user made the index ineffective as an optimization mechanism, it was felt too loud for the purpose of warning even to experienced users, and also resulted in confusing people new to git. This replaces the "empty" diffs with a single warning message at the end. Having many such paths hurts performance, and you can run "git-update-index --refresh" to update the lstat(2) information recorded in the index in such a case. "git-status" does so as a side effect, and that is more familiar to the end-user, so we recommend it to them. The change affects only "git diff" that outputs patch text, because that is where the annoyance of too many "empty" diff is most strongly felt, and because the warning message can be safely ignored by downstream tools without getting mistaken as part of the patch. For the low-level "git diff-files" and "git diff-index", the traditional behaviour is retained. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 180787c commit fb13227

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

builtin-diff.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
222222
prefix = setup_git_directory_gently(&nongit);
223223
git_config(git_diff_ui_config);
224224
init_revisions(&rev, prefix);
225+
rev.diffopt.skip_stat_unmatch = 1;
225226

226227
if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix))
227228
argc = 0;
@@ -344,5 +345,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
344345
ent, ents);
345346
if (rev.diffopt.exit_with_status)
346347
result = rev.diffopt.has_changes;
348+
349+
if ((rev.diffopt.output_format & DIFF_FORMAT_PATCH)
350+
&& (1 < rev.diffopt.skip_stat_unmatch))
351+
printf("Warning: %d path%s touched but unmodified. "
352+
"Consider running git-status.\n",
353+
rev.diffopt.skip_stat_unmatch - 1,
354+
rev.diffopt.skip_stat_unmatch == 2 ? "" : "s");
347355
return result;
348356
}

diff.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,11 +3143,63 @@ static void diffcore_apply_filter(const char *filter)
31433143
*q = outq;
31443144
}
31453145

3146+
static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
3147+
{
3148+
int i;
3149+
struct diff_queue_struct *q = &diff_queued_diff;
3150+
struct diff_queue_struct outq;
3151+
outq.queue = NULL;
3152+
outq.nr = outq.alloc = 0;
3153+
3154+
for (i = 0; i < q->nr; i++) {
3155+
struct diff_filepair *p = q->queue[i];
3156+
3157+
/*
3158+
* 1. Entries that come from stat info dirtyness
3159+
* always have both sides (iow, not create/delete),
3160+
* one side of the object name is unknown, with
3161+
* the same mode and size. Keep the ones that
3162+
* do not match these criteria. They have real
3163+
* differences.
3164+
*
3165+
* 2. At this point, the file is known to be modified,
3166+
* with the same mode and size, and the object
3167+
* name of one side is unknown. Need to inspect
3168+
* the identical contents.
3169+
*/
3170+
if (!DIFF_FILE_VALID(p->one) || /* (1) */
3171+
!DIFF_FILE_VALID(p->two) ||
3172+
(p->one->sha1_valid && p->two->sha1_valid) ||
3173+
(p->one->mode != p->two->mode) ||
3174+
diff_populate_filespec(p->one, 1) ||
3175+
diff_populate_filespec(p->two, 1) ||
3176+
(p->one->size != p->two->size) ||
3177+
3178+
diff_populate_filespec(p->one, 0) || /* (2) */
3179+
diff_populate_filespec(p->two, 0) ||
3180+
memcmp(p->one->data, p->two->data, p->one->size))
3181+
diff_q(&outq, p);
3182+
else {
3183+
/*
3184+
* The caller can subtract 1 from skip_stat_unmatch
3185+
* to determine how many paths were dirty only
3186+
* due to stat info mismatch.
3187+
*/
3188+
diffopt->skip_stat_unmatch++;
3189+
diff_free_filepair(p);
3190+
}
3191+
}
3192+
free(q->queue);
3193+
*q = outq;
3194+
}
3195+
31463196
void diffcore_std(struct diff_options *options)
31473197
{
31483198
if (options->quiet)
31493199
return;
31503200

3201+
if (options->skip_stat_unmatch && !options->find_copies_harder)
3202+
diffcore_skip_stat_unmatch(options);
31513203
if (options->break_opt != -1)
31523204
diffcore_break(options->break_opt);
31533205
if (options->detect_rename)

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct diff_options {
6565
int context;
6666
int break_opt;
6767
int detect_rename;
68+
int skip_stat_unmatch;
6869
int line_termination;
6970
int output_format;
7071
int pickaxe_opts;

0 commit comments

Comments
 (0)