contrib/subtree: add -S/--gpg-sign
Allows optionally signing the commits that git subtree creates. This can be necessary when working in a repository that requires gpg signed commits. Signed-off-by: Patrik Weiskircher <patrik@pspdfkit.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrik Weiskircher committed
Jun 4, 2025 at 14:16 UTC
fea50ce4118c38d74d05c3595abe7756cabee4e1
3 files changed
+145
-19
contrib/subtree/git-subtree.adoc
+13
-6
@@ -9,14 +9,14 @@ git-subtree - Merge subtrees together and split repository into subtrees
9
SYNOPSIS
10
--------
11
[verse]
12
-'git subtree' [<options>] -P <prefix> add <local-commit>
13
-'git subtree' [<options>] -P <prefix> add <repository> <remote-ref>
14
-'git subtree' [<options>] -P <prefix> merge <local-commit> [<repository>]
15
-'git subtree' [<options>] -P <prefix> split [<local-commit>]
12
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] add <local-commit>
13
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] add <repository> <remote-ref>
14
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] merge <local-commit> [<repository>]
15
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] split [<local-commit>]
16
17
[verse]
18
-'git subtree' [<options>] -P <prefix> pull <repository> <remote-ref>
19
-'git subtree' [<options>] -P <prefix> push <repository> <refspec>
18
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] pull <repository> <remote-ref>
19
+'git subtree' [<options>] -P <prefix> [-S[<keyid>]] push <repository> <refspec>
20
21
DESCRIPTION
22
-----------
@@ -149,6 +149,13 @@ OPTIONS FOR ALL COMMANDS
149
want to manipulate. This option is mandatory
150
for all commands.
151
152
+-S[<keyid>]::
153
+--gpg-sign[=<keyid>]::
154
+--no-gpg-sign::
155
+ GPG-sign commits. The `keyid` argument is optional and
156
+ defaults to the committer identity; `--no-gpg-sign` is useful to
157
+ countermand a `--gpg-sign` option given earlier on the command line.
158
+
159
OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull', 'split --rejoin', AND 'push --rejoin')
160
-----------------------------------------------------------------------------------
161
These options for 'add' and 'merge' may also be given to 'pull' (which
contrib/subtree/git-subtree.sh
+19
-13
@@ -26,12 +26,12 @@ then
26
fi
27
28
OPTS_SPEC="\
29
-git subtree add --prefix=<prefix> <commit>
30
-git subtree add --prefix=<prefix> <repository> <ref>
31
-git subtree merge --prefix=<prefix> <commit>
32
-git subtree split --prefix=<prefix> [<commit>]
33
-git subtree pull --prefix=<prefix> <repository> <ref>
34
-git subtree push --prefix=<prefix> <repository> <refspec>
29
+git subtree add --prefix=<prefix> [-S[=<key-id>]] <commit>
30
+git subtree add --prefix=<prefix> [-S[=<key-id>]] <repository> <ref>
31
+git subtree merge --prefix=<prefix> [-S[=<key-id>]] <commit>
32
+git subtree split --prefix=<prefix> [-S[=<key-id>]] [<commit>]
33
+git subtree pull --prefix=<prefix> [-S[=<key-id>]] <repository> <ref>
34
+git subtree push --prefix=<prefix> [-S[=<key-id>]] <repository> <refspec>
35
--
36
h,help! show the help
37
q,quiet! quiet
@@ -46,6 +46,7 @@ rejoin merge the new branch back into HEAD
46
options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin')
47
squash merge subtree changes as a single commit
48
m,message!= use the given message as the commit message for the merge commit
49
+S,gpg-sign?key-id GPG-sign commits. The keyid argument is optional and defaults to the committer identity
50
"
51
52
indent=0
@@ -168,6 +169,7 @@ main () {
169
arg_split_annotate=
170
arg_addmerge_squash=
171
arg_addmerge_message=
172
+ arg_gpg_sign=
173
while test $# -gt 0
174
do
175
opt="$1"
@@ -232,6 +234,9 @@ main () {
234
test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command"
235
arg_addmerge_squash=
236
;;
237
+ --gpg-sign=* | --gpg-sign | --no-gpg-sign)
238
+ arg_gpg_sign="$opt"
239
+ ;;
240
--)
241
break
242
;;
@@ -264,6 +269,7 @@ main () {
269
debug "quiet: {$arg_quiet}"
270
debug "dir: {$dir}"
271
debug "opts: {$*}"
272
+ debug "gpg-sign: {$arg_gpg_sign}"
273
debug
274
275
"cmd_$arg_command" "$@"
@@ -529,7 +535,7 @@ copy_commit () {
535
printf "%s" "$arg_split_annotate"
536
cat
537
) |
532
- git commit-tree "$2" $3 # reads the rest of stdin
538
+ git commit-tree $arg_gpg_sign "$2" $3 # reads the rest of stdin
539
) || die "fatal: can't copy commit $1"
540
}
541
@@ -675,10 +681,10 @@ new_squash_commit () {
681
if test -n "$old"
682
then
683
squash_msg "$dir" "$oldsub" "$newsub" |
678
- git commit-tree "$tree" -p "$old" || exit $?
684
+ git commit-tree $arg_gpg_sign "$tree" -p "$old" || exit $?
685
else
686
squash_msg "$dir" "" "$newsub" |
681
- git commit-tree "$tree" || exit $?
687
+ git commit-tree $arg_gpg_sign "$tree" || exit $?
688
fi
689
}
690
@@ -917,11 +923,11 @@ cmd_add_commit () {
923
then
924
rev=$(new_squash_commit "" "" "$rev") || exit $?
925
commit=$(add_squashed_msg "$rev" "$dir" |
920
- git commit-tree "$tree" $headp -p "$rev") || exit $?
926
+ git commit-tree $arg_gpg_sign "$tree" $headp -p "$rev") || exit $?
927
else
928
revp=$(peel_committish "$rev") || exit $?
929
commit=$(add_msg "$dir" $headrev "$rev" |
924
- git commit-tree "$tree" $headp -p "$revp") || exit $?
930
+ git commit-tree $arg_gpg_sign "$tree" $headp -p "$revp") || exit $?
931
fi
932
git reset "$commit" || exit $?
933
@@ -1072,9 +1078,9 @@ cmd_merge () {
1078
if test -n "$arg_addmerge_message"
1079
then
1080
git merge --no-ff -Xsubtree="$arg_prefix" \
1075
- --message="$arg_addmerge_message" "$rev"
1081
+ --message="$arg_addmerge_message" $arg_gpg_sign "$rev"
1082
else
1077
- git merge --no-ff -Xsubtree="$arg_prefix" $rev
1083
+ git merge --no-ff -Xsubtree="$arg_prefix" $arg_gpg_sign $rev
1084
fi
1085
}
1086
contrib/subtree/t/t7900-subtree.sh
+113
@@ -11,6 +11,7 @@ and push subcommands of git subtree.
11
12
TEST_DIRECTORY=$(pwd)/../../../t
13
. "$TEST_DIRECTORY"/test-lib.sh
14
+. "$TEST_DIRECTORY"/lib-gpg.sh
15
16
# Use our own wrapper around test-lib.sh's test_create_repo, in order
17
# to set log.date=relative. `git subtree` parses the output of `git
@@ -1563,4 +1564,116 @@ test_expect_success 'subtree descendant check' '
1564
)
1565
'
1566
1567
+test_expect_success GPG 'add subproj with GPG signing using -S flag' '
1568
+ subtree_test_create_repo "$test_count" &&
1569
+ subtree_test_create_repo "$test_count/sub proj" &&
1570
+ test_create_commit "$test_count" main1 &&
1571
+ test_create_commit "$test_count/sub proj" sub1 &&
1572
+ (
1573
+ cd "$test_count" &&
1574
+ git fetch ./"sub proj" HEAD &&
1575
+ git subtree add --prefix="sub dir" -S FETCH_HEAD &&
1576
+ git verify-commit HEAD &&
1577
+ test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
1578
+ )
1579
+'
1580
+
1581
+test_expect_success GPG 'add subproj with GPG signing using --gpg-sign flag' '
1582
+ subtree_test_create_repo "$test_count" &&
1583
+ subtree_test_create_repo "$test_count/sub proj" &&
1584
+ test_create_commit "$test_count" main1 &&
1585
+ test_create_commit "$test_count/sub proj" sub1 &&
1586
+ (
1587
+ cd "$test_count" &&
1588
+ git fetch ./"sub proj" HEAD &&
1589
+ git subtree add --prefix="sub dir" --gpg-sign FETCH_HEAD &&
1590
+ git verify-commit HEAD &&
1591
+ test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
1592
+ )
1593
+'
1594
+
1595
+test_expect_success GPG 'add subproj with GPG signing using specific key ID' '
1596
+ subtree_test_create_repo "$test_count" &&
1597
+ subtree_test_create_repo "$test_count/sub proj" &&
1598
+ test_create_commit "$test_count" main1 &&
1599
+ test_create_commit "$test_count/sub proj" sub1 &&
1600
+ (
1601
+ cd "$test_count" &&
1602
+ git fetch ./"sub proj" HEAD &&
1603
+ git subtree add --prefix="sub dir" -S"$GIT_COMMITTER_EMAIL" FETCH_HEAD &&
1604
+ git verify-commit HEAD &&
1605
+ test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
1606
+ )
1607
+'
1608
+
1609
+test_expect_success GPG 'merge with GPG signing' '
1610
+ subtree_test_create_repo "$test_count" &&
1611
+ subtree_test_create_repo "$test_count/sub proj" &&
1612
+ test_create_commit "$test_count" main1 &&
1613
+ test_create_commit "$test_count/sub proj" sub1 &&
1614
+ (
1615
+ cd "$test_count" &&
1616
+ git fetch ./"sub proj" HEAD &&
1617
+ git subtree add --prefix="sub dir" FETCH_HEAD
1618
+ ) &&
1619
+ test_create_commit "$test_count/sub proj" sub2 &&
1620
+ (
1621
+ cd "$test_count" &&
1622
+ git fetch ./"sub proj" HEAD &&
1623
+ git subtree merge --prefix="sub dir" -S FETCH_HEAD &&
1624
+ git verify-commit HEAD
1625
+ )
1626
+'
1627
+
1628
+test_expect_success GPG 'split with GPG signing and --rejoin' '
1629
+ subtree_test_create_repo "$test_count" &&
1630
+ subtree_test_create_repo "$test_count/sub proj" &&
1631
+ test_create_commit "$test_count" main1 &&
1632
+ test_create_commit "$test_count/sub proj" sub1 &&
1633
+ (
1634
+ cd "$test_count" &&
1635
+ git fetch ./"sub proj" HEAD &&
1636
+ git subtree add --prefix="sub dir" FETCH_HEAD
1637
+ ) &&
1638
+ test_create_commit "$test_count" "sub dir/main-sub1" &&
1639
+ (
1640
+ cd "$test_count" &&
1641
+ git subtree split --prefix="sub dir" --rejoin -S &&
1642
+ git verify-commit HEAD
1643
+ )
1644
+'
1645
+
1646
+test_expect_success GPG 'add with --squash and GPG signing' '
1647
+ subtree_test_create_repo "$test_count" &&
1648
+ subtree_test_create_repo "$test_count/sub proj" &&
1649
+ test_create_commit "$test_count" main1 &&
1650
+ test_create_commit "$test_count/sub proj" sub1 &&
1651
+ (
1652
+ cd "$test_count" &&
1653
+ git fetch ./"sub proj" HEAD &&
1654
+ git subtree add --prefix="sub dir" --squash -S FETCH_HEAD &&
1655
+ git verify-commit HEAD &&
1656
+ # With --squash, the commit subject should reference the squash commit (first parent of merge)
1657
+ squash_commit=$(git rev-parse HEAD^2) &&
1658
+ test "$(last_commit_subject)" = "Merge commit '\''$squash_commit'\'' as '\''sub dir'\''"
1659
+ )
1660
+'
1661
+
1662
+test_expect_success GPG 'pull with GPG signing' '
1663
+ subtree_test_create_repo "$test_count" &&
1664
+ subtree_test_create_repo "$test_count/sub proj" &&
1665
+ test_create_commit "$test_count" main1 &&
1666
+ test_create_commit "$test_count/sub proj" sub1 &&
1667
+ (
1668
+ cd "$test_count" &&
1669
+ git subtree add --prefix="sub dir" ./"sub proj" HEAD
1670
+ ) &&
1671
+ test_create_commit "$test_count/sub proj" sub2 &&
1672
+ (
1673
+ cd "$test_count" &&
1674
+ git subtree pull --prefix="sub dir" -S ./"sub proj" HEAD &&
1675
+ git verify-commit HEAD
1676
+ )
1677
+'
1678
+
1679
test_done