subtree: allow --squash to be used with --rejoin

Besides being a genuinely useful thing to do, this also just makes sense
and harmonizes which flags may be used when.  `git subtree split
--rejoin` amounts to "automatically go ahead and do a `git subtree
merge` after doing the main `git subtree split`", so it's weird and
arbitrary that you can't pass `--squash` to `git subtree split --rejoin`
like you can `git subtree merge`.  It's weird that `git subtree split
--rejoin` inherits `git subtree merge`'s `--message` but not `--squash`.

Reconcile the situation by just having `split --rejoin` actually just
call `merge` internally (or call `add` instead, as appropriate), so it
can get access to the full `merge` behavior, including `--squash`.

Signed-off-by: Luke Shumaker <lukeshu@datawire.io>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Luke Shumaker 2021-04-27 15:17:45 -06:00 committed by Junio C Hamano
parent 6468784dd2
commit cb6551447b
3 changed files with 96 additions and 24 deletions

View file

@ -33,15 +33,15 @@ h,help show the help
q quiet q quiet
d show debug messages d show debug messages
P,prefix= the name of the subdir to split out P,prefix= the name of the subdir to split out
m,message= use the given message as the commit message for the merge commit
options for 'split' options for 'split'
annotate= add a prefix to commit message of new commits annotate= add a prefix to commit message of new commits
b,branch= create a new branch from the split subtree b,branch= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD rejoin merge the new branch back into HEAD
options for 'add' and 'merge' (also: 'pull') options for 'add' and 'merge' (also: 'pull' and 'split --rejoin')
squash merge subtree changes as a single commit squash merge subtree changes as a single commit
m,message= use the given message as the commit message for the merge commit
" "
arg_debug= arg_debug=
@ -346,7 +346,8 @@ find_latest_squash () {
then then
# a rejoin commit? # a rejoin commit?
# Pretend its sub was a squash. # Pretend its sub was a squash.
sq="$sub" sq=$(git rev-parse --verify "$sq^2") ||
die
fi fi
debug "Squash found: $sq $sub" debug "Squash found: $sq $sub"
echo "$sq" "$sub" echo "$sq" "$sub"
@ -453,6 +454,13 @@ add_msg () {
else else
commit_message="Add '$dir/' from commit '$latest_new'" commit_message="Add '$dir/' from commit '$latest_new'"
fi fi
if test -n "$arg_split_rejoin"
then
# If this is from a --rejoin, then rejoin_msg has
# already inserted the `git-subtree-xxx:` tags
echo "$commit_message"
return
fi
cat <<-EOF cat <<-EOF
$commit_message $commit_message
@ -775,7 +783,12 @@ cmd_add_commit () {
rev=$(git rev-parse --verify "$1^{commit}") || exit $? rev=$(git rev-parse --verify "$1^{commit}") || exit $?
debug "Adding $dir as '$rev'..." debug "Adding $dir as '$rev'..."
git read-tree --prefix="$dir" $rev || exit $? if test -z "$arg_split_rejoin"
then
# Only bother doing this if this is a genuine 'add',
# not a synthetic 'add' from '--rejoin'.
git read-tree --prefix="$dir" $rev || exit $?
fi
git checkout -- "$dir" || exit $? git checkout -- "$dir" || exit $?
tree=$(git write-tree) || exit $? tree=$(git write-tree) || exit $?
@ -815,6 +828,11 @@ cmd_split () {
die "You must provide exactly one revision. Got: '$*'" die "You must provide exactly one revision. Got: '$*'"
fi fi
if test -n "$arg_split_rejoin"
then
ensure_clean
fi
debug "Splitting $dir..." debug "Splitting $dir..."
cache_setup || exit $? cache_setup || exit $?
@ -857,10 +875,13 @@ cmd_split () {
then then
debug "Merging split branch into HEAD..." debug "Merging split branch into HEAD..."
latest_old=$(cache_get latest_old) || exit $? latest_old=$(cache_get latest_old) || exit $?
git merge -s ours \ arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $?
--allow-unrelated-histories \ if test -z "$(find_latest_squash "$dir")"
-m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \ then
"$latest_new" >&2 || exit $? cmd_add "$latest_new" >&2 || exit $?
else
cmd_merge "$latest_new" >&2 || exit $?
fi
fi fi
if test -n "$arg_split_branch" if test -n "$arg_split_branch"
then then

View file

@ -109,9 +109,6 @@ settings passed to 'split' (such as '--annotate') are the same.
Because of this, if you add new commits and then re-split, the new Because of this, if you add new commits and then re-split, the new
commits will be attached as commits on top of the history you commits will be attached as commits on top of the history you
generated last time, so 'git merge' and friends will work as expected. generated last time, so 'git merge' and friends will work as expected.
+
Note that if you use '--squash' when you merge, you should usually not
just '--rejoin' when you split.
pull <repository> <remote-ref>:: pull <repository> <remote-ref>::
Exactly like 'merge', but parallels 'git pull' in that Exactly like 'merge', but parallels 'git pull' in that
@ -124,8 +121,8 @@ push <repository> <remote-ref>::
<remote-ref>. This can be used to push your subtree to <remote-ref>. This can be used to push your subtree to
different branches of the remote repository. different branches of the remote repository.
OPTIONS OPTIONS FOR ALL COMMANDS
------- ------------------------
-q:: -q::
--quiet:: --quiet::
Suppress unnecessary output messages on stderr. Suppress unnecessary output messages on stderr.
@ -140,15 +137,11 @@ OPTIONS
want to manipulate. This option is mandatory want to manipulate. This option is mandatory
for all commands. for all commands.
-m <message>:: OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull' AND 'split --rejoin')
--message=<message>:: -----------------------------------------------------------------
This option is only valid for 'add', 'merge', 'pull', and 'split --rejoin'.
Specify <message> as the commit message for the merge commit.
OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull')
--------------------------------------------
These options for 'add' and 'merge' may also be given to 'pull' (which These options for 'add' and 'merge' may also be given to 'pull' (which
wraps 'merge'). wraps 'merge') and 'split --rejoin' (which wraps either 'add' or
'merge' as appropriate).
--squash:: --squash::
Instead of merging the entire history from the subtree project, produce Instead of merging the entire history from the subtree project, produce
@ -176,6 +169,9 @@ Whether or not you use '--squash', changes made in your local repository
remain intact and can be later split and send upstream to the remain intact and can be later split and send upstream to the
subproject. subproject.
-m <message>::
--message=<message>::
Specify <message> as the commit message for the merge commit.
OPTIONS FOR 'split' OPTIONS FOR 'split'
------------------- -------------------
@ -229,9 +225,8 @@ Unfortunately, using this option results in 'git log' showing an extra
copy of every new commit that was created (the original, and the copy of every new commit that was created (the original, and the
synthetic one). synthetic one).
+ +
If you do all your merges with '--squash', don't use '--rejoin' when you If you do all your merges with '--squash', make sure you also use
split, because you don't want the subproject's history to be part of '--squash' when you 'split --rejoin'.
your project anyway.
EXAMPLE 1. 'add' command EXAMPLE 1. 'add' command

View file

@ -324,6 +324,62 @@ test_expect_success 'split sub dir/ with --rejoin and --message' '
) )
' '
test_expect_success 'split "sub dir"/ with --rejoin and --squash' '
subtree_test_create_repo "$test_count" &&
subtree_test_create_repo "$test_count/sub proj" &&
test_create_commit "$test_count" main1 &&
test_create_commit "$test_count/sub proj" sub1 &&
(
cd "$test_count" &&
git fetch ./"sub proj" HEAD &&
git subtree add --prefix="sub dir" --squash FETCH_HEAD
) &&
test_create_commit "$test_count" "sub dir"/main-sub1 &&
test_create_commit "$test_count" main2 &&
test_create_commit "$test_count/sub proj" sub2 &&
test_create_commit "$test_count" "sub dir"/main-sub2 &&
(
cd "$test_count" &&
git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
MAIN=$(git rev-parse --verify HEAD) &&
SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
SPLIT=$(git subtree split --prefix="sub dir" --annotate="*" --rejoin --squash) &&
test_must_fail git merge-base --is-ancestor $SUB HEAD &&
test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
git rev-list HEAD ^$MAIN >commit-list &&
test_line_count = 2 commit-list &&
test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''"
)
'
test_expect_success 'split then pull "sub dir"/ with --rejoin and --squash' '
# 1. "add"
subtree_test_create_repo "$test_count" &&
subtree_test_create_repo "$test_count/sub proj" &&
test_create_commit "$test_count" main1 &&
test_create_commit "$test_count/sub proj" sub1 &&
git -C "$test_count" subtree --prefix="sub dir" add --squash ./"sub proj" HEAD &&
# 2. commit from parent
test_create_commit "$test_count" "sub dir"/main-sub1 &&
# 3. "split --rejoin --squash"
git -C "$test_count" subtree --prefix="sub dir" split --rejoin --squash &&
# 4. "pull --squash"
test_create_commit "$test_count/sub proj" sub2 &&
git -C "$test_count" subtree -d --prefix="sub dir" pull --squash ./"sub proj" HEAD &&
test_must_fail git merge-base HEAD FETCH_HEAD
'
test_expect_success 'split "sub dir"/ with --branch' ' test_expect_success 'split "sub dir"/ with --branch' '
subtree_test_create_repo "$test_count" && subtree_test_create_repo "$test_count" &&
subtree_test_create_repo "$test_count/sub proj" && subtree_test_create_repo "$test_count/sub proj" &&