implement reverting commit from revlog (#1057)

This commit is contained in:
Stephan Dilly 2021-12-29 16:40:22 +01:00 committed by GitHub
parent 508ee35ce5
commit d5d36de01e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 226 additions and 57 deletions

View File

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- allow reverting a commit from the commit log ([#927](https://github.com/extrawurst/gitui/issues/927))
### Fixed ### Fixed
- Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035)) - Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035))
- honor `pushurl` when checking credentials for pushing ([#953](https://github.com/extrawurst/gitui/issues/953)) - honor `pushurl` when checking credentials for pushing ([#953](https://github.com/extrawurst/gitui/issues/953))

View File

@ -79,7 +79,6 @@ These are the high level goals before calling out `1.0`:
* notify-based change detection ([#1](https://github.com/extrawurst/gitui/issues/1)) * notify-based change detection ([#1](https://github.com/extrawurst/gitui/issues/1))
* interactive rebase ([#32](https://github.com/extrawurst/gitui/issues/32)) * interactive rebase ([#32](https://github.com/extrawurst/gitui/issues/32))
* popup history and back button ([#846](https://github.com/extrawurst/gitui/issues/846)) * popup history and back button ([#846](https://github.com/extrawurst/gitui/issues/846))
* support reverting a commit ([#927](https://github.com/extrawurst/gitui/issues/927))
## 5. <a name="limitations"></a> Known Limitations <small><sup>[Top ▲](#table-of-contents)</sup></small> ## 5. <a name="limitations"></a> Known Limitations <small><sup>[Top ▲](#table-of-contents)</sup></small>

View File

@ -0,0 +1,51 @@
use super::{CommitId, RepoPath};
use crate::{
error::Result,
sync::{repository::repo, utils::read_file},
};
use scopetime::scope_time;
const GIT_REVERT_HEAD_FILE: &str = "REVERT_HEAD";
///
pub fn revert_commit(
repo_path: &RepoPath,
commit: CommitId,
) -> Result<()> {
scope_time!("revert");
let repo = repo(repo_path)?;
let commit = repo.find_commit(commit.into())?;
repo.revert(&commit, None)?;
Ok(())
}
///
pub fn revert_head(repo_path: &RepoPath) -> Result<CommitId> {
scope_time!("revert_head");
let path = repo(repo_path)?.path().join(GIT_REVERT_HEAD_FILE);
let file_content = read_file(&path)?;
let id = git2::Oid::from_str(file_content.trim())?;
Ok(id.into())
}
///
pub fn commit_revert(
repo_path: &RepoPath,
msg: &str,
) -> Result<CommitId> {
scope_time!("commit_revert");
let id = crate::sync::commit(repo_path, msg)?;
repo(repo_path)?.cleanup_state()?;
Ok(id)
}

View File

@ -36,8 +36,8 @@ pub fn mergehead_ids(repo_path: &RepoPath) -> Result<Vec<CommitId>> {
/// * reset all staged changes, /// * reset all staged changes,
/// * revert all changes in workdir /// * revert all changes in workdir
/// * cleanup repo merge state /// * cleanup repo merge state
pub fn abort_merge(repo_path: &RepoPath) -> Result<()> { pub fn abort_pending_state(repo_path: &RepoPath) -> Result<()> {
scope_time!("cleanup_state"); scope_time!("abort_pending_state");
let repo = repo(repo_path)?; let repo = repo(repo_path)?;

View File

@ -8,6 +8,7 @@ pub mod branch;
mod commit; mod commit;
mod commit_details; mod commit_details;
mod commit_files; mod commit_files;
mod commit_revert;
mod commits_info; mod commits_info;
mod config; mod config;
pub mod cred; pub mod cred;
@ -44,6 +45,7 @@ pub use commit_details::{
get_commit_details, CommitDetails, CommitMessage, CommitSignature, get_commit_details, CommitDetails, CommitMessage, CommitSignature,
}; };
pub use commit_files::get_commit_files; pub use commit_files::get_commit_files;
pub use commit_revert::{commit_revert, revert_commit, revert_head};
pub use commits_info::{ pub use commits_info::{
get_commit_info, get_commits_info, CommitId, CommitInfo, get_commit_info, get_commits_info, CommitId, CommitInfo,
}; };
@ -60,9 +62,9 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
pub use ignore::add_to_ignore; pub use ignore::add_to_ignore;
pub use logwalker::{LogWalker, LogWalkerFilter}; pub use logwalker::{LogWalker, LogWalkerFilter};
pub use merge::{ pub use merge::{
abort_merge, abort_pending_rebase, continue_pending_rebase, abort_pending_rebase, abort_pending_state,
merge_branch, merge_commit, merge_msg, mergehead_ids, continue_pending_rebase, merge_branch, merge_commit, merge_msg,
rebase_progress, mergehead_ids, rebase_progress,
}; };
pub use rebase::rebase_branch; pub use rebase::rebase_branch;
pub use remotes::{ pub use remotes::{

View File

@ -13,6 +13,8 @@ pub enum RepoState {
/// ///
Rebase, Rebase,
/// ///
Revert,
///
Other, Other,
} }
@ -21,6 +23,7 @@ impl From<RepositoryState> for RepoState {
match state { match state {
RepositoryState::Clean => Self::Clean, RepositoryState::Clean => Self::Clean,
RepositoryState::Merge => Self::Merge, RepositoryState::Merge => Self::Merge,
RepositoryState::Revert => Self::Revert,
RepositoryState::RebaseMerge => Self::Rebase, RepositoryState::RebaseMerge => Self::Rebase,
_ => { _ => {
log::warn!("state not supported yet: {:?}", state); log::warn!("state not supported yet: {:?}", state);
@ -38,7 +41,5 @@ pub fn repo_state(repo_path: &RepoPath) -> Result<RepoState> {
let state = repo.state(); let state = repo.state();
// dbg!(&state);
Ok(state.into()) Ok(state.into())
} }

View File

@ -187,6 +187,17 @@ pub(crate) fn repo_write_file(
Ok(()) Ok(())
} }
///
pub fn read_file(path: &Path) -> Result<String> {
use std::io::Read;
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(String::from_utf8(buffer)?)
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn repo_read_file( pub(crate) fn repo_read_file(
repo: &Repository, repo: &Repository,

View File

@ -701,7 +701,7 @@ impl App {
InternalEvent::Tags => { InternalEvent::Tags => {
self.tags_popup.open()?; self.tags_popup.open()?;
} }
InternalEvent::TabSwitch => self.set_tab(0)?, InternalEvent::TabSwitchStatus => self.set_tab(0)?,
InternalEvent::InspectCommit(id, tags) => { InternalEvent::InspectCommit(id, tags) => {
self.inspect_commit_popup.open(id, tags)?; self.inspect_commit_popup.open(id, tags)?;
flags flags
@ -879,8 +879,8 @@ impl App {
self.pull_popup.try_conflict_free_merge(rebase); self.pull_popup.try_conflict_free_merge(rebase);
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
Action::AbortMerge => { Action::AbortRevert | Action::AbortMerge => {
self.status_tab.abort_merge(); self.status_tab.revert_pending_state();
flags.insert(NeedsUpdate::ALL); flags.insert(NeedsUpdate::ALL);
} }
Action::AbortRebase => { Action::AbortRebase => {

View File

@ -446,7 +446,7 @@ impl BranchListComponent {
if sync::repo_state(&self.repo.borrow())? != RepoState::Clean if sync::repo_state(&self.repo.borrow())? != RepoState::Clean
{ {
self.queue.push(InternalEvent::TabSwitch); self.queue.push(InternalEvent::TabSwitchStatus);
} }
Ok(()) Ok(())

View File

@ -39,6 +39,7 @@ enum Mode {
Normal, Normal,
Amend(CommitId), Amend(CommitId),
Merge(Vec<CommitId>), Merge(Vec<CommitId>),
Revert,
} }
pub struct CommitComponent { pub struct CommitComponent {
@ -227,6 +228,9 @@ impl CommitComponent {
Mode::Merge(ids) => { Mode::Merge(ids) => {
sync::merge_commit(&self.repo.borrow(), &msg, ids)? sync::merge_commit(&self.repo.borrow(), &msg, ids)?
} }
Mode::Revert => {
sync::commit_revert(&self.repo.borrow(), &msg)?
}
}; };
if let HookResult::NotOk(e) = if let HookResult::NotOk(e) =
@ -380,31 +384,40 @@ impl Component for CommitComponent {
self.mode = Mode::Normal; self.mode = Mode::Normal;
self.mode = if sync::repo_state(&self.repo.borrow())? let repo_state = sync::repo_state(&self.repo.borrow())?;
== RepoState::Merge
{
let ids = sync::mergehead_ids(&self.repo.borrow())?;
self.input.set_title(strings::commit_title_merge());
self.input
.set_text(sync::merge_msg(&self.repo.borrow())?);
Mode::Merge(ids)
} else {
self.commit_template = get_config_string(
&self.repo.borrow(),
"commit.template",
)
.ok()
.flatten()
.and_then(|path| read_to_string(path).ok());
if self.is_empty() { self.mode = match repo_state {
if let Some(s) = &self.commit_template { RepoState::Merge => {
self.input.set_text(s.clone()); let ids = sync::mergehead_ids(&self.repo.borrow())?;
} self.input.set_title(strings::commit_title_merge());
self.input
.set_text(sync::merge_msg(&self.repo.borrow())?);
Mode::Merge(ids)
} }
RepoState::Revert => {
self.input.set_title(strings::commit_title_revert());
self.input
.set_text(sync::merge_msg(&self.repo.borrow())?);
Mode::Revert
}
_ => {
self.commit_template = get_config_string(
&self.repo.borrow(),
"commit.template",
)
.ok()
.flatten()
.and_then(|path| read_to_string(path).ok());
self.input.set_title(strings::commit_title()); if self.is_empty() {
Mode::Normal 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()?; self.input.show()?;

View File

@ -199,11 +199,15 @@ impl ConfirmComponent {
), ),
Action::AbortMerge => ( Action::AbortMerge => (
strings::confirm_title_abortmerge(), strings::confirm_title_abortmerge(),
strings::confirm_msg_abortmerge(), strings::confirm_msg_revertchanges(),
), ),
Action::AbortRebase => ( Action::AbortRebase => (
strings::confirm_title_abortrebase(), strings::confirm_title_abortrebase(),
strings::confirm_msg_abortrebase(), strings::confirm_msg_abortrebase(),
),
Action::AbortRevert => (
strings::confirm_title_abortrevert(),
strings::confirm_msg_revertchanges(),
), ),
}; };
} }

View File

@ -327,7 +327,7 @@ impl Component for RevisionFilesComponent {
if let Some(file) = self.selected_file() { if let Some(file) = self.selected_file() {
//Note: switch to status tab so its clear we are //Note: switch to status tab so its clear we are
// not altering a file inside a revision here // not altering a file inside a revision here
self.queue.push(InternalEvent::TabSwitch); self.queue.push(InternalEvent::TabSwitchStatus);
self.queue.push( self.queue.push(
InternalEvent::OpenExternalEditor(Some(file)), InternalEvent::OpenExternalEditor(Some(file)),
); );

View File

@ -43,6 +43,7 @@ pub enum Action {
PullMerge { incoming: usize, rebase: bool }, PullMerge { incoming: usize, rebase: bool },
AbortMerge, AbortMerge,
AbortRebase, AbortRebase,
AbortRevert,
} }
/// ///
@ -62,7 +63,7 @@ pub enum InternalEvent {
/// ///
PopupStashing(StashingOptions), PopupStashing(StashingOptions),
/// ///
TabSwitch, TabSwitchStatus,
/// ///
InspectCommit(CommitId, Option<CommitTags>), InspectCommit(CommitId, Option<CommitTags>),
/// ///

View File

@ -90,9 +90,13 @@ pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String {
pub fn commit_title() -> String { pub fn commit_title() -> String {
"Commit".to_string() "Commit".to_string()
} }
pub fn commit_title_merge() -> String { pub fn commit_title_merge() -> String {
"Commit (Merge)".to_string() "Commit (Merge)".to_string()
} }
pub fn commit_title_revert() -> String {
"Commit (Revert)".to_string()
}
pub fn commit_title_amend() -> String { pub fn commit_title_amend() -> String {
"Commit (Amend)".to_string() "Commit (Amend)".to_string()
} }
@ -156,7 +160,10 @@ pub fn confirm_msg_merge(
pub fn confirm_title_abortmerge() -> String { pub fn confirm_title_abortmerge() -> String {
"Abort merge?".to_string() "Abort merge?".to_string()
} }
pub fn confirm_msg_abortmerge() -> String { pub fn confirm_title_abortrevert() -> String {
"Abort revert?".to_string()
}
pub fn confirm_msg_revertchanges() -> String {
"This will revert all uncommitted changes. Are you sure?" "This will revert all uncommitted changes. Are you sure?"
.to_string() .to_string()
} }
@ -647,6 +654,17 @@ pub mod commands {
) )
} }
pub fn abort_revert(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Abort revert [{}]",
key_config.get_hint(key_config.keys.abort_merge),
),
"abort ongoing revert",
CMD_GROUP_GENERAL,
)
}
pub fn continue_rebase( pub fn continue_rebase(
key_config: &SharedKeyConfig, key_config: &SharedKeyConfig,
) -> CommandText { ) -> CommandText {
@ -1035,6 +1053,19 @@ pub mod commands {
CMD_GROUP_LOG, CMD_GROUP_LOG,
) )
} }
pub fn revert_commit(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Revert [{}]",
key_config
.get_hint(key_config.keys.status_reset_item),
),
"revert commit",
CMD_GROUP_LOG,
)
}
pub fn tag_commit_confirm_msg( pub fn tag_commit_confirm_msg(
key_config: &SharedKeyConfig, key_config: &SharedKeyConfig,
) -> CommandText { ) -> CommandText {

View File

@ -6,7 +6,7 @@ use crate::{
}, },
keys::SharedKeyConfig, keys::SharedKeyConfig,
queue::{InternalEvent, Queue}, queue::{InternalEvent, Queue},
strings, strings, try_or_popup,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -190,6 +190,15 @@ impl Revlog {
anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch."); anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch.");
} }
} }
fn revert_commit(&self) -> Result<()> {
if let Some(c) = self.selected_commit() {
sync::revert_commit(&self.repo.borrow(), c)?;
self.queue.push(InternalEvent::TabSwitchStatus);
}
Ok(())
}
} }
impl DrawableComponent for Revlog { impl DrawableComponent for Revlog {
@ -267,6 +276,15 @@ impl Component for Revlog {
); );
} else if k == self.key_config.keys.select_branch { } else if k == self.key_config.keys.select_branch {
self.queue.push(InternalEvent::SelectBranch); self.queue.push(InternalEvent::SelectBranch);
return Ok(EventState::Consumed);
} else if k == self.key_config.keys.status_reset_item
{
try_or_popup!(
self,
"revert error:",
self.revert_commit()
);
return Ok(EventState::Consumed); return Ok(EventState::Consumed);
} else if k == self.key_config.keys.open_file_tree { } else if k == self.key_config.keys.open_file_tree {
return self.selected_commit().map_or( return self.selected_commit().map_or(
@ -385,6 +403,12 @@ impl Component for Revlog {
self.visible || force_all, self.visible || force_all,
)); ));
out.push(CommandInfo::new(
strings::commands::revert_commit(&self.key_config),
self.selected_commit().is_some(),
self.visible || force_all,
));
visibility_blocking(self) visibility_blocking(self)
} }

View File

@ -63,7 +63,7 @@ impl StashList {
match sync::stash_apply(&self.repo.borrow(), e.id, false) match sync::stash_apply(&self.repo.borrow(), e.id, false)
{ {
Ok(_) => { Ok(_) => {
self.queue.push(InternalEvent::TabSwitch); self.queue.push(InternalEvent::TabSwitchStatus);
} }
Err(e) => { Err(e) => {
self.queue.push(InternalEvent::ShowErrorMsg( self.queue.push(InternalEvent::ShowErrorMsg(

View File

@ -276,6 +276,16 @@ impl Status {
String::new() String::new()
} }
} }
RepoState::Revert => {
format!(
"Revert {}",
sync::revert_head(repo)
.ok()
.as_ref()
.map(CommitId::get_short_string)
.unwrap_or_default(),
)
}
_ => format!("{:?}", state), _ => format!("{:?}", state),
} }
} }
@ -611,11 +621,17 @@ impl Status {
== RepoState::Rebase == RepoState::Rebase
} }
pub fn abort_merge(&self) { fn pending_revert(&self) -> bool {
sync::repo_state(&self.repo.borrow())
.unwrap_or(RepoState::Clean)
== RepoState::Revert
}
pub fn revert_pending_state(&self) {
try_or_popup!( try_or_popup!(
self, self,
"abort merge", "revert pending state",
sync::abort_merge(&self.repo.borrow()) sync::abort_pending_state(&self.repo.borrow())
); );
} }
@ -754,11 +770,18 @@ impl Component for Status {
true, true,
self.pending_rebase() || force_all, self.pending_rebase() || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
strings::commands::abort_rebase(&self.key_config), strings::commands::abort_rebase(&self.key_config),
true, true,
self.pending_rebase() || force_all, self.pending_rebase() || force_all,
)); ));
out.push(CommandInfo::new(
strings::commands::abort_revert(&self.key_config),
true,
self.pending_revert() || force_all,
));
} }
{ {
@ -863,20 +886,26 @@ impl Component for Status {
NeedsUpdate::ALL, NeedsUpdate::ALL,
)); ));
Ok(EventState::Consumed) Ok(EventState::Consumed)
} else if k == self.key_config.keys.abort_merge } else if k == self.key_config.keys.abort_merge {
&& self.can_abort_merge() if self.can_abort_merge() {
{ self.queue.push(
self.queue.push(InternalEvent::ConfirmAction( InternalEvent::ConfirmAction(
Action::AbortMerge, Action::AbortMerge,
)); ),
);
Ok(EventState::Consumed) } else if self.pending_rebase() {
} else if k == self.key_config.keys.abort_merge self.queue.push(
&& self.pending_rebase() InternalEvent::ConfirmAction(
{ Action::AbortRebase,
self.queue.push(InternalEvent::ConfirmAction( ),
Action::AbortRebase, );
)); } else if self.pending_revert() {
self.queue.push(
InternalEvent::ConfirmAction(
Action::AbortRevert,
),
);
}
Ok(EventState::Consumed) Ok(EventState::Consumed)
} else if k == self.key_config.keys.rebase_branch } else if k == self.key_config.keys.rebase_branch