mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 14:31:30 +03:00
Extract branch_upstream_integration into it's own file
This commit is contained in:
parent
45fbb1af76
commit
8f9fd0cbe7
@ -1,4 +1,5 @@
|
|||||||
use super::r#virtual as vbranch;
|
use super::r#virtual as vbranch;
|
||||||
|
use crate::branch_upstream_integration;
|
||||||
use crate::move_commits;
|
use crate::move_commits;
|
||||||
use crate::reorder_commits;
|
use crate::reorder_commits;
|
||||||
use crate::upstream_integration::{
|
use crate::upstream_integration::{
|
||||||
@ -176,7 +177,7 @@ pub fn integrate_upstream_commits(project: &Project, branch_id: BranchId) -> Res
|
|||||||
SnapshotDetails::new(OperationKind::MergeUpstream),
|
SnapshotDetails::new(OperationKind::MergeUpstream),
|
||||||
guard.write_permission(),
|
guard.write_permission(),
|
||||||
);
|
);
|
||||||
vbranch::integrate_upstream_commits(&ctx, branch_id).map_err(Into::into)
|
branch_upstream_integration::integrate_upstream_commits(&ctx, branch_id).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_virtual_branch(project: &Project, branch_update: BranchUpdateRequest) -> Result<()> {
|
pub fn update_virtual_branch(project: &Project, branch_update: BranchUpdateRequest) -> Result<()> {
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use gitbutler_branch::{Branch, BranchId};
|
||||||
|
use gitbutler_command_context::CommandContext;
|
||||||
|
use gitbutler_commit::commit_ext::CommitExt as _;
|
||||||
|
use gitbutler_error::error::Marker;
|
||||||
|
use gitbutler_repo::{rebase::cherry_rebase_group, RepoActionsExt as _, RepositoryExt as _};
|
||||||
|
|
||||||
|
use crate::{conflicts, integration::get_workspace_head, VirtualBranchesExt as _};
|
||||||
|
|
||||||
|
/// Integrates upstream work from a remote branch.
|
||||||
|
///
|
||||||
|
/// First we determine strategy based on preferences and branch state. If you
|
||||||
|
/// have allowed force push then it is likely branch commits frequently get
|
||||||
|
/// rebased, meaning we want to cherry pick new upstream work onto our rebased
|
||||||
|
/// commits.
|
||||||
|
///
|
||||||
|
/// If your local branch has been rebased, but you have new local only commits,
|
||||||
|
/// we _must_ rebase the upstream commits on top of the last rebased commit. We
|
||||||
|
/// do this to avoid duplicate commits, but we then need to let the user decide
|
||||||
|
/// if the local only commits get rebased on top of new upstream work or merged
|
||||||
|
/// with the new commits. The latter is sometimes preferable because you have
|
||||||
|
/// at most one merge conflict to resolve, while rebasing requires a multi-step
|
||||||
|
/// interactive process (currently not supported, so we abort).
|
||||||
|
///
|
||||||
|
/// If you do not allow force push then first validate the remote branch and
|
||||||
|
/// your local branch have the same merge base. A different merge base means
|
||||||
|
/// means either you or the remote branch has been rebased, and merging the
|
||||||
|
/// two would introduce duplicate commits (same changes, different hash).
|
||||||
|
///
|
||||||
|
/// Additionally, if we succeed in integrating the upstream commit, we still
|
||||||
|
/// need to merge the new branch tree with the working directory tree. This
|
||||||
|
/// might introduce more conflicts, but there is no need to commit at the
|
||||||
|
/// end since there will only be one parent commit.
|
||||||
|
///
|
||||||
|
pub fn integrate_upstream_commits(ctx: &CommandContext, branch_id: BranchId) -> Result<()> {
|
||||||
|
conflicts::is_conflicting(ctx, None)?;
|
||||||
|
|
||||||
|
let repo = ctx.repository();
|
||||||
|
let project = ctx.project();
|
||||||
|
let vb_state = project.virtual_branches();
|
||||||
|
|
||||||
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
|
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
||||||
|
let upstream_oid = repo.refname_to_id(&upstream_branch.to_string())?;
|
||||||
|
let upstream_commit = repo.find_commit(upstream_oid)?;
|
||||||
|
|
||||||
|
if upstream_commit.id() == branch.head() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let upstream_commits = repo.list_commits(upstream_commit.id(), default_target.sha)?;
|
||||||
|
let branch_commits = repo.list_commits(branch.head(), default_target.sha)?;
|
||||||
|
|
||||||
|
let branch_commit_ids = branch_commits.iter().map(|c| c.id()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let branch_change_ids = branch_commits
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| c.change_id())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut unknown_commits: Vec<git2::Oid> = upstream_commits
|
||||||
|
.iter()
|
||||||
|
.filter(|c| {
|
||||||
|
(!c.change_id()
|
||||||
|
.is_some_and(|cid| branch_change_ids.contains(&cid)))
|
||||||
|
&& !branch_commit_ids.contains(&c.id())
|
||||||
|
})
|
||||||
|
.map(|c| c.id())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let rebased_commits = upstream_commits
|
||||||
|
.iter()
|
||||||
|
.filter(|c| {
|
||||||
|
c.change_id()
|
||||||
|
.is_some_and(|cid| branch_change_ids.contains(&cid))
|
||||||
|
&& !branch_commit_ids.contains(&c.id())
|
||||||
|
})
|
||||||
|
.map(|c| c.id())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// If there are no new commits then there is nothing to do.
|
||||||
|
if unknown_commits.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let merge_base = repo.merge_base(default_target.sha, upstream_oid)?;
|
||||||
|
|
||||||
|
// Booleans needed for a decision on how integrate upstream commits.
|
||||||
|
// let is_same_base = default_target.sha == merge_base;
|
||||||
|
let can_use_force = branch.allow_rebasing;
|
||||||
|
let has_rebased_commits = !rebased_commits.is_empty();
|
||||||
|
|
||||||
|
// We can't proceed if we rebased local commits but no permission to force push. In this
|
||||||
|
// scenario we would need to "cherry rebase" new upstream commits onto the last rebased
|
||||||
|
// local commit.
|
||||||
|
if has_rebased_commits && !can_use_force {
|
||||||
|
return Err(anyhow!("Cannot merge rebased commits without force push")
|
||||||
|
.context("Aborted because force push is disallowed and commits have been rebased")
|
||||||
|
.context(Marker::ProjectConflict));
|
||||||
|
}
|
||||||
|
|
||||||
|
let integration_result = match can_use_force {
|
||||||
|
true => integrate_with_rebase(ctx, &mut branch, &mut unknown_commits),
|
||||||
|
false => {
|
||||||
|
if has_rebased_commits {
|
||||||
|
return Err(anyhow!("Cannot merge rebased commits without force push")
|
||||||
|
.context(
|
||||||
|
"Aborted because force push is disallowed and commits have been rebased",
|
||||||
|
)
|
||||||
|
.context(Marker::ProjectConflict));
|
||||||
|
}
|
||||||
|
integrate_with_merge(ctx, &mut branch, &upstream_commit, merge_base).map(Into::into)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if integration_result.as_ref().err().map_or(false, |err| {
|
||||||
|
err.downcast_ref()
|
||||||
|
.is_some_and(|marker: &Marker| *marker == Marker::ProjectConflict)
|
||||||
|
}) {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_head = integration_result?;
|
||||||
|
let new_head_tree = repo.find_commit(new_head)?.tree()?;
|
||||||
|
let head_commit = repo.find_commit(new_head)?;
|
||||||
|
|
||||||
|
let wd_tree = ctx.repository().create_wd_tree()?;
|
||||||
|
let workspace_tree = repo.find_commit(get_workspace_head(ctx)?)?.tree()?;
|
||||||
|
|
||||||
|
let mut merge_index = repo.merge_trees(&workspace_tree, &new_head_tree, &wd_tree, None)?;
|
||||||
|
|
||||||
|
if merge_index.has_conflicts() {
|
||||||
|
repo.checkout_index_builder(&mut merge_index)
|
||||||
|
.allow_conflicts()
|
||||||
|
.conflict_style_merge()
|
||||||
|
.force()
|
||||||
|
.checkout()?;
|
||||||
|
} else {
|
||||||
|
branch.set_head(new_head);
|
||||||
|
branch.tree = head_commit.tree()?.id();
|
||||||
|
vb_state.set_branch(branch.clone())?;
|
||||||
|
repo.checkout_index_builder(&mut merge_index)
|
||||||
|
.force()
|
||||||
|
.checkout()?;
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::integration::update_workspace_commit(&vb_state, ctx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integrate_with_rebase(
|
||||||
|
ctx: &CommandContext,
|
||||||
|
branch: &mut Branch,
|
||||||
|
unknown_commits: &mut Vec<git2::Oid>,
|
||||||
|
) -> Result<git2::Oid> {
|
||||||
|
cherry_rebase_group(
|
||||||
|
ctx.repository(),
|
||||||
|
branch.head(),
|
||||||
|
unknown_commits.as_mut_slice(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integrate_with_merge(
|
||||||
|
ctx: &CommandContext,
|
||||||
|
branch: &mut Branch,
|
||||||
|
upstream_commit: &git2::Commit,
|
||||||
|
merge_base: git2::Oid,
|
||||||
|
) -> Result<git2::Oid> {
|
||||||
|
let wd_tree = ctx.repository().create_wd_tree()?;
|
||||||
|
let repo = ctx.repository();
|
||||||
|
let remote_tree = upstream_commit.tree().context("failed to get tree")?;
|
||||||
|
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
||||||
|
// let merge_tree = repo.find_commit(merge_base).and_then(|c| c.tree())?;
|
||||||
|
let merge_tree = repo.find_commit(merge_base)?;
|
||||||
|
let merge_tree = merge_tree.tree()?;
|
||||||
|
|
||||||
|
let mut merge_index = repo.merge_trees(&merge_tree, &wd_tree, &remote_tree, None)?;
|
||||||
|
|
||||||
|
if merge_index.has_conflicts() {
|
||||||
|
let conflicts = merge_index.conflicts()?;
|
||||||
|
let merge_conflicts = conflicts
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|c| c.our)
|
||||||
|
.map(|our| gix::path::try_from_bstr(Cow::Owned(our.path.into())))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
conflicts::mark(ctx, merge_conflicts, Some(upstream_commit.id()))?;
|
||||||
|
repo.checkout_index_builder(&mut merge_index)
|
||||||
|
.allow_conflicts()
|
||||||
|
.conflict_style_merge()
|
||||||
|
.force()
|
||||||
|
.checkout()?;
|
||||||
|
return Err(anyhow!("merge problem")).context(Marker::ProjectConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
let merge_tree_oid = merge_index.write_tree_to(ctx.repository())?;
|
||||||
|
let merge_tree = repo.find_tree(merge_tree_oid)?;
|
||||||
|
let head_commit = repo.find_commit(branch.head())?;
|
||||||
|
|
||||||
|
ctx.commit(
|
||||||
|
format!(
|
||||||
|
"Merged {}/{} into {}",
|
||||||
|
upstream_branch.remote(),
|
||||||
|
upstream_branch.branch(),
|
||||||
|
branch.name
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&merge_tree,
|
||||||
|
&[&head_commit, upstream_commit],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
@ -20,6 +20,7 @@ pub use r#virtual::{BranchStatus, VirtualBranch, VirtualBranchHunksByPathMap, Vi
|
|||||||
/// Avoid using these!
|
/// Avoid using these!
|
||||||
/// This was previously `pub use r#virtual::*;`
|
/// This was previously `pub use r#virtual::*;`
|
||||||
pub mod internal {
|
pub mod internal {
|
||||||
|
pub use super::branch_upstream_integration;
|
||||||
pub use super::r#virtual::*;
|
pub use super::r#virtual::*;
|
||||||
pub use super::remote::list_local_branches;
|
pub use super::remote::list_local_branches;
|
||||||
}
|
}
|
||||||
@ -44,6 +45,7 @@ pub use remote::{RemoteBranch, RemoteBranchData, RemoteCommit};
|
|||||||
pub mod conflicts;
|
pub mod conflicts;
|
||||||
|
|
||||||
pub mod branch_trees;
|
pub mod branch_trees;
|
||||||
|
pub mod branch_upstream_integration;
|
||||||
mod move_commits;
|
mod move_commits;
|
||||||
mod reorder_commits;
|
mod reorder_commits;
|
||||||
mod undo_commit;
|
mod undo_commit;
|
||||||
|
@ -19,7 +19,7 @@ use gitbutler_cherry_pick::RepositoryExt as _;
|
|||||||
use gitbutler_command_context::CommandContext;
|
use gitbutler_command_context::CommandContext;
|
||||||
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders};
|
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders};
|
||||||
use gitbutler_diff::{trees, GitHunk, Hunk};
|
use gitbutler_diff::{trees, GitHunk, Hunk};
|
||||||
use gitbutler_error::error::{Code, Marker};
|
use gitbutler_error::error::Code;
|
||||||
use gitbutler_operating_modes::assure_open_workspace_mode;
|
use gitbutler_operating_modes::assure_open_workspace_mode;
|
||||||
use gitbutler_oxidize::git2_signature_to_gix_signature;
|
use gitbutler_oxidize::git2_signature_to_gix_signature;
|
||||||
use gitbutler_project::access::WorktreeWritePermission;
|
use gitbutler_project::access::WorktreeWritePermission;
|
||||||
@ -32,7 +32,7 @@ use gitbutler_stack::{commit_by_oid_or_change_id, Stack};
|
|||||||
use gitbutler_time::time::now_since_unix_epoch_ms;
|
use gitbutler_time::time::now_since_unix_epoch_ms;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf, vec};
|
use std::{collections::HashMap, path::PathBuf, vec};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
// this struct is a mapping to the view `Branch` type in Typescript
|
// this struct is a mapping to the view `Branch` type in Typescript
|
||||||
@ -587,211 +587,6 @@ fn is_requires_force(ctx: &CommandContext, branch: &Branch) -> Result<bool> {
|
|||||||
Ok(merge_base != upstream_commit.id())
|
Ok(merge_base != upstream_commit.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integrates upstream work from a remote branch.
|
|
||||||
///
|
|
||||||
/// First we determine strategy based on preferences and branch state. If you
|
|
||||||
/// have allowed force push then it is likely branch commits frequently get
|
|
||||||
/// rebased, meaning we want to cherry pick new upstream work onto our rebased
|
|
||||||
/// commits.
|
|
||||||
///
|
|
||||||
/// If your local branch has been rebased, but you have new local only commits,
|
|
||||||
/// we _must_ rebase the upstream commits on top of the last rebased commit. We
|
|
||||||
/// do this to avoid duplicate commits, but we then need to let the user decide
|
|
||||||
/// if the local only commits get rebased on top of new upstream work or merged
|
|
||||||
/// with the new commits. The latter is sometimes preferable because you have
|
|
||||||
/// at most one merge conflict to resolve, while rebasing requires a multi-step
|
|
||||||
/// interactive process (currently not supported, so we abort).
|
|
||||||
///
|
|
||||||
/// If you do not allow force push then first validate the remote branch and
|
|
||||||
/// your local branch have the same merge base. A different merge base means
|
|
||||||
/// means either you or the remote branch has been rebased, and merging the
|
|
||||||
/// two would introduce duplicate commits (same changes, different hash).
|
|
||||||
///
|
|
||||||
/// Additionally, if we succeed in integrating the upstream commit, we still
|
|
||||||
/// need to merge the new branch tree with the working directory tree. This
|
|
||||||
/// might introduce more conflicts, but there is no need to commit at the
|
|
||||||
/// end since there will only be one parent commit.
|
|
||||||
///
|
|
||||||
pub fn integrate_upstream_commits(ctx: &CommandContext, branch_id: BranchId) -> Result<()> {
|
|
||||||
conflicts::is_conflicting(ctx, None)?;
|
|
||||||
|
|
||||||
let repo = ctx.repository();
|
|
||||||
let project = ctx.project();
|
|
||||||
let vb_state = project.virtual_branches();
|
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
|
||||||
let default_target = vb_state.get_default_target()?;
|
|
||||||
|
|
||||||
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
|
||||||
let upstream_oid = repo.refname_to_id(&upstream_branch.to_string())?;
|
|
||||||
let upstream_commit = repo.find_commit(upstream_oid)?;
|
|
||||||
|
|
||||||
if upstream_commit.id() == branch.head() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let upstream_commits = repo.list_commits(upstream_commit.id(), default_target.sha)?;
|
|
||||||
let branch_commits = repo.list_commits(branch.head(), default_target.sha)?;
|
|
||||||
|
|
||||||
let branch_commit_ids = branch_commits.iter().map(|c| c.id()).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let branch_change_ids = branch_commits
|
|
||||||
.iter()
|
|
||||||
.filter_map(|c| c.change_id())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut unknown_commits: Vec<git2::Oid> = upstream_commits
|
|
||||||
.iter()
|
|
||||||
.filter(|c| {
|
|
||||||
(!c.change_id()
|
|
||||||
.is_some_and(|cid| branch_change_ids.contains(&cid)))
|
|
||||||
&& !branch_commit_ids.contains(&c.id())
|
|
||||||
})
|
|
||||||
.map(|c| c.id())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let rebased_commits = upstream_commits
|
|
||||||
.iter()
|
|
||||||
.filter(|c| {
|
|
||||||
c.change_id()
|
|
||||||
.is_some_and(|cid| branch_change_ids.contains(&cid))
|
|
||||||
&& !branch_commit_ids.contains(&c.id())
|
|
||||||
})
|
|
||||||
.map(|c| c.id())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// If there are no new commits then there is nothing to do.
|
|
||||||
if unknown_commits.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let merge_base = repo.merge_base(default_target.sha, upstream_oid)?;
|
|
||||||
|
|
||||||
// Booleans needed for a decision on how integrate upstream commits.
|
|
||||||
// let is_same_base = default_target.sha == merge_base;
|
|
||||||
let can_use_force = branch.allow_rebasing;
|
|
||||||
let has_rebased_commits = !rebased_commits.is_empty();
|
|
||||||
|
|
||||||
// We can't proceed if we rebased local commits but no permission to force push. In this
|
|
||||||
// scenario we would need to "cherry rebase" new upstream commits onto the last rebased
|
|
||||||
// local commit.
|
|
||||||
if has_rebased_commits && !can_use_force {
|
|
||||||
return Err(anyhow!("Cannot merge rebased commits without force push")
|
|
||||||
.context("Aborted because force push is disallowed and commits have been rebased")
|
|
||||||
.context(Marker::ProjectConflict));
|
|
||||||
}
|
|
||||||
|
|
||||||
let integration_result = match can_use_force {
|
|
||||||
true => integrate_with_rebase(ctx, &mut branch, &mut unknown_commits),
|
|
||||||
false => {
|
|
||||||
if has_rebased_commits {
|
|
||||||
return Err(anyhow!("Cannot merge rebased commits without force push")
|
|
||||||
.context(
|
|
||||||
"Aborted because force push is disallowed and commits have been rebased",
|
|
||||||
)
|
|
||||||
.context(Marker::ProjectConflict));
|
|
||||||
}
|
|
||||||
integrate_with_merge(ctx, &mut branch, &upstream_commit, merge_base).map(Into::into)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if integration_result.as_ref().err().map_or(false, |err| {
|
|
||||||
err.downcast_ref()
|
|
||||||
.is_some_and(|marker: &Marker| *marker == Marker::ProjectConflict)
|
|
||||||
}) {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_head = integration_result?;
|
|
||||||
let new_head_tree = repo.find_commit(new_head)?.tree()?;
|
|
||||||
let head_commit = repo.find_commit(new_head)?;
|
|
||||||
|
|
||||||
let wd_tree = ctx.repository().create_wd_tree()?;
|
|
||||||
let workspace_tree = repo.find_commit(get_workspace_head(ctx)?)?.tree()?;
|
|
||||||
|
|
||||||
let mut merge_index = repo.merge_trees(&workspace_tree, &new_head_tree, &wd_tree, None)?;
|
|
||||||
|
|
||||||
if merge_index.has_conflicts() {
|
|
||||||
repo.checkout_index_builder(&mut merge_index)
|
|
||||||
.allow_conflicts()
|
|
||||||
.conflict_style_merge()
|
|
||||||
.force()
|
|
||||||
.checkout()?;
|
|
||||||
} else {
|
|
||||||
branch.set_head(new_head);
|
|
||||||
branch.tree = head_commit.tree()?.id();
|
|
||||||
vb_state.set_branch(branch.clone())?;
|
|
||||||
repo.checkout_index_builder(&mut merge_index)
|
|
||||||
.force()
|
|
||||||
.checkout()?;
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::integration::update_workspace_commit(&vb_state, ctx)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn integrate_with_rebase(
|
|
||||||
ctx: &CommandContext,
|
|
||||||
branch: &mut Branch,
|
|
||||||
unknown_commits: &mut Vec<git2::Oid>,
|
|
||||||
) -> Result<git2::Oid> {
|
|
||||||
cherry_rebase_group(
|
|
||||||
ctx.repository(),
|
|
||||||
branch.head(),
|
|
||||||
unknown_commits.as_mut_slice(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn integrate_with_merge(
|
|
||||||
ctx: &CommandContext,
|
|
||||||
branch: &mut Branch,
|
|
||||||
upstream_commit: &git2::Commit,
|
|
||||||
merge_base: git2::Oid,
|
|
||||||
) -> Result<git2::Oid> {
|
|
||||||
let wd_tree = ctx.repository().create_wd_tree()?;
|
|
||||||
let repo = ctx.repository();
|
|
||||||
let remote_tree = upstream_commit.tree().context("failed to get tree")?;
|
|
||||||
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
|
||||||
// let merge_tree = repo.find_commit(merge_base).and_then(|c| c.tree())?;
|
|
||||||
let merge_tree = repo.find_commit(merge_base)?;
|
|
||||||
let merge_tree = merge_tree.tree()?;
|
|
||||||
|
|
||||||
let mut merge_index = repo.merge_trees(&merge_tree, &wd_tree, &remote_tree, None)?;
|
|
||||||
|
|
||||||
if merge_index.has_conflicts() {
|
|
||||||
let conflicts = merge_index.conflicts()?;
|
|
||||||
let merge_conflicts = conflicts
|
|
||||||
.flatten()
|
|
||||||
.filter_map(|c| c.our)
|
|
||||||
.map(|our| gix::path::try_from_bstr(Cow::Owned(our.path.into())))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
conflicts::mark(ctx, merge_conflicts, Some(upstream_commit.id()))?;
|
|
||||||
repo.checkout_index_builder(&mut merge_index)
|
|
||||||
.allow_conflicts()
|
|
||||||
.conflict_style_merge()
|
|
||||||
.force()
|
|
||||||
.checkout()?;
|
|
||||||
return Err(anyhow!("merge problem")).context(Marker::ProjectConflict);
|
|
||||||
}
|
|
||||||
|
|
||||||
let merge_tree_oid = merge_index.write_tree_to(ctx.repository())?;
|
|
||||||
let merge_tree = repo.find_tree(merge_tree_oid)?;
|
|
||||||
let head_commit = repo.find_commit(branch.head())?;
|
|
||||||
|
|
||||||
ctx.commit(
|
|
||||||
format!(
|
|
||||||
"Merged {}/{} into {}",
|
|
||||||
upstream_branch.remote(),
|
|
||||||
upstream_branch.branch(),
|
|
||||||
branch.name
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
&merge_tree,
|
|
||||||
&[&head_commit, upstream_commit],
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_branch(ctx: &CommandContext, branch_update: &BranchUpdateRequest) -> Result<Branch> {
|
pub fn update_branch(ctx: &CommandContext, branch_update: &BranchUpdateRequest) -> Result<Branch> {
|
||||||
let vb_state = ctx.project().virtual_branches();
|
let vb_state = ctx.project().virtual_branches();
|
||||||
let mut branch = vb_state.get_branch_in_workspace(branch_update.id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_update.id)?;
|
||||||
|
@ -17,7 +17,8 @@ use gitbutler_branch::{
|
|||||||
BranchCreateRequest, BranchOwnershipClaims, BranchUpdateRequest, Target, VirtualBranchesHandle,
|
BranchCreateRequest, BranchOwnershipClaims, BranchUpdateRequest, Target, VirtualBranchesHandle,
|
||||||
};
|
};
|
||||||
use gitbutler_branch_actions::{
|
use gitbutler_branch_actions::{
|
||||||
get_applied_status, internal, update_workspace_commit, verify_branch, BranchManagerExt, Get,
|
get_applied_status, internal, update_workspace_commit,
|
||||||
|
verify_branch, BranchManagerExt, Get,
|
||||||
};
|
};
|
||||||
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::CommitHeadersV2};
|
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::CommitHeadersV2};
|
||||||
use gitbutler_reference::{Refname, RemoteRefname};
|
use gitbutler_reference::{Refname, RemoteRefname};
|
||||||
@ -880,7 +881,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
|
|||||||
);
|
);
|
||||||
// assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
// assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
||||||
|
|
||||||
internal::integrate_upstream_commits(ctx, branch1.id)?;
|
internal::branch_upstream_integration::integrate_upstream_commits(ctx, branch1.id)?;
|
||||||
|
|
||||||
let (branches, _) = internal::list_virtual_branches(ctx, guard.write_permission())?;
|
let (branches, _) = internal::list_virtual_branches(ctx, guard.write_permission())?;
|
||||||
let branch1 = &branches[0];
|
let branch1 = &branches[0];
|
||||||
@ -991,7 +992,7 @@ fn merge_vbranch_upstream_conflict() -> Result<()> {
|
|||||||
assert_eq!(branch1.commits.len(), 1);
|
assert_eq!(branch1.commits.len(), 1);
|
||||||
// assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
// assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
|
||||||
|
|
||||||
internal::integrate_upstream_commits(ctx, branch1.id)?;
|
internal::branch_upstream_integration::integrate_upstream_commits(ctx, branch1.id)?;
|
||||||
|
|
||||||
let (branches, _) = internal::list_virtual_branches(ctx, guard.write_permission())?;
|
let (branches, _) = internal::list_virtual_branches(ctx, guard.write_permission())?;
|
||||||
let branch1 = &branches[0];
|
let branch1 = &branches[0];
|
||||||
|
Loading…
Reference in New Issue
Block a user