mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
Merge branch 'jc/checkout-merge-base'
* jc/checkout-merge-base: rebase -i: teach --onto A...B syntax rebase: fix --onto A...B parsing and add tests "rebase --onto A...B" replays history on the merge base between A and B "checkout A...B" switches to the merge base between A and B
This commit is contained in:
commit
1f73566af5
@ -696,7 +696,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
* case 3: git checkout <something> [<paths>]
|
||||
*
|
||||
* With no paths, if <something> is a commit, that is to
|
||||
* switch to the branch or detach HEAD at it.
|
||||
* switch to the branch or detach HEAD at it. As a special case,
|
||||
* if <something> is A...B (missing A or B means HEAD but you can
|
||||
* omit at most one side), and if there is a unique merge base
|
||||
* between A and B, A...B names that merge base.
|
||||
*
|
||||
* With no paths, if <something> is _not_ a commit, no -t nor -b
|
||||
* was given, and there is a tracking branch whose name is
|
||||
@ -722,7 +725,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
if (!strcmp(arg, "-"))
|
||||
arg = "@{-1}";
|
||||
|
||||
if (get_sha1(arg, rev)) {
|
||||
if (get_sha1_mb(arg, rev)) {
|
||||
if (has_dash_dash) /* case (1) */
|
||||
die("invalid reference: %s", arg);
|
||||
if (!patch_mode &&
|
||||
|
1
cache.h
1
cache.h
@ -723,6 +723,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
|
||||
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
|
||||
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
|
||||
extern int interpret_branch_name(const char *str, struct strbuf *);
|
||||
extern int get_sha1_mb(const char *str, unsigned char *sha1);
|
||||
|
||||
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
|
||||
extern const char *ref_rev_parse_rules[];
|
||||
|
@ -495,6 +495,25 @@ get_saved_options () {
|
||||
test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
|
||||
}
|
||||
|
||||
LF='
|
||||
'
|
||||
parse_onto () {
|
||||
case "$1" in
|
||||
*...*)
|
||||
if left=${1%...*} right=${1#*...} &&
|
||||
onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
|
||||
then
|
||||
case "$onto" in
|
||||
?*"$LF"?* | '')
|
||||
exit 1 ;;
|
||||
esac
|
||||
echo "$onto"
|
||||
exit 0
|
||||
fi
|
||||
esac
|
||||
git rev-parse --verify "$1^0"
|
||||
}
|
||||
|
||||
while test $# != 0
|
||||
do
|
||||
case "$1" in
|
||||
@ -602,7 +621,7 @@ first and then run 'git rebase --continue' again."
|
||||
;;
|
||||
--onto)
|
||||
shift
|
||||
ONTO=$(git rev-parse --verify "$1") ||
|
||||
ONTO=$(parse_onto "$1") ||
|
||||
die "Does not point to a valid commit: $1"
|
||||
;;
|
||||
--)
|
||||
|
@ -34,6 +34,8 @@ set_reflog_action rebase
|
||||
require_work_tree
|
||||
cd_to_toplevel
|
||||
|
||||
LF='
|
||||
'
|
||||
OK_TO_SKIP_PRE_REBASE=
|
||||
RESOLVEMSG="
|
||||
When you have resolved this problem run \"git rebase --continue\".
|
||||
@ -417,7 +419,27 @@ fi
|
||||
|
||||
# Make sure the branch to rebase onto is valid.
|
||||
onto_name=${newbase-"$upstream_name"}
|
||||
onto=$(git rev-parse --verify "${onto_name}^0") || exit
|
||||
case "$onto_name" in
|
||||
*...*)
|
||||
if left=${onto_name%...*} right=${onto_name#*...} &&
|
||||
onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
|
||||
then
|
||||
case "$onto" in
|
||||
?*"$LF"?*)
|
||||
die "$onto_name: there are more than one merge bases"
|
||||
;;
|
||||
'')
|
||||
die "$onto_name: there is no merge base"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
die "$onto_name: there is no merge base"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
onto=$(git rev-parse --verify "${onto_name}^0") || exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# If a hook exists, give it a chance to interrupt
|
||||
run_pre_rebase_hook "$upstream_arg" "$@"
|
||||
|
42
sha1_name.c
42
sha1_name.c
@ -794,6 +794,48 @@ release_return:
|
||||
return retval;
|
||||
}
|
||||
|
||||
int get_sha1_mb(const char *name, unsigned char *sha1)
|
||||
{
|
||||
struct commit *one, *two;
|
||||
struct commit_list *mbs;
|
||||
unsigned char sha1_tmp[20];
|
||||
const char *dots;
|
||||
int st;
|
||||
|
||||
dots = strstr(name, "...");
|
||||
if (!dots)
|
||||
return get_sha1(name, sha1);
|
||||
if (dots == name)
|
||||
st = get_sha1("HEAD", sha1_tmp);
|
||||
else {
|
||||
struct strbuf sb;
|
||||
strbuf_init(&sb, dots - name);
|
||||
strbuf_add(&sb, name, dots - name);
|
||||
st = get_sha1(sb.buf, sha1_tmp);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
if (st)
|
||||
return st;
|
||||
one = lookup_commit_reference_gently(sha1_tmp, 0);
|
||||
if (!one)
|
||||
return -1;
|
||||
|
||||
if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
|
||||
return -1;
|
||||
two = lookup_commit_reference_gently(sha1_tmp, 0);
|
||||
if (!two)
|
||||
return -1;
|
||||
mbs = get_merge_bases(one, two, 1);
|
||||
if (!mbs || mbs->next)
|
||||
st = -1;
|
||||
else {
|
||||
st = 0;
|
||||
hashcpy(sha1, mbs->item->object.sha1);
|
||||
}
|
||||
free_commit_list(mbs);
|
||||
return st;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
|
||||
* notably "xyz^" for "parent of xyz"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='checkout can switch to last branch'
|
||||
test_description='checkout can switch to last branch and merge base'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '
|
||||
test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
|
||||
'
|
||||
|
||||
test_expect_success 'merge base test setup' '
|
||||
git checkout -b another other &&
|
||||
echo "hello again" >>world &&
|
||||
git add world &&
|
||||
git commit -m third
|
||||
'
|
||||
|
||||
test_expect_success 'another...master' '
|
||||
git checkout another &&
|
||||
git checkout another...master &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_expect_success '...master' '
|
||||
git checkout another &&
|
||||
git checkout ...master &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_expect_success 'master...' '
|
||||
git checkout another &&
|
||||
git checkout master... &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
105
t/t3415-rebase-onto-threedots.sh
Executable file
105
t/t3415-rebase-onto-threedots.sh
Executable file
@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git rebase --onto A...B'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY/lib-rebase.sh"
|
||||
|
||||
# Rebase only the tip commit of "topic" on merge base between "master"
|
||||
# and "topic". Cannot do this for "side" with "master" because there
|
||||
# is no single merge base.
|
||||
#
|
||||
#
|
||||
# F---G topic G'
|
||||
# / /
|
||||
# A---B---C---D---E master --> A---B---C---D---E
|
||||
# \ \ /
|
||||
# \ x
|
||||
# \ / \
|
||||
# H---I---J---K side
|
||||
|
||||
test_expect_success setup '
|
||||
test_commit A &&
|
||||
test_commit B &&
|
||||
git branch side &&
|
||||
test_commit C &&
|
||||
git branch topic &&
|
||||
git checkout side &&
|
||||
test_commit H &&
|
||||
git checkout master &&
|
||||
test_tick &&
|
||||
git merge H &&
|
||||
git tag D &&
|
||||
test_commit E &&
|
||||
git checkout topic &&
|
||||
test_commit F &&
|
||||
test_commit G &&
|
||||
git checkout side &&
|
||||
test_tick &&
|
||||
git merge C &&
|
||||
git tag I &&
|
||||
test_commit J &&
|
||||
test_commit K
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...topic' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
|
||||
git rebase --onto master...topic F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
|
||||
git rebase --onto master... F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...side' '
|
||||
git reset --hard &&
|
||||
git checkout side &&
|
||||
git reset --hard K &&
|
||||
|
||||
test_must_fail git rebase --onto master...side J
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...topic' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
set_fake_editor &&
|
||||
EXPECT_COUNT=1 git rebase -i --onto master...topic F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
set_fake_editor &&
|
||||
EXPECT_COUNT=1 git rebase -i --onto master... F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...side' '
|
||||
git reset --hard &&
|
||||
git checkout side &&
|
||||
git reset --hard K &&
|
||||
|
||||
test_must_fail git rebase -i --onto master...side J
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user