Merge branch 'pw/subtree-gpg-sign'

"git subtree" (in contrib/) learns to grok GPG signing its commits.

* pw/subtree-gpg-sign:
  contrib/subtree: add -S/--gpg-sign
  contrib/subtree: parse using --stuck-long
This commit is contained in:
Junio C Hamano
2025-06-24 09:48:46 -07:00
3 changed files with 158 additions and 40 deletions

View File

@@ -9,14 +9,14 @@ git-subtree - Merge subtrees together and split repository into subtrees
SYNOPSIS
--------
[verse]
'git subtree' [<options>] -P <prefix> add <local-commit>
'git subtree' [<options>] -P <prefix> add <repository> <remote-ref>
'git subtree' [<options>] -P <prefix> merge <local-commit> [<repository>]
'git subtree' [<options>] -P <prefix> split [<local-commit>]
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] add <local-commit>
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] add <repository> <remote-ref>
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] merge <local-commit> [<repository>]
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] split [<local-commit>]
[verse]
'git subtree' [<options>] -P <prefix> pull <repository> <remote-ref>
'git subtree' [<options>] -P <prefix> push <repository> <refspec>
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] pull <repository> <remote-ref>
'git subtree' [<options>] -P <prefix> [-S[<keyid>]] push <repository> <refspec>
DESCRIPTION
-----------
@@ -149,6 +149,13 @@ OPTIONS FOR ALL COMMANDS
want to manipulate. This option is mandatory
for all commands.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; `--no-gpg-sign` is useful to
countermand a `--gpg-sign` option given earlier on the command line.
OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull', 'split --rejoin', AND 'push --rejoin')
-----------------------------------------------------------------------------------
These options for 'add' and 'merge' may also be given to 'pull' (which

View File

@@ -26,12 +26,12 @@ then
fi
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
git subtree split --prefix=<prefix> [<commit>]
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <refspec>
git subtree add --prefix=<prefix> [-S[=<key-id>]] <commit>
git subtree add --prefix=<prefix> [-S[=<key-id>]] <repository> <ref>
git subtree merge --prefix=<prefix> [-S[=<key-id>]] <commit>
git subtree split --prefix=<prefix> [-S[=<key-id>]] [<commit>]
git subtree pull --prefix=<prefix> [-S[=<key-id>]] <repository> <ref>
git subtree push --prefix=<prefix> [-S[=<key-id>]] <repository> <refspec>
--
h,help! show the help
q,quiet! quiet
@@ -46,6 +46,7 @@ rejoin merge the new branch back into HEAD
options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin')
squash merge subtree changes as a single commit
m,message!= use the given message as the commit message for the merge commit
S,gpg-sign?key-id GPG-sign commits. The keyid argument is optional and defaults to the committer identity
"
indent=0
@@ -115,7 +116,7 @@ main () {
then
set -- -h
fi
set_args="$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
set_args="$(echo "$OPTS_SPEC" | git rev-parse --parseopt --stuck-long -- "$@" || echo exit $?)"
eval "$set_args"
. git-sh-setup
require_work_tree
@@ -131,9 +132,6 @@ main () {
opt="$1"
shift
case "$opt" in
--annotate|-b|-P|-m|--onto)
shift
;;
--rejoin)
arg_split_rejoin=1
;;
@@ -171,48 +169,44 @@ main () {
arg_split_annotate=
arg_addmerge_squash=
arg_addmerge_message=
arg_gpg_sign=
while test $# -gt 0
do
opt="$1"
shift
case "$opt" in
-q)
--quiet)
arg_quiet=1
;;
-d)
--debug)
arg_debug=1
;;
--annotate)
--annotate=*)
test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command"
arg_split_annotate="$1"
shift
arg_split_annotate="${opt#*=}"
;;
--no-annotate)
test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command"
arg_split_annotate=
;;
-b)
--branch=*)
test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command"
arg_split_branch="$1"
shift
arg_split_branch="${opt#*=}"
;;
-P)
arg_prefix="${1%/}"
shift
--prefix=*)
arg_prefix="${opt#*=}"
;;
-m)
--message=*)
test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command"
arg_addmerge_message="$1"
shift
arg_addmerge_message="${opt#*=}"
;;
--no-prefix)
arg_prefix=
;;
--onto)
--onto=*)
test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command"
arg_split_onto="$1"
shift
arg_split_onto="${opt#*=}"
;;
--no-onto)
test -n "$allow_split" || die_incompatible_opt "$opt" "$arg_command"
@@ -240,6 +234,9 @@ main () {
test -n "$allow_addmerge" || die_incompatible_opt "$opt" "$arg_command"
arg_addmerge_squash=
;;
--gpg-sign=* | --gpg-sign | --no-gpg-sign)
arg_gpg_sign="$opt"
;;
--)
break
;;
@@ -272,6 +269,7 @@ main () {
debug "quiet: {$arg_quiet}"
debug "dir: {$dir}"
debug "opts: {$*}"
debug "gpg-sign: {$arg_gpg_sign}"
debug
"cmd_$arg_command" "$@"
@@ -537,7 +535,7 @@ copy_commit () {
printf "%s" "$arg_split_annotate"
cat
) |
git commit-tree "$2" $3 # reads the rest of stdin
git commit-tree $arg_gpg_sign "$2" $3 # reads the rest of stdin
) || die "fatal: can't copy commit $1"
}
@@ -683,10 +681,10 @@ new_squash_commit () {
if test -n "$old"
then
squash_msg "$dir" "$oldsub" "$newsub" |
git commit-tree "$tree" -p "$old" || exit $?
git commit-tree $arg_gpg_sign "$tree" -p "$old" || exit $?
else
squash_msg "$dir" "" "$newsub" |
git commit-tree "$tree" || exit $?
git commit-tree $arg_gpg_sign "$tree" || exit $?
fi
}
@@ -925,11 +923,11 @@ cmd_add_commit () {
then
rev=$(new_squash_commit "" "" "$rev") || exit $?
commit=$(add_squashed_msg "$rev" "$dir" |
git commit-tree "$tree" $headp -p "$rev") || exit $?
git commit-tree $arg_gpg_sign "$tree" $headp -p "$rev") || exit $?
else
revp=$(peel_committish "$rev") || exit $?
commit=$(add_msg "$dir" $headrev "$rev" |
git commit-tree "$tree" $headp -p "$revp") || exit $?
git commit-tree $arg_gpg_sign "$tree" $headp -p "$revp") || exit $?
fi
git reset "$commit" || exit $?
@@ -1080,9 +1078,9 @@ cmd_merge () {
if test -n "$arg_addmerge_message"
then
git merge --no-ff -Xsubtree="$arg_prefix" \
--message="$arg_addmerge_message" "$rev"
--message="$arg_addmerge_message" $arg_gpg_sign "$rev"
else
git merge --no-ff -Xsubtree="$arg_prefix" $rev
git merge --no-ff -Xsubtree="$arg_prefix" $arg_gpg_sign $rev
fi
}

View File

@@ -11,6 +11,7 @@ and push subcommands of git subtree.
TEST_DIRECTORY=$(pwd)/../../../t
. "$TEST_DIRECTORY"/test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
# Use our own wrapper around test-lib.sh's test_create_repo, in order
# to set log.date=relative. `git subtree` parses the output of `git
@@ -1563,4 +1564,116 @@ test_expect_success 'subtree descendant check' '
)
'
test_expect_success GPG 'add subproj with GPG signing using -S flag' '
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" -S FETCH_HEAD &&
git verify-commit HEAD &&
test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
test_expect_success GPG 'add subproj with GPG signing using --gpg-sign flag' '
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" --gpg-sign FETCH_HEAD &&
git verify-commit HEAD &&
test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
test_expect_success GPG 'add subproj with GPG signing using specific key ID' '
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" -S"$GIT_COMMITTER_EMAIL" FETCH_HEAD &&
git verify-commit HEAD &&
test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
)
'
test_expect_success GPG 'merge with GPG signing' '
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" FETCH_HEAD
) &&
test_create_commit "$test_count/sub proj" sub2 &&
(
cd "$test_count" &&
git fetch ./"sub proj" HEAD &&
git subtree merge --prefix="sub dir" -S FETCH_HEAD &&
git verify-commit HEAD
)
'
test_expect_success GPG 'split with GPG signing and --rejoin' '
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" FETCH_HEAD
) &&
test_create_commit "$test_count" "sub dir/main-sub1" &&
(
cd "$test_count" &&
git subtree split --prefix="sub dir" --rejoin -S &&
git verify-commit HEAD
)
'
test_expect_success GPG 'add with --squash and GPG signing' '
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 -S FETCH_HEAD &&
git verify-commit HEAD &&
# With --squash, the commit subject should reference the squash commit (first parent of merge)
squash_commit=$(git rev-parse HEAD^2) &&
test "$(last_commit_subject)" = "Merge commit '\''$squash_commit'\'' as '\''sub dir'\''"
)
'
test_expect_success GPG 'pull with GPG signing' '
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 subtree add --prefix="sub dir" ./"sub proj" HEAD
) &&
test_create_commit "$test_count/sub proj" sub2 &&
(
cd "$test_count" &&
git subtree pull --prefix="sub dir" -S ./"sub proj" HEAD &&
git verify-commit HEAD
)
'
test_done