Skip to content

Commit 03ef18d

Browse files
committed
Use read-only worktree diff
The worktree diff is essentially a 'git status' with a given tree, and that can be expressed with a diff of the workdir, the index and a tree. This comes at the expense of not being able to control which file sizes are diffed.
1 parent c5134f9 commit 03ef18d

File tree

6 files changed

+50
-59
lines changed

6 files changed

+50
-59
lines changed

crates/gitbutler-branch-actions/tests/extra/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ fn commit_id_can_be_generated_or_specified() -> Result<()> {
759759
#[test]
760760
fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
761761
let suite = Suite::default();
762-
let Case { ctx, project, .. } = &suite.new_case();
762+
let Case { ctx, project, .. } = &mut suite.new_case();
763763

764764
// create a commit and set the target
765765
let file_path = Path::new("test.txt");
@@ -829,8 +829,14 @@ fn merge_vbranch_upstream_clean_rebase() -> Result<()> {
829829

830830
// create the branch
831831
let (branches, _) = list_virtual_branches(ctx, guard.write_permission())?;
832+
assert_eq!(branches.len(), 1);
832833
let branch1 = &branches[0];
833-
assert_eq!(branch1.files.len(), 1);
834+
assert_eq!(
835+
branch1.files.len(),
836+
1 + 1,
837+
"'test' (modified compared to index) and 'test2' (untracked).\
838+
This is actually correct when looking at the git repository"
839+
);
834840
assert_eq!(branch1.commits.len(), 1);
835841
// assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 1);
836842

crates/gitbutler-branch-actions/tests/virtual_branches/convert_to_real_branch.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,20 @@ fn conflicting() {
6161

6262
let (branches, _) = controller.list_virtual_branches(project).unwrap();
6363
assert_eq!(branches.len(), 1);
64-
assert!(branches[0].base_current);
65-
assert!(branches[0].active);
64+
let branch = &branches[0];
65+
assert_eq!(
66+
branch.name, "Virtual branch",
67+
"the auto-created branch gets the default name"
68+
);
69+
assert!(branch.base_current);
70+
assert!(branch.active);
6671
assert_eq!(
67-
branches[0].files[0].hunks[0].diff,
72+
branch.files[0].hunks[0].diff,
6873
"@@ -1 +1 @@\n-first\n\\ No newline at end of file\n+conflict\n\\ No newline at end of file\n"
6974
);
7075

7176
let unapplied_branch = controller
72-
.convert_to_real_branch(project, branches[0].id)
77+
.convert_to_real_branch(project, branch.id)
7378
.unwrap();
7479

7580
Refname::from_str(&unapplied_branch).unwrap()
@@ -100,7 +105,6 @@ fn conflicting() {
100105

101106
assert_eq!(branches.len(), 1);
102107
let branch = &branches[0];
103-
// assert!(!branch.base_current);
104108
assert!(branch.conflicted);
105109
assert_eq!(
106110
branch.files[0].hunks[0].diff,

crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ impl Test {
5050
/// Consume this instance and keep the temp directory that held the local repository, returning it.
5151
/// Best used inside a `dbg!(test.debug_local_repo())`
5252
#[allow(dead_code)]
53-
pub fn debug_local_repo(mut self) -> PathBuf {
54-
let repo = std::mem::take(&mut self.repository);
55-
repo.debug_local_repo()
53+
pub fn debug_local_repo(&mut self) -> Option<PathBuf> {
54+
self.repository.debug_local_repo()
5655
}
5756
}
5857

crates/gitbutler-diff/src/diff.rs

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
use std::{
2-
borrow::Cow,
3-
collections::HashMap,
4-
path::{Path, PathBuf},
5-
str,
6-
};
1+
use std::{borrow::Cow, collections::HashMap, path::PathBuf, str};
72

83
use anyhow::{Context, Result};
94
use bstr::{BStr, BString, ByteSlice, ByteVec};
@@ -133,34 +128,6 @@ pub fn workdir(repo: &git2::Repository, commit_oid: &git2::Oid) -> Result<DiffBy
133128
.context("failed to find commit")?;
134129
let old_tree = repo.find_real_tree(&commit, Default::default())?;
135130

136-
let mut workdir_index = repo.index()?;
137-
138-
let mut skipped_files = HashMap::new();
139-
let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
140-
let file_size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
141-
if file_size > 50_000_000 {
142-
skipped_files.insert(
143-
path.to_path_buf(),
144-
FileDiff {
145-
old_path: None,
146-
new_path: None,
147-
hunks: Vec::new(),
148-
skipped: true,
149-
binary: true,
150-
old_size_bytes: 0,
151-
new_size_bytes: 0,
152-
},
153-
);
154-
1 //skips the entry
155-
} else {
156-
0
157-
}
158-
};
159-
workdir_index.add_all(["."], git2::IndexAddOption::DEFAULT, Some(cb))?;
160-
let workdir_tree_id = workdir_index.write_tree()?;
161-
162-
let new_tree = repo.find_tree(workdir_tree_id)?;
163-
164131
let mut diff_opts = git2::DiffOptions::new();
165132
diff_opts
166133
.recurse_untracked_dirs(true)
@@ -170,14 +137,27 @@ pub fn workdir(repo: &git2::Repository, commit_oid: &git2::Oid) -> Result<DiffBy
170137
.ignore_submodules(true)
171138
.context_lines(3);
172139

173-
let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), Some(&mut diff_opts))?;
174-
let diff_files = hunks_by_filepath(Some(repo), &diff);
175-
diff_files.map(|mut df| {
176-
for (key, value) in skipped_files {
177-
df.insert(key, value);
178-
}
179-
df
180-
})
140+
let mut index = repo.index()?;
141+
// Just a hack to resolve conflicts, which don't get diffed.
142+
// Diffed conflicts are something we need though.
143+
// For now, it seems easiest to resolve by adding the path forcefully,
144+
// which will create objects for the diffs at least.
145+
let paths_to_add: Vec<_> = index
146+
.conflicts()?
147+
.filter_map(Result::ok)
148+
.filter_map(|c| {
149+
c.our
150+
.or(c.their)
151+
.or(c.ancestor)
152+
.and_then(|c| c.path.into_string().ok())
153+
})
154+
.collect();
155+
for conflict_path_to_resolve in paths_to_add {
156+
index.add_path(conflict_path_to_resolve.as_ref())?;
157+
}
158+
let diff = repo.diff_tree_to_workdir_with_index(Some(&old_tree), Some(&mut diff_opts))?;
159+
// TODO(ST): bring back support for skipped (large) files.
160+
hunks_by_filepath(Some(repo), &diff)
181161
}
182162

183163
pub fn trees(
@@ -194,9 +174,10 @@ pub fn trees(
194174
.context_lines(3)
195175
.show_untracked_content(true);
196176

177+
// This is not a content-based diff, but also considers modification times apparently,
178+
// maybe related to racy-git. This is why empty diffs have ot be filtered.
197179
let diff =
198180
repository.diff_tree_to_tree(Some(old_tree), Some(new_tree), Some(&mut diff_opts))?;
199-
200181
hunks_by_filepath(None, &diff)
201182
}
202183

crates/gitbutler-testsupport/src/suite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub struct Case {
9292
pub ctx: CommandContext,
9393
pub credentials: Helper,
9494
/// The directory containing the `ctx`
95-
project_tmp: Option<TempDir>,
95+
pub project_tmp: Option<TempDir>,
9696
}
9797

9898
impl Drop for Case {

crates/gitbutler-testsupport/src/test_project.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ pub struct TestProject {
1919
impl Drop for TestProject {
2020
fn drop(&mut self) {
2121
if std::env::var_os(VAR_NO_CLEANUP).is_some() {
22-
let _ = self.local_tmp.take().unwrap().into_path();
23-
let _ = self.remote_tmp.take().unwrap().into_path();
22+
let _ = self.local_tmp.take().map(|tmp| tmp.into_path());
23+
let _ = self.remote_tmp.take().map(|tmp| tmp.into_path());
2424
}
2525
}
2626
}
@@ -83,10 +83,11 @@ impl Default for TestProject {
8383
}
8484

8585
impl TestProject {
86-
/// Consume this instance and keep the temp directory that held the local repository, returning it.
86+
/// Take the tmp directory holding the local repository and make sure it won't be deleted,
87+
/// returning a path to it.
8788
/// Best used inside a `dbg!(test_project.debug_local_repo())`
88-
pub fn debug_local_repo(mut self) -> PathBuf {
89-
self.local_tmp.take().unwrap().into_path()
89+
pub fn debug_local_repo(&mut self) -> Option<PathBuf> {
90+
self.local_tmp.take().map(|tmp| tmp.into_path())
9091
}
9192
pub fn path(&self) -> &std::path::Path {
9293
self.local_repository.workdir().unwrap()

0 commit comments

Comments
 (0)