Merge pull request #5540 from gitbutlerapp/remove-unused-code

remove unused code
This commit is contained in:
Kiril Videlov 2024-11-13 17:38:41 +01:00 committed by GitHub
commit 541a5cf0da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,600 +0,0 @@
use anyhow::{bail, Context as _, Result};
use gitbutler_command_context::CommandContext;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::{rebase::cherry_rebase_group, LogUntil, RepositoryExt as _};
use gitbutler_stack::StackId;
use crate::{
branch_trees::{
checkout_branch_trees, compute_updated_branch_head_for_commits, BranchHeadAndTree,
},
VirtualBranchesExt as _,
};
/// Moves a commit up or down a stack by a certain offset.
///
/// After the commit is moved, the combined branch trees are checked out.
/// A stack must have at least two commits in it. A 0 offset is a no-op.
///
/// Presume we had the stack:
///
/// A
/// B
/// C
/// D
///
/// If C was the subject, and the offset was -1, we would expect:
///
/// A
/// C
/// B
/// D
///
/// Or, if B was the subject, and the offset was 1, we would expect:
///
/// A
/// C
/// B
/// D
pub(crate) fn reorder_commit(
ctx: &CommandContext,
branch_id: StackId,
subject_commit_oid: git2::Oid,
offset: i32,
perm: &mut WorktreeWritePermission,
) -> Result<()> {
let repository = ctx.repository();
let vb_state = ctx.project().virtual_branches();
let default_target = vb_state.get_default_target()?;
let default_target_commit = repository
.find_reference(&default_target.branch.to_string())?
.peel_to_commit()?;
let merge_base = repository.merge_base(default_target_commit.id(), subject_commit_oid)?;
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
let original_commits = repository.l(branch.head(), LogUntil::Commit(merge_base), false)?;
let ReorderResult {
tree,
head,
reordered_commits,
} = inner_reorder_commit(
repository,
merge_base,
subject_commit_oid,
offset,
&original_commits,
&repository.find_tree(branch.tree)?,
)?;
let mut old_oid = None;
for (idx, oid) in reordered_commits.iter().enumerate() {
if *oid == subject_commit_oid {
old_oid = reordered_commits.get(idx + 1);
break;
}
}
let subject_commit = repository.find_commit(subject_commit_oid)?;
if let Some(old_oid) = old_oid {
let old_commit = repository.find_commit(*old_oid)?;
branch.replace_head(ctx, &old_commit, &subject_commit)?;
}
branch.replace_head(ctx, &subject_commit, &subject_commit.parent(0)?)?;
branch.set_stack_head(ctx, head, Some(tree))?;
checkout_branch_trees(ctx, perm)?;
crate::integration::update_workspace_commit(&vb_state, ctx)
.context("failed to update gitbutler workspace")?;
Ok(())
}
struct ReorderResult {
tree: git2::Oid,
head: git2::Oid,
reordered_commits: Vec<git2::Oid>,
}
/// Commit reordering implemented with libgit2 primatives
///
/// This returns a new head commit and tree for the branch.
///
/// If the tree ends up in a conflicted state when rebasing the head commit
/// will actually be the commited tree in a conflicted state, and the tree
/// will be the auto-resolved tree.
///
/// After usage (which should only be in `reorder_commit`), its important to
/// re-merge all the branch trees together and check that out, as otherwise
/// the tree writes will be lost.
///
/// * `base_commit`: The merge base of the branch head and trunk
/// * `subject_commit`: The commit to be moved
/// * `offset`: The offset to move the subject commit by, where
/// negative is up (towards the branch head) and positive is down
/// * `branch_commits`: The commits all the commits in the branch.
/// * `branch_tree`: The tree of the branch
fn inner_reorder_commit(
repository: &git2::Repository,
base_commit: git2::Oid,
subject_commit: git2::Oid,
offset: i32,
branch_commits: &[git2::Oid],
branch_tree: &git2::Tree,
) -> Result<ReorderResult> {
if branch_commits.len() < 2 {
bail!("Cannot re-order less than two commits");
};
if offset == 0 {
return Ok(ReorderResult {
tree: branch_tree.id(),
head: branch_commits[0],
reordered_commits: vec![],
});
};
ensure_offset_within_bounds(subject_commit, offset, branch_commits)?;
let reordered_commits = reorder_commit_list(subject_commit, offset, branch_commits)?;
// Rebase branch commits
// We are passing all the commits to the cherry_rebase_group funcion, but
// this is not a concern as it will verbaitm copy any commits that have
// not had their parents changed.
let new_head_oid = cherry_rebase_group(repository, base_commit, &reordered_commits)?;
// Calculate the new head and tree
let BranchHeadAndTree {
head: new_head_oid,
tree: new_tree_oid,
} = compute_updated_branch_head_for_commits(
repository,
branch_commits[0], // This function only operates on lists of 2 or greater
branch_tree.id(),
new_head_oid,
)?;
Ok(ReorderResult {
head: new_head_oid,
tree: new_tree_oid,
reordered_commits,
})
}
fn reorder_commit_list(
subject_commit: git2::Oid,
offset: i32,
branch_commits: &[git2::Oid],
) -> Result<Vec<git2::Oid>> {
let subject_index = branch_commits
.iter()
.position(|c| *c == subject_commit)
.ok_or(anyhow::anyhow!(
"Subject commit not found in branch commits"
))?;
let mut output = branch_commits.to_owned();
output.remove(subject_index);
output.insert(((subject_index as i32) + offset) as usize, subject_commit);
Ok(output)
}
/// Presume we had the stack:
///
/// A
/// B
/// C // idx 2 // 2 + -1 = 1; 1 < 0 => All good
/// D
///
/// If C was the subject, and the offset was -1, we would expect:
///
/// A
/// C
/// B
/// D
///
/// Presume we had the stack:
///
/// A
/// B // idx 1 // 1 + 1 = 2; 2 >= 4 => All good
/// C
/// D
///
/// If B was the subject, and the offset was 1, we would expect:
///
/// A
/// C
/// B
/// D
fn ensure_offset_within_bounds(
subject_commit: git2::Oid,
offset: i32,
branch_commits: &[git2::Oid],
) -> Result<()> {
let subject_index = branch_commits
.iter()
.position(|c| *c == subject_commit)
.ok_or(anyhow::anyhow!(
"Subject commit not found in branch commits"
))?;
if subject_index as i32 + offset < 0 {
bail!("Offset is out of bounds");
}
if subject_index as i32 + offset >= branch_commits.len() as i32 {
bail!("Offset is out of bounds");
}
Ok(())
}
#[cfg(test)]
mod test {
#[cfg(test)]
mod inner_reorder_commit {
use gitbutler_cherry_pick::RepositoryExt as _;
use gitbutler_commit::commit_ext::CommitExt as _;
use gitbutler_repo::LogUntil;
use gitbutler_repo::RepositoryExt as _;
use gitbutler_testsupport::testing_repository::{
assert_commit_tree_matches, TestingRepository,
};
#[test]
fn less_than_two_commits_is_an_error() {
let test_repository = TestingRepository::open();
let merge_base = test_repository.commit_tree(None, &[("foo.txt", "a")]);
let b = test_repository.commit_tree(Some(&merge_base), &[("foo.txt", "b")]);
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
b.id(),
0,
&[b.id()],
&b.tree().unwrap(),
);
assert!(result.is_err());
}
#[test]
fn unconflicting_reorder() {
let test_repository = TestingRepository::open();
let merge_base = test_repository.commit_tree(None, &[("foo.txt", "a")]);
// Commit A adds file bar
let a = test_repository
.commit_tree(Some(&merge_base), &[("foo.txt", "a"), ("bar.txt", "a")]);
// Commit B adds file baz
let b = test_repository.commit_tree(
Some(&a),
&[("foo.txt", "a"), ("bar.txt", "a"), ("baz.txt", "a")],
);
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
a.id(),
-1,
&[b.id(), a.id()],
&b.tree().unwrap(),
)
.unwrap();
let a_prime: git2::Commit =
test_repository.repository.find_commit(result.head).unwrap();
assert!(!a_prime.is_conflicted());
assert_commit_tree_matches(
&test_repository.repository,
&a_prime,
&[("foo.txt", b"a"), ("bar.txt", b"a"), ("baz.txt", b"a")],
);
let b_prime: git2::Commit = a_prime.parent(0).unwrap();
assert!(!b_prime.is_conflicted());
assert_commit_tree_matches(
&test_repository.repository,
&b_prime,
&[("foo.txt", b"a"), ("baz.txt", b"a")],
);
assert!(b_prime.tree().unwrap().get_name("bar.txt").is_none());
// In this case, the tree should be the same as a_prime, as there
// were no uncommited files
assert_eq!(result.tree, a_prime.tree_id());
}
#[test]
fn conflicting_reorder() {
let test_repository = TestingRepository::open();
let merge_base = test_repository.commit_tree(None, &[("foo.txt", "a")]);
let a = test_repository.commit_tree(Some(&merge_base), &[("foo.txt", "x")]);
let b = test_repository.commit_tree(Some(&a), &[("foo.txt", "y")]);
// When we re-order a and b, they will conflict.
//
// Setup: : Step 1: : Step 2: :
// B: y : : A': x :
// | : : :
// A: x : B': a : B': a : <- B' is the auto-resolved tree
// | : : :
// MB: a : MB: a : MB: a :
//
// Reorder step 1:
// Cherry pick B on top of Merge Base:
// Merge trees: Bt and MBt, with base At. The theirs and ours sides
// both have changes compared to the base so it conflicts.
// We auto resolve to have the content of MBt: "a"
//
// Reorder step 2:
// Cherry pick A on top of B':
// Merge trees: At and B't, with base MBt. MBt is unchange relative
// to the base, so it merges cleanly to "x".
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
a.id(),
-1,
&[b.id(), a.id()],
&b.tree().unwrap(),
)
.unwrap();
let a_prime: git2::Commit =
test_repository.repository.find_commit(result.head).unwrap();
assert!(!a_prime.is_conflicted());
assert_commit_tree_matches(&test_repository.repository, &a_prime, &[("foo.txt", b"x")]);
let b_prime: git2::Commit = a_prime.parent(0).unwrap();
assert!(b_prime.is_conflicted());
assert_commit_tree_matches(
&test_repository.repository,
&b_prime,
&[
(".auto-resolution/foo.txt", b"a"),
(".conflict-base-0/foo.txt", b"x"),
(".conflict-side-0/foo.txt", b"a"),
(".conflict-side-1/foo.txt", b"y"),
],
);
// In this case, the tree should be the same as a_prime, as there
// were no uncommited files
assert_eq!(result.tree, a_prime.tree_id());
// We should now be able to re-order the commits back back to their
// original order and the tree should be the same as the original.
// When we re-order A' and B', they become unconflicted
//
// Setup: : Step 1: : Step 2: :
// A': y : : B'': x :
// | : : :
// B': a : A'': y : A'': y : <- B' is the auto-resolved tree
// | : : :
// MB: a : MB: a : MB: a :
//
// Reorder step 1:
// Cherry pick A'' on top of Merge Base:
// Merge trees: A't and MBt, with base B't (auto-resolution. MBt is
// unchanged relative to the base, so it merges cleanly to "x".
//
// Reorder step 2:
// Cherry pick B' on top of A'':
// Merge trees: A''t and B't (ours == Bt), with base At.
// A''t is y, B't is a, and At is y. This is a clean merge.
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
b_prime.id(),
-1,
&[a_prime.id(), b_prime.id()],
&a_prime.tree().unwrap(),
)
.unwrap();
let b_double_prime: git2::Commit =
test_repository.repository.find_commit(result.head).unwrap();
assert!(!b_double_prime.is_conflicted());
assert_eq!(b_double_prime.tree_id(), b.tree_id());
let a_double_prime: git2::Commit = b_double_prime.parent(0).unwrap();
assert!(!a_double_prime.is_conflicted());
assert_eq!(a_double_prime.tree_id(), a.tree_id());
}
#[test]
fn conflicting_tree() {
let test_repository = TestingRepository::open();
let merge_base = test_repository.commit_tree(None, &[("foo.txt", "a")]);
let a = test_repository.commit_tree(Some(&merge_base), &[("foo.txt", "x")]);
let b = test_repository.commit_tree(Some(&a), &[("foo.txt", "y")]);
let tree = test_repository.commit_tree(Some(&b), &[("foo.txt", "z")]);
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
a.id(),
-1,
&[b.id(), a.id()],
&tree.tree().unwrap(),
)
.unwrap();
let commits: Vec<git2::Commit> = test_repository
.repository
.log(result.head, LogUntil::Commit(merge_base.id()), false)
.unwrap();
assert_eq!(
commits.len(),
3,
"The conflicted tree should be come a commit"
);
let tree_commit = &commits[0];
let a_prime = &commits[1];
let b_prime = &commits[2];
assert!(tree_commit.is_conflicted());
assert!(!a_prime.is_conflicted());
assert!(b_prime.is_conflicted());
assert_eq!(
test_repository
.repository
.find_real_tree(tree_commit, Default::default())
.unwrap()
.id(),
result.tree,
"The tree should be the auto-resolved tree of the tree commit"
);
// Order the commits back to their initial order and all should be
// resolved
let result = crate::reorder_commits::inner_reorder_commit(
&test_repository.repository,
merge_base.id(),
b_prime.id(),
-1,
&[tree_commit.id(), a_prime.id(), b_prime.id()],
&tree.tree().unwrap(),
)
.unwrap();
let commits: Vec<git2::Commit> = test_repository
.repository
.log(result.head, LogUntil::Commit(merge_base.id()), false)
.unwrap();
assert_eq!(commits.len(), 3);
let tree_commit = &commits[0];
let b_double_prime = &commits[1];
let a_double_prime = &commits[2];
assert!(!tree_commit.is_conflicted());
assert!(!b_double_prime.is_conflicted());
assert!(!a_double_prime.is_conflicted());
assert_eq!(tree_commit.tree_id(), result.tree);
}
}
#[cfg(test)]
mod reorder_commit_list {
use gitbutler_testsupport::testing_repository::TestingRepository;
use crate::reorder_commits::reorder_commit_list;
#[test]
fn move_commit_up() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
let branch_commits = reorder_commit_list(c, -1, &[a, b, c, d]).unwrap();
assert_eq!(branch_commits, &[a, c, b, d]);
}
#[test]
fn move_commit_up_to_top() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
let branch_commits = reorder_commit_list(c, -2, &[a, b, c, d]).unwrap();
assert_eq!(branch_commits, &[c, a, b, d]);
}
#[test]
fn move_nowhere() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
let branch_commits = reorder_commit_list(b, 0, &[a, b, c, d]).unwrap();
assert_eq!(branch_commits, &[a, b, c, d]);
}
#[test]
fn move_commit_down() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
let branch_commits = reorder_commit_list(b, 1, &[a, b, c, d]).unwrap();
assert_eq!(branch_commits, &[a, c, b, d]);
}
#[test]
fn move_commit_down_two() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
let branch_commits = reorder_commit_list(b, 2, &[a, b, c, d]).unwrap();
assert_eq!(branch_commits, &[a, c, d, b]);
}
}
#[cfg(test)]
mod ensure_offset_within_bounds {
use crate::reorder_commits::ensure_offset_within_bounds;
use gitbutler_testsupport::testing_repository::TestingRepository;
#[test]
fn test() {
let test_repository = TestingRepository::open();
let a = test_repository.commit_tree(None, &[("foo.txt", "a")]).id();
let b = test_repository.commit_tree(None, &[("foo.txt", "b")]).id();
let c = test_repository.commit_tree(None, &[("foo.txt", "c")]).id();
let d = test_repository.commit_tree(None, &[("foo.txt", "d")]).id();
assert!(ensure_offset_within_bounds(b, -2, &[a, b, c, d]).is_err());
assert!(ensure_offset_within_bounds(b, -1, &[a, b, c, d]).is_ok());
assert!(ensure_offset_within_bounds(b, 0, &[a, b, c, d]).is_ok());
assert!(ensure_offset_within_bounds(b, 1, &[a, b, c, d]).is_ok());
assert!(ensure_offset_within_bounds(b, 2, &[a, b, c, d]).is_ok());
assert!(ensure_offset_within_bounds(b, 3, &[a, b, c, d]).is_err());
}
}
}