Merge pull request #5057 from gitbutlerapp/Refactor-Branch-to-abstract-setting-and-getting-the-head-property

Refactor Branch to abstract setting and getting the `head` property
This commit is contained in:
Kiril Videlov 2024-10-08 13:21:31 +02:00 committed by GitHub
commit 0658920abc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 292 additions and 283 deletions

1
Cargo.lock generated
View File

@ -2151,6 +2151,7 @@ dependencies = [
"gitbutler-patch-reference",
"gitbutler-reference",
"gitbutler-serde",
"gitbutler-time",
"gix",
"hex",
"itertools 0.13.0",

View File

@ -3,7 +3,7 @@ use std::{path::Path, time};
use anyhow::{anyhow, Context, Result};
use git2::Index;
use gitbutler_branch::{
self, Branch, BranchId, BranchOwnershipClaims, Target, VirtualBranchesHandle,
self, Branch, BranchOwnershipClaims, Target, VirtualBranchesHandle,
GITBUTLER_WORKSPACE_REFERENCE,
};
use gitbutler_command_context::CommandContext;
@ -86,7 +86,7 @@ fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Resu
// merge this branches tree with our tree
let branch_head = ctx
.repository()
.find_commit(branch.head)
.find_commit(branch.head())
.context("failed to find branch head")?;
let branch_tree = branch_head
.tree()
@ -208,8 +208,6 @@ pub(crate) fn set_base_branch(
},
);
let now_ms = gitbutler_time::time::now_ms();
let (upstream, upstream_head) = if let Refname::Local(head_name) = &head_name {
let upstream_name = target_branch_ref.with_branch(head_name.branch());
if upstream_name.eq(target_branch_ref) {
@ -235,29 +233,22 @@ pub(crate) fn set_base_branch(
(None, None)
};
let branch = Branch {
id: BranchId::generate(),
name: head_name.to_string().replace("refs/heads/", ""),
notes: String::new(),
source_refname: Some(head_name),
let mut branch = Branch::new(
head_name.to_string().replace("refs/heads/", ""),
Some(head_name),
upstream,
upstream_head,
created_timestamp_ms: now_ms,
updated_timestamp_ms: now_ms,
head: current_head_commit.id(),
tree: gitbutler_diff::write::hunks_onto_commit(
gitbutler_diff::write::hunks_onto_commit(
ctx,
current_head_commit.id(),
gitbutler_diff::diff_files_into_hunks(wd_diff),
)?,
ownership,
order: 0,
selected_for_changes: None,
allow_rebasing: ctx.project().ok_with_force_push.into(),
in_workspace: true,
not_in_workspace_wip_change_id: None,
heads: Default::default(),
};
current_head_commit.id(),
0,
None,
ctx.project().ok_with_force_push.into(),
);
branch.ownership = ownership;
vb_state.set_branch(branch)?;
}
@ -369,20 +360,22 @@ pub(crate) fn update_base_branch(
.map(|(mut branch, _)| -> Result<Option<Branch>> {
let branch_tree = repo.find_tree(branch.tree)?;
let branch_head_commit = repo.find_commit(branch.head).context(format!(
let branch_head_commit = repo.find_commit(branch.head()).context(format!(
"failed to find commit {} for branch {}",
branch.head, branch.id
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
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();
branch.set_head(new_target_commit.id());
// it also means that the branch is fully integrated into the target.
// disconnect it from the upstream
@ -429,9 +422,9 @@ pub(crate) fn update_base_branch(
return result_integrated_detected(branch);
}
if branch.head == target.sha {
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.set_head(new_target_commit.id());
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));
@ -486,7 +479,7 @@ pub(crate) fn update_base_branch(
)
.context("failed to commit merge")?;
branch.head = new_target_head;
branch.set_head(new_target_head);
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
Ok(Some(branch))
@ -501,7 +494,7 @@ pub(crate) fn update_base_branch(
ctx,
new_target_commit.id(),
new_target_commit.id(),
branch.head,
branch.head(),
);
// rebase failed, just do the merge
@ -511,7 +504,7 @@ pub(crate) fn update_base_branch(
if let Some(rebased_head_oid) = rebased_head_oid? {
// rebase worked out, rewrite the branch head
branch.head = rebased_head_oid;
branch.set_head(rebased_head_oid);
branch.tree = branch_merge_index_tree_oid;
vb_state.set_branch(branch.clone())?;
return Ok(Some(branch));

View File

@ -261,7 +261,7 @@ fn branch_group_to_branch(
// If there is a virtual branch let's get it's head. Alternatively, pick the first local branch and use it's head.
// If there are no local branches, pick the first remote branch.
let head_commit = if let Some(vbranch) = virtual_branch {
Some(git2_to_gix_object_id(vbranch.head).attach(repo))
Some(git2_to_gix_object_id(vbranch.head()).attach(repo))
} else if let Some(mut branch) = local_branches.into_iter().next() {
branch.peel_to_id_in_place_packed(packed).ok()
} else if let Some(mut branch) = remote_branches.into_iter().next() {

View File

@ -98,27 +98,17 @@ impl BranchManager<'_> {
}
}
let now = gitbutler_time::time::now_ms();
let mut branch = Branch {
id: BranchId::generate(),
name: name.clone(),
notes: String::new(),
upstream: None,
upstream_head: None,
tree: tree.id(),
head: default_target.sha,
created_timestamp_ms: now,
updated_timestamp_ms: now,
ownership: BranchOwnershipClaims::default(),
let mut branch = Branch::new(
name.clone(),
None,
None,
None,
tree.id(),
default_target.sha,
order,
selected_for_changes,
allow_rebasing: self.ctx.project().ok_with_force_push.into(),
in_workspace: true,
not_in_workspace_wip_change_id: None,
source_refname: None,
heads: Default::default(),
};
self.ctx.project().ok_with_force_push.into(),
);
if let Some(ownership) = &create.ownership {
vbranch::set_ownership(&vb_state, &mut branch, ownership)
@ -199,8 +189,6 @@ impl BranchManager<'_> {
.any(|b| b.selected_for_changes.is_some()))
.then_some(now_since_unix_epoch_ms());
let now = gitbutler_time::time::now_ms();
// add file ownership based off the diff
let target_commit = repo.find_commit(default_target.sha)?;
let merge_base_oid = repo.merge_base(target_commit.id(), head_commit.id())?;
@ -235,7 +223,7 @@ impl BranchManager<'_> {
branch.upstream_head = upstream_branch.is_some().then_some(head_commit.id());
branch.upstream = upstream_branch;
branch.tree = head_commit_tree.id();
branch.head = head_commit.id();
branch.set_head(head_commit.id());
branch.ownership = ownership;
branch.order = order;
branch.selected_for_changes = selected_for_changes;
@ -244,25 +232,18 @@ impl BranchManager<'_> {
branch
} else {
Branch {
id: BranchId::generate(),
name: branch_name.clone(),
notes: String::new(),
source_refname: Some(target.clone()),
upstream_head: upstream_branch.is_some().then_some(head_commit.id()),
upstream: upstream_branch,
tree: head_commit_tree.id(),
head: head_commit.id(),
created_timestamp_ms: now,
updated_timestamp_ms: now,
ownership,
let upstream_head = upstream_branch.is_some().then_some(head_commit.id());
Branch::new(
branch_name.clone(),
Some(target.clone()),
upstream_branch,
upstream_head,
head_commit_tree.id(),
head_commit.id(),
order,
selected_for_changes,
allow_rebasing: self.ctx.project().ok_with_force_push.into(),
in_workspace: true,
not_in_workspace_wip_change_id: None,
heads: Default::default(),
}
self.ctx.project().ok_with_force_push.into(),
)
};
vb_state.set_branch(branch.clone())?;
@ -309,10 +290,11 @@ impl BranchManager<'_> {
// if not, we need to merge or rebase the branch to get it up to date
let merge_base = repo
.merge_base(default_target.sha, branch.head)
.merge_base(default_target.sha, branch.head())
.context(format!(
"failed to find merge base between {} and {}",
default_target.sha, branch.head
default_target.sha,
branch.head()
))?;
// Branch is out of date, merge or rebase it
@ -397,7 +379,7 @@ impl BranchManager<'_> {
}
let new_head = if branch.allow_rebasing {
let commits_to_rebase = repo.l(branch.head, LogUntil::Commit(merge_base))?;
let commits_to_rebase = repo.l(branch.head(), LogUntil::Commit(merge_base))?;
let head_oid =
cherry_rebase_group(repo, default_target.sha, &commits_to_rebase, true)?;
@ -414,7 +396,7 @@ impl BranchManager<'_> {
{
gitbutler_merge_commits(
repo,
repo.find_commit(branch.head)?,
repo.find_commit(branch.head())?,
repo.find_commit(default_target.sha)?,
&branch.name,
default_target.branch.branch(),
@ -425,14 +407,14 @@ impl BranchManager<'_> {
} else {
gitbutler_merge_commits(
repo,
repo.find_commit(branch.head)?,
repo.find_commit(branch.head())?,
repo.find_commit(default_target.sha)?,
&branch.name,
default_target.branch.branch(),
)?
};
branch.head = new_head.id();
branch.set_head(new_head.id());
branch.tree = repo.find_real_tree(&new_head, Default::default())?.id();
vb_state.set_branch(branch.clone())?;
@ -445,15 +427,18 @@ impl BranchManager<'_> {
.context("failed to ensure selected for changes")?;
{
if let Some(wip_commit_to_unapply) = branch.not_in_workspace_wip_change_id {
let potential_wip_commit = repo.find_commit(branch.head)?;
if let Some(wip_commit_to_unapply) = &branch.not_in_workspace_wip_change_id {
let potential_wip_commit = repo.find_commit(branch.head())?;
// Don't try to undo commit if its conflicted
if !potential_wip_commit.is_conflicted() {
if let Some(headers) = potential_wip_commit.gitbutler_headers() {
if headers.change_id == wip_commit_to_unapply {
branch =
crate::undo_commit::undo_commit(self.ctx, branch.id, branch.head)?;
if headers.change_id == wip_commit_to_unapply.clone() {
branch = crate::undo_commit::undo_commit(
self.ctx,
branch.id,
branch.head(),
)?;
}
}

View File

@ -108,7 +108,7 @@ impl BranchManager<'_> {
.map(|file| (file.path, file.hunks))
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
let tree_oid =
gitbutler_diff::write::hunks_onto_oid(self.ctx, branch.head, files)?;
gitbutler_diff::write::hunks_onto_oid(self.ctx, branch.head(), files)?;
let branch_tree = repo.find_tree(tree_oid)?;
let mut result =
repo.merge_trees(&base_tree, &final_tree, &branch_tree, None)?;
@ -147,7 +147,7 @@ impl BranchManager<'_> {
#[instrument(level = tracing::Level::DEBUG, skip(self, vbranch), err(Debug))]
fn build_real_branch(&self, vbranch: &mut Branch) -> Result<git2::Branch<'_>> {
let repo = self.ctx.repository();
let target_commit = repo.find_commit(vbranch.head)?;
let target_commit = repo.find_commit(vbranch.head())?;
let branch_name = vbranch.name.clone();
let branch_name = normalize_branch_name(&branch_name)?;
@ -170,7 +170,7 @@ impl BranchManager<'_> {
// Build wip tree as either any uncommitted changes or an empty tree
let vbranch_wip_tree = repo.find_tree(vbranch.tree)?;
let vbranch_head_tree = repo.find_commit(vbranch.head)?.tree()?;
let vbranch_head_tree = repo.find_commit(vbranch.head())?.tree()?;
let tree = if vbranch_head_tree.id() != vbranch_wip_tree.id() {
vbranch_wip_tree

View File

@ -36,7 +36,7 @@ pub fn checkout_branch_trees<'a>(
Ok(tree)
} else {
let merge_base = repository
.merge_base_octopussy(&branches.iter().map(|b| b.head).collect::<Vec<_>>())?;
.merge_base_octopussy(&branches.iter().map(|b| b.head()).collect::<Vec<_>>())?;
let merge_base_tree = repository.find_commit(merge_base)?.tree()?;
@ -92,7 +92,7 @@ pub fn compute_updated_branch_head(
) -> Result<BranchHeadAndTree> {
compute_updated_branch_head_for_commits(
repository,
branch.head,
branch.head(),
branch.tree,
new_head,
fearless_rebasing,

View File

@ -46,12 +46,12 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
let merge_parent = conflicts::merge_parent(ctx)?.ok_or(anyhow!("No merge parent"))?;
let first_branch = virtual_branches.first().ok_or(anyhow!("No branches"))?;
let merge_base = repo.merge_base(first_branch.head, merge_parent)?;
let merge_base = repo.merge_base(first_branch.head(), merge_parent)?;
workspace_tree = repo.find_commit(merge_base)?.tree()?;
} else {
for branch in virtual_branches.iter_mut() {
let merge_tree = repo.find_commit(target.sha)?.tree()?;
let branch_tree = repo.find_commit(branch.head)?;
let branch_tree = repo.find_commit(branch.head())?;
let branch_tree = repo.find_real_tree(&branch_tree, Default::default())?;
let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
@ -71,8 +71,8 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
let author = gitbutler_branch::signature(SignaturePurpose::Author)?;
let mut heads: Vec<git2::Commit<'_>> = virtual_branches
.iter()
.filter(|b| b.head != target.sha)
.map(|b| repo.find_commit(b.head))
.filter(|b| b.head() != target.sha)
.map(|b| repo.find_commit(b.head()))
.filter_map(Result::ok)
.collect();
@ -183,9 +183,9 @@ pub fn update_workspace_commit(
message.push_str(format!(" ({})", &branch.refname()?).as_str());
message.push('\n');
if branch.head != target.sha {
if branch.head() != target.sha {
message.push_str(" branch head: ");
message.push_str(&branch.head.to_string());
message.push_str(&branch.head().to_string());
message.push('\n');
}
for file in &branch.ownership.claims {
@ -240,7 +240,7 @@ pub fn update_workspace_commit(
// finally, update the refs/gitbutler/ heads to the states of the current virtual branches
for branch in &virtual_branches {
let wip_tree = repo.find_tree(branch.tree)?;
let mut branch_head = repo.find_commit(branch.head)?;
let mut branch_head = repo.find_commit(branch.head())?;
let head_tree = branch_head.tree()?;
// create a wip commit if there is wip
@ -371,7 +371,7 @@ fn verify_head_is_clean(ctx: &CommandContext, perm: &mut WorktreeWritePermission
// rebasing the extra commits onto the new branch
let vb_state = ctx.project().virtual_branches();
// let mut head = new_branch.head;
let mut head = new_branch.head;
let mut head = new_branch.head();
for commit in extra_commits {
let new_branch_head = ctx
.repository()
@ -402,7 +402,7 @@ fn verify_head_is_clean(ctx: &CommandContext, perm: &mut WorktreeWritePermission
rebased_commit_oid
))?;
new_branch.head = rebased_commit.id();
new_branch.set_head(rebased_commit.id());
new_branch.tree = rebased_commit.tree_id();
vb_state
.set_branch(new_branch.clone())

View File

@ -32,7 +32,7 @@ pub(crate) fn move_commit(
let (ref mut source_branch, source_status) = applied_statuses
.iter_mut()
.find(|(b, _)| b.head == commit_id)
.find(|(b, _)| b.head() == commit_id)
.ok_or_else(|| anyhow!("commit {commit_id} to be moved could not be found"))?;
let source_branch_non_comitted_files = source_status;
@ -116,17 +116,17 @@ pub(crate) fn move_commit(
let new_destination_head_oid = cherry_rebase_group(
ctx.repository(),
destination_branch.head,
destination_branch.head(),
&[source_commit.id()],
true,
)?;
// reset the source branch to the parent commit
// and update the destination branch head
source_branch.head = source_branch_head_parent.id();
source_branch.set_head(source_branch_head_parent.id());
vb_state.set_branch(source_branch.clone())?;
destination_branch.head = new_destination_head_oid;
destination_branch.set_head(new_destination_head_oid);
vb_state.set_branch(destination_branch.clone())?;
checkout_branch_trees(ctx, perm)?;

View File

@ -58,13 +58,13 @@ pub(crate) fn reorder_commit(
merge_base,
subject_commit_oid,
offset,
&repository.l(branch.head, LogUntil::Commit(merge_base))?,
&repository.l(branch.head(), LogUntil::Commit(merge_base))?,
&repository.find_tree(branch.tree)?,
ctx.project().succeeding_rebases,
)?;
branch.tree = tree;
branch.head = head;
branch.set_head(head);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;

View File

@ -122,7 +122,7 @@ pub fn push_stack(project: &Project, branch_id: BranchId, with_force: bool) -> R
let repo = ctx.repository();
let merge_base =
repo.find_commit(repo.merge_base(stack.head, state.get_default_target()?.sha)?)?;
repo.find_commit(repo.merge_base(stack.head(), state.get_default_target()?.sha)?)?;
let merge_base = if let Some(change_id) = merge_base.change_id() {
CommitOrChangeId::ChangeId(change_id)
} else {

View File

@ -218,7 +218,7 @@ pub fn get_applied_status_cached(
// write updated state if not resolving
if !ctx.is_resolving() {
for (vbranch, files) in &mut hunks_by_branch {
vbranch.tree = gitbutler_diff::write::hunks_onto_oid(ctx, vbranch.head, files)?;
vbranch.tree = gitbutler_diff::write::hunks_onto_oid(ctx, vbranch.head(), files)?;
vb_state
.set_branch(vbranch.clone())
.context(format!("failed to write virtual branch {}", vbranch.name))?;
@ -261,7 +261,7 @@ fn compute_locks(
let branch_path_diffs = virtual_branches
.iter()
.filter_map(|branch| {
let commit = repository.find_commit(branch.head).ok()?;
let commit = repository.find_commit(branch.head()).ok()?;
let tree = repository
.find_real_tree(&commit, Default::default())
.ok()?;
@ -319,7 +319,7 @@ fn compute_locks(
.iter()
.map(|b| HunkLock {
branch_id: b.id,
commit_id: b.head,
commit_id: b.head(),
})
.collect::<Vec<_>>();

View File

@ -29,12 +29,12 @@ pub(crate) fn undo_commit(
let new_head_commit = inner_undo_commit(
ctx.repository(),
branch.head,
branch.head(),
commit_oid,
succeeding_rebases,
)?;
branch.head = new_head_commit;
branch.set_head(new_head_commit);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;

View File

@ -150,14 +150,14 @@ pub fn upstream_integration_statuses(
.iter()
.map(|virtual_branch| {
let tree = repository.find_tree(virtual_branch.tree)?;
let head = repository.find_commit(virtual_branch.head)?;
let head = repository.find_commit(virtual_branch.head())?;
let head_tree = repository.find_real_tree(&head, Default::default())?;
// Try cherry pick the branch's head commit onto the target to
// see if it conflics. This is equivalent to doing a merge
// but accounts for the commit being conflicted.
let has_commits = virtual_branch.head != old_target.id();
let has_commits = virtual_branch.head() != old_target.id();
let has_uncommited_changes = head_tree.id() != tree.id();
// Is the branch completly empty?
@ -320,7 +320,7 @@ pub(crate) fn integrate_upstream(
continue;
};
branch.head = *head;
branch.set_head(*head);
branch.tree = *tree;
virtual_branches_state.set_branch(branch.clone())?;
@ -422,7 +422,7 @@ fn compute_resolutions(
// Make a merge commit on top of the branch commits,
// then rebase the tree ontop of that. If the tree ends
// up conflicted, commit the tree.
let target_commit = repository.find_commit(virtual_branch.head)?;
let target_commit = repository.find_commit(virtual_branch.head())?;
let new_head = gitbutler_merge_commits(
repository,
@ -465,7 +465,7 @@ fn compute_resolutions(
// Rebase virtual branches' commits
let virtual_branch_commits =
repository.l(virtual_branch.head, LogUntil::Commit(lower_bound))?;
repository.l(virtual_branch.head(), LogUntil::Commit(lower_bound))?;
let new_head = cherry_rebase_group(
repository,
@ -497,33 +497,27 @@ fn compute_resolutions(
#[cfg(test)]
mod test {
use gitbutler_branch::BranchOwnershipClaims;
use gitbutler_commit::commit_ext::CommitExt as _;
use gitbutler_testsupport::testing_repository::TestingRepository;
use uuid::Uuid;
use super::*;
fn make_branch(head: git2::Oid, tree: git2::Oid) -> Branch {
Branch {
id: Uuid::new_v4().into(),
name: "branchy branch".into(),
notes: "bla bla bla".into(),
source_refname: None,
upstream: None,
upstream_head: None,
created_timestamp_ms: 69420,
updated_timestamp_ms: 69420,
let mut branch = Branch::new(
"branchy branch".into(),
None,
None,
None,
tree,
head,
ownership: BranchOwnershipClaims::default(),
order: 0,
selected_for_changes: None,
allow_rebasing: true,
in_workspace: true,
not_in_workspace_wip_change_id: None,
heads: Default::default(),
}
0,
None,
true,
);
branch.created_timestamp_ms = 69420;
branch.updated_timestamp_ms = 69420;
branch.notes = "bla bla bla".into();
branch
}
#[test]

View File

@ -343,7 +343,7 @@ pub fn list_virtual_branches_cached(
let mut is_remote = false;
// find all commits on head that are not on target.sha
let commits = repo.log(branch.head, LogUntil::Commit(default_target.sha))?;
let commits = repo.log(branch.head(), LogUntil::Commit(default_target.sha))?;
let check_commit = IsCommitIntegrated::new(ctx, &default_target)?;
let vbranch_commits = {
let _span = tracing::debug_span!(
@ -385,7 +385,7 @@ pub fn list_virtual_branches_cached(
};
let merge_base = repo
.merge_base(default_target.sha, branch.head)
.merge_base(default_target.sha, branch.head())
.context("failed to find merge base")?;
let base_current = true;
@ -426,6 +426,8 @@ pub fn list_virtual_branches_cached(
vec![]
}
};
let head = branch.head();
let branch = VirtualBranch {
id: branch.id,
name: branch.name,
@ -445,7 +447,7 @@ pub fn list_virtual_branches_cached(
updated_at: branch.updated_timestamp_ms,
selected_for_changes: branch.selected_for_changes == Some(max_selected_for_changes),
allow_rebasing: branch.allow_rebasing,
head: branch.head,
head,
merge_base,
fork_point,
refname,
@ -482,7 +484,7 @@ fn stack_series(
});
let mut patches: Vec<VirtualBranchCommit> = vec![];
for patch in series.clone().local_commits {
let commit = commit_by_oid_or_change_id(&patch, ctx, branch.head, default_target)?;
let commit = commit_by_oid_or_change_id(&patch, ctx, branch.head(), default_target)?;
let is_integrated = check_commit.is_integrated(&commit)?;
let vcommit = commit_to_vbranch_commit(
ctx,
@ -497,7 +499,7 @@ fn stack_series(
patches.reverse();
let mut upstream_patches = vec![];
for patch in series.upstream_only(&stack_series) {
let commit = commit_by_oid_or_change_id(&patch, ctx, branch.head, default_target)?;
let commit = commit_by_oid_or_change_id(&patch, ctx, branch.head(), default_target)?;
let is_integrated = check_commit.is_integrated(&commit)?;
let vcommit = commit_to_vbranch_commit(
ctx,
@ -580,7 +582,7 @@ fn is_requires_force(ctx: &CommandContext, branch: &Branch) -> Result<bool> {
let merge_base = ctx
.repository()
.merge_base(upstream_commit.id(), branch.head)?;
.merge_base(upstream_commit.id(), branch.head())?;
Ok(merge_base != upstream_commit.id())
}
@ -624,12 +626,12 @@ pub fn integrate_upstream_commits(ctx: &CommandContext, branch_id: BranchId) ->
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 {
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_commits = repo.list_commits(branch.head(), default_target.sha)?;
let branch_commit_ids = branch_commits.iter().map(|c| c.id()).collect::<Vec<_>>();
@ -716,7 +718,7 @@ pub fn integrate_upstream_commits(ctx: &CommandContext, branch_id: BranchId) ->
.force()
.checkout()?;
} else {
branch.head = new_head;
branch.set_head(new_head);
branch.tree = head_commit.tree()?.id();
vb_state.set_branch(branch.clone())?;
repo.checkout_index_builder(&mut merge_index)
@ -735,7 +737,7 @@ pub(crate) fn integrate_with_rebase(
) -> Result<git2::Oid> {
cherry_rebase_group(
ctx.repository(),
branch.head,
branch.head(),
unknown_commits.as_mut_slice(),
ctx.project().succeeding_rebases,
)
@ -775,7 +777,7 @@ pub(crate) fn integrate_with_merge(
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)?;
let head_commit = repo.find_commit(branch.head())?;
ctx.commit(
format!(
@ -937,7 +939,7 @@ pub(crate) fn reset_branch(
let default_target = vb_state.get_default_target()?;
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
if branch.head == target_commit_id {
if branch.head() == target_commit_id {
// nothing to do
return Ok(());
}
@ -945,7 +947,7 @@ pub(crate) fn reset_branch(
if default_target.sha != target_commit_id
&& !ctx
.repository()
.l(branch.head, LogUntil::Commit(default_target.sha))?
.l(branch.head(), LogUntil::Commit(default_target.sha))?
.contains(&target_commit_id)
{
bail!("commit {target_commit_id} not in the branch");
@ -955,7 +957,7 @@ pub(crate) fn reset_branch(
// what hunks were released by this reset, and assign them to this branch.
let old_head = get_workspace_head(ctx)?;
branch.head = target_commit_id;
branch.set_head(target_commit_id);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;
@ -1076,19 +1078,19 @@ pub fn commit(
Some((file.path, hunks))
}
});
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head, files)?
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head(), files)?
} else {
let files = files
.into_iter()
.map(|file| (file.path, file.hunks))
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head, files)?
gitbutler_diff::write::hunks_onto_commit(ctx, branch.head(), files)?
};
let git_repository = ctx.repository();
let parent_commit = git_repository
.find_commit(branch.head)
.context(format!("failed to find commit {:?}", branch.head))?;
.find_commit(branch.head())
.context(format!("failed to find commit {:?}", branch.head()))?;
let tree = git_repository
.find_tree(tree_oid)
.context(format!("failed to find tree {:?}", tree_oid))?;
@ -1120,7 +1122,7 @@ pub fn commit(
let vb_state = ctx.project().virtual_branches();
branch.tree = tree_oid;
branch.head = commit_oid;
branch.set_head(commit_oid);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;
if let Err(e) = branch.set_stack_head(ctx, commit_oid) {
@ -1177,10 +1179,10 @@ pub(crate) fn push(
))
};
ctx.push(vbranch.head, &remote_branch, with_force, None, askpass)?;
ctx.push(vbranch.head(), &remote_branch, with_force, None, askpass)?;
vbranch.upstream = Some(remote_branch.clone());
vbranch.upstream_head = Some(vbranch.head);
vbranch.upstream_head = Some(vbranch.head());
vb_state
.set_branch(vbranch.clone())
.context("failed to write target branch after push")?;
@ -1343,7 +1345,7 @@ pub(crate) fn move_commit_file(
// find all the commits upstream from the target "to" commit
let mut upstream_commits = ctx
.repository()
.l(target_branch.head, LogUntil::Commit(amend_commit.id()))?;
.l(target_branch.head(), LogUntil::Commit(amend_commit.id()))?;
// get a list of all the diffs across all the virtual branches
let base_file_diffs = gitbutler_diff::workdir(ctx.repository(), default_target.sha)
@ -1457,19 +1459,23 @@ pub(crate) fn move_commit_file(
.context("commit failed")?;
// rebase everything above the new "from" commit that has the moved changes removed
let new_head =
match cherry_rebase(ctx, new_from_commit_oid, from_commit_id, target_branch.head) {
Ok(Some(new_head)) => new_head,
Ok(None) => bail!("no rebase was performed"),
Err(err) => return Err(err).context("rebase failed"),
};
let new_head = match cherry_rebase(
ctx,
new_from_commit_oid,
from_commit_id,
target_branch.head(),
) {
Ok(Some(new_head)) => new_head,
Ok(None) => bail!("no rebase was performed"),
Err(err) => return Err(err).context("rebase failed"),
};
// ok, now we need to identify which the new "to" commit is in the rebased history
// so we'll take a list of the upstream oids and find it simply based on location
// (since the order should not have changed in our simple rebase)
let old_upstream_commit_oids = ctx
.repository()
.l(target_branch.head, LogUntil::Commit(default_target.sha))?;
.l(target_branch.head(), LogUntil::Commit(default_target.sha))?;
let new_upstream_commit_oids = ctx
.repository()
@ -1529,7 +1535,7 @@ pub(crate) fn move_commit_file(
// if there are no upstream commits (the "to" commit was the branch head), then we're done
if upstream_commits.is_empty() {
target_branch.head = commit_oid;
target_branch.set_head(commit_oid);
vb_state.set_branch(target_branch.clone())?;
crate::integration::update_workspace_commit(&vb_state, ctx)?;
return Ok(commit_oid);
@ -1541,7 +1547,7 @@ pub(crate) fn move_commit_file(
// if that rebase worked, update the branch head and the gitbutler workspace
if let Some(new_head) = new_head {
target_branch.head = new_head;
target_branch.set_head(new_head);
vb_state.set_branch(target_branch.clone())?;
crate::integration::update_workspace_commit(&vb_state, ctx)?;
Ok(commit_oid)
@ -1586,7 +1592,7 @@ pub(crate) fn amend(
if ctx
.repository()
.l(target_branch.head, LogUntil::Commit(default_target.sha))?
.l(target_branch.head(), LogUntil::Commit(default_target.sha))?
.is_empty()
{
bail!("branch has no commits - there is nothing to amend to");
@ -1654,10 +1660,10 @@ pub(crate) fn amend(
// now rebase upstream commits, if needed
let upstream_commits = ctx
.repository()
.l(target_branch.head, LogUntil::Commit(amend_commit.id()))?;
.l(target_branch.head(), LogUntil::Commit(amend_commit.id()))?;
// if there are no upstream commits, we're done
if upstream_commits.is_empty() {
target_branch.head = commit_oid;
target_branch.set_head(commit_oid);
vb_state.set_branch(target_branch.clone())?;
crate::integration::update_workspace_commit(&vb_state, ctx)?;
return Ok(commit_oid);
@ -1668,7 +1674,7 @@ pub(crate) fn amend(
let new_head = cherry_rebase(ctx, commit_oid, amend_commit.id(), last_commit)?;
if let Some(new_head) = new_head {
target_branch.head = new_head;
target_branch.set_head(new_head);
vb_state.set_branch(target_branch.clone())?;
crate::integration::update_workspace_commit(&vb_state, ctx)?;
Ok(commit_oid)
@ -1706,16 +1712,16 @@ pub(crate) fn insert_blank_commit(
.unwrap();
let blank_commit_oid = ctx.commit("", &commit_tree, &[&commit], None)?;
if commit.id() == branch.head && offset < 0 {
if commit.id() == branch.head() && offset < 0 {
// inserting before the first commit
branch.head = blank_commit_oid;
branch.set_head(blank_commit_oid);
crate::integration::update_workspace_commit(&vb_state, ctx)
.context("failed to update gitbutler workspace")?;
} else {
// rebase all commits above it onto the new commit
match cherry_rebase(ctx, blank_commit_oid, commit.id(), branch.head) {
match cherry_rebase(ctx, blank_commit_oid, commit.id(), branch.head()) {
Ok(Some(new_head)) => {
branch.head = new_head;
branch.set_head(new_head);
crate::integration::update_workspace_commit(&vb_state, ctx)
.context("failed to update gitbutler workspace")?;
}
@ -1744,7 +1750,7 @@ pub(crate) fn squash(
let default_target = vb_state.get_default_target()?;
let branch_commit_oids = ctx
.repository()
.l(branch.head, LogUntil::Commit(default_target.sha))?;
.l(branch.head(), LogUntil::Commit(default_target.sha))?;
if !branch_commit_oids.contains(&commit_id) {
bail!("commit {commit_id} not in the branch")
@ -1821,7 +1827,7 @@ pub(crate) fn squash(
) {
Ok(new_head_id) => {
// save new branch head
branch.head = new_head_id;
branch.set_head(new_head_id);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;
@ -1851,7 +1857,7 @@ pub(crate) fn update_commit_message(
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
let branch_commit_oids = ctx
.repository()
.l(branch.head, LogUntil::Commit(default_target.sha))?;
.l(branch.head(), LogUntil::Commit(default_target.sha))?;
if !branch_commit_oids.contains(&commit_id) {
bail!("commit {commit_id} not in the branch");
@ -1907,7 +1913,7 @@ pub(crate) fn update_commit_message(
)
.map_err(|err| err.context("rebase error"))?;
// save new branch head
branch.head = new_head_id;
branch.set_head(new_head_id);
branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state.set_branch(branch.clone())?;

View File

@ -845,7 +845,7 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
.expect("failed to create virtual branch");
branch.upstream = Some(remote_branch.clone());
branch.head = last_push;
branch.set_head(last_push);
vb_state.set_branch(branch.clone())?;
// create the branch
@ -970,7 +970,7 @@ fn merge_vbranch_upstream_conflict() -> Result<()> {
.create_virtual_branch(&BranchCreateRequest::default(), guard.write_permission())
.expect("failed to create virtual branch");
branch.upstream = Some(remote_branch.clone());
branch.head = last_push;
branch.set_head(last_push);
vb_state.set_branch(branch.clone())?;
internal::update_branch(

View File

@ -1,30 +1,25 @@
use gitbutler_branch::{Branch, BranchOwnershipClaims};
use uuid::Uuid;
use gitbutler_branch::Branch;
/// Makes a Branch struct with a bunch of default values.
///
/// This assumes that the only relevant properties for your test are the head
/// and tree Oids.
fn make_branch(head: git2::Oid, tree: git2::Oid) -> Branch {
Branch {
id: Uuid::new_v4().into(),
name: "branchy branch".into(),
notes: "bla bla bla".into(),
source_refname: None,
upstream: None,
upstream_head: None,
created_timestamp_ms: 69420,
updated_timestamp_ms: 69420,
let mut branch = Branch::new(
"branchy branch".into(),
None,
None,
None,
tree,
head,
ownership: BranchOwnershipClaims::default(),
order: 0,
selected_for_changes: None,
allow_rebasing: true,
in_workspace: true,
not_in_workspace_wip_change_id: None,
heads: Default::default(),
}
0,
None,
true,
);
branch.created_timestamp_ms = 69420;
branch.updated_timestamp_ms = 69420;
branch.notes = "bla bla bla".into();
branch
}
#[cfg(test)]
@ -52,7 +47,7 @@ mod compute_updated_branch_head {
compute_updated_branch_head(&test_repository.repository, &branch, head.id(), true)
.unwrap();
assert_eq!(head, branch.head);
assert_eq!(head, branch.head());
assert_eq!(tree, branch.tree);
}

View File

@ -17,6 +17,7 @@ gitbutler-fs.workspace = true
gitbutler-diff.workspace = true
gitbutler-oxidize.workspace = true
gitbutler-patch-reference.workspace = true
gitbutler-time.workspace = true
itertools = "0.13"
toml.workspace = true
serde = { workspace = true, features = ["std"] }

View File

@ -44,7 +44,7 @@ pub struct Branch {
pub tree: git2::Oid,
/// head is id of the last "virtual" commit in this branch
#[serde(with = "gitbutler_serde::oid")]
pub head: git2::Oid,
head: git2::Oid,
pub ownership: BranchOwnershipClaims,
// order is the number by which UI should sort branches
pub order: usize,
@ -86,9 +86,52 @@ where
}
impl Branch {
/// Creates a new `Branch` with the given name. The `in_workspace` flag is set to `true`.
#[allow(clippy::too_many_arguments)]
pub fn new(
name: String,
source_refname: Option<Refname>,
upstream: Option<RemoteRefname>,
upstream_head: Option<git2::Oid>,
tree: git2::Oid,
head: git2::Oid,
order: usize,
selected_for_changes: Option<i64>,
allow_rebasing: bool,
) -> Self {
let now = gitbutler_time::time::now_ms();
Self {
id: BranchId::generate(),
name,
notes: String::new(),
source_refname,
upstream,
upstream_head,
created_timestamp_ms: now,
updated_timestamp_ms: now,
tree,
head,
ownership: BranchOwnershipClaims::default(),
order,
selected_for_changes,
allow_rebasing,
in_workspace: true,
not_in_workspace_wip_change_id: None,
heads: Default::default(),
}
}
pub fn refname(&self) -> anyhow::Result<VirtualRefname> {
self.try_into()
}
pub fn head(&self) -> git2::Oid {
self.head
}
pub fn set_head(&mut self, head: git2::Oid) {
self.head = head;
}
}
impl TryFrom<&Branch> for VirtualRefname {

View File

@ -119,14 +119,12 @@ pub fn reconcile_claims(
});
}
let mut updated_branch = claiming_branch.clone();
updated_branch.ownership.claims = new_claims.to_owned();
// Add the claiming branch to the list of outcomes
claim_outcomes.push(ClaimOutcome {
updated_branch: Branch {
ownership: BranchOwnershipClaims {
claims: new_claims.to_owned(),
},
..claiming_branch.clone()
},
updated_branch,
removed_claims: Vec::new(),
});

View File

@ -1,73 +1,65 @@
use std::{path::PathBuf, vec};
use gitbutler_branch::{reconcile_claims, Branch, BranchId, BranchOwnershipClaims, OwnershipClaim};
use gitbutler_branch::{reconcile_claims, Branch, BranchOwnershipClaims, OwnershipClaim};
use gitbutler_diff::Hunk;
#[test]
fn reconcile_ownership_simple() {
let branch_a = Branch {
name: "a".to_string(),
ownership: BranchOwnershipClaims {
claims: vec![OwnershipClaim {
file_path: PathBuf::from("foo"),
hunks: vec![
Hunk {
start: 1,
end: 3,
hash: Some(Hunk::hash("1,3")),
},
Hunk {
start: 4,
end: 6,
hash: Some(Hunk::hash("4,6")),
},
],
}],
},
tree: git2::Oid::zero(),
head: git2::Oid::zero(),
id: BranchId::default(),
notes: String::default(),
upstream: None,
upstream_head: None,
created_timestamp_ms: u128::default(),
updated_timestamp_ms: u128::default(),
order: usize::default(),
selected_for_changes: None,
allow_rebasing: true,
in_workspace: true,
not_in_workspace_wip_change_id: None,
source_refname: None,
heads: Default::default(),
let mut branch_a = Branch::new(
"a".to_string(),
None,
None,
None,
git2::Oid::zero(),
git2::Oid::zero(),
usize::default(),
None,
true,
);
branch_a.ownership = BranchOwnershipClaims {
claims: vec![OwnershipClaim {
file_path: PathBuf::from("foo"),
hunks: vec![
Hunk {
start: 1,
end: 3,
hash: Some(Hunk::hash("1,3")),
},
Hunk {
start: 4,
end: 6,
hash: Some(Hunk::hash("4,6")),
},
],
}],
};
let branch_b = Branch {
name: "b".to_string(),
ownership: BranchOwnershipClaims {
claims: vec![OwnershipClaim {
file_path: PathBuf::from("foo"),
hunks: vec![Hunk {
start: 7,
end: 9,
hash: Some(Hunk::hash("7,9")),
}],
branch_a.created_timestamp_ms = u128::default();
branch_a.updated_timestamp_ms = u128::default();
let mut branch_b = Branch::new(
"b".to_string(),
None,
None,
None,
git2::Oid::zero(),
git2::Oid::zero(),
usize::default(),
None,
true,
);
branch_b.ownership = BranchOwnershipClaims {
claims: vec![OwnershipClaim {
file_path: PathBuf::from("foo"),
hunks: vec![Hunk {
start: 7,
end: 9,
hash: Some(Hunk::hash("7,9")),
}],
},
tree: git2::Oid::zero(),
head: git2::Oid::zero(),
id: BranchId::default(),
notes: String::default(),
upstream: None,
upstream_head: None,
created_timestamp_ms: u128::default(),
updated_timestamp_ms: u128::default(),
order: usize::default(),
selected_for_changes: None,
allow_rebasing: true,
in_workspace: true,
not_in_workspace_wip_change_id: None,
source_refname: None,
heads: Default::default(),
}],
};
branch_b.created_timestamp_ms = u128::default();
branch_b.updated_timestamp_ms = u128::default();
let all_branches: Vec<Branch> = vec![branch_a.clone(), branch_b.clone()];
let claim: Vec<OwnershipClaim> = vec![OwnershipClaim {
file_path: PathBuf::from("foo"),

View File

@ -216,7 +216,7 @@ pub(crate) fn save_and_return_to_workspace(
.context("Failed to commit new commit")?;
// Rebase all all commits on top of the new commit and update reference
let new_branch_head = cherry_rebase(ctx, new_commit_oid, commit.id(), virtual_branch.head)
let new_branch_head = cherry_rebase(ctx, new_commit_oid, commit.id(), virtual_branch.head())
.context("Failed to rebase commits onto new commit")?
.unwrap_or(new_commit_oid);
@ -232,7 +232,7 @@ pub(crate) fn save_and_return_to_workspace(
fearless_rebasing,
)?;
virtual_branch.head = new_branch_head;
virtual_branch.set_head(new_branch_head);
virtual_branch.tree = new_branch_tree;
virtual_branch.updated_timestamp_ms = gitbutler_time::time::now_ms();
vb_state

View File

@ -372,7 +372,7 @@ fn prepare_snapshot(ctx: &Project, _shared_access: &WorktreeReadPermission) -> R
// let's get all the commits between the branch head and the target
let mut revwalk = repo.revwalk()?;
revwalk.push(branch.head)?;
revwalk.push(branch.head())?;
revwalk.hide(default_target_commit.id())?;
let mut commits_tree_builder = repo.treebuilder(None)?;

View File

@ -79,7 +79,7 @@ impl RepoActionsExt for CommandContext {
.find_reference(&branch.refname()?.to_string())
{
Ok(reference) => match reference.target() {
Some(head_oid) => Ok((head_oid != branch.head, true)),
Some(head_oid) => Ok((head_oid != branch.head(), true)),
None => Ok((true, true)),
},
Err(err) => match err.code() {
@ -93,7 +93,7 @@ impl RepoActionsExt for CommandContext {
self.repository()
.reference(
&branch.refname()?.to_string(),
branch.head,
branch.head(),
with_force,
"new vbranch",
)

View File

@ -147,7 +147,7 @@ impl Stack for Branch {
if self.initialized() {
return Ok(());
}
let commit = ctx.repository().find_commit(self.head)?;
let commit = ctx.repository().find_commit(self.head())?;
let mut reference = PatchReference {
target: if let Some(change_id) = commit.change_id() {
CommitOrChangeId::ChangeId(change_id.to_string())
@ -199,9 +199,9 @@ impl Stack for Branch {
None
};
let state = branch_state(ctx);
let patches = stack_patches(ctx, &state, self.head, true)?;
let patches = stack_patches(ctx, &state, self.head(), true)?;
validate_name(&new_head, ctx, &state)?;
validate_target(&new_head, ctx, self.head, &state)?;
validate_target(&new_head, ctx, self.head(), &state)?;
let updated_heads = add_head(self.heads.clone(), new_head, preceding_head, patches)?;
self.heads = updated_heads;
state.set_branch(self.clone())
@ -240,7 +240,7 @@ impl Stack for Branch {
if !self.initialized() {
return Err(anyhow!("Stack has not been initialized"));
}
if self.head != commit_id {
if self.head() != commit_id {
bail!("The commit {} is not the head of the stack", commit_id);
}
let commit = ctx.repository().find_commit(commit_id)?;
@ -251,12 +251,13 @@ impl Stack for Branch {
};
let state = branch_state(ctx);
let stack_head = self.head();
let head = self
.heads
.last_mut()
.ok_or_else(|| anyhow!("Invalid state: no heads found"))?;
head.target = patch.clone();
validate_target(head, ctx, self.head, &state)?;
validate_target(head, ctx, stack_head, &state)?;
state.set_branch(self.clone())
}
@ -274,7 +275,7 @@ impl Stack for Branch {
}
let state = branch_state(ctx);
let patches = stack_patches(ctx, &state, self.head, true)?;
let patches = stack_patches(ctx, &state, self.head(), true)?;
let mut updated_heads = self.heads.clone();
// Handle target updates
@ -285,7 +286,7 @@ impl Stack for Branch {
.find(|h| h.name == branch_name)
.ok_or_else(|| anyhow!("Series with name {} not found", branch_name))?;
new_head.target = target_update.target.clone();
validate_target(&new_head, ctx, self.head, &state)?;
validate_target(&new_head, ctx, self.head(), &state)?;
let preceding_head = update
.target_update
.clone()
@ -345,7 +346,7 @@ impl Stack for Branch {
let (_, reference) = get_head(&self.heads, &branch_name)?;
let default_target = branch_state(ctx).get_default_target()?;
let commit =
commit_by_oid_or_change_id(&reference.target, ctx, self.head, &default_target)?;
commit_by_oid_or_change_id(&reference.target, ctx, self.head(), &default_target)?;
let remote_name = branch_state(ctx)
.get_default_target()?
.push_remote_name
@ -373,10 +374,10 @@ impl Stack for Branch {
let mut all_series: Vec<Series> = vec![];
let repo = ctx.repository();
let default_target = state.get_default_target()?;
let mut previous_head = repo.merge_base(self.head, default_target.sha)?;
let mut previous_head = repo.merge_base(self.head(), default_target.sha)?;
for head in self.heads.clone() {
let head_commit =
commit_by_oid_or_change_id(&head.target, ctx, self.head, &default_target)?.id();
commit_by_oid_or_change_id(&head.target, ctx, self.head(), &default_target)?.id();
let local_patches = repo
.log(head_commit, LogUntil::Commit(previous_head))?
.iter()

View File

@ -22,7 +22,7 @@ fn init_success() -> Result<()> {
branch.heads[0].target,
CommitOrChangeId::ChangeId(
ctx.repository()
.find_commit(branch.head)?
.find_commit(branch.head())?
.change_id()
.unwrap()
)
@ -101,7 +101,7 @@ fn add_series_top_base() -> Result<()> {
test_ctx.branch.initialize(&ctx)?;
let merge_base = ctx.repository().find_commit(
ctx.repository()
.merge_base(test_ctx.branch.head, test_ctx.default_target.sha)?,
.merge_base(test_ctx.branch.head(), test_ctx.default_target.sha)?,
)?;
let reference = PatchReference {
name: "asdf".into(),
@ -772,11 +772,11 @@ fn test_ctx(ctx: &CommandContext) -> Result<TestContext> {
let target = handle.get_default_target()?;
let mut branch_commits = ctx
.repository()
.log(branch.head, LogUntil::Commit(target.sha))?;
.log(branch.head(), LogUntil::Commit(target.sha))?;
branch_commits.reverse();
let mut other_commits = ctx
.repository()
.log(other_branch.head, LogUntil::Commit(target.sha))?;
.log(other_branch.head(), LogUntil::Commit(target.sha))?;
other_commits.reverse();
Ok(TestContext {
branch: branch.clone(),