Skip to content

Commit d634d61

Browse files
stefanbellergitster
authored andcommitted
xdiff: implement empty line chunk heuristic
In order to produce the smallest possible diff and combine several diff hunks together, we implement a heuristic from GNU Diff which moves diff hunks forward as far as possible when we find common context above and below a diff hunk. This sometimes produces less readable diffs when writing C, Shell, or other programming languages, ie: ... /* + * + * + */ + +/* ... instead of the more readable equivalent of ... +/* + * + * + */ + /* ... Implement the following heuristic to (optionally) produce the desired output. If there are diff chunks which can be shifted around, shift each hunk such that the last common empty line is below the chunk with the rest of the context above. This heuristic appears to resolve the above example and several other common issues without producing significantly weird results. However, as with any heuristic it is not really known whether this will always be more optimal. Thus, it can be disabled via diff.compactionHeuristic. Signed-off-by: Stefan Beller <[email protected]> Signed-off-by: Jacob Keller <[email protected]> Signed-off-by: Stefan Beller <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 92e5b62 commit d634d61

File tree

5 files changed

+50
-0
lines changed

5 files changed

+50
-0
lines changed

Documentation/diff-config.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ diff.tool::
166166

167167
include::mergetools-diff.txt[]
168168

169+
diff.compactionHeuristic::
170+
Set this option to enable an experimental heuristic that
171+
shifts the hunk boundary in an attempt to make the resulting
172+
patch easier to read.
173+
169174
diff.algorithm::
170175
Choose a diff algorithm. The variants are as follows:
171176
+

Documentation/diff-options.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ ifndef::git-format-patch[]
6363
Synonym for `-p --raw`.
6464
endif::git-format-patch[]
6565

66+
--compaction-heuristic::
67+
--no-compaction-heuristic::
68+
These are to help debugging and tuning an experimental
69+
heuristic that shifts the hunk boundary in an attempt to
70+
make the resulting patch easier to read.
71+
6672
--minimal::
6773
Spend extra time to make sure the smallest possible
6874
diff is produced.

diff.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#endif
2626

2727
static int diff_detect_rename_default;
28+
static int diff_compaction_heuristic = 1;
2829
static int diff_rename_limit_default = 400;
2930
static int diff_suppress_blank_empty;
3031
static int diff_use_color_default = -1;
@@ -183,6 +184,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
183184
diff_detect_rename_default = git_config_rename(var, value);
184185
return 0;
185186
}
187+
if (!strcmp(var, "diff.compactionheuristic")) {
188+
diff_compaction_heuristic = git_config_bool(var, value);
189+
return 0;
190+
}
186191
if (!strcmp(var, "diff.autorefreshindex")) {
187192
diff_auto_refresh_index = git_config_bool(var, value);
188193
return 0;
@@ -3235,6 +3240,8 @@ void diff_setup(struct diff_options *options)
32353240
options->use_color = diff_use_color_default;
32363241
options->detect_rename = diff_detect_rename_default;
32373242
options->xdl_opts |= diff_algorithm;
3243+
if (diff_compaction_heuristic)
3244+
DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
32383245

32393246
options->orderfile = diff_order_file_cfg;
32403247

@@ -3712,6 +3719,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
37123719
DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
37133720
else if (!strcmp(arg, "--ignore-blank-lines"))
37143721
DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
3722+
else if (!strcmp(arg, "--compaction-heuristic"))
3723+
DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
3724+
else if (!strcmp(arg, "--no-compaction-heuristic"))
3725+
DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
37153726
else if (!strcmp(arg, "--patience"))
37163727
options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
37173728
else if (!strcmp(arg, "--histogram"))

xdiff/xdiff.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ extern "C" {
4141

4242
#define XDF_IGNORE_BLANK_LINES (1 << 7)
4343

44+
#define XDF_COMPACTION_HEURISTIC (1 << 8)
45+
4446
#define XDL_EMIT_FUNCNAMES (1 << 0)
4547
#define XDL_EMIT_COMMON (1 << 1)
4648
#define XDL_EMIT_FUNCCONTEXT (1 << 2)

xdiff/xdiffi.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
400400
}
401401

402402

403+
static int is_blank_line(xrecord_t **recs, long ix, long flags)
404+
{
405+
return xdl_blankline(recs[ix]->ptr, recs[ix]->size, flags);
406+
}
407+
403408
static int recs_match(xrecord_t **recs, long ixs, long ix, long flags)
404409
{
405410
return (recs[ixs]->ha == recs[ix]->ha &&
@@ -411,6 +416,7 @@ static int recs_match(xrecord_t **recs, long ixs, long ix, long flags)
411416
int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
412417
long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
413418
char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
419+
unsigned int blank_lines;
414420
xrecord_t **recs = xdf->recs;
415421

416422
/*
@@ -444,6 +450,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
444450

445451
do {
446452
grpsiz = ix - ixs;
453+
blank_lines = 0;
447454

448455
/*
449456
* If the line before the current change group, is equal to
@@ -478,6 +485,8 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
478485
* the group.
479486
*/
480487
while (ix < nrec && recs_match(recs, ixs, ix, flags)) {
488+
blank_lines += is_blank_line(recs, ix, flags);
489+
481490
rchg[ixs++] = 0;
482491
rchg[ix++] = 1;
483492

@@ -504,6 +513,23 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
504513
rchg[--ix] = 0;
505514
while (rchgo[--ixo]);
506515
}
516+
517+
/*
518+
* If a group can be moved back and forth, see if there is a
519+
* blank line in the moving space. If there is a blank line,
520+
* make sure the last blank line is the end of the group.
521+
*
522+
* As we already shifted the group forward as far as possible
523+
* in the earlier loop, we need to shift it back only if at all.
524+
*/
525+
if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) {
526+
while (ixs > 0 &&
527+
!is_blank_line(recs, ix - 1, flags) &&
528+
recs_match(recs, ixs - 1, ix - 1, flags)) {
529+
rchg[--ixs] = 1;
530+
rchg[--ix] = 0;
531+
}
532+
}
507533
}
508534

509535
return 0;

0 commit comments

Comments
 (0)