mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 08:58:21 +00:00
merging a branch (#696)
* merging arbitrary branch * cleanup intermediate in-merge state
This commit is contained in:
parent
9f37835dc4
commit
4f6aceb3ec
10 changed files with 210 additions and 62 deletions
49
asyncgit/src/sync/merge.rs
Normal file
49
asyncgit/src/sync/merge.rs
Normal 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(())
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
10
src/keys.rs
10
src/keys.rs
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ pub enum Action {
|
|||
DeleteBranch(String),
|
||||
ForcePush(String, bool),
|
||||
PullMerge { incoming: usize, rebase: bool },
|
||||
AbortMerge,
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,),),
|
||||
|
|
|
|||
Loading…
Reference in a new issue