Skip to content

Commit

Permalink
support commit amend (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephan Dilly committed Jun 12, 2020
1 parent 63e449f commit 676ecdb
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Commit Amend (`ctrl+a`) when in commit popup ([#89](https://github.com/extrawurst/gitui/issues/89))

### Changed
- file trees: `arrow-right` on expanded folder moves down into folder
- better scrolling in diff ([#52](https://github.com/extrawurst/gitui/issues/52))
Expand Down
121 changes: 121 additions & 0 deletions asyncgit/src/sync/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use super::{utils::repo, CommitId};
use crate::error::Result;
use scopetime::scope_time;

///
pub fn get_head(repo_path: &str) -> Result<CommitId> {
scope_time!("get_head");

let repo = repo(repo_path)?;

let head_id = repo.head()?.target().expect("head target error");

Ok(CommitId::new(head_id))
}

///
pub fn amend(
repo_path: &str,
id: CommitId,
msg: &str,
) -> Result<CommitId> {
scope_time!("commit");

let repo = repo(repo_path)?;
let commit = repo.find_commit(id.into())?;

let mut index = repo.index()?;
let tree_id = index.write_tree()?;
let tree = repo.find_tree(tree_id)?;

let new_id = commit.amend(
Some("HEAD"),
None,
None,
None,
Some(msg),
Some(&tree),
)?;

Ok(CommitId::new(new_id))
}

#[cfg(test)]
mod tests {

use crate::error::Result;
use crate::sync::{
commit, get_commit_details, get_commit_files, stage_add_file,
tests::{repo_init, repo_init_empty},
CommitId, LogWalker,
};
use commit::{amend, get_head};
use git2::Repository;
use std::{fs::File, io::Write, path::Path};

fn count_commits(repo: &Repository, max: usize) -> usize {
let mut items = Vec::new();
let mut walk = LogWalker::new(&repo);
walk.read(&mut items, max).unwrap();
items.len()
}

#[test]
fn test_amend() -> Result<()> {
let file_path1 = Path::new("foo");
let file_path2 = Path::new("foo2");
let (_td, repo) = repo_init_empty()?;
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

File::create(&root.join(file_path1))?.write_all(b"test1")?;

stage_add_file(repo_path, file_path1)?;
let id = commit(repo_path, "commit msg")?;

assert_eq!(count_commits(&repo, 10), 1);

File::create(&root.join(file_path2))?.write_all(b"test2")?;

stage_add_file(repo_path, file_path2)?;

let new_id = amend(repo_path, CommitId::new(id), "amended")?;

assert_eq!(count_commits(&repo, 10), 1);

let details = get_commit_details(repo_path, new_id)?;
assert_eq!(details.message.unwrap().subject, "amended");

let files = get_commit_files(repo_path, new_id)?;

assert_eq!(files.len(), 2);

let head = get_head(repo_path)?;

assert_eq!(head, new_id);

Ok(())
}

#[test]
fn test_head_empty() -> Result<()> {
let (_td, repo) = repo_init_empty()?;
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

assert_eq!(get_head(repo_path).is_ok(), false);

Ok(())
}

#[test]
fn test_head() -> Result<()> {
let (_td, repo) = repo_init()?;
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

assert_eq!(get_head(repo_path).is_ok(), true);

Ok(())
}
}
9 changes: 9 additions & 0 deletions asyncgit/src/sync/commit_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ impl CommitMessage {
}
}
}

///
pub fn combine(self) -> String {
if let Some(body) = self.body {
format!("{}{}", self.subject, body)
} else {
self.subject
}
}
}

///
Expand Down
6 changes: 4 additions & 2 deletions asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! sync git api
mod branch;
mod commit;
mod commit_details;
mod commit_files;
mod commits_info;
Expand All @@ -16,6 +17,7 @@ mod tags;
pub mod utils;

pub use branch::get_branch_name;
pub use commit::{amend, get_head};
pub use commit_details::{get_commit_details, CommitDetails};
pub use commit_files::get_commit_files;
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
Expand All @@ -28,8 +30,8 @@ pub use reset::{reset_stage, reset_workdir};
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
pub use tags::{get_tags, Tags};
pub use utils::{
commit, is_bare_repo, is_repo, stage_add_all, stage_add_file,
stage_addremoved,
commit, commit_new, is_bare_repo, is_repo, stage_add_all,
stage_add_file, stage_addremoved,
};

#[cfg(test)]
Expand Down
6 changes: 6 additions & 0 deletions asyncgit/src/sync/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! sync git api (various methods)
use super::CommitId;
use crate::error::{Error, Result};
use git2::{IndexAddOption, Oid, Repository, RepositoryOpenFlags};
use scopetime::scope_time;
Expand Down Expand Up @@ -46,6 +47,11 @@ pub fn work_dir(repo: &Repository) -> &Path {
repo.workdir().expect("unable to query workdir")
}

/// ditto
pub fn commit_new(repo_path: &str, msg: &str) -> Result<CommitId> {
commit(repo_path, msg).map(CommitId::new)
}

/// this does not run any git hooks
pub fn commit(repo_path: &str, msg: &str) -> Result<Oid> {
scope_time!("commit");
Expand Down
62 changes: 54 additions & 8 deletions src/components/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ use super::{
CommandBlocking, CommandInfo, Component, DrawableComponent,
};
use crate::{
keys,
queue::{InternalEvent, NeedsUpdate, Queue},
strings,
ui::style::Theme,
};
use anyhow::Result;
use asyncgit::{sync, CWD};
use crossterm::event::{Event, KeyCode};
use asyncgit::{
sync::{self, CommitId},
CWD,
};
use crossterm::event::Event;
use strings::commands;
use sync::HookResult;
use tui::{backend::Backend, layout::Rect, Frame};

pub struct CommitComponent {
input: TextInputComponent,
amend: Option<CommitId>,
queue: Queue,
}

Expand All @@ -42,8 +47,15 @@ impl Component for CommitComponent {
out.push(CommandInfo::new(
commands::COMMIT_ENTER,
self.can_commit(),
self.is_visible(),
self.is_visible() || force_all,
));

out.push(CommandInfo::new(
commands::COMMIT_AMEND,
self.can_amend(),
self.is_visible() || force_all,
));

visibility_blocking(self)
}

Expand All @@ -54,11 +66,15 @@ impl Component for CommitComponent {
}

if let Event::Key(e) = ev {
match e.code {
KeyCode::Enter if self.can_commit() => {
match e {
keys::ENTER if self.can_commit() => {
self.commit()?;
}

keys::COMMIT_AMEND if self.can_amend() => {
self.amend()?;
}

_ => (),
};

Expand All @@ -79,6 +95,10 @@ impl Component for CommitComponent {
}

fn show(&mut self) -> Result<()> {
self.amend = None;

self.input.clear();
self.input.set_title(strings::COMMIT_TITLE.into());
self.input.show()?;

Ok(())
Expand All @@ -90,9 +110,10 @@ impl CommitComponent {
pub fn new(queue: Queue, theme: &Theme) -> Self {
Self {
queue,
amend: None,
input: TextInputComponent::new(
theme,
strings::COMMIT_TITLE,
"",
strings::COMMIT_MSG,
),
}
Expand All @@ -113,7 +134,12 @@ impl CommitComponent {
return Ok(());
}

if let Err(e) = sync::commit(CWD, &msg) {
let res = if let Some(amend) = self.amend {
sync::amend(CWD, amend, &msg)
} else {
sync::commit_new(CWD, &msg)
};
if let Err(e) = res {
log::error!("commit error: {}", &e);
self.queue.borrow_mut().push_back(
InternalEvent::ShowErrorMsg(format!(
Expand All @@ -134,7 +160,6 @@ impl CommitComponent {
);
}

self.input.clear();
self.hide();

self.queue
Expand All @@ -147,4 +172,25 @@ impl CommitComponent {
fn can_commit(&self) -> bool {
!self.input.get_text().is_empty()
}

fn can_amend(&self) -> bool {
self.amend.is_none()
&& sync::get_head(CWD).is_ok()
&& self.input.get_text().is_empty()
}

fn amend(&mut self) -> Result<()> {
let id = sync::get_head(CWD)?;
self.amend = Some(id);

let details = sync::get_commit_details(CWD, id)?;

self.input.set_title(strings::COMMIT_TITLE_AMEND.into());

if let Some(msg) = details.message {
self.input.set_text(msg.combine());
}

Ok(())
}
}
16 changes: 14 additions & 2 deletions src/components/textinput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
ui::style::Theme,
};
use anyhow::Result;
use crossterm::event::{Event, KeyCode};
use crossterm::event::{Event, KeyCode, KeyModifiers};
use std::borrow::Cow;
use strings::commands;
use tui::{
Expand Down Expand Up @@ -52,6 +52,16 @@ impl TextInputComponent {
pub const fn get_text(&self) -> &String {
&self.msg
}

///
pub fn set_text(&mut self, msg: String) {
self.msg = msg;
}

///
pub fn set_title(&mut self, t: String) {
self.title = t;
}
}

impl DrawableComponent for TextInputComponent {
Expand Down Expand Up @@ -110,12 +120,14 @@ impl Component for TextInputComponent {
fn event(&mut self, ev: Event) -> Result<bool> {
if self.visible {
if let Event::Key(e) = ev {
let is_ctrl =
e.modifiers.contains(KeyModifiers::CONTROL);
match e.code {
KeyCode::Esc => {
self.hide();
return Ok(true);
}
KeyCode::Char(c) => {
KeyCode::Char(c) if !is_ctrl => {
self.msg.push(c);
return Ok(true);
}
Expand Down
2 changes: 2 additions & 0 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ pub const STASH_DROP: KeyEvent =
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
pub const CMD_BAR_TOGGLE: KeyEvent = no_mod(KeyCode::Char('.'));
pub const LOG_COMMIT_DETAILS: KeyEvent = no_mod(KeyCode::Enter);
pub const COMMIT_AMEND: KeyEvent =
with_mod(KeyCode::Char('a'), KeyModifiers::CONTROL);
7 changes: 7 additions & 0 deletions src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub static CMD_SPLITTER: &str = " ";

pub static MSG_TITLE_ERROR: &str = "Error";
pub static COMMIT_TITLE: &str = "Commit";
pub static COMMIT_TITLE_AMEND: &str = "Commit (Amend)";
pub static COMMIT_MSG: &str = "type commit message..";
pub static STASH_POPUP_TITLE: &str = "Stash";
pub static STASH_POPUP_MSG: &str = "type name (optional)";
Expand Down Expand Up @@ -148,6 +149,12 @@ pub mod commands {
CMD_GROUP_COMMIT,
);
///
pub static COMMIT_AMEND: CommandText = CommandText::new(
"Amend [^a]",
"amend last commit",
CMD_GROUP_COMMIT,
);
///
pub static STAGE_ITEM: CommandText = CommandText::new(
"Stage Item [enter]",
"stage currently selected file or entire path",
Expand Down

0 comments on commit 676ecdb

Please sign in to comment.