From: Scott Chacon <scott@gitbutler.net>
To: git@vger.kernel.org
Subject: [RFC PATCH 4/4] gpg: add gpg.treeHash to sign a tree-sha256 header by default
Date: Fri, 2 Oct 2026 10:18:46 +0200 [thread overview]
Message-ID: <20261002081846.25144-5-scott@gitbutler.net> (raw)
In-Reply-To: <20261002081846.25144-1-scott@gitbutler.net>
Someone who wants their signatures to cover the contents of their
trees wants it for every tag and commit they sign, and shouldn't have
to remember "--hash=sha256" each time, much as "tag.gpgSign" and
"commit.gpgSign" save them from remembering "-s" and "-S".
Add "gpg.treeHash", which "git tag" and "git commit" use as the
default for "--hash". It is a single variable rather than one for
each command, since the reason for wanting it is the same for both.
It only applies to objects that are signed: with it set, unsigned
commits and annotated or lightweight tags are made as before, rather
than failing as an explicit "--hash=sha256" without signing does.
"--hash=none" overrides it.
---
Documentation/config/gpg.adoc | 6 +++++
Documentation/git-commit.adoc | 2 +-
Documentation/git-tag.adoc | 2 +-
builtin/commit.c | 8 +++++++
builtin/tag.c | 14 ++++++++++-
t/t7032-tree-sha256-signed.sh | 44 +++++++++++++++++++++++++++++++++++
tree-sha256.h | 4 ++--
7 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/gpg.adoc b/Documentation/config/gpg.adoc
index 240e46c050..6728c13a62 100644
--- a/Documentation/config/gpg.adoc
+++ b/Documentation/config/gpg.adoc
@@ -16,6 +16,12 @@ gpg.format::
See linkgit:gitformat-signature[5] for the signature format, which differs
based on the selected `gpg.format`.
+gpg.treeHash::
+ When set to `sha256`, `git commit` and `git tag` add a
+ `tree-sha256` header to every commit and tag they sign, as if
+ `--hash=sha256` were given. Defaults to `none`. See the
+ `--hash` option in linkgit:git-commit[1] and linkgit:git-tag[1].
+
gpg.<format>.program::
Use this to customize the program used for the signing format you
chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index c027de2adb..1ed6635eee 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -405,7 +405,7 @@ changes to tracked files.
digest of every file in the commit's tree, including the
contents of checked-out submodules, so that the signature covers
the content directly rather than only its SHA-1 object names.
- _<algorithm>_ is `sha256`, or `none` (the default).
+ _<algorithm>_ is `sha256`, or `none` to override `gpg.treeHash`.
Giving `--hash=sha256` without signing is an error. All
submodules must be checked out.
diff --git a/Documentation/git-tag.adoc b/Documentation/git-tag.adoc
index 8901090a6d..445be41db1 100644
--- a/Documentation/git-tag.adoc
+++ b/Documentation/git-tag.adoc
@@ -89,7 +89,7 @@ OPTIONS
digest of every file in the tagged object's tree, including the
contents of checked-out submodules, so that the signature covers
the content directly rather than only its SHA-1 object names.
- _<algorithm>_ is `sha256`, or `none` (the default).
+ _<algorithm>_ is `sha256`, or `none` to override `gpg.treeHash`.
Giving `--hash=sha256` without signing is an error. All
submodules must be checked out.
diff --git a/builtin/commit.c b/builtin/commit.c
index 871a2bdcd7..7e56c434db 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1687,6 +1687,14 @@ static int git_commit_config(const char *k, const char *v,
sign_commit = git_config_bool(k, v) ? "" : NULL;
return 0;
}
+ if (!strcmp(k, "gpg.treehash")) {
+ if (!v)
+ return config_error_nonbool(k);
+ tree_hash = parse_signing_hash(v);
+ if (tree_hash < 0)
+ return error(_("invalid value for '%s': '%s'"), k, v);
+ return 0;
+ }
if (!strcmp(k, "commit.verbose")) {
int is_bool;
config_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,
diff --git a/builtin/tag.c b/builtin/tag.c
index 9bc4c946d1..86871317ed 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -51,6 +51,7 @@ static const char * const git_tag_usage[] = {
static unsigned int colopts;
static int force_sign_annotate;
static int config_sign_tag = -1; /* unspecified */
+static int config_tree_hash;
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format)
@@ -223,6 +224,15 @@ static int git_tag_config(const char *var, const char *value,
return 0;
}
+ if (!strcmp(var, "gpg.treehash")) {
+ if (!value)
+ return config_error_nonbool(var);
+ config_tree_hash = parse_signing_hash(value);
+ if (config_tree_hash < 0)
+ return error(_("invalid value for '%s': '%s'"), var, value);
+ return 0;
+ }
+
if (!strcmp(var, "tag.forcesignannotated")) {
force_sign_annotate = git_config_bool(var, value);
return 0;
@@ -601,6 +611,8 @@ int cmd_tag(int argc,
}
create_tag_object = (opt.sign || annotate || msg.given || msgfile ||
edit_flag || trailer_args.nr || opt.tree_hash);
+ if (!hash_arg)
+ opt.tree_hash = config_tree_hash;
if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
@@ -704,7 +716,7 @@ int cmd_tag(int argc,
if (create_tag_object) {
if (force_sign_annotate && !annotate)
opt.sign = 1;
- if (opt.tree_hash && !opt.sign)
+ if (opt.tree_hash && !opt.sign && hash_arg)
die(_("--hash=%s requires a signed tag (-s or -u)"), hash_arg);
path = repo_git_path(the_repository, "TAG_EDITMSG");
create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,
diff --git a/t/t7032-tree-sha256-signed.sh b/t/t7032-tree-sha256-signed.sh
index 44c363b5d2..5a656a7816 100755
--- a/t/t7032-tree-sha256-signed.sh
+++ b/t/t7032-tree-sha256-signed.sh
@@ -122,4 +122,48 @@ test_expect_success GPGSSH 'amending recomputes or drops the header' '
test_must_be_empty actual
'
+test_expect_success GPGSSH 'gpg.treeHash signs the header by default' '
+ test-tool tree-sha256 HEAD >expect &&
+ test_config gpg.treeHash sha256 &&
+ git tag -s -m release v6 &&
+ header_of tag v6 >actual &&
+ test_cmp expect actual &&
+ test_tick &&
+ git commit --allow-empty -S -m signed &&
+ header_of commit HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'gpg.treeHash leaves unsigned objects alone' '
+ test_config gpg.treeHash sha256 &&
+ git tag -a -m annotated v7 &&
+ header_of tag v7 >actual &&
+ test_must_be_empty actual &&
+ git tag v8 &&
+ test "$(git cat-file -t v8)" = commit &&
+ test_tick &&
+ git commit --allow-empty -m unsigned &&
+ header_of commit HEAD >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success GPGSSH '--hash=none overrides gpg.treeHash' '
+ test_config gpg.treeHash sha256 &&
+ git tag -s --hash=none -m release v9 &&
+ header_of tag v9 >actual &&
+ test_must_be_empty actual &&
+ test_tick &&
+ git commit --allow-empty -S --hash=none -m signed &&
+ header_of commit HEAD >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success GPGSSH 'invalid gpg.treeHash is an error' '
+ test_config gpg.treeHash md5 &&
+ test_must_fail git tag -s -m release v10 2>err &&
+ test_grep "invalid value for .gpg.treehash." err &&
+ test_must_fail git commit --allow-empty -S -m signed 2>err &&
+ test_grep "invalid value for .gpg.treehash." err
+'
+
test_done
diff --git a/tree-sha256.h b/tree-sha256.h
index dc070129ea..6d54c2c9f9 100644
--- a/tree-sha256.h
+++ b/tree-sha256.h
@@ -28,8 +28,8 @@ int tree_sha256_hex(struct repository *r, const struct object_id *oid,
struct strbuf *hex);
/*
- * Parse the value of a --hash=<algorithm> option. Returns 1 for
- * "sha256", 0 for "none", and -1 for anything else.
+ * Parse the value of a --hash=<algorithm> option or of gpg.treeHash.
+ * Returns 1 for "sha256", 0 for "none", and -1 for anything else.
*/
int parse_signing_hash(const char *value);
--
2.50.1 (Apple Git-155)
next prev parent reply other threads:[~2026-10-02 8:18 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-02 8:18 [RFC PATCH 0/4] sign a SHA-256 digest of the tree in commits and tags Scott Chacon
2026-10-02 8:18 ` [RFC PATCH 1/4] tree-sha256: hash the contents of a tree with SHA-256 Scott Chacon
2026-10-02 15:45 ` Junio C Hamano
2026-10-02 8:18 ` [RFC PATCH 2/4] tag: add --hash=sha256 to sign a tree-sha256 header Scott Chacon
2026-10-02 15:49 ` Junio C Hamano
2026-10-02 8:18 ` [RFC PATCH 3/4] commit: " Scott Chacon
2026-10-02 8:18 ` Scott Chacon [this message]
2026-10-02 15:52 ` [RFC PATCH 0/4] sign a SHA-256 digest of the tree in commits and tags Junio C Hamano
2026-10-02 19:06 ` brian m. carlson
2026-10-05 9:32 ` Scott Chacon
2026-10-05 12:41 ` Patrick Steinhardt
2026-10-05 14:16 ` Scott Chacon
2026-10-05 22:57 ` brian m. carlson
2026-10-06 13:36 ` Johannes Schindelin
2026-10-06 16:16 ` Kristoffer Haugsbakk
2026-10-06 21:55 ` brian m. carlson
2026-10-06 22:38 ` Junio C Hamano
2026-10-06 23:40 ` brian m. carlson
2026-10-06 9:00 ` Christian Couder
2026-10-06 22:26 ` brian m. carlson
2026-10-07 12:26 ` Christian Couder
2026-10-07 21:07 ` brian m. carlson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261002081846.25144-5-scott@gitbutler.net \
--to=scott@gitbutler.net \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox