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
|
||||
|
||||
### 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 discarding selected lines ([#59](https://github.com/extrawurst/gitui/issues/59))
|
||||
- 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_ff;
|
||||
pub mod merge_rebase;
|
||||
pub mod rename;
|
||||
|
||||
use super::{
|
||||
@ -108,6 +109,20 @@ pub(crate) fn branch_set_upstream(
|
||||
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(
|
||||
repo_path: &str,
|
||||
|
@ -25,11 +25,12 @@ mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub use branch::{
|
||||
branch_compare_upstream, checkout_branch, create_branch,
|
||||
delete_branch, get_branches_info,
|
||||
branch_compare_upstream, checkout_branch, config_is_pull_rebase,
|
||||
create_branch, delete_branch, get_branches_info,
|
||||
merge_commit::merge_upstream_commit,
|
||||
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_details::{
|
||||
|
@ -524,8 +524,8 @@ impl App {
|
||||
.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Push(branch, force)),
|
||||
Action::PullMerge(_) => {
|
||||
self.pull_popup.try_conflict_free_merge();
|
||||
Action::PullMerge { rebase, .. } => {
|
||||
self.pull_popup.try_conflict_free_merge(rebase);
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
}
|
||||
},
|
||||
|
@ -151,12 +151,12 @@ impl PullComponent {
|
||||
let branch_compare =
|
||||
sync::branch_compare_upstream(CWD, &self.branch)?;
|
||||
if branch_compare.behind > 0 {
|
||||
let merge_res = sync::branch_merge_upstream_fastforward(
|
||||
let ff_res = sync::branch_merge_upstream_fastforward(
|
||||
CWD,
|
||||
&self.branch,
|
||||
);
|
||||
if let Err(err) = merge_res {
|
||||
log::trace!("ff merge failed: {}", err);
|
||||
if let Err(err) = ff_res {
|
||||
log::trace!("ff failed: {}", err);
|
||||
self.confirm_merge(branch_compare.behind);
|
||||
}
|
||||
}
|
||||
@ -166,17 +166,29 @@ impl PullComponent {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_conflict_free_merge(&self) {
|
||||
pub fn try_conflict_free_merge(&self, rebase: bool) {
|
||||
if rebase {
|
||||
try_or_popup!(
|
||||
self,
|
||||
"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) {
|
||||
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();
|
||||
}
|
||||
|
@ -173,9 +173,9 @@ impl ResetComponent {
|
||||
branch.rsplit('/').next().expect("There was no / in the head reference which is impossible in git"),
|
||||
),
|
||||
),
|
||||
Action::PullMerge(incoming) => (
|
||||
strings::confirm_title_merge(&self.key_config),
|
||||
strings::confirm_msg_merge(&self.key_config,*incoming),
|
||||
Action::PullMerge{incoming,rebase} => (
|
||||
strings::confirm_title_merge(&self.key_config,*rebase),
|
||||
strings::confirm_msg_merge(&self.key_config,*incoming,*rebase),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ pub enum Action {
|
||||
StashDrop(CommitId),
|
||||
DeleteBranch(String),
|
||||
ForcePush(String, bool),
|
||||
PullMerge(usize),
|
||||
PullMerge { incoming: usize, rebase: bool },
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -89,14 +89,26 @@ pub fn confirm_title_stashdrop(
|
||||
) -> String {
|
||||
"Drop".to_string()
|
||||
}
|
||||
pub fn confirm_title_merge(_key_config: &SharedKeyConfig) -> String {
|
||||
"Merge".to_string()
|
||||
pub fn confirm_title_merge(
|
||||
_key_config: &SharedKeyConfig,
|
||||
rebase: bool,
|
||||
) -> String {
|
||||
if rebase {
|
||||
"Merge (via rebase)".to_string()
|
||||
} else {
|
||||
"Merge (via commit)".to_string()
|
||||
}
|
||||
}
|
||||
pub fn confirm_msg_merge(
|
||||
_key_config: &SharedKeyConfig,
|
||||
incoming: usize,
|
||||
rebase: bool,
|
||||
) -> 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 {
|
||||
"confirm file reset?".to_string()
|
||||
|
Loading…
Reference in New Issue
Block a user