Skip to content

Commit eb053ce

Browse files
authored
V3 stashing (#9725)
* upgrade gix for improved conflict support * flyby fixes Just some fixes while perusing the codebase. * Sketch of the APIs involved in `branch::apply()`. * Implement `snapshot::create_tree()` with worktree snapshot support * Convert all usese of `allow` to `expect` and fix those that aren't relevant anymore. * The `TreeChange` now keeps track of the underlying `gix` status item. That way it's possible to reconstruct the exact status, and know if it's coming from a change in the index, or a change in the worktree. This is relevant later when storing index changes. * Store index data in the snapshot, and resolve it as well.
1 parent a8002d5 commit eb053ce

File tree

85 files changed

+1279
-567
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1279
-567
lines changed

Cargo.lock

Lines changed: 106 additions & 109 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/but-action/src/generate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use schemars::{JsonSchema, schema_for};
1010

1111
use crate::OpenAiProvider;
1212

13-
#[allow(dead_code)]
13+
#[expect(dead_code)]
1414
pub fn commit_message_blocking(
1515
openai: &OpenAiProvider,
1616
external_summary: &str,

crates/but-action/src/grouping.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub enum BranchSuggestion {
1515
}
1616

1717
impl BranchSuggestion {
18-
#[allow(dead_code)]
18+
#[expect(dead_code)]
1919
pub fn name(&self) -> String {
2020
match self {
2121
BranchSuggestion::New(name) => name.clone(),
@@ -63,7 +63,7 @@ pub struct Grouping {
6363
pub groups: Vec<Group>,
6464
}
6565

66-
#[allow(dead_code)]
66+
#[expect(dead_code)]
6767
pub fn group(openai: &OpenAiProvider, project_status: &ProjectStatus) -> anyhow::Result<Grouping> {
6868
let system_message ="
6969
You are an expert in grouping file changes into logical units for version control.

crates/but-action/src/openai.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use schemars::{JsonSchema, schema_for};
1919
use serde::de::DeserializeOwned;
2020
use tokio::sync::Mutex;
2121

22-
#[allow(unused)]
2322
#[derive(Debug, Clone, serde::Serialize, strum::Display)]
2423
pub enum CredentialsKind {
2524
EnvVarOpenAiKey,
@@ -116,7 +115,6 @@ impl OpenAiProvider {
116115
}
117116
}
118117

119-
#[allow(dead_code)]
120118
pub fn structured_output_blocking<
121119
T: serde::Serialize + DeserializeOwned + JsonSchema + std::marker::Send + 'static,
122120
>(
@@ -144,7 +142,6 @@ pub fn structured_output_blocking<
144142
.unwrap()
145143
}
146144

147-
#[allow(dead_code)]
148145
pub async fn structured_output<T: serde::Serialize + DeserializeOwned + JsonSchema>(
149146
client: &Client<OpenAIConfig>,
150147
messages: Vec<ChatCompletionRequestMessage>,
@@ -177,7 +174,6 @@ pub async fn structured_output<T: serde::Serialize + DeserializeOwned + JsonSche
177174
Ok(None)
178175
}
179176

180-
#[allow(dead_code)]
181177
pub fn tool_calling_blocking(
182178
client: &OpenAiProvider,
183179
messages: Vec<ChatCompletionRequestMessage>,
@@ -197,7 +193,6 @@ pub fn tool_calling_blocking(
197193
.unwrap()
198194
}
199195

200-
#[allow(dead_code)]
201196
pub async fn tool_calling(
202197
client: &Client<OpenAIConfig>,
203198
messages: Vec<ChatCompletionRequestMessage>,

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ use bstr::{BStr, ByteSlice};
44
pub use tree_changes::tree_changes;
55

66
mod worktree;
7-
use crate::{ChangeState, ModeFlags, TreeChange, TreeStatus, TreeStatusKind};
8-
pub use worktree::worktree_changes;
7+
use crate::{
8+
ChangeState, IgnoredWorktreeChange, ModeFlags, TreeChange, TreeStatus, TreeStatusKind,
9+
};
10+
pub use worktree::{worktree_changes, worktree_changes_no_renames};
911

1012
/// conversion functions for use in the UI
1113
pub mod ui;
@@ -59,6 +61,24 @@ impl TreeChange {
5961
}
6062
}
6163

64+
impl std::fmt::Debug for TreeChange {
65+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66+
f.debug_struct("TreeChange")
67+
.field("path", &self.path)
68+
.field("status", &self.status)
69+
.finish()
70+
}
71+
}
72+
73+
impl std::fmt::Debug for IgnoredWorktreeChange {
74+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75+
f.debug_struct("IgnoredWorktreeChange")
76+
.field("path", &self.path)
77+
.field("status", &self.status)
78+
.finish()
79+
}
80+
}
81+
6282
impl ModeFlags {
6383
fn calculate(old: &ChangeState, new: &ChangeState) -> Option<Self> {
6484
Self::calculate_inner(old.kind, new.kind)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl From<gix::object::tree::diff::ChangeDetached> for TreeChange {
8181
},
8282
is_untracked: false,
8383
},
84+
status_item: None,
8485
},
8586
Change::Deletion {
8687
location,
@@ -95,6 +96,7 @@ impl From<gix::object::tree::diff::ChangeDetached> for TreeChange {
9596
kind: entry_mode.kind(),
9697
},
9798
},
99+
status_item: None,
98100
},
99101
Change::Modification {
100102
location,
@@ -118,6 +120,7 @@ impl From<gix::object::tree::diff::ChangeDetached> for TreeChange {
118120
state,
119121
flags: ModeFlags::calculate(&previous_state, &state),
120122
},
123+
status_item: None,
121124
}
122125
}
123126
Change::Rewrite {
@@ -147,6 +150,7 @@ impl From<gix::object::tree::diff::ChangeDetached> for TreeChange {
147150
state,
148151
flags: ModeFlags::calculate(&previous_state, &state),
149152
},
153+
status_item: None,
150154
}
151155
}
152156
}

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

Lines changed: 59 additions & 12 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 }
@@ -75,7 +99,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
7599
let mut ignored_changes = Vec::new();
76100
for change in status_changes {
77101
let change = change?;
78-
let change = match change {
102+
let change = match change.clone() {
79103
status::Item::TreeIndex(gix::diff::index::Change::Deletion {
80104
location,
81105
id,
@@ -91,6 +115,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
91115
},
92116
},
93117
path: location.into_owned(),
118+
status_item: Some(change),
94119
},
95120
),
96121
status::Item::TreeIndex(gix::diff::index::Change::Addition {
@@ -109,6 +134,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
109134
kind: into_tree_entry_kind(entry_mode)?,
110135
},
111136
},
137+
status_item: Some(change),
112138
},
113139
),
114140
status::Item::TreeIndex(gix::diff::index::Change::Modification {
@@ -136,6 +162,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
136162
state,
137163
flags: ModeFlags::calculate(&previous_state, &state),
138164
},
165+
status_item: Some(change),
139166
},
140167
)
141168
}
@@ -154,6 +181,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
154181
kind: into_tree_entry_kind(entry.mode)?,
155182
},
156183
},
184+
status_item: Some(change),
157185
},
158186
),
159187
status::Item::IndexWorktree(index_worktree::Item::Modification {
@@ -180,6 +208,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
180208
state,
181209
flags: ModeFlags::calculate(&previous_state, &state),
182210
},
211+
status_item: Some(change),
183212
},
184213
)
185214
}
@@ -216,6 +245,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
216245
state,
217246
flags: ModeFlags::calculate(&previous_state, &state),
218247
},
248+
status_item: Some(change),
219249
},
220250
)
221251
}
@@ -237,6 +267,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
237267
},
238268
is_untracked: false,
239269
},
270+
status_item: Some(change),
240271
},
241272
),
242273
status::Item::IndexWorktree(index_worktree::Item::DirectoryContents {
@@ -269,17 +300,20 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
269300
},
270301
is_untracked: true,
271302
},
303+
status_item: Some(change),
272304
},
273305
)
274306
}
275307
status::Item::IndexWorktree(index_worktree::Item::Modification {
276308
rela_path,
277309
entry,
278310
status:
279-
EntryStatus::Change(index_as_worktree::Change::SubmoduleModification(change)),
311+
EntryStatus::Change(index_as_worktree::Change::SubmoduleModification(
312+
submodule_change,
313+
)),
280314
..
281315
}) => {
282-
let Some(checked_out_head_id) = change.checked_out_head_id else {
316+
let Some(checked_out_head_id) = submodule_change.checked_out_head_id else {
283317
continue;
284318
};
285319
// We can arrive here if the user configures to `ignore = none`, and there are
@@ -306,6 +340,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
306340
state,
307341
flags: ModeFlags::calculate(&previous_state, &state),
308342
},
343+
status_item: Some(change),
309344
},
310345
)
311346
}
@@ -361,6 +396,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
361396
state,
362397
flags: ModeFlags::calculate(&previous_state, &state),
363398
},
399+
status_item: Some(change),
364400
},
365401
)
366402
}
@@ -391,17 +427,19 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
391427
state,
392428
flags: ModeFlags::calculate(&previous_state, &state),
393429
},
430+
status_item: Some(change),
394431
},
395432
)
396433
}
397434
status::Item::IndexWorktree(index_worktree::Item::Modification {
398435
rela_path,
399-
status: EntryStatus::Conflict(_conflict),
436+
status: EntryStatus::Conflict { .. },
400437
..
401438
}) => {
402439
ignored_changes.push(IgnoredWorktreeChange {
403440
path: rela_path,
404441
status: IgnoredWorktreeTreeChangeStatus::Conflict,
442+
status_item: Some(change),
405443
});
406444
continue;
407445
}
@@ -470,15 +508,17 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
470508
changes.push(merged);
471509
IgnoredWorktreeTreeChangeStatus::TreeIndex
472510
}
473-
[Some(first), Some(second)] => {
511+
[Some(mut first), Some(mut second)] => {
474512
ignored_changes.push(IgnoredWorktreeChange {
475513
path: first.path.clone(),
476514
status: IgnoredWorktreeTreeChangeStatus::TreeIndex,
515+
status_item: first.status_item.take(),
477516
});
478517
changes.push(first);
479518
ignored_changes.push(IgnoredWorktreeChange {
480519
path: second.path.clone(),
481520
status: IgnoredWorktreeTreeChangeStatus::TreeIndex,
521+
status_item: second.status_item.take(),
482522
});
483523
changes.push(second);
484524
continue;
@@ -487,6 +527,7 @@ pub fn worktree_changes(repo: &gix::Repository) -> anyhow::Result<WorktreeChange
487527
ignored_changes.push(IgnoredWorktreeChange {
488528
path: change_path,
489529
status,
530+
status_item: None,
490531
});
491532
continue;
492533
}
@@ -655,6 +696,8 @@ fn merge_changes(
655696
status: TreeStatus::Deletion {
656697
previous_state: *previous_state,
657698
},
699+
// NOTE: not relevant, as renames are disabled for snapshots where this is used.
700+
status_item: None,
658701
}));
659702
}
660703
(
@@ -674,6 +717,8 @@ fn merge_changes(
674717
// It's just in the index, which to us doesn't exist.
675718
is_untracked: true,
676719
},
720+
// NOTE: not relevant, as renames are disabled for snapshots where this is used.
721+
status_item: None,
677722
}),
678723
Some(TreeChange {
679724
path: index_wt.path,
@@ -685,6 +730,8 @@ fn merge_changes(
685730
// read the initial state of a file from.
686731
is_untracked: true,
687732
},
733+
// NOTE: not relevant, as renames are disabled for snapshots where this is used.
734+
status_item: None,
688735
}),
689736
]);
690737
}

0 commit comments

Comments
 (0)