From 9cc5bb32e878a5334a7e3afc50350205c7f5a80a Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Wed, 12 Jul 2023 21:56:43 +0200
Subject: [PATCH 01/13] feat: add signoff key to commit editor

---
 src/components/commit.rs | 25 +++++++++++++++++++++++++
 src/keys/key_list.rs     |  2 ++
 src/strings.rs           | 15 +++++++++++++++
 3 files changed, 42 insertions(+)

diff --git a/src/components/commit.rs b/src/components/commit.rs
index 875d0a35e9..92a2fd777e 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -43,6 +43,7 @@ enum Mode {
 	Merge(Vec<CommitId>),
 	Revert,
 	Reword(CommitId),
+	Signoff,
 }
 
 pub struct CommitComponent {
@@ -301,6 +302,7 @@ impl CommitComponent {
 
 				commit
 			}
+			Mode::Signoff => sync::commit(&self.repo.borrow(), msg)?,
 		};
 		Ok(())
 	}
@@ -341,6 +343,18 @@ impl CommitComponent {
 
 		Ok(())
 	}
+	fn toggle_signoff(&mut self) {
+		match self.mode {
+			Mode::Normal => {
+				self.mode = Mode::Signoff;
+				self.input.set_title(strings::commit_title_signoff());
+			}
+			_ => {
+				self.mode = Mode::Normal;
+				self.input.set_title(strings::commit_title());
+			}
+		}
+	}
 	fn toggle_verify(&mut self) {
 		self.verify = !self.verify;
 	}
@@ -464,6 +478,12 @@ impl Component for CommitComponent {
 				true,
 			));
 
+			out.push(CommandInfo::new(
+				strings::commands::commit_signoff(&self.key_config),
+				true,
+				true,
+			));
+
 			out.push(CommandInfo::new(
 				strings::commands::commit_open_editor(
 					&self.key_config,
@@ -531,6 +551,11 @@ impl Component for CommitComponent {
 						self.input.set_text(msg);
 						self.commit_msg_history_idx += 1;
 					}
+				} else if key_match(
+					e,
+					self.key_config.keys.commit_signoff,
+				) {
+					self.toggle_signoff();
 				}
 				// stop key event propagation
 				return Ok(EventState::Consumed);
diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs
index 134d992d87..3912a9c388 100644
--- a/src/keys/key_list.rs
+++ b/src/keys/key_list.rs
@@ -88,6 +88,7 @@ pub struct KeysList {
 	pub log_reset_comit: GituiKeyEvent,
 	pub log_reword_comit: GituiKeyEvent,
 	pub commit_amend: GituiKeyEvent,
+	pub commit_signoff: GituiKeyEvent,
 	pub toggle_verify: GituiKeyEvent,
 	pub copy: GituiKeyEvent,
 	pub create_branch: GituiKeyEvent,
@@ -174,6 +175,7 @@ impl Default for KeysList {
 			log_reset_comit: GituiKeyEvent { code: KeyCode::Char('R'), modifiers: KeyModifiers::SHIFT },
 			log_reword_comit: GituiKeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty() },
 			commit_amend: GituiKeyEvent::new(KeyCode::Char('a'),  KeyModifiers::CONTROL),
+			commit_signoff: GituiKeyEvent::new(KeyCode::Char('s'),  KeyModifiers::CONTROL),
 			toggle_verify: GituiKeyEvent::new(KeyCode::Char('f'),  KeyModifiers::CONTROL),
 			copy: GituiKeyEvent::new(KeyCode::Char('y'),  KeyModifiers::empty()),
 			create_branch: GituiKeyEvent::new(KeyCode::Char('c'),  KeyModifiers::empty()),
diff --git a/src/strings.rs b/src/strings.rs
index 3e1ff80633..cf8d3bb109 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -117,6 +117,9 @@ pub fn commit_title_revert() -> String {
 pub fn commit_title_amend() -> String {
 	"Commit (Amend)".to_string()
 }
+pub fn commit_title_signoff() -> String {
+	"Commit (Sign-off)".to_string()
+}
 pub fn commit_msg(_key_config: &SharedKeyConfig) -> String {
 	"type commit message..".to_string()
 }
@@ -980,6 +983,18 @@ pub mod commands {
 			CMD_GROUP_COMMIT_POPUP,
 		)
 	}
+	pub fn commit_signoff(
+		key_config: &SharedKeyConfig,
+	) -> CommandText {
+		CommandText::new(
+			format!(
+				"Sing-off [{}]",
+				key_config.get_hint(key_config.keys.commit_signoff),
+			),
+			"sign-off commit (-s option)",
+			CMD_GROUP_COMMIT_POPUP,
+		)
+	}
 	pub fn edit_item(key_config: &SharedKeyConfig) -> CommandText {
 		CommandText::new(
 			format!(

From 4dcb5a75df588995f93be479c69c853329554c7d Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Wed, 12 Jul 2023 22:50:18 +0200
Subject: [PATCH 02/13] feat: Adding sign off string manually

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 asyncgit/src/sync/commit.rs        | 40 ++++++++++++++++++++++++------
 asyncgit/src/sync/commit_revert.rs |  2 +-
 src/components/commit.rs           | 10 +++++---
 src/keys/key_list.rs               |  4 +--
 src/strings.rs                     |  2 +-
 5 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index d1af74d85d..dd9d83d73f 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -60,8 +60,28 @@ pub(crate) fn signature_allow_undefined_name(
 	signature
 }
 
+fn add_sign_off<'a>(
+	msg: &'a str,
+	signature: &'a Signature,
+) -> &'a str {
+	match (signature.name(), signature.email()) {
+		(Some(name), Some(mail)) => {
+			msg.to_owned().push_str(&format!(
+				"Signed-off-by {} <{}>",
+				name, mail
+			));
+			msg
+		}
+		_ => msg,
+	}
+}
+
 /// this does not run any git hooks, git-hooks have to be executed manually, checkout `hooks_commit_msg` for example
-pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
+pub fn commit(
+	repo_path: &RepoPath,
+	msg: &str,
+	sign_off: bool,
+) -> Result<CommitId> {
 	scope_time!("commit");
 
 	let repo = repo(repo_path)?;
@@ -79,12 +99,18 @@ pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
 
 	let parents = parents.iter().collect::<Vec<_>>();
 
+	let msg = if sign_off {
+		add_sign_off(&msg, &signature)
+	} else {
+		msg
+	};
+
 	Ok(repo
 		.commit(
 			Some("HEAD"),
 			&signature,
 			&signature,
-			msg,
+			&msg,
 			&tree,
 			parents.as_slice(),
 		)?
@@ -162,7 +188,7 @@ mod tests {
 
 		assert_eq!(get_statuses(repo_path), (0, 1));
 
-		commit(repo_path, "commit msg").unwrap();
+		commit(repo_path, "commit msg", false).unwrap();
 
 		assert_eq!(get_statuses(repo_path), (0, 0));
 	}
@@ -188,7 +214,7 @@ mod tests {
 
 		assert_eq!(get_statuses(repo_path), (0, 1));
 
-		commit(repo_path, "commit msg").unwrap();
+		commit(repo_path, "commit msg", false).unwrap();
 
 		assert_eq!(get_statuses(repo_path), (0, 0));
 	}
@@ -322,13 +348,13 @@ mod tests {
 
 		repo.config()?.remove("user.email")?;
 
-		let error = commit(repo_path, "commit msg");
+		let error = commit(repo_path, "commit msg", false);
 
 		assert!(matches!(error, Err(_)));
 
 		repo.config()?.set_str("user.email", "email")?;
 
-		let success = commit(repo_path, "commit msg");
+		let success = commit(repo_path, "commit msg", false);
 
 		assert!(matches!(success, Ok(_)));
 		assert_eq!(count_commits(&repo, 10), 1);
@@ -358,7 +384,7 @@ mod tests {
 
 		repo.config()?.remove("user.name")?;
 
-		let mut success = commit(repo_path, "commit msg");
+		let mut success = commit(repo_path, "commit msg", false);
 
 		assert!(matches!(success, Ok(_)));
 		assert_eq!(count_commits(&repo, 10), 1);
diff --git a/asyncgit/src/sync/commit_revert.rs b/asyncgit/src/sync/commit_revert.rs
index 2d66a2a1d4..326525d8dd 100644
--- a/asyncgit/src/sync/commit_revert.rs
+++ b/asyncgit/src/sync/commit_revert.rs
@@ -43,7 +43,7 @@ pub fn commit_revert(
 ) -> Result<CommitId> {
 	scope_time!("commit_revert");
 
-	let id = crate::sync::commit(repo_path, msg)?;
+	let id = crate::sync::commit(repo_path, msg, false)?;
 
 	repo(repo_path)?.cleanup_state()?;
 
diff --git a/src/components/commit.rs b/src/components/commit.rs
index 92a2fd777e..fb240b909a 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -285,7 +285,9 @@ impl CommitComponent {
 
 	fn do_commit(&self, msg: &str) -> Result<()> {
 		match &self.mode {
-			Mode::Normal => sync::commit(&self.repo.borrow(), msg)?,
+			Mode::Normal => {
+				sync::commit(&self.repo.borrow(), msg, false)?
+			}
 			Mode::Amend(amend) => {
 				sync::amend(&self.repo.borrow(), *amend, msg)?
 			}
@@ -302,7 +304,9 @@ impl CommitComponent {
 
 				commit
 			}
-			Mode::Signoff => sync::commit(&self.repo.borrow(), msg)?,
+			Mode::Signoff => {
+				sync::commit(&self.repo.borrow(), msg, true)?
+			}
 		};
 		Ok(())
 	}
@@ -553,7 +557,7 @@ impl Component for CommitComponent {
 					}
 				} else if key_match(
 					e,
-					self.key_config.keys.commit_signoff,
+					self.key_config.keys.toggle_signoff,
 				) {
 					self.toggle_signoff();
 				}
diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs
index 3912a9c388..619dde5ee1 100644
--- a/src/keys/key_list.rs
+++ b/src/keys/key_list.rs
@@ -88,7 +88,7 @@ pub struct KeysList {
 	pub log_reset_comit: GituiKeyEvent,
 	pub log_reword_comit: GituiKeyEvent,
 	pub commit_amend: GituiKeyEvent,
-	pub commit_signoff: GituiKeyEvent,
+	pub toggle_signoff: GituiKeyEvent,
 	pub toggle_verify: GituiKeyEvent,
 	pub copy: GituiKeyEvent,
 	pub create_branch: GituiKeyEvent,
@@ -175,7 +175,7 @@ impl Default for KeysList {
 			log_reset_comit: GituiKeyEvent { code: KeyCode::Char('R'), modifiers: KeyModifiers::SHIFT },
 			log_reword_comit: GituiKeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty() },
 			commit_amend: GituiKeyEvent::new(KeyCode::Char('a'),  KeyModifiers::CONTROL),
-			commit_signoff: GituiKeyEvent::new(KeyCode::Char('s'),  KeyModifiers::CONTROL),
+			toggle_signoff: GituiKeyEvent::new(KeyCode::Char('s'),  KeyModifiers::CONTROL),
 			toggle_verify: GituiKeyEvent::new(KeyCode::Char('f'),  KeyModifiers::CONTROL),
 			copy: GituiKeyEvent::new(KeyCode::Char('y'),  KeyModifiers::empty()),
 			create_branch: GituiKeyEvent::new(KeyCode::Char('c'),  KeyModifiers::empty()),
diff --git a/src/strings.rs b/src/strings.rs
index cf8d3bb109..6477906d31 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -989,7 +989,7 @@ pub mod commands {
 		CommandText::new(
 			format!(
 				"Sing-off [{}]",
-				key_config.get_hint(key_config.keys.commit_signoff),
+				key_config.get_hint(key_config.keys.toggle_signoff),
 			),
 			"sign-off commit (-s option)",
 			CMD_GROUP_COMMIT_POPUP,

From 4d421bb1ccfaf2901545e572e8c353d67e8ede00 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Thu, 13 Jul 2023 10:23:55 +0200
Subject: [PATCH 03/13] chore: refactores sync::commit to keep function's
 signature stable

Signed-off-by: Dominik Tacke <tacdom+github@gmail.com>
---
 asyncgit/src/sync/commit.rs        | 38 ++++++++++++++++++++----------
 asyncgit/src/sync/commit_revert.rs |  2 +-
 asyncgit/src/sync/mod.rs           |  2 +-
 src/components/commit.rs           | 21 +++++++----------
 4 files changed, 35 insertions(+), 28 deletions(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index dd9d83d73f..10e20c347a 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -66,18 +66,15 @@ fn add_sign_off<'a>(
 ) -> &'a str {
 	match (signature.name(), signature.email()) {
 		(Some(name), Some(mail)) => {
-			msg.to_owned().push_str(&format!(
-				"Signed-off-by {} <{}>",
-				name, mail
-			));
+			msg.to_owned()
+				.push_str(&format!("Signed-off-by {name} <{mail}>"));
 			msg
 		}
 		_ => msg,
 	}
 }
 
-/// this does not run any git hooks, git-hooks have to be executed manually, checkout `hooks_commit_msg` for example
-pub fn commit(
+fn do_commit(
 	repo_path: &RepoPath,
 	msg: &str,
 	sign_off: bool,
@@ -100,7 +97,7 @@ pub fn commit(
 	let parents = parents.iter().collect::<Vec<_>>();
 
 	let msg = if sign_off {
-		add_sign_off(&msg, &signature)
+		add_sign_off(msg, &signature)
 	} else {
 		msg
 	};
@@ -110,13 +107,28 @@ pub fn commit(
 			Some("HEAD"),
 			&signature,
 			&signature,
-			&msg,
+			msg,
 			&tree,
 			parents.as_slice(),
 		)?
 		.into())
 }
 
+/// this does not run any git hooks, git-hooks have to be executed manually, checkout `hooks_commit_msg` for example
+pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
+	do_commit(repo_path, msg, false)
+}
+
+/// Do a commit including the sign-off option
+///
+/// Refer to [git documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s)
+pub fn commit_with_singoff(
+	repo_path: &RepoPath,
+	msg: &str,
+) -> Result<CommitId> {
+	do_commit(repo_path, msg, true)
+}
+
 /// Tag a commit.
 ///
 /// This function will return an `Err(…)` variant if the tag’s name is refused
@@ -188,7 +200,7 @@ mod tests {
 
 		assert_eq!(get_statuses(repo_path), (0, 1));
 
-		commit(repo_path, "commit msg", false).unwrap();
+		commit(repo_path, "commit msg").unwrap();
 
 		assert_eq!(get_statuses(repo_path), (0, 0));
 	}
@@ -214,7 +226,7 @@ mod tests {
 
 		assert_eq!(get_statuses(repo_path), (0, 1));
 
-		commit(repo_path, "commit msg", false).unwrap();
+		commit(repo_path, "commit msg").unwrap();
 
 		assert_eq!(get_statuses(repo_path), (0, 0));
 	}
@@ -348,13 +360,13 @@ mod tests {
 
 		repo.config()?.remove("user.email")?;
 
-		let error = commit(repo_path, "commit msg", false);
+		let error = commit(repo_path, "commit msg");
 
 		assert!(matches!(error, Err(_)));
 
 		repo.config()?.set_str("user.email", "email")?;
 
-		let success = commit(repo_path, "commit msg", false);
+		let success = commit(repo_path, "commit msg");
 
 		assert!(matches!(success, Ok(_)));
 		assert_eq!(count_commits(&repo, 10), 1);
@@ -384,7 +396,7 @@ mod tests {
 
 		repo.config()?.remove("user.name")?;
 
-		let mut success = commit(repo_path, "commit msg", false);
+		let mut success = commit(repo_path, "commit msg");
 
 		assert!(matches!(success, Ok(_)));
 		assert_eq!(count_commits(&repo, 10), 1);
diff --git a/asyncgit/src/sync/commit_revert.rs b/asyncgit/src/sync/commit_revert.rs
index 326525d8dd..2d66a2a1d4 100644
--- a/asyncgit/src/sync/commit_revert.rs
+++ b/asyncgit/src/sync/commit_revert.rs
@@ -43,7 +43,7 @@ pub fn commit_revert(
 ) -> Result<CommitId> {
 	scope_time!("commit_revert");
 
-	let id = crate::sync::commit(repo_path, msg, false)?;
+	let id = crate::sync::commit(repo_path, msg)?;
 
 	repo(repo_path)?.cleanup_state()?;
 
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index a7bf7d64ed..2c67df0c00 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -43,7 +43,7 @@ pub use branch::{
 	merge_rebase::merge_upstream_rebase, rename::rename_branch,
 	validate_branch_name, BranchCompare, BranchDetails, BranchInfo,
 };
-pub use commit::{amend, commit, tag_commit};
+pub use commit::{amend, commit, commit_with_singoff, tag_commit};
 pub use commit_details::{
 	get_commit_details, CommitDetails, CommitMessage, CommitSignature,
 };
diff --git a/src/components/commit.rs b/src/components/commit.rs
index fb240b909a..c52ce5a249 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -285,9 +285,7 @@ impl CommitComponent {
 
 	fn do_commit(&self, msg: &str) -> Result<()> {
 		match &self.mode {
-			Mode::Normal => {
-				sync::commit(&self.repo.borrow(), msg, false)?
-			}
+			Mode::Normal => sync::commit(&self.repo.borrow(), msg)?,
 			Mode::Amend(amend) => {
 				sync::amend(&self.repo.borrow(), *amend, msg)?
 			}
@@ -305,7 +303,7 @@ impl CommitComponent {
 				commit
 			}
 			Mode::Signoff => {
-				sync::commit(&self.repo.borrow(), msg, true)?
+				sync::commit_with_singoff(&self.repo.borrow(), msg)?
 			}
 		};
 		Ok(())
@@ -348,15 +346,12 @@ impl CommitComponent {
 		Ok(())
 	}
 	fn toggle_signoff(&mut self) {
-		match self.mode {
-			Mode::Normal => {
-				self.mode = Mode::Signoff;
-				self.input.set_title(strings::commit_title_signoff());
-			}
-			_ => {
-				self.mode = Mode::Normal;
-				self.input.set_title(strings::commit_title());
-			}
+		if matches!(self.mode, Mode::Normal) {
+			self.mode = Mode::Signoff;
+			self.input.set_title(strings::commit_title_signoff());
+		} else {
+			self.mode = Mode::Normal;
+			self.input.set_title(strings::commit_title());
 		}
 	}
 	fn toggle_verify(&mut self) {

From 60d181cfb3ffa3f16450eb2a93656c6a0ecc93a4 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Thu, 13 Jul 2023 13:26:34 +0200
Subject: [PATCH 04/13] fix: typo in function name

---
 asyncgit/src/sync/commit.rs | 2 +-
 asyncgit/src/sync/mod.rs    | 2 +-
 src/components/commit.rs    | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index 10e20c347a..79d3acf8bd 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -122,7 +122,7 @@ pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
 /// Do a commit including the sign-off option
 ///
 /// Refer to [git documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s)
-pub fn commit_with_singoff(
+pub fn commit_with_signoff(
 	repo_path: &RepoPath,
 	msg: &str,
 ) -> Result<CommitId> {
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index 2c67df0c00..a626bfec2e 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -43,7 +43,7 @@ pub use branch::{
 	merge_rebase::merge_upstream_rebase, rename::rename_branch,
 	validate_branch_name, BranchCompare, BranchDetails, BranchInfo,
 };
-pub use commit::{amend, commit, commit_with_singoff, tag_commit};
+pub use commit::{amend, commit, commit_with_signoff, tag_commit};
 pub use commit_details::{
 	get_commit_details, CommitDetails, CommitMessage, CommitSignature,
 };
diff --git a/src/components/commit.rs b/src/components/commit.rs
index c52ce5a249..58f06787e3 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -303,7 +303,7 @@ impl CommitComponent {
 				commit
 			}
 			Mode::Signoff => {
-				sync::commit_with_singoff(&self.repo.borrow(), msg)?
+				sync::commit_with_signoff(&self.repo.borrow(), msg)?
 			}
 		};
 		Ok(())

From 0e5be1ab09ff12a110621f43b98f99ca5ab47e8b Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Thu, 13 Jul 2023 13:42:07 +0200
Subject: [PATCH 05/13] fix: add sign-off to message

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 asyncgit/src/sync/commit.rs | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index 79d3acf8bd..92b06e9454 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -60,17 +60,14 @@ pub(crate) fn signature_allow_undefined_name(
 	signature
 }
 
-fn add_sign_off<'a>(
-	msg: &'a str,
-	signature: &'a Signature,
-) -> &'a str {
+fn add_sign_off(msg: &str, signature: &Signature) -> String {
 	match (signature.name(), signature.email()) {
 		(Some(name), Some(mail)) => {
-			msg.to_owned()
-				.push_str(&format!("Signed-off-by {name} <{mail}>"));
+			let mut msg = msg.to_owned();
+			msg.push_str(&format!("\nSigned-off-by {name} <{mail}>"));
 			msg
 		}
-		_ => msg,
+		_ => msg.to_owned(),
 	}
 }
 
@@ -99,7 +96,7 @@ fn do_commit(
 	let msg = if sign_off {
 		add_sign_off(msg, &signature)
 	} else {
-		msg
+		msg.to_owned()
 	};
 
 	Ok(repo
@@ -107,7 +104,7 @@ fn do_commit(
 			Some("HEAD"),
 			&signature,
 			&signature,
-			msg,
+			&msg,
 			&tree,
 			parents.as_slice(),
 		)?

From 3a656f2c0d9b4dc684f2a06976624b902062792e Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Thu, 13 Jul 2023 15:20:11 +0200
Subject: [PATCH 06/13] test: add unit test for creating the signed-off commit
 message

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 asyncgit/src/sync/commit.rs | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index 92b06e9454..e5cebc94e1 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -168,9 +168,11 @@ mod tests {
 		LogWalker,
 	};
 	use commit::{amend, tag_commit};
-	use git2::Repository;
+	use git2::{Repository, Signature};
 	use std::{fs::File, io::Write, path::Path};
 
+	use super::add_sign_off;
+
 	fn count_commits(repo: &Repository, max: usize) -> usize {
 		let mut items = Vec::new();
 		let mut walk = LogWalker::new(repo, max).unwrap();
@@ -419,4 +421,16 @@ mod tests {
 
 		Ok(())
 	}
+
+	#[test]
+	fn test_add_sign_off_to_commit_msg() {
+		let in_msg = "test commit";
+		let signature =
+			Signature::now("MyName", "my@mail.com").unwrap();
+
+		let expected =
+			format!("{in_msg}\nSigned-off-by MyName <my@mail.com>");
+		let result = add_sign_off(in_msg, &signature);
+		assert_eq!(expected, result);
+	}
 }

From 0cf23f274637ce6a6c26ae829d7418bd75087031 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Thu, 13 Jul 2023 15:20:32 +0200
Subject: [PATCH 07/13] docs: added sign off feature to changelog

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bceb31185c..f5dcab69d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 * support 'n'/'p' key to move to the next/prev hunk in diff component [[@hamflx](https://github.com/hamflx)] ([#1523](https://github.com/extrawurst/gitui/issues/1523))
 * simplify theme overrides [[@cruessler](https://github.com/cruessler)] ([#1367](https://github.com/extrawurst/gitui/issues/1367))
+* Support for sign-off of commits [#1757]https://github.com/extrawurst/gitui/issues/1757
 
 ### Fixes
 * fix commit dialog char count for multibyte characters ([#1726](https://github.com/extrawurst/gitui/issues/1726))

From 34dacf0a8316916356c4d54a8230df3b98b1c221 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Fri, 14 Jul 2023 08:16:56 +0200
Subject: [PATCH 08/13] fix(CHANGELOG): formatting

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5dcab69d5..4c25dc867f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 * support 'n'/'p' key to move to the next/prev hunk in diff component [[@hamflx](https://github.com/hamflx)] ([#1523](https://github.com/extrawurst/gitui/issues/1523))
 * simplify theme overrides [[@cruessler](https://github.com/cruessler)] ([#1367](https://github.com/extrawurst/gitui/issues/1367))
-* Support for sign-off of commits [#1757]https://github.com/extrawurst/gitui/issues/1757
+* support for sign-off of commits [#1757](https://github.com/extrawurst/gitui/issues/1757)
 
 ### Fixes
 * fix commit dialog char count for multibyte characters ([#1726](https://github.com/extrawurst/gitui/issues/1726))

From 176bc8266ed91550aa9682385ab3279709ec4b43 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Tue, 25 Jul 2023 21:02:25 +0200
Subject: [PATCH 09/13] refactor: sign-off is not a state, just adds signature
 to message

---
 src/components/commit.rs | 44 ++++++++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 9 deletions(-)

diff --git a/src/components/commit.rs b/src/components/commit.rs
index 58f06787e3..d1da3d2e2e 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -7,7 +7,8 @@ use crate::{
 	keys::{key_match, SharedKeyConfig},
 	options::SharedOptions,
 	queue::{InternalEvent, NeedsUpdate, Queue},
-	strings, try_or_popup,
+	strings::{self, commit_title_signoff},
+	try_or_popup,
 	ui::style::SharedTheme,
 };
 use anyhow::{bail, Ok, Result};
@@ -43,7 +44,6 @@ enum Mode {
 	Merge(Vec<CommitId>),
 	Revert,
 	Reword(CommitId),
-	Signoff,
 }
 
 pub struct CommitComponent {
@@ -58,6 +58,7 @@ pub struct CommitComponent {
 	commit_msg_history_idx: usize,
 	options: SharedOptions,
 	verify: bool,
+	sign_off: bool,
 }
 
 const FIRST_LINE_LIMIT: usize = 50;
@@ -89,6 +90,7 @@ impl CommitComponent {
 			commit_msg_history_idx: 0,
 			options,
 			verify: true,
+			sign_off: false,
 		}
 	}
 
@@ -256,7 +258,13 @@ impl CommitComponent {
 				return Ok(CommitResult::Aborted);
 			}
 		}
+
 		let mut msg = message_prettify(msg, Some(b'#'))?;
+
+		if self.sign_off {
+			msg = self.add_sign_off(&msg)?;
+		}
+
 		if verify {
 			// run commit message check hook - can reject commit
 			if let HookResult::NotOk(e) =
@@ -302,9 +310,6 @@ impl CommitComponent {
 
 				commit
 			}
-			Mode::Signoff => {
-				sync::commit_with_signoff(&self.repo.borrow(), msg)?
-			}
 		};
 		Ok(())
 	}
@@ -346,11 +351,10 @@ impl CommitComponent {
 		Ok(())
 	}
 	fn toggle_signoff(&mut self) {
-		if matches!(self.mode, Mode::Normal) {
-			self.mode = Mode::Signoff;
-			self.input.set_title(strings::commit_title_signoff());
+		self.sign_off = !self.sign_off;
+		if self.sign_off {
+			self.input.set_title(commit_title_signoff());
 		} else {
-			self.mode = Mode::Normal;
 			self.input.set_title(strings::commit_title());
 		}
 	}
@@ -429,6 +433,28 @@ impl CommitComponent {
 
 		Ok(())
 	}
+
+	fn add_sign_off(&self, msg: &str) -> Result<String> {
+		const CONFIG_KEY_USER_NAME: &str = "user.name";
+		const CONFIG_KEY_USER_MAIL: &str = "user.email";
+
+		let user = get_config_string(
+			&self.repo.borrow(),
+			CONFIG_KEY_USER_NAME,
+		)?;
+
+		let mail = get_config_string(
+			&self.repo.borrow(),
+			CONFIG_KEY_USER_MAIL,
+		)?;
+
+		if let (Some(user), Some(mail)) = (user, mail) {
+			let mut msg = msg.to_owned();
+			msg.push_str(&format!("\nSigned-off-by {user} <{mail}>"));
+		}
+
+		Ok(msg.to_string())
+	}
 }
 
 impl DrawableComponent for CommitComponent {

From 24acc64be4c96c694e830a2eee8e85ffe3bd15d7 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Tue, 25 Jul 2023 21:07:33 +0200
Subject: [PATCH 10/13] fix: message creation

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 src/components/commit.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/commit.rs b/src/components/commit.rs
index d1da3d2e2e..bb77eee2e6 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -448,12 +448,12 @@ impl CommitComponent {
 			CONFIG_KEY_USER_MAIL,
 		)?;
 
+		let mut msg = msg.to_owned();
 		if let (Some(user), Some(mail)) = (user, mail) {
-			let mut msg = msg.to_owned();
 			msg.push_str(&format!("\nSigned-off-by {user} <{mail}>"));
 		}
 
-		Ok(msg.to_string())
+		Ok(msg)
 	}
 }
 

From f411c9cdfd2f8895c29ec7f54f2c47a998b07f04 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Tue, 25 Jul 2023 21:15:25 +0200
Subject: [PATCH 11/13] refactor: undid changes to asyncgit

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 asyncgit/src/sync/commit.rs | 57 +++----------------------------------
 asyncgit/src/sync/mod.rs    |  2 +-
 2 files changed, 5 insertions(+), 54 deletions(-)

diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs
index e5cebc94e1..d1af74d85d 100644
--- a/asyncgit/src/sync/commit.rs
+++ b/asyncgit/src/sync/commit.rs
@@ -60,22 +60,8 @@ pub(crate) fn signature_allow_undefined_name(
 	signature
 }
 
-fn add_sign_off(msg: &str, signature: &Signature) -> String {
-	match (signature.name(), signature.email()) {
-		(Some(name), Some(mail)) => {
-			let mut msg = msg.to_owned();
-			msg.push_str(&format!("\nSigned-off-by {name} <{mail}>"));
-			msg
-		}
-		_ => msg.to_owned(),
-	}
-}
-
-fn do_commit(
-	repo_path: &RepoPath,
-	msg: &str,
-	sign_off: bool,
-) -> Result<CommitId> {
+/// this does not run any git hooks, git-hooks have to be executed manually, checkout `hooks_commit_msg` for example
+pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
 	scope_time!("commit");
 
 	let repo = repo(repo_path)?;
@@ -93,39 +79,18 @@ fn do_commit(
 
 	let parents = parents.iter().collect::<Vec<_>>();
 
-	let msg = if sign_off {
-		add_sign_off(msg, &signature)
-	} else {
-		msg.to_owned()
-	};
-
 	Ok(repo
 		.commit(
 			Some("HEAD"),
 			&signature,
 			&signature,
-			&msg,
+			msg,
 			&tree,
 			parents.as_slice(),
 		)?
 		.into())
 }
 
-/// this does not run any git hooks, git-hooks have to be executed manually, checkout `hooks_commit_msg` for example
-pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
-	do_commit(repo_path, msg, false)
-}
-
-/// Do a commit including the sign-off option
-///
-/// Refer to [git documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s)
-pub fn commit_with_signoff(
-	repo_path: &RepoPath,
-	msg: &str,
-) -> Result<CommitId> {
-	do_commit(repo_path, msg, true)
-}
-
 /// Tag a commit.
 ///
 /// This function will return an `Err(…)` variant if the tag’s name is refused
@@ -168,11 +133,9 @@ mod tests {
 		LogWalker,
 	};
 	use commit::{amend, tag_commit};
-	use git2::{Repository, Signature};
+	use git2::Repository;
 	use std::{fs::File, io::Write, path::Path};
 
-	use super::add_sign_off;
-
 	fn count_commits(repo: &Repository, max: usize) -> usize {
 		let mut items = Vec::new();
 		let mut walk = LogWalker::new(repo, max).unwrap();
@@ -421,16 +384,4 @@ mod tests {
 
 		Ok(())
 	}
-
-	#[test]
-	fn test_add_sign_off_to_commit_msg() {
-		let in_msg = "test commit";
-		let signature =
-			Signature::now("MyName", "my@mail.com").unwrap();
-
-		let expected =
-			format!("{in_msg}\nSigned-off-by MyName <my@mail.com>");
-		let result = add_sign_off(in_msg, &signature);
-		assert_eq!(expected, result);
-	}
 }
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index a626bfec2e..a7bf7d64ed 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -43,7 +43,7 @@ pub use branch::{
 	merge_rebase::merge_upstream_rebase, rename::rename_branch,
 	validate_branch_name, BranchCompare, BranchDetails, BranchInfo,
 };
-pub use commit::{amend, commit, commit_with_signoff, tag_commit};
+pub use commit::{amend, commit, tag_commit};
 pub use commit_details::{
 	get_commit_details, CommitDetails, CommitMessage, CommitSignature,
 };

From ec0b5955d38b4db52333a071606f4e41311301cc Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Tue, 25 Jul 2023 22:09:13 +0200
Subject: [PATCH 12/13] fix(changelog): format

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c25dc867f..f9005d557c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 * support 'n'/'p' key to move to the next/prev hunk in diff component [[@hamflx](https://github.com/hamflx)] ([#1523](https://github.com/extrawurst/gitui/issues/1523))
 * simplify theme overrides [[@cruessler](https://github.com/cruessler)] ([#1367](https://github.com/extrawurst/gitui/issues/1367))
-* support for sign-off of commits [#1757](https://github.com/extrawurst/gitui/issues/1757)
+* support for sign-off of commits [[@domtac](https://github.com/domtac)]([#1757](https://github.com/extrawurst/gitui/issues/1757))
 
 ### Fixes
 * fix commit dialog char count for multibyte characters ([#1726](https://github.com/extrawurst/gitui/issues/1726))

From 0d2d578304f78d68d62ec161fdeb7fb0c09efa66 Mon Sep 17 00:00:00 2001
From: Dominik Tacke <tacdom+github@gmail.com>
Date: Tue, 25 Jul 2023 22:11:13 +0200
Subject: [PATCH 13/13] refactor: build in proposed changes

Signed-off-by Dominik Tacke <tacdom+github@gmail.com>
---
 src/components/commit.rs | 26 ++++++++++----------------
 src/strings.rs           |  3 ---
 2 files changed, 10 insertions(+), 19 deletions(-)

diff --git a/src/components/commit.rs b/src/components/commit.rs
index bb77eee2e6..586dbb2569 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -7,8 +7,7 @@ use crate::{
 	keys::{key_match, SharedKeyConfig},
 	options::SharedOptions,
 	queue::{InternalEvent, NeedsUpdate, Queue},
-	strings::{self, commit_title_signoff},
-	try_or_popup,
+	strings, try_or_popup,
 	ui::style::SharedTheme,
 };
 use anyhow::{bail, Ok, Result};
@@ -58,7 +57,6 @@ pub struct CommitComponent {
 	commit_msg_history_idx: usize,
 	options: SharedOptions,
 	verify: bool,
-	sign_off: bool,
 }
 
 const FIRST_LINE_LIMIT: usize = 50;
@@ -90,7 +88,6 @@ impl CommitComponent {
 			commit_msg_history_idx: 0,
 			options,
 			verify: true,
-			sign_off: false,
 		}
 	}
 
@@ -261,10 +258,6 @@ impl CommitComponent {
 
 		let mut msg = message_prettify(msg, Some(b'#'))?;
 
-		if self.sign_off {
-			msg = self.add_sign_off(&msg)?;
-		}
-
 		if verify {
 			// run commit message check hook - can reject commit
 			if let HookResult::NotOk(e) =
@@ -350,12 +343,11 @@ impl CommitComponent {
 
 		Ok(())
 	}
-	fn toggle_signoff(&mut self) {
-		self.sign_off = !self.sign_off;
-		if self.sign_off {
-			self.input.set_title(commit_title_signoff());
-		} else {
-			self.input.set_title(strings::commit_title());
+	fn signoff_commit(&mut self) {
+		let msg = self.input.get_text();
+		let signed_msg = self.add_sign_off(msg);
+		if let std::result::Result::Ok(signed_msg) = signed_msg {
+			self.input.set_text(signed_msg);
 		}
 	}
 	fn toggle_verify(&mut self) {
@@ -450,7 +442,9 @@ impl CommitComponent {
 
 		let mut msg = msg.to_owned();
 		if let (Some(user), Some(mail)) = (user, mail) {
-			msg.push_str(&format!("\nSigned-off-by {user} <{mail}>"));
+			msg.push_str(&format!(
+				"\n\nSigned-off-by {user} <{mail}>"
+			));
 		}
 
 		Ok(msg)
@@ -580,7 +574,7 @@ impl Component for CommitComponent {
 					e,
 					self.key_config.keys.toggle_signoff,
 				) {
-					self.toggle_signoff();
+					self.signoff_commit();
 				}
 				// stop key event propagation
 				return Ok(EventState::Consumed);
diff --git a/src/strings.rs b/src/strings.rs
index 6477906d31..1f856b9c5d 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -117,9 +117,6 @@ pub fn commit_title_revert() -> String {
 pub fn commit_title_amend() -> String {
 	"Commit (Amend)".to_string()
 }
-pub fn commit_title_signoff() -> String {
-	"Commit (Sign-off)".to_string()
-}
 pub fn commit_msg(_key_config: &SharedKeyConfig) -> String {
 	"type commit message..".to_string()
 }