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>

Luke Shumaker committed Apr 27, 2021 at 15:17 UTC cb6551447b8e0c35408b766ad605ba357f720a0d
3 files changed +96 -24
contrib/subtree/git-subtree.sh
+29 -8
@@ -33,15 +33,15 @@ h,help show the help
33 q quiet
34 d show debug messages
35 P,prefix= the name of the subdir to split out
36 -m,message= use the given message as the commit message for the merge commit
36 options for 'split'
37 annotate= add a prefix to commit message of new commits
38 b,branch= create a new branch from the split subtree
39 ignore-joins ignore prior --rejoin commits
40 onto= try connecting new tree to an existing one
41 rejoin merge the new branch back into HEAD
43 - options for 'add' and 'merge' (also: 'pull')
42 + options for 'add' and 'merge' (also: 'pull' and 'split --rejoin')
43 squash merge subtree changes as a single commit
44 +m,message= use the given message as the commit message for the merge commit
45 "
46
47 arg_debug=
@@ -346,7 +346,8 @@ find_latest_squash () {
346 then
347 # a rejoin commit?
348 # Pretend its sub was a squash.
349 - sq="$sub"
349 + sq=$(git rev-parse --verify "$sq^2") ||
350 + die
351 fi
352 debug "Squash found: $sq $sub"
353 echo "$sq" "$sub"
@@ -453,6 +454,13 @@ add_msg () {
454 else
455 commit_message="Add '$dir/' from commit '$latest_new'"
456 fi
457 + if test -n "$arg_split_rejoin"
458 + then
459 + # If this is from a --rejoin, then rejoin_msg has
460 + # already inserted the `git-subtree-xxx:` tags
461 + echo "$commit_message"
462 + return
463 + fi
464 cat <<-EOF
465 $commit_message
466
@@ -775,7 +783,12 @@ cmd_add_commit () {
783 rev=$(git rev-parse --verify "$1^{commit}") || exit $?
784
785 debug "Adding $dir as '$rev'..."
778 - git read-tree --prefix="$dir" $rev || exit $?
786 + if test -z "$arg_split_rejoin"
787 + then
788 + # Only bother doing this if this is a genuine 'add',
789 + # not a synthetic 'add' from '--rejoin'.
790 + git read-tree --prefix="$dir" $rev || exit $?
791 + fi
792 git checkout -- "$dir" || exit $?
793 tree=$(git write-tree) || exit $?
794
@@ -815,6 +828,11 @@ cmd_split () {
828 die "You must provide exactly one revision. Got: '$*'"
829 fi
830
831 + if test -n "$arg_split_rejoin"
832 + then
833 + ensure_clean
834 + fi
835 +
836 debug "Splitting $dir..."
837 cache_setup || exit $?
838
@@ -857,10 +875,13 @@ cmd_split () {
875 then
876 debug "Merging split branch into HEAD..."
877 latest_old=$(cache_get latest_old) || exit $?
860 - git merge -s ours \
861 - --allow-unrelated-histories \
862 - -m "$(rejoin_msg "$dir" "$latest_old" "$latest_new")" \
863 - "$latest_new" >&2 || exit $?
878 + arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $?
879 + if test -z "$(find_latest_squash "$dir")"
880 + then
881 + cmd_add "$latest_new" >&2 || exit $?
882 + else
883 + cmd_merge "$latest_new" >&2 || exit $?
884 + fi
885 fi
886 if test -n "$arg_split_branch"
887 then
contrib/subtree/git-subtree.txt
+11 -16
@@ -109,9 +109,6 @@ settings passed to 'split' (such as '--annotate') are the same.
109 Because of this, if you add new commits and then re-split, the new
110 commits will be attached as commits on top of the history you
111 generated last time, so 'git merge' and friends will work as expected.
112 -+
113 -Note that if you use '--squash' when you merge, you should usually not
114 -just '--rejoin' when you split.
112
113 pull <repository> <remote-ref>::
114 Exactly like 'merge', but parallels 'git pull' in that
@@ -124,8 +121,8 @@ push <repository> <remote-ref>::
121 <remote-ref>. This can be used to push your subtree to
122 different branches of the remote repository.
123
127 -OPTIONS
128 --------
124 +OPTIONS FOR ALL COMMANDS
125 +------------------------
126 -q::
127 --quiet::
128 Suppress unnecessary output messages on stderr.
@@ -140,15 +137,11 @@ OPTIONS
137 want to manipulate. This option is mandatory
138 for all commands.
139
143 --m <message>::
144 ---message=<message>::
145 - This option is only valid for 'add', 'merge', 'pull', and 'split --rejoin'.
146 - Specify <message> as the commit message for the merge commit.
147 -
148 -OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull')
149 ---------------------------------------------
140 +OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull' AND 'split --rejoin')
141 +-----------------------------------------------------------------
142 These options for 'add' and 'merge' may also be given to 'pull' (which
151 -wraps 'merge').
143 +wraps 'merge') and 'split --rejoin' (which wraps either 'add' or
144 +'merge' as appropriate).
145
146 --squash::
147 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
169 remain intact and can be later split and send upstream to the
170 subproject.
171
172 +-m <message>::
173 +--message=<message>::
174 + Specify <message> as the commit message for the merge commit.
175
176 OPTIONS FOR 'split'
177 -------------------
@@ -229,9 +225,8 @@ Unfortunately, using this option results in 'git log' showing an extra
225 copy of every new commit that was created (the original, and the
226 synthetic one).
227 +
232 -If you do all your merges with '--squash', don't use '--rejoin' when you
233 -split, because you don't want the subproject's history to be part of
234 -your project anyway.
228 +If you do all your merges with '--squash', make sure you also use
229 +'--squash' when you 'split --rejoin'.
230
231
232 EXAMPLE 1. 'add' command
contrib/subtree/t/t7900-subtree.sh
+56
@@ -324,6 +324,62 @@ test_expect_success 'split sub dir/ with --rejoin and --message' '
324 )
325 '
326
327 +test_expect_success 'split "sub dir"/ with --rejoin and --squash' '
328 + subtree_test_create_repo "$test_count" &&
329 + subtree_test_create_repo "$test_count/sub proj" &&
330 + test_create_commit "$test_count" main1 &&
331 + test_create_commit "$test_count/sub proj" sub1 &&
332 + (
333 + cd "$test_count" &&
334 + git fetch ./"sub proj" HEAD &&
335 + git subtree add --prefix="sub dir" --squash FETCH_HEAD
336 + ) &&
337 + test_create_commit "$test_count" "sub dir"/main-sub1 &&
338 + test_create_commit "$test_count" main2 &&
339 + test_create_commit "$test_count/sub proj" sub2 &&
340 + test_create_commit "$test_count" "sub dir"/main-sub2 &&
341 + (
342 + cd "$test_count" &&
343 + git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
344 + MAIN=$(git rev-parse --verify HEAD) &&
345 + SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
346 +
347 + SPLIT=$(git subtree split --prefix="sub dir" --annotate="*" --rejoin --squash) &&
348 +
349 + test_must_fail git merge-base --is-ancestor $SUB HEAD &&
350 + test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
351 + git rev-list HEAD ^$MAIN >commit-list &&
352 + test_line_count = 2 commit-list &&
353 + test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
354 + test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
355 + test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
356 + test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
357 + test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
358 + test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''"
359 + )
360 +'
361 +
362 +test_expect_success 'split then pull "sub dir"/ with --rejoin and --squash' '
363 + # 1. "add"
364 + subtree_test_create_repo "$test_count" &&
365 + subtree_test_create_repo "$test_count/sub proj" &&
366 + test_create_commit "$test_count" main1 &&
367 + test_create_commit "$test_count/sub proj" sub1 &&
368 + git -C "$test_count" subtree --prefix="sub dir" add --squash ./"sub proj" HEAD &&
369 +
370 + # 2. commit from parent
371 + test_create_commit "$test_count" "sub dir"/main-sub1 &&
372 +
373 + # 3. "split --rejoin --squash"
374 + git -C "$test_count" subtree --prefix="sub dir" split --rejoin --squash &&
375 +
376 + # 4. "pull --squash"
377 + test_create_commit "$test_count/sub proj" sub2 &&
378 + git -C "$test_count" subtree -d --prefix="sub dir" pull --squash ./"sub proj" HEAD &&
379 +
380 + test_must_fail git merge-base HEAD FETCH_HEAD
381 +'
382 +
383 test_expect_success 'split "sub dir"/ with --branch' '
384 subtree_test_create_repo "$test_count" &&
385 subtree_test_create_repo "$test_count/sub proj" &&