Skip to content

Commit

Permalink
Merge branch 'vn/revision-shorthand-for-side-branch-log'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
gitster committed Oct 6, 2016
2 parents 66c22ba + 8779351 commit 8c98a68
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 19 deletions.
17 changes: 15 additions & 2 deletions Documentation/revisions.txt
Expand Up @@ -283,16 +283,23 @@ empty range that is both reachable and unreachable from HEAD.

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

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

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

The '<rev>{caret}-{<n>}' notation includes '<rev>' but excludes the <n>th
parent (i.e. a shorthand for '<rev>{caret}<n>..<rev>'), with '<n>' = 1 if
not given. This is typically useful for merge commits where you
can just pass '<commit>{caret}-' to get all the commits in the branch
that was merged in merge commit '<commit>' (including '<commit>'
itself).

While '<rev>{caret}<n>' was about specifying a single commit parent, these
two notations consider all its parents. For example you can say
three notations also consider its parents. For example you can say
'HEAD{caret}2{caret}@', however you cannot say 'HEAD{caret}@{caret}2'.

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

'<rev>{caret}-{<n>}', e.g. 'HEAD{caret}-, HEAD{caret}-2'::
Equivalent to '<rev>{caret}<n>..<rev>', with '<n>' = 1 if not
given.

Here are a handful of examples using the Loeliger illustration above,
with each step in the notation's expansion and selection carefully
spelt out:
Expand All @@ -339,6 +350,8 @@ spelt out:
C I J F C
B..C = ^B C C
B...C = B ^F C G H D E B C
B^- = B^..B
= ^B^1 B E I J F B
C^@ = C^1
= F I J F
B^@ = B^1 B^2 B^3
Expand Down
54 changes: 41 additions & 13 deletions builtin/rev-parse.c
Expand Up @@ -298,14 +298,30 @@ static int try_parent_shorthands(const char *arg)
unsigned char sha1[20];
struct commit *commit;
struct commit_list *parents;
int parents_only;

if ((dotdot = strstr(arg, "^!")))
parents_only = 0;
else if ((dotdot = strstr(arg, "^@")))
parents_only = 1;

if (!dotdot || dotdot[2])
int parent_number;
int include_rev = 0;
int include_parents = 0;
int exclude_parent = 0;

if ((dotdot = strstr(arg, "^!"))) {
include_rev = 1;
if (dotdot[2])
return 0;
} else if ((dotdot = strstr(arg, "^@"))) {
include_parents = 1;
if (dotdot[2])
return 0;
} else if ((dotdot = strstr(arg, "^-"))) {
include_rev = 1;
exclude_parent = 1;

if (dotdot[2]) {
char *end;
exclude_parent = strtoul(dotdot + 2, &end, 10);
if (*end != '\0' || !exclude_parent)
return 0;
}
} else
return 0;

*dotdot = 0;
Expand All @@ -314,12 +330,24 @@ static int try_parent_shorthands(const char *arg)
return 0;
}

if (!parents_only)
show_rev(NORMAL, sha1, arg);
commit = lookup_commit_reference(sha1);
for (parents = commit->parents; parents; parents = parents->next)
show_rev(parents_only ? NORMAL : REVERSED,
parents->item->object.oid.hash, arg);
if (exclude_parent &&
exclude_parent > commit_list_count(commit->parents)) {
*dotdot = '^';
return 0;
}

if (include_rev)
show_rev(NORMAL, sha1, arg);
for (parents = commit->parents, parent_number = 1;
parents;
parents = parents->next, parent_number++) {
if (exclude_parent && parent_number != exclude_parent)
continue;

show_rev(include_parents ? NORMAL : REVERSED,
parents->item->object.oid.hash, arg);
}

*dotdot = '^';
return 1;
Expand Down
34 changes: 30 additions & 4 deletions revision.c
Expand Up @@ -1289,12 +1289,14 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned flags)
}
}

static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
static int add_parents_only(struct rev_info *revs, const char *arg_, int flags,
int exclude_parent)
{
unsigned char sha1[20];
struct object *it;
struct commit *commit;
struct commit_list *parents;
int parent_number;
const char *arg = arg_;

if (*arg == '^') {
Expand All @@ -1316,7 +1318,15 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
if (it->type != OBJ_COMMIT)
return 0;
commit = (struct commit *)it;
for (parents = commit->parents; parents; parents = parents->next) {
if (exclude_parent &&
exclude_parent > commit_list_count(commit->parents))
return 0;
for (parents = commit->parents, parent_number = 1;
parents;
parents = parents->next, parent_number++) {
if (exclude_parent && parent_number != exclude_parent)
continue;

it = &parents->item->object;
it->flags |= flags;
add_rev_cmdline(revs, it, arg_, REV_CMD_PARENTS_ONLY, flags);
Expand Down Expand Up @@ -1519,17 +1529,33 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
}
*dotdot = '.';
}

dotdot = strstr(arg, "^@");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
if (add_parents_only(revs, arg, flags))
if (add_parents_only(revs, arg, flags, 0))
return 0;
*dotdot = '^';
}
dotdot = strstr(arg, "^!");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
*dotdot = '^';
}
dotdot = strstr(arg, "^-");
if (dotdot) {
int exclude_parent = 1;

if (dotdot[2]) {
char *end;
exclude_parent = strtoul(dotdot + 2, &end, 10);
if (*end != '\0' || !exclude_parent)
return -1;
}

*dotdot = 0;
if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
*dotdot = '^';
}

Expand Down
94 changes: 94 additions & 0 deletions t/t6101-rev-parse-parents.sh
Expand Up @@ -102,4 +102,98 @@ test_expect_success 'short SHA-1 works' '
test_cmp_rev_output start "git rev-parse ${start%?}"
'

# rev^- tests; we can use a simpler setup for these

test_expect_success 'setup for rev^- tests' '
test_commit one &&
test_commit two &&
test_commit three &&
# Merge in a branch for testing rev^-
git checkout -b branch &&
git checkout HEAD^^ &&
git merge -m merge --no-edit --no-ff branch &&
git checkout -b merge
'

# The merged branch has 2 commits + the merge
test_expect_success 'rev-list --count merge^- = merge^..merge' '
git rev-list --count merge^..merge >expect &&
echo 3 >actual &&
test_cmp expect actual
'

# All rev^- rev-parse tests

test_expect_success 'rev-parse merge^- = merge^..merge' '
git rev-parse merge^..merge >expect &&
git rev-parse merge^- >actual &&
test_cmp expect actual
'

test_expect_success 'rev-parse merge^-1 = merge^..merge' '
git rev-parse merge^1..merge >expect &&
git rev-parse merge^-1 >actual &&
test_cmp expect actual
'

test_expect_success 'rev-parse merge^-2 = merge^2..merge' '
git rev-parse merge^2..merge >expect &&
git rev-parse merge^-2 >actual &&
test_cmp expect actual
'

test_expect_success 'rev-parse merge^-0 (invalid parent)' '
test_must_fail git rev-parse merge^-0
'

test_expect_success 'rev-parse merge^-3 (invalid parent)' '
test_must_fail git rev-parse merge^-3
'

test_expect_success 'rev-parse merge^-^ (garbage after ^-)' '
test_must_fail git rev-parse merge^-^
'

test_expect_success 'rev-parse merge^-1x (garbage after ^-1)' '
test_must_fail git rev-parse merge^-1x
'

# All rev^- rev-list tests (should be mostly the same as rev-parse; the reason
# for the duplication is that rev-parse and rev-list use different parsers).

test_expect_success 'rev-list merge^- = merge^..merge' '
git rev-list merge^..merge >expect &&
git rev-list merge^- >actual &&
test_cmp expect actual
'

test_expect_success 'rev-list merge^-1 = merge^1..merge' '
git rev-list merge^1..merge >expect &&
git rev-list merge^-1 >actual &&
test_cmp expect actual
'

test_expect_success 'rev-list merge^-2 = merge^2..merge' '
git rev-list merge^2..merge >expect &&
git rev-list merge^-2 >actual &&
test_cmp expect actual
'

test_expect_success 'rev-list merge^-0 (invalid parent)' '
test_must_fail git rev-list merge^-0
'

test_expect_success 'rev-list merge^-3 (invalid parent)' '
test_must_fail git rev-list merge^-3
'

test_expect_success 'rev-list merge^-^ (garbage after ^-)' '
test_must_fail git rev-list merge^-^
'

test_expect_success 'rev-list merge^-1x (garbage after ^-1)' '
test_must_fail git rev-list merge^-1x
'

test_done

0 comments on commit 8c98a68

Please sign in to comment.