aboutsummaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
authorJustin Tobler <jltobler@gmail.com>2026-03-12 20:39:38 -0500
committerJunio C Hamano <gitster@pobox.com>2026-03-12 21:28:20 -0700
commitee66c793f84ef1c84ec3fe732bb26394ebefd257 (patch)
treef096ae01321a350041133d3156dd39d9db27ea3a /builtin
parent86ebf870b909a7f4707aa2601d290bc992d21a53 (diff)
downloadgit-ee66c793f84ef1c84ec3fe732bb26394ebefd257.tar.xz
fast-import: add mode to sign commits with invalid signatures
With git-fast-import(1), handling of signed commits is controlled via the `--signed-commits=<mode>` option. When an invalid signature is encountered, a user may want the option to sign the commit again as opposed to just stripping the signature. To facilitate this, introduce a "sign-if-invalid" mode for the `--signed-commits` option. Optionally, a key ID may be explicitly provided in the form `sign-if-invalid[=<keyid>]` to specify which signing key should be used when signing invalid commit signatures. Note that to properly support interoperability mode when signing commit signatures, the commit buffer must be created in both the repository and compatability object formats to generate the appropriate signatures accordingly. As currently implemented, the commit buffer for the compatability object format is not reconstructed and thus signing commits in interoperability mode is not yet supported. Support may be added in the future. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'builtin')
-rw-r--r--builtin/fast-export.c8
-rw-r--r--builtin/fast-import.c99
2 files changed, 85 insertions, 22 deletions
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 0c5d2386d8..13621b0d6a 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -64,7 +64,7 @@ static int parse_opt_sign_mode(const struct option *opt,
if (unset)
return 0;
- if (parse_sign_mode(arg, val))
+ if (parse_sign_mode(arg, val, NULL))
return error(_("unknown %s mode: %s"), opt->long_name, arg);
return 0;
@@ -825,6 +825,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
case SIGN_STRIP_IF_INVALID:
die(_("'strip-if-invalid' is not a valid mode for "
"git fast-export with --signed-commits=<mode>"));
+ case SIGN_SIGN_IF_INVALID:
+ die(_("'sign-if-invalid' is not a valid mode for "
+ "git fast-export with --signed-commits=<mode>"));
default:
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
}
@@ -970,6 +973,9 @@ static void handle_tag(const char *name, struct tag *tag)
case SIGN_STRIP_IF_INVALID:
die(_("'strip-if-invalid' is not a valid mode for "
"git fast-export with --signed-tags=<mode>"));
+ case SIGN_SIGN_IF_INVALID:
+ die(_("'sign-if-invalid' is not a valid mode for "
+ "git fast-export with --signed-tags=<mode>"));
default:
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
}
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index b8a7757cfd..935e688e33 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -190,6 +190,7 @@ static const char *global_prefix;
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
+static const char *signed_commit_keyid;
/* Memory pools */
static struct mem_pool fi_mem_pool = {
@@ -2836,10 +2837,46 @@ static void finalize_commit_buffer(struct strbuf *new_data,
strbuf_addbuf(new_data, msg);
}
-static void handle_strip_if_invalid(struct strbuf *new_data,
- struct signature_data *sig_sha1,
- struct signature_data *sig_sha256,
- struct strbuf *msg)
+static void warn_invalid_signature(struct signature_check *check,
+ const char *msg, enum sign_mode mode)
+{
+ const char *signer = check->signer ? check->signer : _("unknown");
+ const char *subject;
+ int subject_len = find_commit_subject(msg, &subject);
+
+ switch (mode) {
+ case SIGN_STRIP_IF_INVALID:
+ if (subject_len > 100)
+ warning(_("stripping invalid signature for commit '%.100s...'\n"
+ " allegedly by %s"), subject, signer);
+ else if (subject_len > 0)
+ warning(_("stripping invalid signature for commit '%.*s'\n"
+ " allegedly by %s"), subject_len, subject, signer);
+ else
+ warning(_("stripping invalid signature for commit\n"
+ " allegedly by %s"), signer);
+ break;
+ case SIGN_SIGN_IF_INVALID:
+ if (subject_len > 100)
+ warning(_("replacing invalid signature for commit '%.100s...'\n"
+ " allegedly by %s"), subject, signer);
+ else if (subject_len > 0)
+ warning(_("replacing invalid signature for commit '%.*s'\n"
+ " allegedly by %s"), subject_len, subject, signer);
+ else
+ warning(_("replacing invalid signature for commit\n"
+ " allegedly by %s"), signer);
+ break;
+ default:
+ BUG("unsupported signing mode");
+ }
+}
+
+static void handle_signature_if_invalid(struct strbuf *new_data,
+ struct signature_data *sig_sha1,
+ struct signature_data *sig_sha256,
+ struct strbuf *msg,
+ enum sign_mode mode)
{
struct strbuf tmp_buf = STRBUF_INIT;
struct signature_check signature_check = { 0 };
@@ -2851,20 +2888,34 @@ static void handle_strip_if_invalid(struct strbuf *new_data,
ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);
if (ret) {
- const char *signer = signature_check.signer ?
- signature_check.signer : _("unknown");
- const char *subject;
- int subject_len = find_commit_subject(msg->buf, &subject);
+ warn_invalid_signature(&signature_check, msg->buf, mode);
- if (subject_len > 100)
- warning(_("stripping invalid signature for commit '%.100s...'\n"
- " allegedly by %s"), subject, signer);
- else if (subject_len > 0)
- warning(_("stripping invalid signature for commit '%.*s'\n"
- " allegedly by %s"), subject_len, subject, signer);
- else
- warning(_("stripping invalid signature for commit\n"
- " allegedly by %s"), signer);
+ if (mode == SIGN_SIGN_IF_INVALID) {
+ struct strbuf signature = STRBUF_INIT;
+ struct strbuf payload = STRBUF_INIT;
+
+ /*
+ * NEEDSWORK: To properly support interoperability mode
+ * when signing commit signatures, the commit buffer
+ * must be provided in both the repository and
+ * compatibility object formats. As currently
+ * implemented, only the repository object format is
+ * considered meaning compatibility signatures cannot be
+ * generated. Thus, attempting to sign commit signatures
+ * in interoperability mode is currently unsupported.
+ */
+ if (the_repository->compat_hash_algo)
+ die(_("signing commits in interoperability mode is unsupported"));
+
+ strbuf_addstr(&payload, signature_check.payload);
+ if (sign_buffer(&payload, &signature, signed_commit_keyid,
+ SIGN_BUFFER_USE_DEFAULT_KEY))
+ die(_("failed to sign commit object"));
+ add_header_signature(new_data, &signature, the_hash_algo);
+
+ strbuf_release(&signature);
+ strbuf_release(&payload);
+ }
finalize_commit_buffer(new_data, NULL, NULL, msg);
} else {
@@ -2927,6 +2978,7 @@ static void parse_new_commit(const char *arg)
/* fallthru */
case SIGN_VERBATIM:
case SIGN_STRIP_IF_INVALID:
+ case SIGN_SIGN_IF_INVALID:
import_one_signature(&sig_sha1, &sig_sha256, v);
break;
@@ -3011,9 +3063,11 @@ static void parse_new_commit(const char *arg)
"encoding %s\n",
encoding);
- if (signed_commit_mode == SIGN_STRIP_IF_INVALID &&
+ if ((signed_commit_mode == SIGN_STRIP_IF_INVALID ||
+ signed_commit_mode == SIGN_SIGN_IF_INVALID) &&
(sig_sha1.hash_algo || sig_sha256.hash_algo))
- handle_strip_if_invalid(&new_data, &sig_sha1, &sig_sha256, &msg);
+ handle_signature_if_invalid(&new_data, &sig_sha1, &sig_sha256,
+ &msg, signed_commit_mode);
else
finalize_commit_buffer(&new_data, &sig_sha1, &sig_sha256, &msg);
@@ -3060,6 +3114,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
case SIGN_STRIP_IF_INVALID:
die(_("'strip-if-invalid' is not a valid mode for "
"git fast-import with --signed-tags=<mode>"));
+ case SIGN_SIGN_IF_INVALID:
+ die(_("'sign-if-invalid' is not a valid mode for "
+ "git fast-import with --signed-tags=<mode>"));
default:
BUG("invalid signed_tag_mode value %d from tag '%s'",
signed_tag_mode, name);
@@ -3649,10 +3706,10 @@ static int parse_one_option(const char *option)
} else if (skip_prefix(option, "export-pack-edges=", &option)) {
option_export_pack_edges(option);
} else if (skip_prefix(option, "signed-commits=", &option)) {
- if (parse_sign_mode(option, &signed_commit_mode))
+ if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))
usagef(_("unknown --signed-commits mode '%s'"), option);
} else if (skip_prefix(option, "signed-tags=", &option)) {
- if (parse_sign_mode(option, &signed_tag_mode))
+ if (parse_sign_mode(option, &signed_tag_mode, NULL))
usagef(_("unknown --signed-tags mode '%s'"), option);
} else if (!strcmp(option, "quiet")) {
show_stats = 0;