Skip to content

Commit 1eb4136

Browse files
committed
diff: --{rotate,skip}-to=<path>
In the implementation of "git difftool", there is a case where the user wants to start viewing the diffs at a specific path and continue on to the rest, optionally wrapping around to the beginning. Since it is somewhat cumbersome to implement such a feature as a post-processing step of "git diff" output, let's support it internally with two new options. - "git diff --rotate-to=C", when the resulting patch would show paths A B C D E without the option, would "rotate" the paths to shows patch to C D E A B instead. It is an error when there is no patch for C is shown. - "git diff --skip-to=C" would instead "skip" the paths before C, and shows patch to C D E. Again, it is an error when there is no patch for C is shown. - "git log [-p]" also accepts these two options, but it is not an error if there is no change to the specified path. Instead, the set of output paths are rotated or skipped to the specified path or the first path that sorts after the specified path. Signed-off-by: Junio C Hamano <[email protected]>
1 parent c6102b7 commit 1eb4136

File tree

12 files changed

+197
-1
lines changed

12 files changed

+197
-1
lines changed

Documentation/diff-options.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ matches a pattern if removing any number of the final pathname
700700
components matches the pattern. For example, the pattern "`foo*bar`"
701701
matches "`fooasdfbar`" and "`foo/bar/baz/asdf`" but not "`foobarx`".
702702

703+
--skip-to=<file>::
704+
--rotate-to=<file>::
705+
Discard the files before the named <file> from the output
706+
(i.e. 'skip to'), or move them to the end of the output
707+
(i.e. 'rotate to'). These were invented primarily for use
708+
of the `git difftool` command, and may not be very useful
709+
otherwise.
710+
703711
ifndef::git-format-patch[]
704712
-R::
705713
Swap two inputs; that is, show differences from index or

Documentation/gitdiffcore.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ into another list. There are currently 5 such transformations:
7474
- diffcore-merge-broken
7575
- diffcore-pickaxe
7676
- diffcore-order
77+
- diffcore-rotate
7778

7879
These are applied in sequence. The set of filepairs 'git diff-{asterisk}'
7980
commands find are used as the input to diffcore-break, and
@@ -276,6 +277,26 @@ Documentation
276277
t
277278
------------------------------------------------
278279

280+
diffcore-rotate: For Changing At Which Path Output Starts
281+
---------------------------------------------------------
282+
283+
This transformation takes one pathname, and rotates the set of
284+
filepairs so that the filepair for the given pathname comes first,
285+
optionally discarding the paths that come before it. This is used
286+
to implement the `--skip-to` and the `--rotate-to` options. It is
287+
an error when the specified pathname is not in the set of filepairs,
288+
but it is not useful to error out when used with "git log" family of
289+
commands, because it is unreasonable to expect that a given path
290+
would be modified by each and every commit shown by the "git log"
291+
command. For this reason, when used with "git log", the filepair
292+
that sorts the same as, or the first one that sorts after, the given
293+
pathname is where the output starts.
294+
295+
Use of this transformation combined with diffcore-order will produce
296+
unexpected results, as the input to this transformation is likely
297+
not sorted when diffcore-order is in effect.
298+
299+
279300
SEE ALSO
280301
--------
281302
linkgit:git-diff[1],

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,7 @@ LIB_OBJS += diffcore-delta.o
863863
LIB_OBJS += diffcore-order.o
864864
LIB_OBJS += diffcore-pickaxe.o
865865
LIB_OBJS += diffcore-rename.o
866+
LIB_OBJS += diffcore-rotate.o
866867
LIB_OBJS += dir-iterator.o
867868
LIB_OBJS += dir.o
868869
LIB_OBJS += editor.o

builtin/diff-files.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
5454
}
5555
if (!rev.diffopt.output_format)
5656
rev.diffopt.output_format = DIFF_FORMAT_RAW;
57+
rev.diffopt.rotate_to_strict = 1;
5758

5859
/*
5960
* Make sure there are NO revision (i.e. pending object) parameter,

builtin/diff-index.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
4141
if (!rev.diffopt.output_format)
4242
rev.diffopt.output_format = DIFF_FORMAT_RAW;
4343

44+
rev.diffopt.rotate_to_strict = 1;
45+
4446
/*
4547
* Make sure there is one revision (i.e. pending object),
4648
* and there is no revision filtering parameters.

builtin/diff-tree.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
156156
if (merge_base && opt->pending.nr != 2)
157157
die(_("--merge-base only works with two commits"));
158158

159+
opt->diffopt.rotate_to_strict = 1;
160+
159161
/*
160162
* NOTE! We expect "a..b" to expand to "^a b" but it is
161163
* perfectly valid for revision range parser to yield "b ^a",
@@ -192,6 +194,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
192194
int saved_nrl = 0;
193195
int saved_dcctc = 0;
194196

197+
opt->diffopt.rotate_to_strict = 0;
195198
if (opt->diffopt.detect_rename) {
196199
if (!the_index.cache)
197200
repo_read_index(the_repository);

builtin/diff.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
491491
}
492492

493493
rev.diffopt.flags.recursive = 1;
494+
rev.diffopt.rotate_to_strict = 1;
494495

495496
setup_diff_pager(&rev.diffopt);
496497

diff.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5348,6 +5348,19 @@ static int diff_opt_word_diff_regex(const struct option *opt,
53485348
return 0;
53495349
}
53505350

5351+
static int diff_opt_rotate_to(const struct option *opt, const char *arg, int unset)
5352+
{
5353+
struct diff_options *options = opt->value;
5354+
5355+
BUG_ON_OPT_NEG(unset);
5356+
if (!strcmp(opt->long_name, "skip-to"))
5357+
options->skip_instead_of_rotate = 1;
5358+
else
5359+
options->skip_instead_of_rotate = 0;
5360+
options->rotate_to = arg;
5361+
return 0;
5362+
}
5363+
53515364
static void prep_parse_options(struct diff_options *options)
53525365
{
53535366
struct option parseopts[] = {
@@ -5599,6 +5612,12 @@ static void prep_parse_options(struct diff_options *options)
55995612
DIFF_PICKAXE_REGEX, PARSE_OPT_NONEG),
56005613
OPT_FILENAME('O', NULL, &options->orderfile,
56015614
N_("control the order in which files appear in the output")),
5615+
OPT_CALLBACK_F(0, "rotate-to", options, N_("<path>"),
5616+
N_("show the change in the specified path first"),
5617+
PARSE_OPT_NONEG, diff_opt_rotate_to),
5618+
OPT_CALLBACK_F(0, "skip-to", options, N_("<path>"),
5619+
N_("skip the output to the specified path"),
5620+
PARSE_OPT_NONEG, diff_opt_rotate_to),
56025621
OPT_CALLBACK_F(0, "find-object", options, N_("<object-id>"),
56035622
N_("look for differences that change the number of occurrences of the specified object"),
56045623
PARSE_OPT_NONEG, diff_opt_find_object),
@@ -6669,6 +6688,8 @@ void diffcore_std(struct diff_options *options)
66696688
diffcore_pickaxe(options);
66706689
if (options->orderfile)
66716690
diffcore_order(options->orderfile);
6691+
if (options->rotate_to)
6692+
diffcore_rotate(options);
66726693
if (!options->found_follow)
66736694
/* See try_to_follow_renames() in tree-diff.c */
66746695
diff_resolve_rename_copy();

diff.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,27 @@ enum diff_submodule_format {
227227
struct diff_options {
228228
const char *orderfile;
229229

230+
/*
231+
* "--rotate-to=<file>" would start showing at <file> and when
232+
* the output reaches the end, wrap around by default.
233+
* Setting skip_instead_of_rotate to true stops the output at the
234+
* end, effectively discarding the earlier part of the output
235+
* before <file>'s diff (this is used to implement the
236+
* "--skip-to=<file>" option).
237+
*
238+
* When rotate_to_strict is set, it is an error if there is no
239+
* <file> in the diff. Otherwise, the output starts at the
240+
* path that is the same as, or first path that sorts after,
241+
* <file>. Because it is unreasonable to require the exact
242+
* match for "git log -p --rotate-to=<file>" (i.e. not all
243+
* commit would touch that single <file>), "git log" sets it
244+
* to false. "git diff" sets it to true to detect an error
245+
* in the command line option.
246+
*/
247+
const char *rotate_to;
248+
int skip_instead_of_rotate;
249+
int rotate_to_strict;
250+
230251
/**
231252
* A constant string (can and typically does contain newlines to look for
232253
* a block of text, not just a single line) to filter out the filepairs

diffcore-rotate.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (C) 2021, Google LLC.
3+
* Based on diffcore-order.c, which is Copyright (C) 2005, Junio C Hamano
4+
*/
5+
#include "cache.h"
6+
#include "diff.h"
7+
#include "diffcore.h"
8+
9+
void diffcore_rotate(struct diff_options *opt)
10+
{
11+
struct diff_queue_struct *q = &diff_queued_diff;
12+
struct diff_queue_struct outq;
13+
int rotate_to, i;
14+
15+
if (!q->nr)
16+
return;
17+
18+
for (i = 0; i < q->nr; i++) {
19+
int cmp = strcmp(opt->rotate_to, q->queue[i]->two->path);
20+
if (!cmp)
21+
break; /* exact match */
22+
if (!opt->rotate_to_strict && cmp < 0)
23+
break; /* q->queue[i] is now past the target pathname */
24+
}
25+
26+
if (q->nr <= i) {
27+
/* we did not find the specified path */
28+
if (opt->rotate_to_strict)
29+
die(_("No such path '%s' in the diff"), opt->rotate_to);
30+
return;
31+
}
32+
33+
DIFF_QUEUE_CLEAR(&outq);
34+
rotate_to = i;
35+
36+
for (i = rotate_to; i < q->nr; i++)
37+
diff_q(&outq, q->queue[i]);
38+
for (i = 0; i < rotate_to; i++) {
39+
if (opt->skip_instead_of_rotate)
40+
diff_free_filepair(q->queue[i]);
41+
else
42+
diff_q(&outq, q->queue[i]);
43+
}
44+
free(q->queue);
45+
*q = outq;
46+
}

0 commit comments

Comments
 (0)