revision: new rev^-n shorthand for rev^n..rev
"git log rev^..rev" is commonly used to show all work done on and merged from a side branch. This patch introduces a shorthand "rev^-" for this and additionally allows "rev^-$n" to mean "reachable from rev, excluding what is reachable from the nth parent of rev". For example, for a two-parent merge, you can use rev^-2 to get the set of commits which were made to the main branch while the topic branch was prepared. Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
7c0304af62
commit
8779351dd7
@ -283,7 +283,7 @@ 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'.
|
||||
@ -291,8 +291,15 @@ 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
|
||||
@ -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:
|
||||
@ -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
|
||||
|
@ -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;
|
||||
int parent_number;
|
||||
int include_rev = 0;
|
||||
int include_parents = 0;
|
||||
int exclude_parent = 0;
|
||||
|
||||
if ((dotdot = strstr(arg, "^!")))
|
||||
parents_only = 0;
|
||||
else if ((dotdot = strstr(arg, "^@")))
|
||||
parents_only = 1;
|
||||
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 || dotdot[2])
|
||||
if (dotdot[2]) {
|
||||
char *end;
|
||||
exclude_parent = strtoul(dotdot + 2, &end, 10);
|
||||
if (*end != '\0' || !exclude_parent)
|
||||
return 0;
|
||||
}
|
||||
} else
|
||||
return 0;
|
||||
|
||||
*dotdot = 0;
|
||||
@ -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;
|
||||
|
34
revision.c
34
revision.c
@ -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 == '^') {
|
||||
@ -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);
|
||||
@ -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 = '^';
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user