mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-22 19:29:14 +03:00
support rebase merge (conflict free only) (#567)
This commit is contained in:
parent
b5ef9b10f1
commit
bfa240115c
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `[s]` key repurposed to trigger line based (un)stage
|
- `[s]` key repurposed to trigger line based (un)stage
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- support pull via rebase (using config `pull.rebase`) ([#566](https://github.com/extrawurst/gitui/issues/566))
|
||||||
- support stage/unstage selected lines ([#59](https://github.com/extrawurst/gitui/issues/59))
|
- support stage/unstage selected lines ([#59](https://github.com/extrawurst/gitui/issues/59))
|
||||||
- support discarding selected lines ([#59](https://github.com/extrawurst/gitui/issues/59))
|
- support discarding selected lines ([#59](https://github.com/extrawurst/gitui/issues/59))
|
||||||
- support for pushing tags ([#568](https://github.com/extrawurst/gitui/issues/568))
|
- support for pushing tags ([#568](https://github.com/extrawurst/gitui/issues/568))
|
||||||
|
226
asyncgit/src/sync/branch/merge_rebase.rs
Normal file
226
asyncgit/src/sync/branch/merge_rebase.rs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
//! merging from upstream (rebase)
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{Error, Result},
|
||||||
|
sync::utils,
|
||||||
|
};
|
||||||
|
use git2::BranchType;
|
||||||
|
use scopetime::scope_time;
|
||||||
|
|
||||||
|
/// trys merging current branch with its upstrema using rebase
|
||||||
|
pub fn merge_upstream_rebase(
|
||||||
|
repo_path: &str,
|
||||||
|
branch_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
scope_time!("merge_upstream_rebase");
|
||||||
|
|
||||||
|
let repo = utils::repo(repo_path)?;
|
||||||
|
let branch = repo.find_branch(branch_name, BranchType::Local)?;
|
||||||
|
let upstream = branch.upstream()?;
|
||||||
|
let upstream_commit = upstream.get().peel_to_commit()?;
|
||||||
|
let annotated_upstream =
|
||||||
|
repo.find_annotated_commit(upstream_commit.id())?;
|
||||||
|
|
||||||
|
let branch_commit = branch.get().peel_to_commit()?;
|
||||||
|
let annotated_branch =
|
||||||
|
repo.find_annotated_commit(branch_commit.id())?;
|
||||||
|
|
||||||
|
let rebase = repo.rebase(
|
||||||
|
Some(&annotated_branch),
|
||||||
|
Some(&annotated_upstream),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let signature =
|
||||||
|
crate::sync::commit::signature_allow_undefined_name(&repo)?;
|
||||||
|
|
||||||
|
for e in rebase {
|
||||||
|
let _op = e?;
|
||||||
|
// dbg!(op.id());
|
||||||
|
// dbg!(op.kind());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rebase = repo.open_rebase(None)?;
|
||||||
|
|
||||||
|
if repo.index()?.has_conflicts() {
|
||||||
|
rebase.abort()?;
|
||||||
|
|
||||||
|
Err(Error::Generic(String::from("conflicts while merging")))
|
||||||
|
} else {
|
||||||
|
rebase.commit(None, &signature, None)?;
|
||||||
|
|
||||||
|
rebase.finish(Some(&signature))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::sync::{
|
||||||
|
branch_compare_upstream, get_commits_info,
|
||||||
|
remotes::{fetch_origin, push::push},
|
||||||
|
tests::{
|
||||||
|
debug_cmd_print, get_commit_ids, repo_clone,
|
||||||
|
repo_init_bare, write_commit_file,
|
||||||
|
},
|
||||||
|
RepoState,
|
||||||
|
};
|
||||||
|
use git2::Repository;
|
||||||
|
|
||||||
|
fn get_commit_msgs(r: &Repository) -> Vec<String> {
|
||||||
|
let commits = get_commit_ids(r, 10);
|
||||||
|
get_commits_info(
|
||||||
|
r.workdir().unwrap().to_str().unwrap(),
|
||||||
|
&commits,
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.message)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_normal() {
|
||||||
|
let (r1_dir, _repo) = repo_init_bare().unwrap();
|
||||||
|
|
||||||
|
let (clone1_dir, clone1) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let clone1_dir = clone1_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
// clone1
|
||||||
|
|
||||||
|
let _commit1 =
|
||||||
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
|
||||||
|
push(clone1_dir, "origin", "master", false, None, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// clone2
|
||||||
|
|
||||||
|
let (clone2_dir, clone2) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let clone2_dir = clone2_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
let _commit2 = write_commit_file(
|
||||||
|
&clone2,
|
||||||
|
"test2.txt",
|
||||||
|
"test",
|
||||||
|
"commit2",
|
||||||
|
);
|
||||||
|
|
||||||
|
push(clone2_dir, "origin", "master", false, None, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// clone1
|
||||||
|
|
||||||
|
let _commit3 = write_commit_file(
|
||||||
|
&clone1,
|
||||||
|
"test3.txt",
|
||||||
|
"test",
|
||||||
|
"commit3",
|
||||||
|
);
|
||||||
|
|
||||||
|
//lets fetch from origin
|
||||||
|
let bytes =
|
||||||
|
fetch_origin(clone1_dir, "master", None, None).unwrap();
|
||||||
|
assert!(bytes > 0);
|
||||||
|
|
||||||
|
//we should be one commit behind
|
||||||
|
assert_eq!(
|
||||||
|
branch_compare_upstream(clone1_dir, "master")
|
||||||
|
.unwrap()
|
||||||
|
.behind,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// debug_cmd_print(clone1_dir, "git log");
|
||||||
|
|
||||||
|
merge_upstream_rebase(clone1_dir, "master").unwrap();
|
||||||
|
|
||||||
|
debug_cmd_print(clone1_dir, "git log");
|
||||||
|
|
||||||
|
let state = crate::sync::repo_state(clone1_dir).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(state, RepoState::Clean);
|
||||||
|
|
||||||
|
let commits = get_commit_msgs(&clone1);
|
||||||
|
assert_eq!(
|
||||||
|
commits,
|
||||||
|
vec![
|
||||||
|
String::from("commit3"),
|
||||||
|
String::from("commit2"),
|
||||||
|
String::from("commit1")
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_conflict() {
|
||||||
|
let (r1_dir, _repo) = repo_init_bare().unwrap();
|
||||||
|
|
||||||
|
let (clone1_dir, clone1) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let clone1_dir = clone1_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
// clone1
|
||||||
|
|
||||||
|
let _commit1 =
|
||||||
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
|
||||||
|
push(clone1_dir, "origin", "master", false, None, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// clone2
|
||||||
|
|
||||||
|
let (clone2_dir, clone2) =
|
||||||
|
repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let clone2_dir = clone2_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
let _commit2 = write_commit_file(
|
||||||
|
&clone2,
|
||||||
|
"test2.txt",
|
||||||
|
"test",
|
||||||
|
"commit2",
|
||||||
|
);
|
||||||
|
|
||||||
|
push(clone2_dir, "origin", "master", false, None, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// clone1
|
||||||
|
|
||||||
|
let _commit3 =
|
||||||
|
write_commit_file(&clone1, "test2.txt", "foo", "commit3");
|
||||||
|
|
||||||
|
let bytes =
|
||||||
|
fetch_origin(clone1_dir, "master", None, None).unwrap();
|
||||||
|
assert!(bytes > 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
branch_compare_upstream(clone1_dir, "master")
|
||||||
|
.unwrap()
|
||||||
|
.behind,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = merge_upstream_rebase(clone1_dir, "master");
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
let state = crate::sync::repo_state(clone1_dir).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(state, RepoState::Clean);
|
||||||
|
|
||||||
|
let commits = get_commit_msgs(&clone1);
|
||||||
|
assert_eq!(
|
||||||
|
commits,
|
||||||
|
vec![String::from("commit3"), String::from("commit1")]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub mod merge_commit;
|
pub mod merge_commit;
|
||||||
pub mod merge_ff;
|
pub mod merge_ff;
|
||||||
|
pub mod merge_rebase;
|
||||||
pub mod rename;
|
pub mod rename;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -108,6 +109,20 @@ pub(crate) fn branch_set_upstream(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns whether the pull merge strategy is set to rebase
|
||||||
|
pub fn config_is_pull_rebase(repo_path: &str) -> Result<bool> {
|
||||||
|
let repo = utils::repo(repo_path)?;
|
||||||
|
let config = repo.config()?;
|
||||||
|
|
||||||
|
if let Ok(rebase) = config.get_entry("pull.rebase") {
|
||||||
|
let value =
|
||||||
|
rebase.value().map(String::from).unwrap_or_default();
|
||||||
|
return Ok(value == "true");
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn branch_compare_upstream(
|
pub fn branch_compare_upstream(
|
||||||
repo_path: &str,
|
repo_path: &str,
|
||||||
|
@ -25,11 +25,12 @@ mod tags;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use branch::{
|
pub use branch::{
|
||||||
branch_compare_upstream, checkout_branch, create_branch,
|
branch_compare_upstream, checkout_branch, config_is_pull_rebase,
|
||||||
delete_branch, get_branches_info,
|
create_branch, delete_branch, get_branches_info,
|
||||||
merge_commit::merge_upstream_commit,
|
merge_commit::merge_upstream_commit,
|
||||||
merge_ff::branch_merge_upstream_fastforward,
|
merge_ff::branch_merge_upstream_fastforward,
|
||||||
rename::rename_branch, BranchCompare, BranchInfo,
|
merge_rebase::merge_upstream_rebase, rename::rename_branch,
|
||||||
|
BranchCompare, BranchInfo,
|
||||||
};
|
};
|
||||||
pub use commit::{amend, commit, tag};
|
pub use commit::{amend, commit, tag};
|
||||||
pub use commit_details::{
|
pub use commit_details::{
|
||||||
|
@ -524,8 +524,8 @@ impl App {
|
|||||||
.queue
|
.queue
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.push_back(InternalEvent::Push(branch, force)),
|
.push_back(InternalEvent::Push(branch, force)),
|
||||||
Action::PullMerge(_) => {
|
Action::PullMerge { rebase, .. } => {
|
||||||
self.pull_popup.try_conflict_free_merge();
|
self.pull_popup.try_conflict_free_merge(rebase);
|
||||||
flags.insert(NeedsUpdate::ALL);
|
flags.insert(NeedsUpdate::ALL);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -151,12 +151,12 @@ impl PullComponent {
|
|||||||
let branch_compare =
|
let branch_compare =
|
||||||
sync::branch_compare_upstream(CWD, &self.branch)?;
|
sync::branch_compare_upstream(CWD, &self.branch)?;
|
||||||
if branch_compare.behind > 0 {
|
if branch_compare.behind > 0 {
|
||||||
let merge_res = sync::branch_merge_upstream_fastforward(
|
let ff_res = sync::branch_merge_upstream_fastforward(
|
||||||
CWD,
|
CWD,
|
||||||
&self.branch,
|
&self.branch,
|
||||||
);
|
);
|
||||||
if let Err(err) = merge_res {
|
if let Err(err) = ff_res {
|
||||||
log::trace!("ff merge failed: {}", err);
|
log::trace!("ff failed: {}", err);
|
||||||
self.confirm_merge(branch_compare.behind);
|
self.confirm_merge(branch_compare.behind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,17 +166,29 @@ impl PullComponent {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_conflict_free_merge(&self) {
|
pub fn try_conflict_free_merge(&self, rebase: bool) {
|
||||||
try_or_popup!(
|
if rebase {
|
||||||
self,
|
try_or_popup!(
|
||||||
"merge failed:",
|
self,
|
||||||
sync::merge_upstream_commit(CWD, &self.branch)
|
"rebase failed:",
|
||||||
);
|
sync::merge_upstream_rebase(CWD, &self.branch)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try_or_popup!(
|
||||||
|
self,
|
||||||
|
"merge failed:",
|
||||||
|
sync::merge_upstream_commit(CWD, &self.branch)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_merge(&mut self, incoming: usize) {
|
fn confirm_merge(&mut self, incoming: usize) {
|
||||||
self.queue.borrow_mut().push_back(
|
self.queue.borrow_mut().push_back(
|
||||||
InternalEvent::ConfirmAction(Action::PullMerge(incoming)),
|
InternalEvent::ConfirmAction(Action::PullMerge {
|
||||||
|
incoming,
|
||||||
|
rebase: sync::config_is_pull_rebase(CWD)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
self.hide();
|
self.hide();
|
||||||
}
|
}
|
||||||
|
@ -173,9 +173,9 @@ impl ResetComponent {
|
|||||||
branch.rsplit('/').next().expect("There was no / in the head reference which is impossible in git"),
|
branch.rsplit('/').next().expect("There was no / in the head reference which is impossible in git"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Action::PullMerge(incoming) => (
|
Action::PullMerge{incoming,rebase} => (
|
||||||
strings::confirm_title_merge(&self.key_config),
|
strings::confirm_title_merge(&self.key_config,*rebase),
|
||||||
strings::confirm_msg_merge(&self.key_config,*incoming),
|
strings::confirm_msg_merge(&self.key_config,*incoming,*rebase),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ pub enum Action {
|
|||||||
StashDrop(CommitId),
|
StashDrop(CommitId),
|
||||||
DeleteBranch(String),
|
DeleteBranch(String),
|
||||||
ForcePush(String, bool),
|
ForcePush(String, bool),
|
||||||
PullMerge(usize),
|
PullMerge { incoming: usize, rebase: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -89,14 +89,26 @@ pub fn confirm_title_stashdrop(
|
|||||||
) -> String {
|
) -> String {
|
||||||
"Drop".to_string()
|
"Drop".to_string()
|
||||||
}
|
}
|
||||||
pub fn confirm_title_merge(_key_config: &SharedKeyConfig) -> String {
|
pub fn confirm_title_merge(
|
||||||
"Merge".to_string()
|
_key_config: &SharedKeyConfig,
|
||||||
|
rebase: bool,
|
||||||
|
) -> String {
|
||||||
|
if rebase {
|
||||||
|
"Merge (via rebase)".to_string()
|
||||||
|
} else {
|
||||||
|
"Merge (via commit)".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn confirm_msg_merge(
|
pub fn confirm_msg_merge(
|
||||||
_key_config: &SharedKeyConfig,
|
_key_config: &SharedKeyConfig,
|
||||||
incoming: usize,
|
incoming: usize,
|
||||||
|
rebase: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
format!("confirm merge of {} incoming commits? ", incoming)
|
if rebase {
|
||||||
|
format!("Rebase onto {} incoming commits?", incoming)
|
||||||
|
} else {
|
||||||
|
format!("Merge of {} incoming commits?", incoming)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String {
|
pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String {
|
||||||
"confirm file reset?".to_string()
|
"confirm file reset?".to_string()
|
||||||
|
Loading…
Reference in New Issue
Block a user