threads / patch / 63564

patch, 2 partscontrib/subtree: Add -S/-gpg-sign

Subject: [PATCH 0/2] contrib/subtree: Add -S/-gpg-sign

## tl;dr

12 messages between Jun 2, 2025 and Jun 4, 2025. Diffs are folded; open one to read it.

replies: 11people: 4as markdown or json

Patrik Weiskircher via GitGitGadget· Jun 2, 2025, 16:40 UTC · lore

This patch adds support for the -S/--gpg-sign option to the git subtree command.

Currently, git subtree creates squash and merge commits without applying GPG signatures, even when commit signing is configured via commit.gpgSign. This causes issues in repositories that require signed commits for policy reasons.

The implementation ensures that -S/--gpg-sign behaves consistently with other Git commands, passing the flag through to git commit-tree and git merge where appropriate.

This change improves compatibility with workflows and repositories that enforce signed commits.

Changes have been made to this patch according to a discussion with Junio C Hamano gitster@pobox.com on my previous patch attempt.

Patrik Weiskircher (2):
  contrib/subtree: parse using --stuck-long
  contrib/subtree: add -S/--gpg-sign
 contrib/subtree/git-subtree.adoc   |  19 +++--
 contrib/subtree/git-subtree.sh     |  66 ++++++++---------
 contrib/subtree/t/t7900-subtree.sh | 113 +++++++++++++++++++++++++++++
 3 files changed, 158 insertions(+), 40 deletions(-)
base-commit: 7014b55638da979331baf8dc31c4e1d697cf2d67
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1928%2Fpweiskircher%2Fsubtree-sign-the-second-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1928/pweiskircher/subtree-sign-the-second-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1928
-- 
gitgitgadget
Patrik Weiskircher via GitGitGadget· Jun 2, 2025, 16:40 UTC · re: Patrik Weiskircher via GitGitGadget · lore

[PATCH 1/2] contrib/subtree: parse using --stuck-long

From: Patrik Weiskircher <patrik@pspdfkit.com>

-S/--gpg-sign requires an optional parameter. Optional parameter handling only works unambiguous with git rev-parse --parseopt when using the --stuck-long option.

Signed-off-by: Patrik Weiskircher <patrik@pspdfkit.com>
---
 contrib/subtree/git-subtree.sh | 34 +++++++++++++---------------------
 1 file changed, 13 insertions(+), 21 deletions(-)
Show changes to contrib/subtree/git-subtree.sh +13 −21
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 15ae86db1b27..60b2431b8bba 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -115,7 +115,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 +131,6 @@ main () {
 		opt="$1"
 		shift
 		case "$opt" in
-			--annotate|-b|-P|-m|--onto)
-				shift
-				;;
 			--rejoin)
 				arg_split_rejoin=1
 				;;
@@ -177,42 +174,37 @@ main () {
 		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"
-- 
gitgitgadget
D. Ben Knoble· Jun 3, 2025, 20:42 UTC · re: Patrik Weiskircher via GitGitGadget · lore

Re: [PATCH 1/2] contrib/subtree: parse using --stuck-long

On Mon, Jun 2, 2025 at 12:41 PM Patrik Weiskircher via GitGitGadget <gitgitgadget@gmail.com> wrote:

Show 6 quoted lines
>
> From: Patrik Weiskircher <patrik@pspdfkit.com>
>
> -S/--gpg-sign requires an optional parameter. Optional parameter
> handling only works unambiguous with git rev-parse --parseopt when using
> the --stuck-long option.
Here we mention "-S", but that flag isn't implemented yet, right?
Perhaps something like:
    Optional parameter handling only works unambiguous with git rev-parse
    --parseopt when using the --stuck-long option. To prepare for future commits
    which add flags with optional parameters, parse with --stuck-long.
Show 88 quoted lines
>
> Signed-off-by: Patrik Weiskircher <patrik@pspdfkit.com>
> ---
>  contrib/subtree/git-subtree.sh | 34 +++++++++++++---------------------
>  1 file changed, 13 insertions(+), 21 deletions(-)
>
> diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
> index 15ae86db1b27..60b2431b8bba 100755
> --- a/contrib/subtree/git-subtree.sh
> +++ b/contrib/subtree/git-subtree.sh
> @@ -115,7 +115,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 +131,6 @@ main () {
>                 opt="$1"
>                 shift
>                 case "$opt" in
> -                       --annotate|-b|-P|-m|--onto)
> -                               shift
> -                               ;;
>                         --rejoin)
>                                 arg_split_rejoin=1
>                                 ;;
> @@ -177,42 +174,37 @@ main () {
>                 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"
> --
> gitgitgadget
>
>
-- 
D. Ben Knoble
Patrik Weiskircher· Jun 4, 2025, 13:56 UTC · re: D. Ben Knoble · lore

Re: [PATCH 1/2] contrib/subtree: parse using --stuck-long

On Tue, Jun 3, 2025 at 4:42 PM D. Ben Knoble <ben.knoble@gmail.com> wrote:
Show 18 quoted lines
>
> On Mon, Jun 2, 2025 at 12:41 PM Patrik Weiskircher via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: Patrik Weiskircher <patrik@pspdfkit.com>
> >
> > -S/--gpg-sign requires an optional parameter. Optional parameter
> > handling only works unambiguous with git rev-parse --parseopt when using
> > the --stuck-long option.
>
> Here we mention "-S", but that flag isn't implemented yet, right?
>
> Perhaps something like:
>
>     Optional parameter handling only works unambiguous with git rev-parse
>     --parseopt when using the --stuck-long option. To prepare for future commits
>     which add flags with optional parameters, parse with --stuck-long.
>

Makes sense! Changing that. What is a good policy to resubmit something? Should I wait longer? Sorry, very new here!

Kristoffer Haugsbakk· Jun 4, 2025, 14:09 UTC · re: Patrik Weiskircher · lore

Re: [PATCH 1/2] contrib/subtree: parse using --stuck-long

On Wed, Jun 4, 2025, at 15:56, Patrik Weiskircher wrote:
Show 12 quoted lines
>>
>> Here we mention "-S", but that flag isn't implemented yet, right?
>>
>> Perhaps something like:
>>
>>     Optional parameter handling only works unambiguous with git rev-parse
>>     --parseopt when using the --stuck-long option. To prepare for future commits
>>     which add flags with optional parameters, parse with --stuck-long.
>>
>
> Makes sense! Changing that. What is a good policy to resubmit
> something? Should I wait longer? Sorry, very new here!
• Force-push your branch to gitgitgadget
• Edit the PR description with something like “Changes since v1:” to
  summarize the changes
• (`/preview` comment)
• To send the next version: `/submit` comment again
I think that’s it. :)
I don’t think there’s a need to wait if you don’t want to.
-- 
Kristoffer Haugsbakk
Junio C Hamano· Jun 4, 2025, 15:41 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH 1/2] contrib/subtree: parse using --stuck-long

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
Show 23 quoted lines
> On Wed, Jun 4, 2025, at 15:56, Patrik Weiskircher wrote:
>>>
>>> Here we mention "-S", but that flag isn't implemented yet, right?
>>>
>>> Perhaps something like:
>>>
>>>     Optional parameter handling only works unambiguous with git rev-parse
>>>     --parseopt when using the --stuck-long option. To prepare for future commits
>>>     which add flags with optional parameters, parse with --stuck-long.
>>>
>>
>> Makes sense! Changing that. What is a good policy to resubmit
>> something? Should I wait longer? Sorry, very new here!
>
> • Force-push your branch to gitgitgadget
> • Edit the PR description with something like “Changes since v1:” to
>   summarize the changes
> • (`/preview` comment)
> • To send the next version: `/submit` comment again
>
> I think that’s it. :)
>
> I don’t think there’s a need to wait if you don’t want to.

It would be nice for potential reviewers to give at least 24 hours to ensure people anywhere on the globe have a chance to comment, and a chance for you to respond to them, before sending your next iteration.

Also, for future reference, when responding to a review comment that causes you to drastically change the course of the series, you can respond whenever you want to, but it is nice to other potential reviewers to give at least 24 hours to voice their opinions, before sending an updated series based on that comment, since suggested changes in such a comment may be controversial and after seeing you spend some time already to adjust to it, others may feel discouraged to make you redo your series again even whey they think the suggested changes are not taking us in the right direction.

Patrik Weiskircher· Jun 4, 2025, 15:43 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/2] contrib/subtree: parse using --stuck-long

On Wed, Jun 4, 2025 at 11:41 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 41 quoted lines
>
> "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
>
> > On Wed, Jun 4, 2025, at 15:56, Patrik Weiskircher wrote:
> >>>
> >>> Here we mention "-S", but that flag isn't implemented yet, right?
> >>>
> >>> Perhaps something like:
> >>>
> >>>     Optional parameter handling only works unambiguous with git rev-parse
> >>>     --parseopt when using the --stuck-long option. To prepare for future commits
> >>>     which add flags with optional parameters, parse with --stuck-long.
> >>>
> >>
> >> Makes sense! Changing that. What is a good policy to resubmit
> >> something? Should I wait longer? Sorry, very new here!
> >
> > • Force-push your branch to gitgitgadget
> > • Edit the PR description with something like “Changes since v1:” to
> >   summarize the changes
> > • (`/preview` comment)
> > • To send the next version: `/submit` comment again
> >
> > I think that’s it. :)
> >
> > I don’t think there’s a need to wait if you don’t want to.
>
> It would be nice for potential reviewers to give at least 24 hours
> to ensure people anywhere on the globe have a chance to comment, and
> a chance for you to respond to them, before sending your next
> iteration.
>
> Also, for future reference, when responding to a review comment that
> causes you to drastically change the course of the series, you can
> respond whenever you want to, but it is nice to other potential
> reviewers to give at least 24 hours to voice their opinions, before
> sending an updated series based on that comment, since suggested
> changes in such a comment may be controversial and after seeing you
> spend some time already to adjust to it, others may feel discouraged
> to make you redo your series again even whey they think the
> suggested changes are not taking us in the right direction.
Makes sense! I'll keep that in mind for the future!
Patrik Weiskircher via GitGitGadget· Jun 2, 2025, 16:40 UTC · re: Patrik Weiskircher via GitGitGadget · lore

[PATCH 2/2] contrib/subtree: add -S/--gpg-sign

From: Patrik Weiskircher <patrik@pspdfkit.com>

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>
---
 contrib/subtree/git-subtree.adoc   |  19 +++--
 contrib/subtree/git-subtree.sh     |  32 ++++----
 contrib/subtree/t/t7900-subtree.sh | 113 +++++++++++++++++++++++++++++
 3 files changed, 145 insertions(+), 19 deletions(-)
Show changes to 3 files +145 −19

contrib/subtree/git-subtree.adoc, contrib/subtree/git-subtree.sh, contrib/subtree/t/t7900-subtree.sh

diff --git a/contrib/subtree/git-subtree.adoc b/contrib/subtree/git-subtree.adoc
index 004abf415b8e..b2bcbcad0d05 100644
--- a/contrib/subtree/git-subtree.adoc
+++ b/contrib/subtree/git-subtree.adoc
@@ -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
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 60b2431b8bba..3fddba797cb9 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -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
@@ -168,6 +169,7 @@ main () {
 	arg_split_annotate=
 	arg_addmerge_squash=
 	arg_addmerge_message=
+    arg_gpg_sign=
 	while test $# -gt 0
 	do
 		opt="$1"
@@ -232,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
 			;;
@@ -264,6 +269,7 @@ main () {
 	debug "quiet: {$arg_quiet}"
 	debug "dir: {$dir}"
 	debug "opts: {$*}"
+    debug "gpg-sign: {$arg_gpg_sign}"
 	debug
 
 	"cmd_$arg_command" "$@"
@@ -529,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"
 }
 
@@ -675,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
 }
 
@@ -917,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 $?
 
@@ -1072,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
 }
 
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 3c6103f6d270..3edbb33af469 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -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
-- 
gitgitgadget
Patrik Weiskircher via GitGitGadget· Jun 4, 2025, 14:16 UTC · re: Patrik Weiskircher via GitGitGadget · lore

[PATCH v2 0/2] contrib/subtree: Add -S/-gpg-sign

This patch adds support for the -S/--gpg-sign option to the git subtree command.

Currently, git subtree creates squash and merge commits without applying GPG signatures, even when commit signing is configured via commit.gpgSign. This causes issues in repositories that require signed commits for policy reasons.

The implementation ensures that -S/--gpg-sign behaves consistently with other Git commands, passing the flag through to git commit-tree and git merge where appropriate.

This change improves compatibility with workflows and repositories that enforce signed commits.

Changes have been made to this patch according to a discussion with Junio C Hamano gitster@pobox.com on my previous patch attempt.

Changes since v1:
 * Adjusted commit message to not mention the not implemented flag yet.
Patrik Weiskircher (2):
  contrib/subtree: parse using --stuck-long
  contrib/subtree: add -S/--gpg-sign
 contrib/subtree/git-subtree.adoc   |  19 +++--
 contrib/subtree/git-subtree.sh     |  66 ++++++++---------
 contrib/subtree/t/t7900-subtree.sh | 113 +++++++++++++++++++++++++++++
 3 files changed, 158 insertions(+), 40 deletions(-)
base-commit: 0bd2d791cc9f745ebaedafc0e1cbebdebe41343e
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1928%2Fpweiskircher%2Fsubtree-sign-the-second-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1928/pweiskircher/subtree-sign-the-second-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1928
Range-diff vs v1:
 1:  fe7e918ba18 ! 1:  5bbdbf01636 contrib/subtree: parse using --stuck-long
     @@ Metadata
       ## Commit message ##
          contrib/subtree: parse using --stuck-long
      
     -    -S/--gpg-sign requires an optional parameter. Optional parameter
     -    handling only works unambiguous with git rev-parse --parseopt when using
     -    the --stuck-long option.
     +    Optional parameter handling only works unambiguous with git rev-parse
     +    --parseopt when using the --stuck-long option. To prepare for future commits
     +    which add flags with optional parameters, parse with --stuck-long.
      
          Signed-off-by: Patrik Weiskircher <patrik@pspdfkit.com>
      
 2:  45ae2c31bc0 = 2:  af5b4a65b63 contrib/subtree: add -S/--gpg-sign
-- 
gitgitgadget
Patrik Weiskircher via GitGitGadget· Jun 4, 2025, 14:16 UTC · re: Patrik Weiskircher via GitGitGadget · lore

[PATCH v2 1/2] contrib/subtree: parse using --stuck-long

From: Patrik Weiskircher <patrik@pspdfkit.com>

Optional parameter handling only works unambiguous with git rev-parse --parseopt when using the --stuck-long option. To prepare for future commits which add flags with optional parameters, parse with --stuck-long.

Signed-off-by: Patrik Weiskircher <patrik@pspdfkit.com>
---
 contrib/subtree/git-subtree.sh | 34 +++++++++++++---------------------
 1 file changed, 13 insertions(+), 21 deletions(-)
Show changes to contrib/subtree/git-subtree.sh +13 −21
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 15ae86db1b27..60b2431b8bba 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -115,7 +115,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 +131,6 @@ main () {
 		opt="$1"
 		shift
 		case "$opt" in
-			--annotate|-b|-P|-m|--onto)
-				shift
-				;;
 			--rejoin)
 				arg_split_rejoin=1
 				;;
@@ -177,42 +174,37 @@ main () {
 		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"
-- 
gitgitgadget
Patrik Weiskircher via GitGitGadget· Jun 4, 2025, 14:16 UTC · re: Patrik Weiskircher via GitGitGadget · lore

[PATCH v2 2/2] contrib/subtree: add -S/--gpg-sign

From: Patrik Weiskircher <patrik@pspdfkit.com>

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>
---
 contrib/subtree/git-subtree.adoc   |  19 +++--
 contrib/subtree/git-subtree.sh     |  32 ++++----
 contrib/subtree/t/t7900-subtree.sh | 113 +++++++++++++++++++++++++++++
 3 files changed, 145 insertions(+), 19 deletions(-)
Show changes to 3 files +145 −19

contrib/subtree/git-subtree.adoc, contrib/subtree/git-subtree.sh, contrib/subtree/t/t7900-subtree.sh

diff --git a/contrib/subtree/git-subtree.adoc b/contrib/subtree/git-subtree.adoc
index 004abf415b8e..b2bcbcad0d05 100644
--- a/contrib/subtree/git-subtree.adoc
+++ b/contrib/subtree/git-subtree.adoc
@@ -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
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 60b2431b8bba..3fddba797cb9 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -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
@@ -168,6 +169,7 @@ main () {
 	arg_split_annotate=
 	arg_addmerge_squash=
 	arg_addmerge_message=
+    arg_gpg_sign=
 	while test $# -gt 0
 	do
 		opt="$1"
@@ -232,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
 			;;
@@ -264,6 +269,7 @@ main () {
 	debug "quiet: {$arg_quiet}"
 	debug "dir: {$dir}"
 	debug "opts: {$*}"
+    debug "gpg-sign: {$arg_gpg_sign}"
 	debug
 
 	"cmd_$arg_command" "$@"
@@ -529,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"
 }
 
@@ -675,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
 }
 
@@ -917,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 $?
 
@@ -1072,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
 }
 
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 3c6103f6d270..3edbb33af469 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -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
-- 
gitgitgadget
Junio C Hamano· Jun 4, 2025, 16:33 UTC · re: Patrik Weiskircher via GitGitGadget · lore

Re: [PATCH v2 0/2] contrib/subtree: Add -S/-gpg-sign

"Patrik Weiskircher via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 12 quoted lines
> Changes since v1:
>
>  * Adjusted commit message to not mention the not implemented flag yet.
>
> Patrik Weiskircher (2):
>   contrib/subtree: parse using --stuck-long
>   contrib/subtree: add -S/--gpg-sign
>
>  contrib/subtree/git-subtree.adoc   |  19 +++--
>  contrib/subtree/git-subtree.sh     |  66 ++++++++---------
>  contrib/subtree/t/t7900-subtree.sh | 113 +++++++++++++++++++++++++++++
>  3 files changed, 158 insertions(+), 40 deletions(-)
Still looking good.  Will queue.  Thanks.

← back to recent threads