Merge pull request #4945 from gitbutlerapp/Fix-wrong-files-displaying-in-edit-mode

Fix wrong files displaying in edit mode
This commit is contained in:
Caleb Owens 2024-09-18 12:18:27 +02:00 committed by GitHub
commit f34d901c87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 98 deletions

View File

@ -1,4 +1,3 @@
use crate::integration::get_workspace_head;
use crate::{RemoteBranchFile, VirtualBranchesExt};
use anyhow::{bail, Context, Result};
use bstr::{BStr, ByteSlice};
@ -10,7 +9,7 @@ use gitbutler_command_context::CommandContext;
use gitbutler_diff::DiffByPathMap;
use gitbutler_project::access::WorktreeReadPermission;
use gitbutler_reference::normalize_branch_name;
use gitbutler_repo::GixRepositoryExt;
use gitbutler_repo::{GixRepositoryExt, RepositoryExt as _};
use gitbutler_serde::BStringForFrontend;
use gix::object::tree::diff::Action;
use gix::prelude::ObjectIdExt;
@ -31,7 +30,7 @@ pub(crate) fn get_uncommited_files_raw(
ctx: &CommandContext,
_permission: &WorktreeReadPermission,
) -> Result<DiffByPathMap> {
gitbutler_diff::workdir(ctx.repository(), get_workspace_head(ctx)?)
gitbutler_diff::workdir(ctx.repository(), ctx.repository().head_commit()?.id())
.context("Failed to list uncommited files")
}

View File

@ -613,6 +613,8 @@ mod test {
let tempdir = tempdir().unwrap();
let repository = git2::Repository::init(tempdir.path()).unwrap();
let initial_commit = commit_file(&repository, None, &[("foo.txt", "bar")]);
// Create refs/heads/master
repository.branch("master", &initial_commit, false).unwrap();
let old_target = commit_file(&repository, Some(&initial_commit), &[("foo.txt", "baz")]);
let branch_head = commit_file(&repository, Some(&old_target), &[("foo.txt", "fux")]);
let new_target = commit_file(&repository, Some(&old_target), &[("foo.txt", "qux")]);

View File

@ -27,7 +27,6 @@ use gitbutler_repo::{
pub mod commands;
pub const EDIT_UNCOMMITED_FILES_REF: &str = "refs/gitbutler/edit_uncommited_files";
pub const EDIT_INITIAL_STATE_REF: &str = "refs/gitbutler/edit_initial_state";
fn save_uncommited_files(ctx: &CommandContext) -> Result<()> {
let repository = ctx.repository();
@ -123,7 +122,6 @@ fn checkout_edit_branch(ctx: &CommandContext, commit: &git2::Commit) -> Result<(
commit.parent(0)?
};
repository.reference(EDIT_BRANCH_REF, commit_parent.id(), true, "")?;
repository.reference(EDIT_INITIAL_STATE_REF, commit_parent.id(), true, "")?;
repository.set_head(EDIT_BRANCH_REF)?;
repository.checkout_head(Some(CheckoutBuilder::new().force().remove_untracked(true)))?;
@ -140,18 +138,6 @@ fn checkout_edit_branch(ctx: &CommandContext, commit: &git2::Commit) -> Result<(
),
)?;
let tree = repository.create_wd_tree()?;
// Commit initial state commit
repository.commit(
Some(EDIT_INITIAL_STATE_REF),
&author_signature,
&committer_signature,
"Initial state commit",
&tree,
&[&commit_parent],
)?;
Ok(())
}
@ -365,12 +351,9 @@ pub(crate) fn starting_index_state(
let commit_parent = commit.parent(0)?;
let commit_parent_tree = repository.find_real_tree(&commit_parent, Default::default())?;
let initial_state = repository
.find_reference(EDIT_INITIAL_STATE_REF)?
.peel_to_tree()?;
let index = get_commit_index(repository, &commit)?;
let diff =
repository.diff_tree_to_tree(Some(&commit_parent_tree), Some(&initial_state), None)?;
let diff = repository.diff_tree_to_index(Some(&commit_parent_tree), Some(&index), None)?;
let diff_files = hunks_by_filepath(Some(repository), &diff)?
.into_iter()

View File

@ -10,7 +10,7 @@ use gitbutler_commit::{
};
use gitbutler_error::error::Marker;
use crate::{LogUntil, RepositoryExt as _};
use crate::{temporary_workdir::TemporaryWorkdir, LogUntil, RepositoryExt as _};
/// cherry-pick based rebase, which handles empty commits
/// this function takes a commit range and generates a Vector of commit oids
@ -73,7 +73,7 @@ pub fn cherry_rebase_group(
return Ok(to_rebase);
};
let cherrypick_index = repository
let mut cherrypick_index = repository
.cherry_pick_gitbutler(&head, &to_rebase, None)
.context("failed to cherry pick")?;
@ -81,7 +81,12 @@ pub fn cherry_rebase_group(
if !succeeding_rebases {
return Err(anyhow!("failed to rebase")).context(Marker::BranchConflict);
}
commit_conflicted_cherry_result(repository, head, to_rebase, cherrypick_index)
commit_conflicted_cherry_result(
repository,
head,
to_rebase,
&mut cherrypick_index,
)
} else {
commit_unconflicted_cherry_result(repository, head, to_rebase, cherrypick_index)
}
@ -141,7 +146,7 @@ fn commit_conflicted_cherry_result<'repository>(
repository: &'repository git2::Repository,
head: git2::Commit,
to_rebase: git2::Commit,
cherrypick_index: git2::Index,
cherrypick_index: &mut git2::Index,
) -> Result<git2::Commit<'repository>> {
let commit_headers = to_rebase.gitbutler_headers();
@ -158,42 +163,9 @@ fn commit_conflicted_cherry_result<'repository>(
b"You have checked out a GitButler Conflicted commit. You probably didn't mean to do this.";
let readme_blob = repository.blob(readme_content)?;
let mut conflicted_files = Vec::new();
let conflicted_files = resolve_index(repository, cherrypick_index)?;
// get a list of conflicted files from the index
let index_conflicts = cherrypick_index.conflicts()?.flatten().collect::<Vec<_>>();
for conflict in index_conflicts {
// For some reason we have to resolve the index with the "their" side
// rather than the "our" side, so we then go and later overwrite the
// output tree with the "our" side.
let index_entry = conflict.their.or(conflict.our);
if let Some(entry) = index_entry {
let path = std::str::from_utf8(&entry.path).unwrap().to_string();
conflicted_files.push(path)
}
}
let mut resolved_index = repository.cherry_pick_gitbutler(
&head,
&to_rebase,
Some(git2::MergeOptions::default().file_favor(git2::FileFavor::Ours)),
)?;
let resolved_index_conflicts = resolved_index.conflicts()?.flatten().collect::<Vec<_>>();
for conflict in resolved_index_conflicts {
if let (Some(their), None) = (&conflict.their, &conflict.our) {
let path = std::str::from_utf8(&their.path).unwrap();
let path = Path::new(path);
resolved_index.remove_path(path)?;
}
if let (None, Some(our)) = (conflict.their, conflict.our) {
let path = std::str::from_utf8(&our.path).unwrap();
let path = Path::new(path);
resolved_index.remove_path(path)?;
}
}
let resolved_tree_id = resolved_index.write_tree_to(repository)?;
let resolved_tree_id = cherrypick_index.write_tree_to(repository)?;
// convert files into a string and save as a blob
let conflicted_files_string = conflicted_files.join("\n");
@ -255,6 +227,55 @@ fn commit_conflicted_cherry_result<'repository>(
.context("failed to find commit")
}
/// Automatically resolves an index with a preferences for the "our" side
///
/// Within our rebasing and merging logic, "their" is the commit that is getting
/// cherry picked, and "our" is the commit that it is getting cherry picked on
/// to.
///
/// This means that if we experience a conflict, we drop the changes that are
/// in the commit that is getting cherry picked in favor of what came before it
fn resolve_index(
repository: &git2::Repository,
cherrypick_index: &mut git2::Index,
) -> Result<Vec<String>, anyhow::Error> {
let mut conflicted_files = vec![];
let workdir = TemporaryWorkdir::open(repository)?;
workdir.repository().set_index(cherrypick_index)?;
let index_conflicts = cherrypick_index.conflicts()?.flatten().collect::<Vec<_>>();
for mut conflict in index_conflicts {
if let Some(ancestor) = &conflict.ancestor {
let path = std::str::from_utf8(&ancestor.path).unwrap();
let path = Path::new(path);
cherrypick_index.remove_path(path)?;
}
if let (Some(their), None) = (&conflict.their, &conflict.our) {
let path = std::str::from_utf8(&their.path).unwrap();
conflicted_files.push(path.to_string());
let their_path = Path::new(path);
cherrypick_index.remove_path(their_path)?;
} else if let (None, Some(our)) = (&conflict.their, &mut conflict.our) {
let path = std::str::from_utf8(&our.path).unwrap();
conflicted_files.push(path.to_string());
let blob = repository.find_blob(our.id)?;
cherrypick_index.add_frombuffer(our, blob.content())?;
} else if let (Some(their), Some(our)) = (&conflict.their, &mut conflict.our) {
let their_path = std::str::from_utf8(&their.path).unwrap();
let our_path = std::str::from_utf8(&our.path).unwrap();
conflicted_files.push(our_path.to_string());
let blob = repository.find_blob(our.id)?;
let their_path = Path::new(their_path);
cherrypick_index.remove_path(their_path)?;
cherrypick_index.add_frombuffer(our, blob.content())?;
}
}
Ok(conflicted_files)
}
pub fn gitbutler_merge_commits<'repository>(
repository: &'repository git2::Repository,
target_commit: git2::Commit<'repository>,
@ -269,45 +290,12 @@ pub fn gitbutler_merge_commits<'repository>(
let target_tree = repository.find_real_tree(&target_commit, Default::default())?;
let incoming_tree = repository.find_real_tree(&incoming_commit, ConflictedTreeKey::Theirs)?;
let merged_index = repository.merge_trees(&base_tree, &target_tree, &incoming_tree, None)?;
let mut merged_index =
repository.merge_trees(&base_tree, &target_tree, &incoming_tree, None)?;
let mut conflicted_files = vec![];
let conflicted_files = resolve_index(repository, &mut merged_index)?;
// get a list of conflicted files from the index
let index_conflicts = merged_index.conflicts()?.flatten().collect::<Vec<_>>();
for conflict in index_conflicts {
// For some reason we have to resolve the index with the "their" side
// rather than the "our" side, so we then go and later overwrite the
// output tree with the "our" side.
let index_entry = conflict.their.or(conflict.our);
if let Some(entry) = index_entry {
let path = std::str::from_utf8(&entry.path).unwrap().to_string();
conflicted_files.push(path)
}
}
let mut resolved_index = repository.merge_trees(
&base_tree,
&target_tree,
&incoming_tree,
Some(git2::MergeOptions::default().file_favor(git2::FileFavor::Ours)),
)?;
let resolved_index_conflicts = resolved_index.conflicts()?.flatten().collect::<Vec<_>>();
for conflict in resolved_index_conflicts {
if let (Some(their), None) = (&conflict.their, &conflict.our) {
let path = std::str::from_utf8(&their.path).unwrap();
let path = Path::new(path);
resolved_index.remove_path(path)?;
}
if let (None, Some(our)) = (conflict.their, conflict.our) {
let path = std::str::from_utf8(&our.path).unwrap();
let path = Path::new(path);
resolved_index.remove_path(path)?;
}
}
let resolved_tree_id = resolved_index.write_tree_to(repository)?;
let resolved_tree_id = merged_index.write_tree_to(repository)?;
let (author, committer) = repository.signatures()?;