Skip to content

Commit 8c98a68

Browse files
committed
Merge branch 'vn/revision-shorthand-for-side-branch-log'
"git log rev^..rev" is an often-used revision range specification to show what was done on a side branch merged at rev. This has gained a short-hand "rev^-1". In general "rev^-$n" is the same as "^rev^$n rev", i.e. what has happened on other branches while the history leading to nth parent was looking the other way. * vn/revision-shorthand-for-side-branch-log: revision: new rev^-n shorthand for rev^n..rev
2 parents 66c22ba + 8779351 commit 8c98a68

File tree

4 files changed

+180
-19
lines changed

4 files changed

+180
-19
lines changed

Documentation/revisions.txt

+15-2
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,23 @@ empty range that is both reachable and unreachable from HEAD.
283283

284284
Other <rev>{caret} Parent Shorthand Notations
285285
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
286-
Two other shorthands exist, particularly useful for merge commits,
286+
Three other shorthands exist, particularly useful for merge commits,
287287
for naming a set that is formed by a commit and its parent commits.
288288

289289
The 'r1{caret}@' notation means all parents of 'r1'.
290290

291291
The 'r1{caret}!' notation includes commit 'r1' but excludes all of its parents.
292292
By itself, this notation denotes the single commit 'r1'.
293293

294+
The '<rev>{caret}-{<n>}' notation includes '<rev>' but excludes the <n>th
295+
parent (i.e. a shorthand for '<rev>{caret}<n>..<rev>'), with '<n>' = 1 if
296+
not given. This is typically useful for merge commits where you
297+
can just pass '<commit>{caret}-' to get all the commits in the branch
298+
that was merged in merge commit '<commit>' (including '<commit>'
299+
itself).
300+
294301
While '<rev>{caret}<n>' was about specifying a single commit parent, these
295-
two notations consider all its parents. For example you can say
302+
three notations also consider its parents. For example you can say
296303
'HEAD{caret}2{caret}@', however you cannot say 'HEAD{caret}@{caret}2'.
297304

298305
Revision Range Summary
@@ -326,6 +333,10 @@ Revision Range Summary
326333
as giving commit '<rev>' and then all its parents prefixed with
327334
'{caret}' to exclude them (and their ancestors).
328335

336+
'<rev>{caret}-{<n>}', e.g. 'HEAD{caret}-, HEAD{caret}-2'::
337+
Equivalent to '<rev>{caret}<n>..<rev>', with '<n>' = 1 if not
338+
given.
339+
329340
Here are a handful of examples using the Loeliger illustration above,
330341
with each step in the notation's expansion and selection carefully
331342
spelt out:
@@ -339,6 +350,8 @@ spelt out:
339350
C I J F C
340351
B..C = ^B C C
341352
B...C = B ^F C G H D E B C
353+
B^- = B^..B
354+
= ^B^1 B E I J F B
342355
C^@ = C^1
343356
= F I J F
344357
B^@ = B^1 B^2 B^3

builtin/rev-parse.c

+41-13
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,30 @@ static int try_parent_shorthands(const char *arg)
298298
unsigned char sha1[20];
299299
struct commit *commit;
300300
struct commit_list *parents;
301-
int parents_only;
302-
303-
if ((dotdot = strstr(arg, "^!")))
304-
parents_only = 0;
305-
else if ((dotdot = strstr(arg, "^@")))
306-
parents_only = 1;
307-
308-
if (!dotdot || dotdot[2])
301+
int parent_number;
302+
int include_rev = 0;
303+
int include_parents = 0;
304+
int exclude_parent = 0;
305+
306+
if ((dotdot = strstr(arg, "^!"))) {
307+
include_rev = 1;
308+
if (dotdot[2])
309+
return 0;
310+
} else if ((dotdot = strstr(arg, "^@"))) {
311+
include_parents = 1;
312+
if (dotdot[2])
313+
return 0;
314+
} else if ((dotdot = strstr(arg, "^-"))) {
315+
include_rev = 1;
316+
exclude_parent = 1;
317+
318+
if (dotdot[2]) {
319+
char *end;
320+
exclude_parent = strtoul(dotdot + 2, &end, 10);
321+
if (*end != '\0' || !exclude_parent)
322+
return 0;
323+
}
324+
} else
309325
return 0;
310326

311327
*dotdot = 0;
@@ -314,12 +330,24 @@ static int try_parent_shorthands(const char *arg)
314330
return 0;
315331
}
316332

317-
if (!parents_only)
318-
show_rev(NORMAL, sha1, arg);
319333
commit = lookup_commit_reference(sha1);
320-
for (parents = commit->parents; parents; parents = parents->next)
321-
show_rev(parents_only ? NORMAL : REVERSED,
322-
parents->item->object.oid.hash, arg);
334+
if (exclude_parent &&
335+
exclude_parent > commit_list_count(commit->parents)) {
336+
*dotdot = '^';
337+
return 0;
338+
}
339+
340+
if (include_rev)
341+
show_rev(NORMAL, sha1, arg);
342+
for (parents = commit->parents, parent_number = 1;
343+
parents;
344+
parents = parents->next, parent_number++) {
345+
if (exclude_parent && parent_number != exclude_parent)
346+
continue;
347+
348+
show_rev(include_parents ? NORMAL : REVERSED,
349+
parents->item->object.oid.hash, arg);
350+
}
323351

324352
*dotdot = '^';
325353
return 1;

revision.c

+30-4
Original file line numberDiff line numberDiff line change
@@ -1289,12 +1289,14 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned flags)
12891289
}
12901290
}
12911291

1292-
static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
1292+
static int add_parents_only(struct rev_info *revs, const char *arg_, int flags,
1293+
int exclude_parent)
12931294
{
12941295
unsigned char sha1[20];
12951296
struct object *it;
12961297
struct commit *commit;
12971298
struct commit_list *parents;
1299+
int parent_number;
12981300
const char *arg = arg_;
12991301

13001302
if (*arg == '^') {
@@ -1316,7 +1318,15 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
13161318
if (it->type != OBJ_COMMIT)
13171319
return 0;
13181320
commit = (struct commit *)it;
1319-
for (parents = commit->parents; parents; parents = parents->next) {
1321+
if (exclude_parent &&
1322+
exclude_parent > commit_list_count(commit->parents))
1323+
return 0;
1324+
for (parents = commit->parents, parent_number = 1;
1325+
parents;
1326+
parents = parents->next, parent_number++) {
1327+
if (exclude_parent && parent_number != exclude_parent)
1328+
continue;
1329+
13201330
it = &parents->item->object;
13211331
it->flags |= flags;
13221332
add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags);
@@ -1519,17 +1529,33 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
15191529
}
15201530
*dotdot = '.';
15211531
}
1532+
15221533
dotdot = strstr(arg, "^@");
15231534
if (dotdot && !dotdot[2]) {
15241535
*dotdot = 0;
1525-
if (add_parents_only(revs, arg, flags))
1536+
if (add_parents_only(revs, arg, flags, 0))
15261537
return 0;
15271538
*dotdot = '^';
15281539
}
15291540
dotdot = strstr(arg, "^!");
15301541
if (dotdot && !dotdot[2]) {
15311542
*dotdot = 0;
1532-
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
1543+
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
1544+
*dotdot = '^';
1545+
}
1546+
dotdot = strstr(arg, "^-");
1547+
if (dotdot) {
1548+
int exclude_parent = 1;
1549+
1550+
if (dotdot[2]) {
1551+
char *end;
1552+
exclude_parent = strtoul(dotdot + 2, &end, 10);
1553+
if (*end != '\0' || !exclude_parent)
1554+
return -1;
1555+
}
1556+
1557+
*dotdot = 0;
1558+
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
15331559
*dotdot = '^';
15341560
}
15351561

t/t6101-rev-parse-parents.sh

+94
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,98 @@ test_expect_success 'short SHA-1 works' '
102102
test_cmp_rev_output start "git rev-parse ${start%?}"
103103
'
104104

105+
# rev^- tests; we can use a simpler setup for these
106+
107+
test_expect_success 'setup for rev^- tests' '
108+
test_commit one &&
109+
test_commit two &&
110+
test_commit three &&
111+
112+
# Merge in a branch for testing rev^-
113+
git checkout -b branch &&
114+
git checkout HEAD^^ &&
115+
git merge -m merge --no-edit --no-ff branch &&
116+
git checkout -b merge
117+
'
118+
119+
# The merged branch has 2 commits + the merge
120+
test_expect_success 'rev-list --count merge^- = merge^..merge' '
121+
git rev-list --count merge^..merge >expect &&
122+
echo 3 >actual &&
123+
test_cmp expect actual
124+
'
125+
126+
# All rev^- rev-parse tests
127+
128+
test_expect_success 'rev-parse merge^- = merge^..merge' '
129+
git rev-parse merge^..merge >expect &&
130+
git rev-parse merge^- >actual &&
131+
test_cmp expect actual
132+
'
133+
134+
test_expect_success 'rev-parse merge^-1 = merge^..merge' '
135+
git rev-parse merge^1..merge >expect &&
136+
git rev-parse merge^-1 >actual &&
137+
test_cmp expect actual
138+
'
139+
140+
test_expect_success 'rev-parse merge^-2 = merge^2..merge' '
141+
git rev-parse merge^2..merge >expect &&
142+
git rev-parse merge^-2 >actual &&
143+
test_cmp expect actual
144+
'
145+
146+
test_expect_success 'rev-parse merge^-0 (invalid parent)' '
147+
test_must_fail git rev-parse merge^-0
148+
'
149+
150+
test_expect_success 'rev-parse merge^-3 (invalid parent)' '
151+
test_must_fail git rev-parse merge^-3
152+
'
153+
154+
test_expect_success 'rev-parse merge^-^ (garbage after ^-)' '
155+
test_must_fail git rev-parse merge^-^
156+
'
157+
158+
test_expect_success 'rev-parse merge^-1x (garbage after ^-1)' '
159+
test_must_fail git rev-parse merge^-1x
160+
'
161+
162+
# All rev^- rev-list tests (should be mostly the same as rev-parse; the reason
163+
# for the duplication is that rev-parse and rev-list use different parsers).
164+
165+
test_expect_success 'rev-list merge^- = merge^..merge' '
166+
git rev-list merge^..merge >expect &&
167+
git rev-list merge^- >actual &&
168+
test_cmp expect actual
169+
'
170+
171+
test_expect_success 'rev-list merge^-1 = merge^1..merge' '
172+
git rev-list merge^1..merge >expect &&
173+
git rev-list merge^-1 >actual &&
174+
test_cmp expect actual
175+
'
176+
177+
test_expect_success 'rev-list merge^-2 = merge^2..merge' '
178+
git rev-list merge^2..merge >expect &&
179+
git rev-list merge^-2 >actual &&
180+
test_cmp expect actual
181+
'
182+
183+
test_expect_success 'rev-list merge^-0 (invalid parent)' '
184+
test_must_fail git rev-list merge^-0
185+
'
186+
187+
test_expect_success 'rev-list merge^-3 (invalid parent)' '
188+
test_must_fail git rev-list merge^-3
189+
'
190+
191+
test_expect_success 'rev-list merge^-^ (garbage after ^-)' '
192+
test_must_fail git rev-list merge^-^
193+
'
194+
195+
test_expect_success 'rev-list merge^-1x (garbage after ^-1)' '
196+
test_must_fail git rev-list merge^-1x
197+
'
198+
105199
test_done

0 commit comments

Comments
 (0)