merging a branch (#696)

* merging arbitrary branch
* cleanup intermediate in-merge state
This commit is contained in:
Stephan Dilly 2021-05-09 22:53:12 +02:00 committed by GitHub
parent 9f37835dc4
commit 4f6aceb3ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 210 additions and 62 deletions

View file

@ -0,0 +1,49 @@
use crate::{
error::{Error, Result},
sync::{reset_stage, reset_workdir, utils},
};
use git2::{BranchType, MergeOptions};
use scopetime::scope_time;
/// does these steps:
/// * reset all staged changes,
/// * revert all changes in workdir
/// * cleanup repo merge state
pub fn abort_merge(repo_path: &str) -> Result<()> {
scope_time!("cleanup_state");
let repo = utils::repo(repo_path)?;
reset_stage(repo_path, "*")?;
reset_workdir(repo_path, "*")?;
repo.cleanup_state()?;
Ok(())
}
///
pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> {
scope_time!("merge_branch");
let repo = utils::repo(repo_path)?;
let branch = repo.find_branch(branch, BranchType::Local)?;
let id = branch.into_reference().peel_to_commit()?;
let annotated = repo.find_annotated_commit(id.id())?;
let (analysis, _) = repo.merge_analysis(&[&annotated])?;
//TODO: support merge on unborn
if analysis.is_unborn() {
return Err(Error::Generic("head is unborn".into()));
}
let mut opt = MergeOptions::default();
repo.merge(&[&annotated], Some(&mut opt), None)?;
Ok(())
}

View file

@ -15,6 +15,7 @@ mod hooks;
mod hunks;
mod ignore;
mod logwalker;
mod merge;
mod patches;
pub mod remotes;
mod reset;
@ -49,6 +50,7 @@ pub use hooks::{
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
pub use ignore::add_to_ignore;
pub use logwalker::LogWalker;
pub use merge::{abort_merge, merge_branch};
pub use remotes::{
get_default_remote, get_remotes, push::AsyncProgress,
tags::PushTagsProgress,

View file

@ -607,6 +607,10 @@ impl App {
self.pull_popup.try_conflict_free_merge(rebase);
flags.insert(NeedsUpdate::ALL);
}
Action::AbortMerge => {
self.status_tab.abort_merge();
flags.insert(NeedsUpdate::ALL);
}
};
Ok(())

View file

@ -12,7 +12,7 @@ use crate::{
use anyhow::Result;
use asyncgit::{
sync::{
branch::checkout_remote_branch, checkout_branch,
self, branch::checkout_remote_branch, checkout_branch,
get_branches_info, BranchInfo,
},
CWD,
@ -150,6 +150,14 @@ impl Component for BranchListComponent {
self.local,
));
out.push(CommandInfo::new(
strings::commands::merge_branch_popup(
&self.key_config,
),
!self.selection_is_cur_branch(),
self.local,
));
out.push(CommandInfo::new(
strings::commands::rename_branch_popup(
&self.key_config,
@ -222,6 +230,16 @@ impl Component for BranchListComponent {
),
),
);
} else if e == self.key_config.merge_branch
&& !self.selection_is_cur_branch()
&& self.valid_selection()
{
try_or_popup!(
self,
"merge branch error:",
self.merge_branch()
);
self.hide();
} else if e == self.key_config.tab_toggle {
self.local = !self.local;
self.update_branches()?;
@ -294,6 +312,16 @@ impl BranchListComponent {
!self.branches.is_empty()
}
fn merge_branch(&self) -> Result<()> {
if let Some(branch) =
self.branches.get(usize::from(self.selection))
{
sync::merge_branch(CWD, &branch.name)?;
}
Ok(())
}
fn selection_is_cur_branch(&self) -> bool {
self.branches
.iter()

View file

@ -138,8 +138,8 @@ impl ResetComponent {
if let Some(ref a) = self.target {
return match a {
Action::Reset(_) => (
strings::confirm_title_reset(&self.key_config),
strings::confirm_msg_reset(&self.key_config),
strings::confirm_title_reset(),
strings::confirm_msg_reset(),
),
Action::StashDrop(_) => (
strings::confirm_title_stashdrop(
@ -152,12 +152,12 @@ impl ResetComponent {
strings::confirm_msg_stashpop(&self.key_config),
),
Action::ResetHunk(_, _) => (
strings::confirm_title_reset(&self.key_config),
strings::confirm_title_reset(),
strings::confirm_msg_resethunk(&self.key_config),
),
Action::ResetLines(_, lines) => (
strings::confirm_title_reset(&self.key_config),
strings::confirm_msg_reset_lines(&self.key_config,lines.len()),
strings::confirm_title_reset(),
strings::confirm_msg_reset_lines(lines.len()),
),
Action::DeleteBranch(branch_ref) => (
strings::confirm_title_delete_branch(
@ -181,6 +181,10 @@ impl ResetComponent {
strings::confirm_title_merge(&self.key_config,*rebase),
strings::confirm_msg_merge(&self.key_config,*incoming,*rebase),
),
Action::AbortMerge => (
strings::confirm_title_abortmerge(),
strings::confirm_msg_abortmerge(),
),
};
}

View file

@ -68,9 +68,11 @@ pub struct KeyConfig {
pub rename_branch: KeyEvent,
pub select_branch: KeyEvent,
pub delete_branch: KeyEvent,
pub merge_branch: KeyEvent,
pub push: KeyEvent,
pub force_push: KeyEvent,
pub pull: KeyEvent,
pub abort_merge: KeyEvent,
}
#[rustfmt::skip]
@ -121,13 +123,15 @@ impl Default for KeyConfig {
log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()},
commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE},
rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::NONE},
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::NONE},
create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()},
rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty()},
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
delete_branch: KeyEvent{code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
merge_branch: KeyEvent{code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
abort_merge: KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT},
}
}
}

View file

@ -33,6 +33,7 @@ pub enum Action {
DeleteBranch(String),
ForcePush(String, bool),
PullMerge { incoming: usize, rebase: bool },
AbortMerge,
}
///

View file

@ -86,7 +86,7 @@ pub fn stash_popup_title(_key_config: &SharedKeyConfig) -> String {
pub fn stash_popup_msg(_key_config: &SharedKeyConfig) -> String {
"type name (optional)".to_string()
}
pub fn confirm_title_reset(_key_config: &SharedKeyConfig) -> String {
pub fn confirm_title_reset() -> String {
"Reset".to_string()
}
pub fn confirm_title_stashdrop(
@ -120,13 +120,17 @@ pub fn confirm_msg_merge(
format!("Merge of {} incoming commits?", incoming)
}
}
pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String {
pub fn confirm_title_abortmerge() -> String {
"Abort merge?".to_string()
}
pub fn confirm_msg_abortmerge() -> String {
"This will revert all changes. Are you sure?".to_string()
}
pub fn confirm_msg_reset() -> String {
"confirm file reset?".to_string()
}
pub fn confirm_msg_reset_lines(
_key_config: &SharedKeyConfig,
lines: usize,
) -> String {
pub fn confirm_msg_reset_lines(lines: usize) -> String {
format!(
"are you sure you want to discard {} selected lines?",
lines
@ -520,6 +524,16 @@ pub mod commands {
CMD_GROUP_GENERAL,
)
}
pub fn abort_merge(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Abort merge [{}]",
key_config.get_hint(key_config.abort_merge),
),
"abort ongoing merge",
CMD_GROUP_GENERAL,
)
}
pub fn select_staging(
key_config: &SharedKeyConfig,
) -> CommandText {
@ -918,6 +932,18 @@ pub mod commands {
CMD_GROUP_GENERAL,
)
}
pub fn merge_branch_popup(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Merge [{}]",
key_config.get_hint(key_config.merge_branch),
),
"merge a branch",
CMD_GROUP_GENERAL,
)
}
pub fn select_branch_popup(
key_config: &SharedKeyConfig,
) -> CommandText {

View file

@ -8,7 +8,7 @@ use crate::{
},
keys::SharedKeyConfig,
queue::{Action, InternalEvent, Queue, ResetItem},
strings,
strings, try_or_popup,
ui::style::SharedTheme,
};
use anyhow::Result;
@ -465,6 +465,61 @@ impl Status {
.as_ref()
.map_or(true, |state| state.ahead > 0)
}
fn can_abort_merge() -> bool {
sync::repo_state(CWD).unwrap_or(RepoState::Clean)
== RepoState::Merge
}
pub fn abort_merge(&self) {
try_or_popup!(self, "abort merge", sync::abort_merge(CWD))
}
fn commands_nav(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) {
let focus_on_diff = self.is_focus_on_diff();
out.push(
CommandInfo::new(
strings::commands::diff_focus_left(&self.key_config),
true,
(self.visible && focus_on_diff) || force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::diff_focus_right(&self.key_config),
self.can_focus_diff(),
(self.visible && !focus_on_diff) || force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::select_staging(&self.key_config),
!focus_on_diff,
(self.visible
&& !focus_on_diff
&& self.focus == Focus::WorkDir)
|| force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::select_unstaged(&self.key_config),
!focus_on_diff,
(self.visible
&& !focus_on_diff
&& self.focus == Focus::Stage)
|| force_all,
)
.order(strings::order::NAV),
);
}
}
impl Component for Status {
@ -507,6 +562,12 @@ impl Component for Status {
true,
!focus_on_diff,
));
out.push(CommandInfo::new(
strings::commands::abort_merge(&self.key_config),
true,
Self::can_abort_merge() || force_all,
));
}
{
@ -519,52 +580,6 @@ impl Component for Status {
},
self.visible || force_all,
));
out.push(
CommandInfo::new(
strings::commands::diff_focus_left(
&self.key_config,
),
true,
(self.visible && focus_on_diff) || force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::diff_focus_right(
&self.key_config,
),
self.can_focus_diff(),
(self.visible && !focus_on_diff) || force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::select_staging(
&self.key_config,
),
!focus_on_diff,
(self.visible
&& !focus_on_diff
&& self.focus == Focus::WorkDir)
|| force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
strings::commands::select_unstaged(
&self.key_config,
),
!focus_on_diff,
(self.visible
&& !focus_on_diff
&& self.focus == Focus::Stage)
|| force_all,
)
.order(strings::order::NAV),
);
out.push(
CommandInfo::new(
@ -576,6 +591,8 @@ impl Component for Status {
)
.hidden(),
);
self.commands_nav(out, force_all);
}
visibility_blocking(self)
@ -653,6 +670,16 @@ impl Component for Status {
&& !self.is_focus_on_diff()
{
self.pull();
Ok(EventState::Consumed)
} else if k == self.key_config.abort_merge
&& Self::can_abort_merge()
{
self.queue.borrow_mut().push_back(
InternalEvent::ConfirmAction(
Action::AbortMerge,
),
);
Ok(EventState::Consumed)
} else {
Ok(EventState::NotConsumed)

View file

@ -72,6 +72,9 @@
rename_branch: ( code: Char('r'), modifiers: ( bits: 0,),),
select_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
delete_branch: ( code: Char('D'), modifiers: ( bits: 1,),),
merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),),
abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),),
push: ( code: Char('p'), modifiers: ( bits: 0,),),
force_push: ( code: Char('P'), modifiers: ( bits: 1,),),
pull: ( code: Char('f'), modifiers: ( bits: 0,),),