Skip to content

Commit

Permalink
Merge pull request #4454 from gitbutlerapp/slightly-refactor-get_appl…
Browse files Browse the repository at this point in the history
…ied_status

slightly refactor get_applied_status
  • Loading branch information
krlvi authored Jul 20, 2024
2 parents 4654529 + 8bfd897 commit bcb0916
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 550 deletions.
321 changes: 157 additions & 164 deletions crates/gitbutler-branch-actions/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use serde::Serialize;
use super::r#virtual as vb;
use crate::branch_manager::BranchManagerExt;
use crate::conflicts::RepoConflictsExt;
use crate::integration::{get_workspace_head, update_gitbutler_integration};
use crate::integration::update_gitbutler_integration;
use crate::remote::{commit_to_remote_commit, RemoteCommit};
use crate::status::get_applied_status;
use crate::{VirtualBranchHunk, VirtualBranchesExt};
use gitbutler_branch::GITBUTLER_INTEGRATION_REFERENCE;
use gitbutler_error::error::Marker;
Expand Down Expand Up @@ -363,182 +364,174 @@ pub(crate) fn update_base_branch(
))?;

let vb_state = project_repository.project().virtual_branches();
let integration_commit = get_workspace_head(&vb_state, project_repository)?;

// try to update every branch
let updated_vbranches =
vb::get_status_by_branch(project_repository, Some(&integration_commit), None)?
.0
.into_iter()
.map(|(branch, _)| branch)
.map(|mut branch: Branch| -> Result<Option<Branch>> {
let branch_tree = repo.find_tree(branch.tree)?;

let branch_head_commit = repo.find_commit(branch.head).context(format!(
"failed to find commit {} for branch {}",
branch.head, branch.id
))?;
let branch_head_tree = branch_head_commit.tree().context(format!(
"failed to find tree for commit {} for branch {}",
branch.head, branch.id
))?;

let result_integrated_detected = |mut branch: Branch| -> Result<Option<Branch>> {
// branch head tree is the same as the new target tree.
// meaning we can safely use the new target commit as the branch head.

branch.head = new_target_commit.id();

// it also means that the branch is fully integrated into the target.
// disconnect it from the upstream
branch.upstream = None;
branch.upstream_head = None;

let non_commited_files = gitbutler_diff::trees(
project_repository.repo(),
&branch_head_tree,
&branch_tree,
)?;
if non_commited_files.is_empty() {
// if there are no commited files, then the branch is fully merged
// and we can delete it.
vb_state.mark_as_not_in_workspace(branch.id)?;
project_repository.delete_branch_reference(&branch)?;
Ok(None)
} else {
vb_state.set_branch(branch.clone())?;
Ok(Some(branch))
}
};

if branch_head_tree.id() == new_target_tree.id() {
return result_integrated_detected(branch);
}

// try to merge branch head with new target
let mut branch_tree_merge_index = repo
.merge_trees(&old_target_tree, &branch_tree, &new_target_tree, None)
.context(format!("failed to merge trees for branch {}", branch.id))?;

if branch_tree_merge_index.has_conflicts() {
// branch tree conflicts with new target, unapply branch for now. we'll handle it later, when user applies it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch = branch_manager.convert_to_real_branch(
branch.id,
Default::default(),
perm,
)?;

unapplied_branch_names.push(unapplied_real_branch);

return Ok(None);
}

let branch_merge_index_tree_oid =
branch_tree_merge_index.write_tree_to(project_repository.repo())?;

if branch_merge_index_tree_oid == new_target_tree.id() {
return result_integrated_detected(branch);
}
let updated_vbranches = get_applied_status(project_repository, None)?
.branches
.into_iter()
.map(|(branch, _)| branch)
.map(|mut branch: Branch| -> Result<Option<Branch>> {
let branch_tree = repo.find_tree(branch.tree)?;

if branch.head == target.sha {
// there are no commits on the branch, so we can just update the head to the new target and calculate the new tree
branch.head = new_target_commit.id();
branch.tree = branch_merge_index_tree_oid;
let branch_head_commit = repo.find_commit(branch.head).context(format!(
"failed to find commit {} for branch {}",
branch.head, branch.id
))?;
let branch_head_tree = branch_head_commit.tree().context(format!(
"failed to find tree for commit {} for branch {}",
branch.head, branch.id
))?;

let result_integrated_detected = |mut branch: Branch| -> Result<Option<Branch>> {
// branch head tree is the same as the new target tree.
// meaning we can safely use the new target commit as the branch head.

branch.head = new_target_commit.id();

// it also means that the branch is fully integrated into the target.
// disconnect it from the upstream
branch.upstream = None;
branch.upstream_head = None;

let non_commited_files = gitbutler_diff::trees(
project_repository.repo(),
&branch_head_tree,
&branch_tree,
)?;
if non_commited_files.is_empty() {
// if there are no commited files, then the branch is fully merged
// and we can delete it.
vb_state.mark_as_not_in_workspace(branch.id)?;
project_repository.delete_branch_reference(&branch)?;
Ok(None)
} else {
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));
Ok(Some(branch))
}
};

let mut branch_head_merge_index = repo
.merge_trees(&old_target_tree, &branch_head_tree, &new_target_tree, None)
.context(format!(
"failed to merge head tree for branch {}",
branch.id
))?;

if branch_head_merge_index.has_conflicts() {
// branch commits conflict with new target, make sure the branch is
// unapplied. conflicts witll be dealt with when applying it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch = branch_manager.convert_to_real_branch(
branch.id,
Default::default(),
perm,
)?;
unapplied_branch_names.push(unapplied_real_branch);

return Ok(None);
}
if branch_head_tree.id() == new_target_tree.id() {
return result_integrated_detected(branch);
}

// try to merge branch head with new target
let mut branch_tree_merge_index = repo
.merge_trees(&old_target_tree, &branch_tree, &new_target_tree, None)
.context(format!("failed to merge trees for branch {}", branch.id))?;

if branch_tree_merge_index.has_conflicts() {
// branch tree conflicts with new target, unapply branch for now. we'll handle it later, when user applies it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch =
branch_manager.convert_to_real_branch(branch.id, Default::default(), perm)?;

unapplied_branch_names.push(unapplied_real_branch);

return Ok(None);
}

let branch_merge_index_tree_oid =
branch_tree_merge_index.write_tree_to(project_repository.repo())?;

if branch_merge_index_tree_oid == new_target_tree.id() {
return result_integrated_detected(branch);
}

if branch.head == target.sha {
// there are no commits on the branch, so we can just update the head to the new target and calculate the new tree
branch.head = new_target_commit.id();
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));
}

let mut branch_head_merge_index = repo
.merge_trees(&old_target_tree, &branch_head_tree, &new_target_tree, None)
.context(format!(
"failed to merge head tree for branch {}",
branch.id
))?;

// branch commits do not conflict with new target, so lets merge them
let branch_head_merge_tree_oid = branch_head_merge_index
.write_tree_to(project_repository.repo())
.context(format!(
"failed to write head merge index for {}",
branch.id
))?;

let ok_with_force_push = branch.allow_rebasing;

let result_merge = |mut branch: Branch| -> Result<Option<Branch>> {
// branch was pushed to upstream, and user doesn't like force pushing.
// create a merge commit to avoid the need of force pushing then.
let branch_head_merge_tree = repo
.find_tree(branch_head_merge_tree_oid)
.context("failed to find tree")?;

let new_target_head = project_repository
.commit(
format!(
"Merged {}/{} into {}",
target.branch.remote(),
target.branch.branch(),
branch.name,
)
.as_str(),
&branch_head_merge_tree,
&[&branch_head_commit, &new_target_commit],
None,
if branch_head_merge_index.has_conflicts() {
// branch commits conflict with new target, make sure the branch is
// unapplied. conflicts witll be dealt with when applying it back.
let branch_manager = project_repository.branch_manager();
let unapplied_real_branch =
branch_manager.convert_to_real_branch(branch.id, Default::default(), perm)?;
unapplied_branch_names.push(unapplied_real_branch);

return Ok(None);
}

// branch commits do not conflict with new target, so lets merge them
let branch_head_merge_tree_oid = branch_head_merge_index
.write_tree_to(project_repository.repo())
.context(format!(
"failed to write head merge index for {}",
branch.id
))?;

let ok_with_force_push = branch.allow_rebasing;

let result_merge = |mut branch: Branch| -> Result<Option<Branch>> {
// branch was pushed to upstream, and user doesn't like force pushing.
// create a merge commit to avoid the need of force pushing then.
let branch_head_merge_tree = repo
.find_tree(branch_head_merge_tree_oid)
.context("failed to find tree")?;

let new_target_head = project_repository
.commit(
format!(
"Merged {}/{} into {}",
target.branch.remote(),
target.branch.branch(),
branch.name,
)
.context("failed to commit merge")?;
.as_str(),
&branch_head_merge_tree,
&[&branch_head_commit, &new_target_commit],
None,
)
.context("failed to commit merge")?;

branch.head = new_target_head;
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
Ok(Some(branch))
};

branch.head = new_target_head;
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
Ok(Some(branch))
};
if branch.upstream.is_some() && !ok_with_force_push {
return result_merge(branch);
}

if branch.upstream.is_some() && !ok_with_force_push {
return result_merge(branch);
}
// branch was not pushed to upstream yet. attempt a rebase,
let rebased_head_oid = cherry_rebase(
project_repository,
new_target_commit.id(),
new_target_commit.id(),
branch.head,
);

// branch was not pushed to upstream yet. attempt a rebase,
let rebased_head_oid = cherry_rebase(
project_repository,
new_target_commit.id(),
new_target_commit.id(),
branch.head,
);

// rebase failed, just do the merge
if rebased_head_oid.is_err() {
return result_merge(branch);
}
// rebase failed, just do the merge
if rebased_head_oid.is_err() {
return result_merge(branch);
}

if let Some(rebased_head_oid) = rebased_head_oid? {
// rebase worked out, rewrite the branch head
branch.head = rebased_head_oid;
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));
}
if let Some(rebased_head_oid) = rebased_head_oid? {
// rebase worked out, rewrite the branch head
branch.head = rebased_head_oid;
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));
}

result_merge(branch)
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
result_merge(branch)
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();

// ok, now all the problematic branches have been unapplied
// now we calculate and checkout new tree for the working directory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,12 @@ impl BranchManager<'_> {

let repo = self.project_repository.repo();

let integration_commit = repo.integration_commit()?;
let target_commit = repo.target_commit()?;
let base_tree = target_commit.tree().context("failed to get target tree")?;

let virtual_branches = vb_state
.list_branches_in_workspace()
.context("failed to read virtual branches")?;

let (applied_statuses, _, _) = get_applied_status(
self.project_repository,
&integration_commit.id(),
virtual_branches,
None,
)
.context("failed to get status by branch")?;
let applied_statuses = get_applied_status(self.project_repository, None)
.context("failed to get status by branch")?
.branches;

// go through the other applied branches and merge them into the final tree
// then check that out into the working directory
Expand Down
Loading

0 comments on commit bcb0916

Please sign in to comment.