summaryrefslogtreecommitdiffstats
path: root/src/components/commit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/commit.rs')
-rw-r--r--src/components/commit.rs736
1 files changed, 368 insertions, 368 deletions
diff --git a/src/components/commit.rs b/src/components/commit.rs
index 1b7788bd..61a30abf 100644
--- a/src/components/commit.rs
+++ b/src/components/commit.rs
@@ -1,398 +1,398 @@
use super::{
- textinput::TextInputComponent, visibility_blocking,
- CommandBlocking, CommandInfo, Component, DrawableComponent,
- EventState, ExternalEditorComponent,
+ textinput::TextInputComponent, visibility_blocking,
+ CommandBlocking, CommandInfo, Component, DrawableComponent,
+ EventState, ExternalEditorComponent,
};
use crate::{
- keys::SharedKeyConfig,
- queue::{InternalEvent, NeedsUpdate, Queue},
- strings, try_or_popup,
- ui::style::SharedTheme,
+ keys::SharedKeyConfig,
+ queue::{InternalEvent, NeedsUpdate, Queue},
+ strings, try_or_popup,
+ ui::style::SharedTheme,
};
use anyhow::Result;
use asyncgit::{
- cached,
- sync::{
- self, get_config_string, CommitId, HookResult, RepoState,
- },
- CWD,
+ cached,
+ sync::{
+ self, get_config_string, CommitId, HookResult, RepoState,
+ },
+ CWD,
};
use crossterm::event::Event;
use easy_cast::Cast;
use std::{
- fs::{read_to_string, File},
- io::{Read, Write},
+ fs::{read_to_string, File},
+ io::{Read, Write},
};
use tui::{
- backend::Backend,
- layout::{Alignment, Rect},
- widgets::Paragraph,
- Frame,
+ backend::Backend,
+ layout::{Alignment, Rect},
+ widgets::Paragraph,
+ Frame,
};
enum Mode {
- Normal,
- Amend(CommitId),
- Merge(Vec<CommitId>),
+ Normal,
+ Amend(CommitId),
+ Merge(Vec<CommitId>),
}
pub struct CommitComponent {
- input: TextInputComponent,
- mode: Mode,
- queue: Queue,
- key_config: SharedKeyConfig,
- git_branch_name: cached::BranchName,
- commit_template: Option<String>,
- theme: SharedTheme,
+ input: TextInputComponent,
+ mode: Mode,
+ queue: Queue,
+ key_config: SharedKeyConfig,
+ git_branch_name: cached::BranchName,
+ commit_template: Option<String>,
+ theme: SharedTheme,
}
const FIRST_LINE_LIMIT: usize = 50;
impl CommitComponent {
- ///
- pub fn new(
- queue: Queue,
- theme: SharedTheme,
- key_config: SharedKeyConfig,
- ) -> Self {
- Self {
- queue,
- mode: Mode::Normal,
-
- input: TextInputComponent::new(
- theme.clone(),
- key_config.clone(),
- "",
- &strings::commit_msg(&key_config),
- true,
- ),
- key_config,
- git_branch_name: cached::BranchName::new(CWD),
- commit_template: None,
- theme,
- }
- }
-
- ///
- pub fn update(&mut self) {
- self.git_branch_name.lookup().ok();
- }
-
- fn draw_branch_name<B: Backend>(&self, f: &mut Frame<B>) {
- if let Some(name) = self.git_branch_name.last() {
- let w = Paragraph::new(format!("{{{}}}", name))
- .alignment(Alignment::Right);
-
- let rect = {
- let mut rect = self.input.get_area();
- rect.height = 1;
- rect.width = rect.width.saturating_sub(1);
- rect
- };
-
- f.render_widget(w, rect);
- }
- }
-
- fn draw_warnings<B: Backend>(&self, f: &mut Frame<B>) {
- let first_line = self
- .input
- .get_text()
- .lines()
- .next()
- .map(str::len)
- .unwrap_or_default();
-
- if first_line > FIRST_LINE_LIMIT {
- let msg = strings::commit_first_line_warning(first_line);
- let msg_length: u16 = msg.len().cast();
- let w =
- Paragraph::new(msg).style(self.theme.text_danger());
-
- let rect = {
- let mut rect = self.input.get_area();
- rect.y += rect.height.saturating_sub(1);
- rect.height = 1;
- let offset =
- rect.width.saturating_sub(msg_length + 1);
- rect.width = rect.width.saturating_sub(offset + 1);
- rect.x += offset;
-
- rect
- };
-
- f.render_widget(w, rect);
- }
- }
-
- pub fn show_editor(&mut self) -> Result<()> {
- let file_path = sync::repo_dir(CWD)?.join("COMMIT_EDITMSG");
-
- {
- let mut file = File::create(&file_path)?;
- file.write_fmt(format_args!(
- "{}\n",
- self.input.get_text()
- ))?;
- file.write_all(
- strings::commit_editor_msg(&self.key_config)
- .as_bytes(),
- )?;
- }
-
- ExternalEditorComponent::open_file_in_editor(&file_path)?;
-
- let mut message = String::new();
-
- let mut file = File::open(&file_path)?;
- file.read_to_string(&mut message)?;
- drop(file);
- std::fs::remove_file(&file_path)?;
-
- let message: String = message
- .lines()
- .flat_map(|l| {
- if l.starts_with('#') {
- vec![]
- } else {
- vec![l, "\n"]
- }
- })
- .collect();
-
- let message = message.trim().to_string();
-
- self.input.set_text(message);
- self.input.show()?;
-
- Ok(())
- }
-
- fn commit(&mut self) -> Result<()> {
- let gpgsign = get_config_string(CWD, "commit.gpgsign")
- .ok()
- .flatten()
- .and_then(|path| path.parse::<bool>().ok())
- .unwrap_or_default();
-
- if gpgsign {
- anyhow::bail!("config commit.gpgsign=true detected.\ngpg signing not supported.\ndeactivate in your repo/gitconfig to be able to commit without signing.");
- }
-
- let msg = self.input.get_text().clone();
- self.input.clear();
- self.commit_with_msg(msg)
- }
-
- fn commit_with_msg(&mut self, msg: String) -> Result<()> {
- if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? {
- log::error!("pre-commit hook error: {}", e);
- self.queue.push(InternalEvent::ShowErrorMsg(format!(
- "pre-commit hook error:\n{}",
- e
- )));
- return Ok(());
- }
- let mut msg = msg;
- if let HookResult::NotOk(e) =
- sync::hooks_commit_msg(CWD, &mut msg)?
- {
- log::error!("commit-msg hook error: {}", e);
- self.queue.push(InternalEvent::ShowErrorMsg(format!(
- "commit-msg hook error:\n{}",
- e
- )));
- return Ok(());
- }
-
- let res = match &self.mode {
- Mode::Normal => sync::commit(CWD, &msg),
- Mode::Amend(amend) => sync::amend(CWD, *amend, &msg),
- Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids),
- };
-
- if let Err(e) = res {
- log::error!("commit error: {}", &e);
- self.queue.push(InternalEvent::ShowErrorMsg(format!(
- "commit failed:\n{}",
- &e
- )));
- return Ok(());
- }
-
- if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD)? {
- log::error!("post-commit hook error: {}", e);
- self.queue.push(InternalEvent::ShowErrorMsg(format!(
- "post-commit hook error:\n{}",
- e
- )));
- }
-
- self.hide();
-
- self.queue.push(InternalEvent::Update(NeedsUpdate::ALL));
-
- Ok(())
- }
-
- fn can_commit(&self) -> bool {
- !self.is_empty() && self.is_changed()
- }
-
- fn can_amend(&self) -> bool {
- matches!(self.mode, Mode::Normal)
- && sync::get_head(CWD).is_ok()
- && (self.is_empty() || !self.is_changed())
- }
-
- fn is_empty(&self) -> bool {
- self.input.get_text().is_empty()
- }
-
- fn is_changed(&self) -> bool {
- Some(self.input.get_text().trim())
- != self.commit_template.as_ref().map(|s| s.trim())
- }
-
- fn amend(&mut self) -> Result<()> {
- if self.can_amend() {
- let id = sync::get_head(CWD)?;
- self.mode = Mode::Amend(id);
-
- let details = sync::get_commit_details(CWD, id)?;
-
- self.input.set_title(strings::commit_title_amend());
-
- if let Some(msg) = details.message {
- self.input.set_text(msg.combine());
- }
- }
-
- Ok(())
- }
+ ///
+ pub fn new(
+ queue: Queue,
+ theme: SharedTheme,
+ key_config: SharedKeyConfig,
+ ) -> Self {
+ Self {
+ queue,
+ mode: Mode::Normal,
+
+ input: TextInputComponent::new(
+ theme.clone(),
+ key_config.clone(),
+ "",
+ &strings::commit_msg(&key_config),
+ true,
+ ),
+ key_config,
+ git_branch_name: cached::BranchName::new(CWD),
+ commit_template: None,
+ theme,
+ }
+ }
+
+ ///
+ pub fn update(&mut self) {
+ self.git_branch_name.lookup().ok();
+ }
+
+ fn draw_branch_name<B: Backend>(&self, f: &mut Frame<B>) {
+ if let Some(name) = self.git_branch_name.last() {
+ let w = Paragraph::new(format!("{{{}}}", name))
+ .alignment(Alignment::Right);
+
+ let rect = {
+ let mut rect = self.input.get_area();
+ rect.height = 1;
+ rect.width = rect.width.saturating_sub(1);
+ rect
+ };
+
+ f.render_widget(w, rect);
+ }
+ }
+
+ fn draw_warnings<B: Backend>(&self, f: &mut Frame<B>) {
+ let first_line = self
+ .input
+ .get_text()
+ .lines()
+ .next()
+ .map(str::len)
+ .unwrap_or_default();
+
+ if first_line > FIRST_LINE_LIMIT {
+ let msg = strings::commit_first_line_warning(first_line);
+ let msg_length: u16 = msg.len().cast();
+ let w =
+ Paragraph::new(msg).style(self.theme.text_danger());
+
+ let rect = {
+ let mut rect = self.input.get_area();
+ rect.y += rect.height.saturating_sub(1);
+ rect.height = 1;
+ let offset =
+ rect.width.saturating_sub(msg_length + 1);
+ rect.width = rect.width.saturating_sub(offset + 1);
+ rect.x += offset;
+
+ rect
+ };
+
+ f.render_widget(w, rect);
+ }
+ }
+
+ pub fn show_editor(&mut self) -> Result<()> {
+ let file_path = sync::repo_dir(CWD)?.join("COMMIT_EDITMSG");
+
+ {
+ let mut file = File::create(&file_path)?;
+ file.write_fmt(format_args!(
+ "{}\n",
+ self.input.get_text()
+ ))?;
+ file.write_all(
+ strings::commit_editor_msg(&self.key_config)
+ .as_bytes(),
+ )?;
+ }
+
+ ExternalEditorComponent::open_file_in_editor(&file_path)?;
+
+ let mut message = String::new();
+
+ let mut file = File::open(&file_path)?;
+ file.read_to_string(&mut message)?;
+ drop(file);
+ std::fs::remove_file(&file_path)?;
+
+ let message: String = message
+ .lines()
+ .flat_map(|l| {
+ if l.starts_with('#') {
+ vec![]
+ } else {
+ vec![l, "\n"]
+ }
+ })
+ .collect();
+
+ let message = message.trim().to_string();
+
+ self.input.set_text(message);
+ self.input.show()?;
+
+ Ok(())
+ }
+
+ fn commit(&mut self) -> Result<()> {
+ let gpgsign = get_config_string(CWD, "commit.gpgsign")
+ .ok()
+ .flatten()
+ .and_then(|path| path.parse::<bool>().ok())
+ .unwrap_or_default();
+
+ if gpgsign {
+ anyhow::bail!("config commit.gpgsign=true detected.\ngpg signing not supported.\ndeactivate in your repo/gitconfig to be able to commit without signing.");
+ }
+
+ let msg = self.input.get_text().clone();
+ self.input.clear();
+ self.commit_with_msg(msg)
+ }
+
+ fn commit_with_msg(&mut self, msg: String) -> Result<()> {
+ if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? {
+ log::error!("pre-commit hook error: {}", e);
+ self.queue.push(InternalEvent::ShowErrorMsg(format!(
+ "pre-commit hook error:\n{}",
+ e
+ )));
+ return Ok(());
+ }
+ let mut msg = msg;
+ if let HookResult::NotOk(e) =
+ sync::hooks_commit_msg(CWD, &mut msg)?
+ {
+ log::error!("commit-msg hook error: {}", e);
+ self.queue.push(InternalEvent::ShowErrorMsg(format!(
+ "commit-msg hook error:\n{}",
+ e
+ )));
+ return Ok(());
+ }
+
+ let res = match &self.mode {
+ Mode::Normal => sync::commit(CWD, &msg),
+ Mode::Amend(amend) => sync::amend(CWD, *amend, &msg),
+ Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids),
+ };
+
+ if let Err(e) = res {
+ log::error!("commit error: {}", &e);
+ self.queue.push(InternalEvent::ShowErrorMsg(format!(
+ "commit failed:\n{}",
+ &e
+ )));
+ return Ok(());
+ }
+
+ if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD)? {
+ log::error!("post-commit hook error: {}", e);
+ self.queue.push(InternalEvent::ShowErrorMsg(format!(
+ "post-commit hook error:\n{}",
+ e
+ )));
+ }
+
+ self.hide();
+
+ self.queue.push(InternalEvent::Update(NeedsUpdate::ALL));
+
+ Ok(())
+ }
+
+ fn can_commit(&self) -> bool {
+ !self.is_empty() && self.is_changed()
+ }
+
+ fn can_amend(&self) -> bool {
+ matches!(self.mode, Mode::Normal)
+ && sync::get_head(CWD).is_ok()
+ && (self.is_empty() || !self.is_changed())
+ }
+
+ fn is_empty(&self) -> bool {
+ self.input.get_text().is_empty()
+ }
+
+ fn is_changed(&self) -> bool {
+ Some(self.input.get_text().trim())
+ != self.commit_template.as_ref().map(|s| s.trim())
+ }
+
+ fn amend(&mut self) -> Result<()> {
+ if self.can_amend() {
+ let id = sync::get_head(CWD)?;
+ self.mode = Mode::Amend(id);
+
+ let details = sync::get_commit_details(CWD, id)?;
+
+ self.input.set_title(strings::commit_title_amend());
+
+ if let Some(msg) = details.message {
+ self.input.set_text(msg.combine());
+ }
+ }
+
+ Ok(())
+ }
}
impl DrawableComponent for CommitComponent {
- fn draw<B: Backend>(
- &self,
- f: &mut Frame<B>,
- rect: Rect,
- ) -> Result<()> {
- if self.is_visible() {
- self.input.draw(f, rect)?;
- self.draw_branch_name(f);
- self.draw_warnings(f);
- }
-
- Ok(())
- }
+ fn draw<B: Backend>(
+ &self,
+ f: &mut Frame<B>,
+ rect: Rect,
+ ) -> Result<()> {
+ if self.is_visible() {
+ self.input.draw(f, rect)?;
+ self.draw_branch_name(f);
+ self.draw_warnings(f);
+ }
+
+ Ok(())
+ }
}
impl Component for CommitComponent {
- fn commands(
- &self,
- out: &mut Vec<CommandInfo>,
- force_all: bool,
- ) -> CommandBlocking {
- self.input.commands(out, force_all);
-
- if self.is_visible() || force_all {
- out.push(CommandInfo::new(
- strings::commands::commit_enter(&self.key_config),
- self.can_commit(),
- true,
- ));
-
- out.push(CommandInfo::new(
- strings::commands::commit_amend(&self.key_config),
- self.can_amend(),
- true,
- ));
-
- out.push(CommandInfo::new(
- strings::commands::commit_open_editor(
- &self.key_config,
- ),
- true,
- true,
- ));
- }
-
- visibility_blocking(self)
- }
-
- fn event(&mut self, ev: Event) -> Result<EventState> {
- if self.is_visible() {
- if self.input.event(ev)?.is_consumed() {
- return Ok(EventState::Consumed);
- }
-
- if let Event::Key(e) = ev {
- if e == self.key_config.enter && self.can_commit() {
- try_or_popup!(
- self,
- "commit error:",
- self.commit()
- );
- } else if e == self.key_config.commit_amend
- && self.can_amend()
- {
- self.amend()?;
- } else if e == self.key_config.open_commit_editor {
- self.queue.push(
- InternalEvent::OpenExternalEditor(None),
- );
- self.hide();
- } else {
- }
- // stop key event propagation
- return Ok(EventState::Consumed);
- }
- }
-
- Ok(EventState::NotConsumed)
- }
-
- fn is_visible(&self) -> bool {
- self.input.is_visible()
- }
-
- fn hide(&mut self) {
- self.input.hide();
- }
-
- fn show(&mut self) -> Result<()> {
- //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited
- if !matches!(self.mode, Mode::Normal) {
- self.input.clear();
- }
-
- self.mode = Mode::Normal;
-
- self.mode = if sync::repo_state(CWD)? == RepoState::Merge {
- let ids = sync::mergehead_ids(CWD)?;
- self.input.set_title(strings::commit_title_merge());
- self.input.set_text(sync::merge_msg(CWD)?);
- Mode::Merge(ids)
- } else {
- self.commit_template =
- get_config_string(CWD, "commit.template")
- .ok()
- .flatten()
- .and_then(|path| read_to_string(path).ok());
-
- if self.is_empty() {
- if let Some(s) = &self.commit_template {
- self.input.set_text(s.clone());
- }
- }
-
- self.input.set_title(strings::commit_title());
- Mode::Normal
- };
-
- self.input.show()?;
-
- Ok(())
- }
+ fn commands(
+ &self,
+ out: &mut Vec<CommandInfo>,
+ force_all: bool,
+ ) -> CommandBlocking {
+ self.input.commands(out, force_all);
+
+ if self.is_visible() || force_all {
+ out.push(CommandInfo::new(
+ strings::commands::commit_enter(&self.key_config),
+ self.can_commit(),
+ true,
+ ));
+
+ out.push(CommandInfo::new(
+ strings::commands::commit_amend(&self.key_config),
+ self.can_amend(),
+ true,
+ ));
+
+ out.push(CommandInfo::new(
+ strings::commands::commit_open_editor(
+ &self.key_config,
+ ),
+ true,
+ true,
+ ));
+ }
+
+ visibility_blocking(self)
+ }
+
+ fn event(&mut self, ev: Event) -> Result<EventState> {
+ if self.is_visible() {
+ if self.input.event(ev)?.is_consumed() {
+ return Ok(EventState::Consumed);
+ }
+
+ if let Event::Key(e) = ev {
+ if e == self.key_config.enter && self.can_commit() {
+ try_or_popup!(
+ self,
+ "commit error:",
+ self.commit()
+ );
+ } else if e == self.key_config.commit_amend
+ && self.can_amend()
+ {
+ self.amend()?;
+ } else if e == self.key_config.open_commit_editor {
+ self.queue.push(
+ InternalEvent::OpenExternalEditor(None),
+ );
+ self.hide();
+ } else {
+ }
+ // stop key event propagation
+ return Ok(EventState::Consumed);
+ }
+ }
+
+ Ok(EventState::NotConsumed)
+ }
+
+ fn is_visible(&self) -> bool {
+ self.input.is_visible()
+ }
+
+ fn hide(&mut self) {
+ self.input.hide();
+ }
+
+ fn show(&mut self) -> Result<()> {
+ //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited
+ if !matches!(self.mode, Mode::Normal) {
+ self.input.clear();
+ }
+
+ self.mode = Mode::Normal;
+
+ self.mode = if sync::repo_state(CWD)? == RepoState::Merge {
+ let ids = sync::mergehead_ids(CWD)?;
+ self.input.set_title(strings::commit_title_merge());
+ self.input.set_text(sync::merge_msg(CWD)?);
+ Mode::Merge(ids)
+ } else {
+ self.commit_template =
+ get_config_string(CWD, "commit.template")
+ .ok()
+ .flatten()
+ .and_then(|path| read_to_string(path).ok());
+
+ if self.is_empty() {
+ if let Some(s) = &self.commit_template {
+ self.input.set_text(s.clone());
+ }
+ }
+
+ self.input.set_title(strings::commit_title());
+ Mode::Normal
+ };
+
+ self.input.show()?;
+
+ Ok(())
+ }
}