diff --git a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs index d2da289fd2..2884107110 100644 --- a/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs +++ b/crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs @@ -81,6 +81,7 @@ impl BranchManager<'_> { let (applied_statuses, _) = get_applied_status( self.project_repository, &integration_commit.id(), + &target_commit.id(), virtual_branches, None, ) diff --git a/crates/gitbutler-branch-actions/src/virtual.rs b/crates/gitbutler-branch-actions/src/virtual.rs index 28a7cd04a3..c2dd5f17aa 100644 --- a/crates/gitbutler-branch-actions/src/virtual.rs +++ b/crates/gitbutler-branch-actions/src/virtual.rs @@ -1,3 +1,4 @@ +use git2::ErrorCode; use gitbutler_branch::diff::{self, diff_files_into_hunks, trees, FileDiff, GitHunk}; use gitbutler_branch::{dedup, BranchUpdateRequest, VirtualBranchesHandle}; use gitbutler_branch::{dedup_fmt, Branch, BranchCreateRequest, BranchId}; @@ -12,6 +13,7 @@ use gitbutler_repo::credentials::Helper; use gitbutler_repo::{LogUntil, RepoActionsExt, RepositoryExt}; use itertools::Itertools; use std::borrow::Borrow; +use std::default; #[cfg(target_family = "unix")] use std::os::unix::prelude::PermissionsExt; use std::time::SystemTime; @@ -20,6 +22,7 @@ use std::{ path::{Path, PathBuf}, time, vec, }; +use tokio::time::Instant; use anyhow::{anyhow, bail, Context, Result}; use bstr::{BString, ByteSlice, ByteVec}; @@ -218,6 +221,7 @@ pub fn unapply_ownership( project_repository.assure_resolved()?; let vb_state = project_repository.project().virtual_branches(); + let default_target = &vb_state.get_default_target()?; let virtual_branches = vb_state .list_branches_in_workspace() @@ -228,6 +232,7 @@ pub fn unapply_ownership( let (applied_statuses, _) = get_applied_status( project_repository, &integration_commit_id, + &default_target.sha, virtual_branches, None, ) @@ -1068,6 +1073,7 @@ pub fn get_status_by_branch( project_repository, // TODO: Keep this optional or update lots of tests? integration_commit.unwrap_or(&default_target.sha), + &default_target.sha, virtual_branches, perm, )?; @@ -1075,6 +1081,93 @@ pub fn get_status_by_branch( Ok((applied_status, skipped_files)) } +fn compute_merge_base( + project_repository: &ProjectRepository, + target_sha: &git2::Oid, + virtual_branches: &Vec, +) -> Result { + let repo = project_repository.repo(); + let mut merge_base = *target_sha; + for branch in virtual_branches { + if let Some(last) = project_repository + .l(branch.head, LogUntil::Commit(*target_sha))? + .last() + .map(|id| repo.find_commit(id.to_owned())) + { + if let Ok(parent) = last?.parent(0) { + merge_base = repo.merge_base(parent.id(), merge_base)?; + } + } + } + Ok(merge_base) +} + +fn compute_locks( + project_repository: &ProjectRepository, + integration_commit: &git2::Oid, + target_sha: &git2::Oid, + base_diffs: &BranchStatus, + virtual_branches: &Vec, +) -> Result>> { + let merge_base = compute_merge_base(project_repository, target_sha, virtual_branches)?; + let mut locked_hunk_map = HashMap::>::new(); + + let mut commit_to_branch = HashMap::new(); + for branch in virtual_branches { + for commit in project_repository.log(branch.head, LogUntil::Commit(*target_sha))? { + commit_to_branch.insert(commit.id(), branch.id); + } + } + + for (path, hunks) in base_diffs.clone().into_iter() { + for hunk in hunks { + let blame = match project_repository.repo().blame( + &path, + hunk.old_start, + hunk.old_start + hunk.old_lines, + merge_base, + *integration_commit, + ) { + Ok(blame) => blame, + Err(error) => { + if error.code() == ErrorCode::NotFound { + continue; + } else { + return Err(error.into()); + } + } + }; + + for blame_hunk in blame.iter() { + let commit_id = blame_hunk.orig_commit_id(); + dbg!(commit_id); + if commit_id == *integration_commit || commit_id == *target_sha { + continue; + } + let hash = Hunk::hash_diff(&hunk.diff_lines); + dbg!(hash); + let Some(branch_id) = commit_to_branch.get(&commit_id) else { + continue; + }; + dbg!(branch_id); + + let hunk_lock = diff::HunkLock { + branch_id: *branch_id, + commit_id, + }; + dbg!(&hunk_lock); + locked_hunk_map + .entry(hash) + .and_modify(|locks| { + locks.push(hunk_lock); + }) + .or_insert(vec![hunk_lock]); + } + } + } + Ok(locked_hunk_map) +} + fn new_compute_locks( repository: &git2::Repository, unstaged_hunks_by_path: &HashMap>, @@ -1166,6 +1259,7 @@ fn new_compute_locks( pub(crate) fn get_applied_status( project_repository: &ProjectRepository, integration_commit: &git2::Oid, + target_sha: &git2::Oid, mut virtual_branches: Vec, perm: Option<&mut WorktreeWritePermission>, ) -> Result<(AppliedStatuses, Vec)> { @@ -1200,7 +1294,14 @@ pub(crate) fn get_applied_status( .map(|branch| (branch.id, HashMap::new())) .collect(); - let locks = new_compute_locks(project_repository.repo(), &base_diffs, &virtual_branches)?; + let locks = compute_locks( + project_repository, + integration_commit, + target_sha, + &base_diffs, + &virtual_branches, + )?; + // let locks = new_compute_locks(project_repository.repo(), &base_diffs, &virtual_branches)?; for branch in &mut virtual_branches { let old_claims = branch.ownership.claims.clone(); @@ -2179,6 +2280,7 @@ pub(crate) fn amend( let (mut applied_statuses, _) = get_applied_status( project_repository, &integration_commit_id, + &default_target.sha, virtual_branches, None, )?; @@ -2661,11 +2763,13 @@ pub(crate) fn move_commit( bail!("branch {target_branch_id} is not among applied branches") } + let default_target = &vb_state.get_default_target()?; let integration_commit_id = get_workspace_head(&vb_state, project_repository)?; let (mut applied_statuses, _) = get_applied_status( project_repository, &integration_commit_id, + &default_target.sha, applied_branches, None, )?; @@ -2873,6 +2977,8 @@ fn update_conflict_markers( #[cfg(test)] mod tests { + use git2::ErrorCode; + use super::*; #[test] fn joined_test() { diff --git a/crates/gitbutler-repo/src/repository_ext.rs b/crates/gitbutler-repo/src/repository_ext.rs index da9cb1209d..692ecca524 100644 --- a/crates/gitbutler-repo/src/repository_ext.rs +++ b/crates/gitbutler-repo/src/repository_ext.rs @@ -224,8 +224,7 @@ impl RepositoryExt for Repository { opts.min_line(min_line as usize) .max_line(max_line as usize) .newest_commit(newest_commit) - .oldest_commit(oldest_commit) - .first_parent(true); + .oldest_commit(oldest_commit); self.blame_file(path, Some(&mut opts)) }