git-branchless
git-branchless copied to clipboard
Crash when trying to reword or amend and `user.email` is empty
Description of the bug
Running git reword
or git amend
produces the following error output:
The application panicked (crashed).
Message: A fatal error occurred:
0: could not create commit signature: failed to parse signature - Signature cannot have an empty name or email; class=Invalid (3)
1: failed to parse signature - Signature cannot have an empty name or email; class=Invalid (3)
Location:
git-branchless-lib/src/core/rewrite/execute.rs:728
Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
Location: git-branchless/src/commands/mod.rs:221
Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
For reference, my git config --global user.name
is set, but my git config --global user.email
is empty. However, even when I set the latter to [email protected]
, the crash above persisted when calling git amend
or git reword
as before.
I'm using git-branchless v0.7.1 on top of git v2.30.1, on a mac running OSX 12.1 Monterey.
Expected behavior
I expect git amend|reword
to succeed without crashing.
Actual behavior
The program crashed, with the error output provided above.
Version of rustc
This is not a build issue, but 1.69.0-nightly
Automated bug report
Software version
git-branchless 0.7.1
Operating system
macOS 12.1 (Darwin 21.2.0)
Command-line
/usr/local/bin/git-branchless bug-report
Environment variables
SHELL=/bin/bash
EDITOR=vim
Git version
> git version
git version 2.30.1 (Apple Git-130)
Hooks
Show 7 hooks
Hook post-applypatch
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook post-applypatch "$@"
## END BRANCHLESS CONFIG
Hook post-checkout
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook post-checkout "$@"
## END BRANCHLESS CONFIG
Hook post-commit
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook post-commit "$@"
## END BRANCHLESS CONFIG
Hook post-merge
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook post-merge "$@"
## END BRANCHLESS CONFIG
Hook post-rewrite
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook post-rewrite "$@"
## END BRANCHLESS CONFIG
Hook pre-auto-gc
#!/bin/sh
## START BRANCHLESS CONFIG
git branchless hook pre-auto-gc "$@"
## END BRANCHLESS CONFIG
Hook reference-transaction
#!/bin/sh
## START BRANCHLESS CONFIG
# Avoid canceling the reference transaction in the case that `branchless` fails
# for whatever reason.
git branchless hook reference-transaction "$@" || (
echo 'branchless: Failed to process reference transaction!'
echo 'branchless: Some events (e.g. branch updates) may have been lost.'
echo 'branchless: This is a bug. Please report it.'
)
## END BRANCHLESS CONFIG
Events
Show 5 events
Event ID: 6, transaction ID: 46 (message: amendold)
-
RefUpdateEvent { timestamp: 1681254261.486797, event_tx_id: EventTransactionId(46), ref_name: ReferenceName("HEAD"), old_oid: 1454e771901ff2a8437de85da63522577e73fcd6, new_oid: 9f2f932cb31680084f2e4fe8c659f246ded3f816, message: None }
-
RefUpdateEvent { timestamp: 1681254261.486797, event_tx_id: EventTransactionId(46), ref_name: ReferenceName("refs/heads/redacted-ref-0"), old_oid: 1454e771901ff2a8437de85da63522577e73fcd6, new_oid: 9f2f932cb31680084f2e4fe8c659f246ded3f816, message: None }
-
CommitEvent { timestamp: 1681254260.0, event_tx_id: EventTransactionId(46), commit_oid: NonZeroOid(9f2f932cb31680084f2e4fe8c659f246ded3f816) }
-
RewriteEvent { timestamp: 1681254261.738377, event_tx_id: EventTransactionId(46), old_commit_oid: 1454e771901ff2a8437de85da63522577e73fcd6, new_commit_oid: 9f2f932cb31680084f2e4fe8c659f246ded3f816 }
O 08c4dfe 7m (main) xxxxxx xxxxxxxxxx
|
@ 9f2f932 6m (redacted-ref-0) xxxxxx xxx xxxx xxxxxx xxxxxx
Event ID: 3, transaction ID: 42 (message: record)
-
RefUpdateEvent { timestamp: 1681254225.450446, event_tx_id: EventTransactionId(42), ref_name: ReferenceName("HEAD"), old_oid: 08c4dfeffdb1c4d43d531fbb85a665bf8b04ff2f, new_oid: 1454e771901ff2a8437de85da63522577e73fcd6, message: None }
-
RefUpdateEvent { timestamp: 1681254225.450446, event_tx_id: EventTransactionId(42), ref_name: ReferenceName("refs/heads/redacted-ref-0"), old_oid: 08c4dfeffdb1c4d43d531fbb85a665bf8b04ff2f, new_oid: 1454e771901ff2a8437de85da63522577e73fcd6, message: None }
-
CommitEvent { timestamp: 1681254225.0, event_tx_id: EventTransactionId(42), commit_oid: NonZeroOid(1454e771901ff2a8437de85da63522577e73fcd6) }
O 08c4dfe 7m (main) xxxxxx xxxxxxxxxx
|
@ 9f2f932 6m (redacted-ref-0) xxxxxx xxx xxxx xxxxxx xxxxxx
Event ID: 1, transaction ID: 38 (message: co)
-
RefUpdateEvent { timestamp: 1681254203.575399, event_tx_id: EventTransactionId(38), ref_name: ReferenceName("refs/heads/redacted-ref-0"), old_oid: 0000000000000000000000000000000000000000, new_oid: 08c4dfeffdb1c4d43d531fbb85a665bf8b04ff2f, message: None }
-
RefUpdateEvent { timestamp: 1681254203.739983, event_tx_id: EventTransactionId(38), ref_name: ReferenceName("HEAD"), old_oid: 08c4dfeffdb1c4d43d531fbb85a665bf8b04ff2f, new_oid: 08c4dfeffdb1c4d43d531fbb85a665bf8b04ff2f, message: None }
O 08c4dfe 7m (main) xxxxxx xxxxxxxxxx
|
@ 9f2f932 6m (redacted-ref-0) xxxxxx xxx xxxx xxxxxx xxxxxx
There are no previous available events.
O 08c4dfe 7m (main) xxxxxx xxxxxxxxxx
|
@ 9f2f932 6m (redacted-ref-0) xxxxxx xxx xxxx xxxxxx xxxxxx
There are no previous available events.
O 08c4dfe 7m (main) xxxxxx xxxxxxxxxx
|
@ 9f2f932 6m (redacted-ref-0) xxxxxx xxx xxxx xxxxxx xxxxxx
Version of git-branchless
0.7.1
Version of git
2.30.1
Hi @azaslavsky, thanks for reporting. I think this is a limitation in the git2
crate, and arguably in libgit2 itself. In libgit2, it claims that an empty email is invalid:
https://github.com/libgit2/libgit2/blob/abb0b313172d1b4477fe0c6e88102ce4bb8db90c/src/libgit2/signature.c#L93-L96
However, it's obviously possible to create signatures with empty emails. If we were using libgit2 directly, it would probably be possible to bypass the constructor and set the email
string ourselves, or perhaps use the function to parse a signature from a buffer. However, the git2
Rust wrappers don't seem to have any workaround (without unsafely interfacing with libgit2 itself).
As a workaround, I recommend that you use git commit --amend
to assign a non-empty signature to the commit in question, after which you should be able to use git amend
or git reword
as desired. I would also recommend that you check out Jujutsu, which has first-class support for empty names and emails and even commit messages, and can be used in a Git repository.
For my own future reference, while I was able to reproduce the issue with manual testing, the following patch did not expose the issue (it somehow successfully amended the commit?):
Patch
From 53affaae2d670f82364c01d4477a0f9ba64f1d0c Mon Sep 17 00:00:00 2001
From: Waleed Khan <[email protected]>
Date: Sat, 22 Apr 2023 14:25:29 -0700
Subject: [PATCH] temp: attempt to create test exposing empty email issue
---
git-branchless-testing/src/lib.rs | 23 ++++++++--
git-branchless/tests/test_amend.rs | 71 +++++++++++++++++++++++++++++-
2 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/git-branchless-testing/src/lib.rs b/git-branchless-testing/src/lib.rs
index c011cb2f..5f135a48 100644
--- a/git-branchless-testing/src/lib.rs
+++ b/git-branchless-testing/src/lib.rs
@@ -55,6 +55,8 @@ pub struct GitInitOptions {
/// If `true`, run `git branchless init` as part of initialization process.
pub run_branchless_init: bool,
+
+ pub configure_signature: bool,
}
impl Default for GitInitOptions {
@@ -62,6 +64,7 @@ impl Default for GitInitOptions {
GitInitOptions {
make_initial_commit: true,
run_branchless_init: true,
+ configure_signature: true,
}
}
}
@@ -171,6 +174,9 @@ impl Git {
let new_path = self.get_path_for_env();
let envs = vec![
("GIT_CONFIG_NOSYSTEM", OsString::from("1")),
+ ("GIT_CONFIG_SYSTEM", OsString::new()), // since Git v2.32.0 https://stackoverflow.com/a/67512433
+ ("GIT_CONFIG_GLOBAL", OsString::new()), // since Git v2.32.0 https://stackoverflow.com/a/67512433
+ ("GIT_ATTR_NOSYSTEM", OsString::from("1")), // mysterious undocumented config option? https://stackoverflow.com/questions/43881807/how-to-tell-git-to-ignore-global-config/67512433#comment74799506_43882168
("GIT_AUTHOR_DATE", date.clone()),
("GIT_COMMITTER_DATE", date),
("GIT_EDITOR", git_editor),
@@ -390,12 +396,21 @@ then you can only run tests in the main `git-branchless` and \
/// with it.
#[instrument]
pub fn init_repo_with_options(&self, options: &GitInitOptions) -> eyre::Result<()> {
+ let GitInitOptions {
+ make_initial_commit,
+ run_branchless_init,
+ configure_signature,
+ } = options;
+
self.run(&["init"])?;
- self.run(&["config", "user.name", DUMMY_NAME])?;
- self.run(&["config", "user.email", DUMMY_EMAIL])?;
self.run(&["config", "core.abbrev", "7"])?;
- if options.make_initial_commit {
+ if *configure_signature {
+ self.run(&["config", "user.name", DUMMY_NAME])?;
+ self.run(&["config", "user.email", DUMMY_EMAIL])?;
+ }
+
+ if *make_initial_commit {
self.commit_file("initial", 0)?;
}
@@ -415,7 +430,7 @@ then you can only run tests in the main `git-branchless` and \
// ```
self.run(&["config", "core.autocrlf", "false"])?;
- if options.run_branchless_init {
+ if *run_branchless_init {
self.branchless("init", &[])?;
}
diff --git a/git-branchless/tests/test_amend.rs b/git-branchless/tests/test_amend.rs
index 8c8b6fc6..608f0a24 100644
--- a/git-branchless/tests/test_amend.rs
+++ b/git-branchless/tests/test_amend.rs
@@ -1,4 +1,6 @@
-use git_branchless_testing::{make_git, remove_rebase_lines, trim_lines, GitRunOptions};
+use git_branchless_testing::{
+ make_git, remove_rebase_lines, trim_lines, GitInitOptions, GitRunOptions,
+};
#[test]
fn test_amend_with_children() -> eyre::Result<()> {
@@ -1135,3 +1137,70 @@ fn test_amend_move_detached_branch() -> eyre::Result<()> {
Ok(())
}
+
+#[test]
+fn test_amend_empty_email() -> eyre::Result<()> {
+ let git = make_git()?;
+ git.init_repo_with_options(&GitInitOptions {
+ configure_signature: false,
+ ..Default::default()
+ })?;
+
+ git.run(&["config", "user.name", "Foo Bar"])?;
+ git.run(&["config", "user.email", ""])?;
+ git.detach_head()?;
+ git.commit_file("test1", 1)?;
+
+ {
+ let (stdout, _stderr) = git.run(&["show"])?;
+ insta::assert_snapshot!(stdout, @r###"
+ commit 2823d3098a992b872a6fd551eeb10fc743e8e39d
+ Author: Foo Bar <>
+ Date: Thu Oct 29 12:34:56 2020 -0100
+
+ create test1.txt
+
+ diff --git a/test1.txt b/test1.txt
+ new file mode 100644
+ index 0000000..7432a8f
+ --- /dev/null
+ +++ b/test1.txt
+ @@ -0,0 +1 @@
+ +test1 contents
+ "###);
+ }
+
+ git.write_file_txt("test1", "updated contents\n")?;
+ {
+ let (stdout, stderr) = git.branchless("amend", &[])?;
+ insta::assert_snapshot!(stderr, @r###"
+ branchless: creating working copy snapshot
+ branchless: processing 1 update: ref HEAD
+ "###);
+ insta::assert_snapshot!(stdout, @r###"
+ branchless: running command: <git-executable> reset 937a25b6ca80c7dc92a179befb36e39a8ecfa8bf
+ Amended with 1 uncommitted change.
+ "###);
+ }
+
+ {
+ let (stdout, _stderr) = git.run(&["show"])?;
+ insta::assert_snapshot!(stdout, @r###"
+ commit 937a25b6ca80c7dc92a179befb36e39a8ecfa8bf
+ Author: Foo Bar <>
+ Date: Thu Oct 29 12:34:56 2020 -0100
+
+ create test1.txt
+
+ diff --git a/test1.txt b/test1.txt
+ new file mode 100644
+ index 0000000..27e2fc9
+ --- /dev/null
+ +++ b/test1.txt
+ @@ -0,0 +1 @@
+ +updated contents
+ "###);
+ }
+
+ Ok(())
+}
--
2.40.0
Hmm, I tried your suggestion (first commit using the old-school git commit --amend
, then try git amend/reword
, but the latter commands till fail in the same way they did before? It seems like the issue is with trying to generate a new signature when the commit message or contents change, though this is just me speculating. I'll try to build git-branchless from source with some print debug statements to see what could be the issue on my end.
You may also need to pass --reset-author
or similar to fully reset the signature. Make sure both the author and committer signatures have been updated.