Skip to content

Commit 31383b4

Browse files
committed
Store index data in the snapshot, and resolve it as well.
1 parent 952b68a commit 31383b4

File tree

7 files changed

+151
-37
lines changed

7 files changed

+151
-37
lines changed

crates/but-core/src/diff/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use tree_changes::tree_changes;
55

66
mod worktree;
77
use crate::{ChangeState, ModeFlags, TreeChange, TreeStatus, TreeStatusKind};
8-
pub use worktree::worktree_changes;
8+
pub use worktree::{worktree_changes, worktree_changes_no_renames};
99

1010
/// conversion functions for use in the UI
1111
pub mod ui;

crates/but-core/src/diff/worktree.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,44 @@ enum Origin {
3333
/// to get a commit with a tree equal to the current worktree.
3434
#[instrument(skip(repo), err(Debug))]
3535
pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChanges> {
36-
let rewrites = gix::diff::Rewrites::default(); /* standard Git rewrite handling for everything */
37-
debug_assert!(
38-
rewrites.copies.is_none(),
39-
"TODO: copy tracking needs specific support wherever 'previous_path()' is called."
40-
);
36+
worktree_changes_inner(repo, RenameTracking::Always)
37+
}
38+
39+
/// Just like [`worktree_changes()`], but don't do any rename tracking for performance.
40+
#[instrument(skip(repo), err(Debug))]
41+
pub fn worktree_changes_no_renames(repo: &gix::Repository) -> anyhow::Result<WorktreeChanges> {
42+
worktree_changes_inner(repo, RenameTracking::Disabled)
43+
}
44+
45+
enum RenameTracking {
46+
Always,
47+
Disabled,
48+
}
49+
50+
fn worktree_changes_inner(
51+
repo: &gix::Repository,
52+
renames: RenameTracking,
53+
) -> anyhow::Result<WorktreeChanges> {
54+
let (tree_index_rewrites, worktree_rewrites) = match renames {
55+
RenameTracking::Always => {
56+
let rewrites = gix::diff::Rewrites::default(); /* standard Git rewrite handling for everything */
57+
debug_assert!(
58+
rewrites.copies.is_none(),
59+
"TODO: copy tracking needs specific support wherever 'previous_path()' is called."
60+
);
61+
(TrackRenames::Given(rewrites), Some(rewrites))
62+
}
63+
RenameTracking::Disabled => (TrackRenames::Disabled, None),
64+
};
4165
let has_submodule_ignore_configuration = repo.modules()?.is_some_and(|modules| {
4266
modules
4367
.names()
4468
.any(|name| modules.ignore(name).ok().flatten().is_some())
4569
});
4670
let status_changes = repo
4771
.status(gix::progress::Discard)?
48-
.tree_index_track_renames(TrackRenames::Given(rewrites))
49-
.index_worktree_rewrites(rewrites)
72+
.tree_index_track_renames(tree_index_rewrites)
73+
.index_worktree_rewrites(worktree_rewrites)
5074
// Learn about submodule changes, but only do the cheap checks, showing only what we could commit.
5175
.index_worktree_submodules(if has_submodule_ignore_configuration {
5276
gix::status::Submodule::AsConfigured { check_dirty: true }

crates/but-workspace/src/snapshot/create_tree.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::collections::BTreeSet;
55
/// A way to determine what should be included in the snapshot when calling [create_tree()](function::create_tree).
66
#[derive(Debug, Clone)]
77
pub struct State {
8-
/// The result of a previous worktree changes call.
8+
/// The result of a previous worktree changes call, but [the one **without** renames](but_core::diff::worktree_changes_no_renames()).
99
///
1010
/// It contains detailed information about the complete set of possible changes to become part of the worktree.
1111
pub changes: but_core::WorktreeChanges,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
3+
### Description
4+
# A newly initialized git repository with an executable, a normal file, a symlink and a fifo, added to the index.
5+
set -eu -o pipefail
6+
7+
git init
8+
echo content > untracked
9+
echo exe > untracked-exe && chmod +x untracked-exe
10+
ln -s untracked link
11+
mkdir dir
12+
mkfifo dir/fifo-should-be-ignored
13+
14+
git add .
15+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use crate::snapshot::args_for_worktree_changes;
2+
use crate::utils::{read_only_in_memory_scenario, visualize_index};
3+
use but_testsupport::visualize_tree;
4+
use but_workspace::snapshot;
5+
use gix::prelude::ObjectIdExt;
6+
7+
#[test]
8+
fn unborn_added_to_index() -> anyhow::Result<()> {
9+
let repo = read_only_in_memory_scenario("unborn-all-file-types-added-to-index")?;
10+
let (head_tree_id, state, no_workspace_and_meta) = args_for_worktree_changes(&repo)?;
11+
12+
let out = snapshot::create_tree(head_tree_id, state, no_workspace_and_meta)?;
13+
insta::assert_snapshot!(visualize_tree(out.snapshot_tree.attach(&repo)), @r#""#);
14+
insta::assert_debug_snapshot!(out, @r"");
15+
16+
let res_out = snapshot::resolve_tree(
17+
out.snapshot_tree.attach(&repo),
18+
out.head_tree,
19+
snapshot::resolve_tree::Options::default(),
20+
)?;
21+
let mut cherry_pick = res_out
22+
.worktree_cherry_pick
23+
.expect("a worktree change was applied");
24+
assert_eq!(cherry_pick.tree.write()?, out.worktree.unwrap());
25+
let index = res_out
26+
.index
27+
.expect("the index was altered with many added files");
28+
insta::assert_snapshot!(visualize_index(&index), @r"");
29+
30+
assert!(res_out.metadata.is_none());
31+
assert!(
32+
res_out.workspace_references.is_none(),
33+
"didn't ask to store this"
34+
);
35+
Ok(())
36+
}
37+
38+
#[test]
39+
fn with_conflicts() -> anyhow::Result<()> {
40+
let repo = read_only_in_memory_scenario("merge-with-two-branches-conflict")?;
41+
let (head_tree_id, state, no_workspace_and_meta) = args_for_worktree_changes(&repo)?;
42+
43+
let out = snapshot::create_tree(head_tree_id, state, no_workspace_and_meta)?;
44+
insta::assert_snapshot!(visualize_tree(out.snapshot_tree.attach(&repo)), @r#""#);
45+
insta::assert_debug_snapshot!(out, @r"");
46+
47+
let res_out = snapshot::resolve_tree(
48+
out.snapshot_tree.attach(&repo),
49+
out.head_tree,
50+
snapshot::resolve_tree::Options::default(),
51+
)?;
52+
let mut cherry_pick = res_out
53+
.worktree_cherry_pick
54+
.expect("a worktree change was applied");
55+
assert_eq!(cherry_pick.tree.write()?, out.worktree.unwrap());
56+
let index = res_out
57+
.index
58+
.expect("the index was altered with many added files");
59+
insta::assert_snapshot!(visualize_index(&index), @r"");
60+
61+
assert!(res_out.metadata.is_none());
62+
assert!(
63+
res_out.workspace_references.is_none(),
64+
"didn't ask to store this"
65+
);
66+
Ok(())
67+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,36 @@
1+
mod index_create_and_resolve;
12
mod worktree_create_and_resolve;
3+
4+
mod utils {
5+
use but_graph::VirtualBranchesTomlMetadata;
6+
use but_workspace::snapshot;
7+
8+
/// Produce all args needed for creating a snapshot tree, and assure everything is selected.
9+
#[expect(clippy::type_complexity)]
10+
pub fn args_for_worktree_changes(
11+
repo: &gix::Repository,
12+
) -> anyhow::Result<(
13+
gix::Id<'_>,
14+
snapshot::create_tree::State,
15+
Option<(
16+
&'static but_graph::projection::Workspace<'static>,
17+
&'static VirtualBranchesTomlMetadata,
18+
)>,
19+
)> {
20+
let changes = but_core::diff::worktree_changes_no_renames(repo)?;
21+
let state = snapshot::create_tree::State {
22+
selection: changes
23+
.changes
24+
.iter()
25+
.map(|c| c.path.clone())
26+
.chain(changes.ignored_changes.iter().map(|c| c.path.clone()))
27+
.collect(),
28+
changes,
29+
head: false,
30+
};
31+
let head_tree_id = repo.head_tree_id_or_empty()?;
32+
33+
Ok((head_tree_id, state, None))
34+
}
35+
}
36+
pub use utils::args_for_worktree_changes;

crates/but-workspace/tests/workspace/snapshot/worktree_create_and_resolve.rs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::snapshot::args_for_worktree_changes;
12
use crate::utils::read_only_in_memory_scenario;
2-
use but_graph::VirtualBranchesTomlMetadata;
33
use but_testsupport::visualize_tree;
44
use but_workspace::snapshot;
55
use gix::prelude::ObjectIdExt;
@@ -155,30 +155,3 @@ fn worktree_all_filetypes() -> anyhow::Result<()> {
155155
Ok(())
156156
}
157157

158-
/// Produce all args needed for creating a snapshot tree, and assure everything is selected.
159-
#[expect(clippy::type_complexity)]
160-
fn args_for_worktree_changes(
161-
repo: &gix::Repository,
162-
) -> anyhow::Result<(
163-
gix::Id<'_>,
164-
snapshot::create_tree::State,
165-
Option<(
166-
&'static but_graph::projection::Workspace<'static>,
167-
&'static VirtualBranchesTomlMetadata,
168-
)>,
169-
)> {
170-
let changes = but_core::diff::worktree_changes(repo)?;
171-
let state = snapshot::create_tree::State {
172-
selection: changes
173-
.changes
174-
.iter()
175-
.map(|c| c.path.clone())
176-
.chain(changes.ignored_changes.iter().map(|c| c.path.clone()))
177-
.collect(),
178-
changes,
179-
head: false,
180-
};
181-
let head_tree_id = repo.head_tree_id_or_empty()?;
182-
183-
Ok((head_tree_id, state, None))
184-
}

0 commit comments

Comments
 (0)