mirror of
https://github.com/extrawurst/gitui.git
synced 2024-12-28 03:22:51 +03:00
allow rebase with conflicts (#897)
This commit is contained in:
parent
9f8fc6b907
commit
e4c7867564
@ -1,13 +1,18 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
sync::{
|
sync::{
|
||||||
branch::merge_commit::commit_merge_with_head, reset_stage,
|
branch::merge_commit::commit_merge_with_head,
|
||||||
reset_workdir, utils, CommitId,
|
rebase::{
|
||||||
|
abort_rebase, continue_rebase, get_rebase_progress,
|
||||||
|
},
|
||||||
|
reset_stage, reset_workdir, utils, CommitId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use git2::{BranchType, Commit, MergeOptions, Repository};
|
use git2::{BranchType, Commit, MergeOptions, Repository};
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
|
|
||||||
|
use super::rebase::{RebaseProgress, RebaseState};
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn mergehead_ids(repo_path: &str) -> Result<Vec<CommitId>> {
|
pub fn mergehead_ids(repo_path: &str) -> Result<Vec<CommitId>> {
|
||||||
scope_time!("mergehead_ids");
|
scope_time!("mergehead_ids");
|
||||||
@ -51,6 +56,35 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn rebase_progress(repo_path: &str) -> Result<RebaseProgress> {
|
||||||
|
scope_time!("rebase_progress");
|
||||||
|
|
||||||
|
let repo = utils::repo(repo_path)?;
|
||||||
|
|
||||||
|
get_rebase_progress(&repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn continue_pending_rebase(
|
||||||
|
repo_path: &str,
|
||||||
|
) -> Result<RebaseState> {
|
||||||
|
scope_time!("continue_pending_rebase");
|
||||||
|
|
||||||
|
let repo = utils::repo(repo_path)?;
|
||||||
|
|
||||||
|
continue_rebase(&repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn abort_pending_rebase(repo_path: &str) -> Result<()> {
|
||||||
|
scope_time!("abort_pending_rebase");
|
||||||
|
|
||||||
|
let repo = utils::repo(repo_path)?;
|
||||||
|
|
||||||
|
abort_rebase(&repo)
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn merge_branch_repo(
|
pub fn merge_branch_repo(
|
||||||
repo: &Repository,
|
repo: &Repository,
|
||||||
|
@ -58,7 +58,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, merge_branch, merge_commit, merge_msg, mergehead_ids,
|
abort_merge, abort_pending_rebase, continue_pending_rebase,
|
||||||
|
merge_branch, merge_commit, merge_msg, mergehead_ids,
|
||||||
|
rebase_progress,
|
||||||
};
|
};
|
||||||
pub use rebase::rebase_branch;
|
pub use rebase::rebase_branch;
|
||||||
pub use remotes::{
|
pub use remotes::{
|
||||||
|
@ -12,7 +12,7 @@ use super::CommitId;
|
|||||||
pub fn rebase_branch(
|
pub fn rebase_branch(
|
||||||
repo_path: &str,
|
repo_path: &str,
|
||||||
branch: &str,
|
branch: &str,
|
||||||
) -> Result<CommitId> {
|
) -> Result<RebaseState> {
|
||||||
scope_time!("rebase_branch");
|
scope_time!("rebase_branch");
|
||||||
|
|
||||||
let repo = utils::repo(repo_path)?;
|
let repo = utils::repo(repo_path)?;
|
||||||
@ -23,13 +23,13 @@ pub fn rebase_branch(
|
|||||||
fn rebase_branch_repo(
|
fn rebase_branch_repo(
|
||||||
repo: &Repository,
|
repo: &Repository,
|
||||||
branch_name: &str,
|
branch_name: &str,
|
||||||
) -> Result<CommitId> {
|
) -> Result<RebaseState> {
|
||||||
let branch = repo.find_branch(branch_name, BranchType::Local)?;
|
let branch = repo.find_branch(branch_name, BranchType::Local)?;
|
||||||
|
|
||||||
let annotated =
|
let annotated =
|
||||||
repo.reference_to_annotated_commit(&branch.into_reference())?;
|
repo.reference_to_annotated_commit(&branch.into_reference())?;
|
||||||
|
|
||||||
conflict_free_rebase(repo, &annotated)
|
rebase(repo, &annotated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// rebase attempt which aborts and undo's rebase if any conflict appears
|
/// rebase attempt which aborts and undo's rebase if any conflict appears
|
||||||
@ -66,16 +66,133 @@ pub fn conflict_free_rebase(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum RebaseState {
|
||||||
|
///
|
||||||
|
Finished,
|
||||||
|
///
|
||||||
|
Conflicted,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// rebase
|
||||||
|
pub fn rebase(
|
||||||
|
repo: &git2::Repository,
|
||||||
|
commit: &git2::AnnotatedCommit,
|
||||||
|
) -> Result<RebaseState> {
|
||||||
|
let mut rebase = repo.rebase(None, Some(commit), None, None)?;
|
||||||
|
let signature =
|
||||||
|
crate::sync::commit::signature_allow_undefined_name(repo)?;
|
||||||
|
|
||||||
|
while let Some(op) = rebase.next() {
|
||||||
|
let _op = op?;
|
||||||
|
// dbg!(op.id());
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
return Ok(RebaseState::Conflicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebase.commit(None, &signature, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
return Ok(RebaseState::Conflicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebase.finish(Some(&signature))?;
|
||||||
|
|
||||||
|
Ok(RebaseState::Finished)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// continue pending rebase
|
||||||
|
pub fn continue_rebase(
|
||||||
|
repo: &git2::Repository,
|
||||||
|
) -> Result<RebaseState> {
|
||||||
|
let mut rebase = repo.open_rebase(None)?;
|
||||||
|
let signature =
|
||||||
|
crate::sync::commit::signature_allow_undefined_name(repo)?;
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
return Ok(RebaseState::Conflicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try commit current rebase step
|
||||||
|
if !repo.index()?.is_empty() {
|
||||||
|
rebase.commit(None, &signature, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(op) = rebase.next() {
|
||||||
|
let _op = op?;
|
||||||
|
// dbg!(op.id());
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
return Ok(RebaseState::Conflicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebase.commit(None, &signature, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
return Ok(RebaseState::Conflicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebase.finish(Some(&signature))?;
|
||||||
|
|
||||||
|
Ok(RebaseState::Finished)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct RebaseProgress {
|
||||||
|
///
|
||||||
|
pub steps: usize,
|
||||||
|
///
|
||||||
|
pub current: usize,
|
||||||
|
///
|
||||||
|
pub current_commit: Option<CommitId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn get_rebase_progress(
|
||||||
|
repo: &git2::Repository,
|
||||||
|
) -> Result<RebaseProgress> {
|
||||||
|
let mut rebase = repo.open_rebase(None)?;
|
||||||
|
|
||||||
|
let current_commit: Option<CommitId> = rebase
|
||||||
|
.operation_current()
|
||||||
|
.and_then(|idx| rebase.nth(idx))
|
||||||
|
.map(|op| op.id().into());
|
||||||
|
|
||||||
|
let progress = RebaseProgress {
|
||||||
|
steps: rebase.len(),
|
||||||
|
current: rebase.operation_current().unwrap_or_default(),
|
||||||
|
current_commit,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn abort_rebase(repo: &git2::Repository) -> Result<()> {
|
||||||
|
let mut rebase = repo.open_rebase(None)?;
|
||||||
|
|
||||||
|
rebase.abort()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod test_conflict_free_rebase {
|
||||||
use crate::sync::{
|
use crate::sync::{
|
||||||
checkout_branch, create_branch,
|
checkout_branch, create_branch,
|
||||||
rebase::rebase_branch,
|
rebase::{rebase_branch, RebaseState},
|
||||||
repo_state,
|
repo_state,
|
||||||
tests::{repo_init, write_commit_file},
|
tests::{repo_init, write_commit_file},
|
||||||
CommitId, RepoState,
|
utils, CommitId, RepoState,
|
||||||
};
|
};
|
||||||
use git2::Repository;
|
use git2::{BranchType, Repository};
|
||||||
|
|
||||||
|
use super::conflict_free_rebase;
|
||||||
|
|
||||||
fn parent_ids(repo: &Repository, c: CommitId) -> Vec<CommitId> {
|
fn parent_ids(repo: &Repository, c: CommitId) -> Vec<CommitId> {
|
||||||
let foo = repo
|
let foo = repo
|
||||||
@ -88,6 +205,23 @@ mod tests {
|
|||||||
foo
|
foo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
fn test_rebase_branch_repo(
|
||||||
|
repo_path: &str,
|
||||||
|
branch_name: &str,
|
||||||
|
) -> CommitId {
|
||||||
|
let repo = utils::repo(repo_path).unwrap();
|
||||||
|
|
||||||
|
let branch =
|
||||||
|
repo.find_branch(branch_name, BranchType::Local).unwrap();
|
||||||
|
|
||||||
|
let annotated = repo
|
||||||
|
.reference_to_annotated_commit(&branch.into_reference())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
conflict_free_rebase(&repo, &annotated).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_smoke() {
|
fn test_smoke() {
|
||||||
let (_td, repo) = repo_init().unwrap();
|
let (_td, repo) = repo_init().unwrap();
|
||||||
@ -111,7 +245,7 @@ mod tests {
|
|||||||
|
|
||||||
checkout_branch(repo_path, "refs/heads/foo").unwrap();
|
checkout_branch(repo_path, "refs/heads/foo").unwrap();
|
||||||
|
|
||||||
let r = rebase_branch(repo_path, "master").unwrap();
|
let r = test_rebase_branch_repo(repo_path, "master");
|
||||||
|
|
||||||
assert_eq!(parent_ids(&repo, r), vec![c3]);
|
assert_eq!(parent_ids(&repo, r), vec![c3]);
|
||||||
}
|
}
|
||||||
@ -136,7 +270,64 @@ mod tests {
|
|||||||
|
|
||||||
let res = rebase_branch(repo_path, "master");
|
let res = rebase_branch(repo_path, "master");
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(matches!(res.unwrap(), RebaseState::Conflicted));
|
||||||
|
|
||||||
|
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_rebase {
|
||||||
|
use crate::sync::{
|
||||||
|
checkout_branch, create_branch,
|
||||||
|
rebase::{
|
||||||
|
abort_rebase, get_rebase_progress, RebaseProgress,
|
||||||
|
RebaseState,
|
||||||
|
},
|
||||||
|
rebase_branch, repo_state,
|
||||||
|
tests::{repo_init, write_commit_file},
|
||||||
|
RepoState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conflicted_abort() {
|
||||||
|
let (_td, repo) = repo_init().unwrap();
|
||||||
|
let root = repo.path().parent().unwrap();
|
||||||
|
let repo_path = root.as_os_str().to_str().unwrap();
|
||||||
|
|
||||||
|
write_commit_file(&repo, "test.txt", "test1", "commit1");
|
||||||
|
|
||||||
|
create_branch(repo_path, "foo").unwrap();
|
||||||
|
|
||||||
|
let c =
|
||||||
|
write_commit_file(&repo, "test.txt", "test2", "commit2");
|
||||||
|
|
||||||
|
checkout_branch(repo_path, "refs/heads/master").unwrap();
|
||||||
|
|
||||||
|
write_commit_file(&repo, "test.txt", "test3", "commit3");
|
||||||
|
|
||||||
|
checkout_branch(repo_path, "refs/heads/foo").unwrap();
|
||||||
|
|
||||||
|
assert!(get_rebase_progress(&repo).is_err());
|
||||||
|
|
||||||
|
// rebase
|
||||||
|
|
||||||
|
let r = rebase_branch(repo_path, "master").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(r, RebaseState::Conflicted);
|
||||||
|
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
|
||||||
|
assert_eq!(
|
||||||
|
get_rebase_progress(&repo).unwrap(),
|
||||||
|
RebaseProgress {
|
||||||
|
current: 0,
|
||||||
|
steps: 1,
|
||||||
|
current_commit: Some(c)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// abort
|
||||||
|
|
||||||
|
abort_rebase(&repo).unwrap();
|
||||||
|
|
||||||
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Clean);
|
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Clean);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ pub enum RepoState {
|
|||||||
///
|
///
|
||||||
Merge,
|
Merge,
|
||||||
///
|
///
|
||||||
|
Rebase,
|
||||||
|
///
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +20,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::RebaseMerge => Self::Rebase,
|
||||||
_ => Self::Other,
|
_ => Self::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,5 +32,9 @@ pub fn repo_state(repo_path: &str) -> Result<RepoState> {
|
|||||||
|
|
||||||
let repo = utils::repo(repo_path)?;
|
let repo = utils::repo(repo_path)?;
|
||||||
|
|
||||||
Ok(repo.state().into())
|
let state = repo.state();
|
||||||
|
|
||||||
|
// dbg!(&state);
|
||||||
|
|
||||||
|
Ok(state.into())
|
||||||
}
|
}
|
||||||
|
@ -821,6 +821,10 @@ impl App {
|
|||||||
self.status_tab.abort_merge();
|
self.status_tab.abort_merge();
|
||||||
flags.insert(NeedsUpdate::ALL);
|
flags.insert(NeedsUpdate::ALL);
|
||||||
}
|
}
|
||||||
|
Action::AbortRebase => {
|
||||||
|
self.status_tab.abort_rebase();
|
||||||
|
flags.insert(NeedsUpdate::ALL);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -192,43 +192,35 @@ impl Component for ChangesComponent {
|
|||||||
if self.is_working_dir {
|
if self.is_working_dir {
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::stage_all(&self.key_config),
|
strings::commands::stage_all(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::stage_item(&self.key_config),
|
strings::commands::stage_item(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::reset_item(&self.key_config),
|
strings::commands::reset_item(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::ignore_item(&self.key_config),
|
strings::commands::ignore_item(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::unstage_item(&self.key_config),
|
strings::commands::unstage_item(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::unstage_all(&self.key_config),
|
strings::commands::unstage_all(&self.key_config),
|
||||||
some_selection,
|
true,
|
||||||
self.focused(),
|
some_selection && self.focused(),
|
||||||
));
|
));
|
||||||
out.push(
|
|
||||||
CommandInfo::new(
|
|
||||||
strings::commands::commit_open(&self.key_config),
|
|
||||||
!self.is_empty(),
|
|
||||||
self.focused() || force_all,
|
|
||||||
)
|
|
||||||
.order(-1),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandBlocking::PassingOn
|
CommandBlocking::PassingOn
|
||||||
@ -241,13 +233,7 @@ impl Component for ChangesComponent {
|
|||||||
|
|
||||||
if self.focused() {
|
if self.focused() {
|
||||||
if let Event::Key(e) = ev {
|
if let Event::Key(e) = ev {
|
||||||
return if e == self.key_config.open_commit
|
return if e == self.key_config.enter {
|
||||||
&& !self.is_working_dir
|
|
||||||
&& !self.is_empty()
|
|
||||||
{
|
|
||||||
self.queue.push(InternalEvent::OpenCommit);
|
|
||||||
Ok(EventState::Consumed)
|
|
||||||
} else if e == self.key_config.enter {
|
|
||||||
try_or_popup!(
|
try_or_popup!(
|
||||||
self,
|
self,
|
||||||
"staging error:",
|
"staging error:",
|
||||||
|
@ -200,6 +200,10 @@ impl ConfirmComponent {
|
|||||||
Action::AbortMerge => (
|
Action::AbortMerge => (
|
||||||
strings::confirm_title_abortmerge(),
|
strings::confirm_title_abortmerge(),
|
||||||
strings::confirm_msg_abortmerge(),
|
strings::confirm_msg_abortmerge(),
|
||||||
|
),
|
||||||
|
Action::AbortRebase => (
|
||||||
|
strings::confirm_title_abortrebase(),
|
||||||
|
strings::confirm_msg_abortrebase(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ impl Default for KeyConfig {
|
|||||||
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
|
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
|
||||||
undo_commit: KeyEvent { code: KeyCode::Char('U'), modifiers: KeyModifiers::SHIFT},
|
undo_commit: KeyEvent { code: KeyCode::Char('U'), modifiers: KeyModifiers::SHIFT},
|
||||||
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
||||||
abort_merge: KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT},
|
abort_merge: KeyEvent { code: KeyCode::Char('A'), modifiers: KeyModifiers::SHIFT},
|
||||||
open_file_tree: KeyEvent { code: KeyCode::Char('F'), modifiers: KeyModifiers::SHIFT},
|
open_file_tree: KeyEvent { code: KeyCode::Char('F'), modifiers: KeyModifiers::SHIFT},
|
||||||
file_find: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
file_find: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ pub enum Action {
|
|||||||
ForcePush(String, bool),
|
ForcePush(String, bool),
|
||||||
PullMerge { incoming: usize, rebase: bool },
|
PullMerge { incoming: usize, rebase: bool },
|
||||||
AbortMerge,
|
AbortMerge,
|
||||||
|
AbortRebase,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -153,6 +153,13 @@ pub fn confirm_msg_abortmerge() -> String {
|
|||||||
"This will revert all uncommitted changes. Are you sure?"
|
"This will revert all uncommitted changes. Are you sure?"
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
pub fn confirm_title_abortrebase() -> String {
|
||||||
|
"Abort rebase?".to_string()
|
||||||
|
}
|
||||||
|
pub fn confirm_msg_abortrebase() -> String {
|
||||||
|
"This will revert all uncommitted changes. Are you sure?"
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
pub fn confirm_msg_reset() -> String {
|
pub fn confirm_msg_reset() -> String {
|
||||||
"confirm file reset?".to_string()
|
"confirm file reset?".to_string()
|
||||||
}
|
}
|
||||||
@ -628,6 +635,31 @@ pub mod commands {
|
|||||||
CMD_GROUP_GENERAL,
|
CMD_GROUP_GENERAL,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn continue_rebase(
|
||||||
|
key_config: &SharedKeyConfig,
|
||||||
|
) -> CommandText {
|
||||||
|
CommandText::new(
|
||||||
|
format!(
|
||||||
|
"Continue rebase [{}]",
|
||||||
|
key_config.get_hint(key_config.rebase_branch),
|
||||||
|
),
|
||||||
|
"continue ongoing rebase",
|
||||||
|
CMD_GROUP_GENERAL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abort_rebase(key_config: &SharedKeyConfig) -> CommandText {
|
||||||
|
CommandText::new(
|
||||||
|
format!(
|
||||||
|
"Abort rebase [{}]",
|
||||||
|
key_config.get_hint(key_config.abort_merge),
|
||||||
|
),
|
||||||
|
"abort ongoing rebase",
|
||||||
|
CMD_GROUP_GENERAL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select_staging(
|
pub fn select_staging(
|
||||||
key_config: &SharedKeyConfig,
|
key_config: &SharedKeyConfig,
|
||||||
) -> CommandText {
|
) -> CommandText {
|
||||||
|
@ -14,8 +14,8 @@ use crate::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::{
|
use asyncgit::{
|
||||||
cached,
|
cached,
|
||||||
sync::BranchCompare,
|
|
||||||
sync::{self, status::StatusType, RepoState},
|
sync::{self, status::StatusType, RepoState},
|
||||||
|
sync::{BranchCompare, CommitId},
|
||||||
AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams,
|
AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams,
|
||||||
DiffType, StatusParams, CWD,
|
DiffType, StatusParams, CWD,
|
||||||
};
|
};
|
||||||
@ -215,14 +215,12 @@ impl Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_repo_state<B: tui::backend::Backend>(
|
fn repo_state_text(state: &RepoState) -> String {
|
||||||
f: &mut tui::Frame<B>,
|
match state {
|
||||||
r: tui::layout::Rect,
|
RepoState::Merge => {
|
||||||
) -> Result<()> {
|
|
||||||
if let Ok(state) = sync::repo_state(CWD) {
|
|
||||||
if state != RepoState::Clean {
|
|
||||||
let ids =
|
let ids =
|
||||||
sync::mergehead_ids(CWD).unwrap_or_default();
|
sync::mergehead_ids(CWD).unwrap_or_default();
|
||||||
|
|
||||||
let ids = format!(
|
let ids = format!(
|
||||||
"({})",
|
"({})",
|
||||||
ids.iter()
|
ids.iter()
|
||||||
@ -231,7 +229,39 @@ impl Status {
|
|||||||
))
|
))
|
||||||
.join(",")
|
.join(",")
|
||||||
);
|
);
|
||||||
let txt = format!("{:?} {}", state, ids);
|
|
||||||
|
format!("{:?} {}", state, ids)
|
||||||
|
}
|
||||||
|
RepoState::Rebase => {
|
||||||
|
let progress =
|
||||||
|
if let Ok(p) = sync::rebase_progress(CWD) {
|
||||||
|
format!(
|
||||||
|
"[{}] {}/{}",
|
||||||
|
p.current_commit
|
||||||
|
.as_ref()
|
||||||
|
.map(CommitId::get_short_string)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
p.current + 1,
|
||||||
|
p.steps
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{:?} ({})", state, progress)
|
||||||
|
}
|
||||||
|
_ => format!("{:?}", state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_repo_state<B: tui::backend::Backend>(
|
||||||
|
f: &mut tui::Frame<B>,
|
||||||
|
r: tui::layout::Rect,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Ok(state) = sync::repo_state(CWD) {
|
||||||
|
if state != RepoState::Clean {
|
||||||
|
let txt = Self::repo_state_text(&state);
|
||||||
|
|
||||||
let txt_len = u16::try_from(txt.len())?;
|
let txt_len = u16::try_from(txt.len())?;
|
||||||
let w = Paragraph::new(txt)
|
let w = Paragraph::new(txt)
|
||||||
.style(Style::default().fg(Color::Red))
|
.style(Style::default().fg(Color::Red))
|
||||||
@ -519,10 +549,31 @@ impl Status {
|
|||||||
== RepoState::Merge
|
== RepoState::Merge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pending_rebase() -> bool {
|
||||||
|
sync::repo_state(CWD).unwrap_or(RepoState::Clean)
|
||||||
|
== RepoState::Rebase
|
||||||
|
}
|
||||||
|
|
||||||
pub fn abort_merge(&self) {
|
pub fn abort_merge(&self) {
|
||||||
try_or_popup!(self, "abort merge", sync::abort_merge(CWD));
|
try_or_popup!(self, "abort merge", sync::abort_merge(CWD));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn abort_rebase(&self) {
|
||||||
|
try_or_popup!(
|
||||||
|
self,
|
||||||
|
"abort rebase",
|
||||||
|
sync::abort_pending_rebase(CWD)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn continue_rebase(&self) {
|
||||||
|
try_or_popup!(
|
||||||
|
self,
|
||||||
|
"continue rebase",
|
||||||
|
sync::continue_pending_rebase(CWD)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn commands_nav(
|
fn commands_nav(
|
||||||
&self,
|
&self,
|
||||||
out: &mut Vec<CommandInfo>,
|
out: &mut Vec<CommandInfo>,
|
||||||
@ -566,6 +617,12 @@ impl Status {
|
|||||||
.order(strings::order::NAV),
|
.order(strings::order::NAV),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_commit(&self) -> bool {
|
||||||
|
self.index.focused()
|
||||||
|
&& !self.index.is_empty()
|
||||||
|
&& !Self::pending_rebase()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Status {
|
impl Component for Status {
|
||||||
@ -583,6 +640,15 @@ impl Component for Status {
|
|||||||
self.components().as_slice(),
|
self.components().as_slice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
out.push(
|
||||||
|
CommandInfo::new(
|
||||||
|
strings::commands::commit_open(&self.key_config),
|
||||||
|
true,
|
||||||
|
self.can_commit() || force_all,
|
||||||
|
)
|
||||||
|
.order(-1),
|
||||||
|
);
|
||||||
|
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::open_branch_select_popup(
|
strings::commands::open_branch_select_popup(
|
||||||
&self.key_config,
|
&self.key_config,
|
||||||
@ -612,7 +678,8 @@ impl Component for Status {
|
|||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
strings::commands::undo_commit(&self.key_config),
|
strings::commands::undo_commit(&self.key_config),
|
||||||
true,
|
true,
|
||||||
!focus_on_diff,
|
(!Self::pending_rebase() && !focus_on_diff)
|
||||||
|
|| force_all,
|
||||||
));
|
));
|
||||||
|
|
||||||
out.push(CommandInfo::new(
|
out.push(CommandInfo::new(
|
||||||
@ -620,6 +687,17 @@ impl Component for Status {
|
|||||||
true,
|
true,
|
||||||
Self::can_abort_merge() || force_all,
|
Self::can_abort_merge() || force_all,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::continue_rebase(&self.key_config),
|
||||||
|
true,
|
||||||
|
Self::pending_rebase() || force_all,
|
||||||
|
));
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::abort_rebase(&self.key_config),
|
||||||
|
true,
|
||||||
|
Self::pending_rebase() || force_all,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -639,6 +717,7 @@ impl Component for Status {
|
|||||||
visibility_blocking(self)
|
visibility_blocking(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||||
fn event(
|
fn event(
|
||||||
&mut self,
|
&mut self,
|
||||||
ev: crossterm::event::Event,
|
ev: crossterm::event::Event,
|
||||||
@ -664,6 +743,11 @@ impl Component for Status {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(EventState::Consumed)
|
Ok(EventState::Consumed)
|
||||||
|
} else if k == self.key_config.open_commit
|
||||||
|
&& self.can_commit()
|
||||||
|
{
|
||||||
|
self.queue.push(InternalEvent::OpenCommit);
|
||||||
|
Ok(EventState::Consumed)
|
||||||
} else if k == self.key_config.toggle_workarea
|
} else if k == self.key_config.toggle_workarea
|
||||||
&& !self.is_focus_on_diff()
|
&& !self.is_focus_on_diff()
|
||||||
{
|
{
|
||||||
@ -725,6 +809,22 @@ impl Component for Status {
|
|||||||
Action::AbortMerge,
|
Action::AbortMerge,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
Ok(EventState::Consumed)
|
||||||
|
} else if k == self.key_config.abort_merge
|
||||||
|
&& Self::pending_rebase()
|
||||||
|
{
|
||||||
|
self.queue.push(InternalEvent::ConfirmAction(
|
||||||
|
Action::AbortRebase,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(EventState::Consumed)
|
||||||
|
} else if k == self.key_config.rebase_branch
|
||||||
|
&& Self::pending_rebase()
|
||||||
|
{
|
||||||
|
self.continue_rebase();
|
||||||
|
self.queue.push(InternalEvent::Update(
|
||||||
|
NeedsUpdate::ALL,
|
||||||
|
));
|
||||||
Ok(EventState::Consumed)
|
Ok(EventState::Consumed)
|
||||||
} else {
|
} else {
|
||||||
Ok(EventState::NotConsumed)
|
Ok(EventState::NotConsumed)
|
||||||
|
Loading…
Reference in New Issue
Block a user