Skip to content

Commit 79a06df

Browse files
committed
Reuse the worktree-diff during 'recalulate-everything'
It's used for emission of changed files, but also for listing branch information. Make sure we only run the worktree-status once to save time. This also unifies the acquisition of the `HEAD^{commit}`, which one day will be a natural feature once `gix::Repository` is used.
1 parent 13deabd commit 79a06df

File tree

8 files changed

+114
-43
lines changed

8 files changed

+114
-43
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gitbutler-branch-actions/src/actions.rs

+40-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
use super::r#virtual as branch;
2+
use crate::branch::get_uncommited_files_raw;
3+
use crate::{
4+
base::{
5+
get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch,
6+
BaseBranch,
7+
},
8+
branch::get_uncommited_files,
9+
branch_manager::BranchManagerExt,
10+
file::RemoteBranchFile,
11+
remote::{get_branch_data, list_remote_branches, RemoteBranch, RemoteBranchData},
12+
VirtualBranchesExt,
13+
};
114
use anyhow::{Context, Result};
215
use gitbutler_branch::{
316
BranchCreateRequest, BranchId, BranchOwnershipClaims, BranchUpdateRequest, ChangeReference,
417
};
518
use gitbutler_command_context::CommandContext;
19+
use gitbutler_diff::DiffByPathMap;
620
use gitbutler_operating_modes::assure_open_workspace_mode;
721
use gitbutler_oplog::{
822
entry::{OperationKind, SnapshotDetails},
@@ -13,19 +27,6 @@ use gitbutler_reference::{ReferenceName, Refname, RemoteRefname};
1327
use gitbutler_repo::{credentials::Helper, RepoActionsExt, RepositoryExt};
1428
use tracing::instrument;
1529

16-
use super::r#virtual as branch;
17-
use crate::{
18-
base::{
19-
get_base_branch_data, set_base_branch, set_target_push_remote, update_base_branch,
20-
BaseBranch,
21-
},
22-
branch::get_uncommited_files,
23-
branch_manager::BranchManagerExt,
24-
file::RemoteBranchFile,
25-
remote::{get_branch_data, list_remote_branches, RemoteBranch, RemoteBranchData},
26-
VirtualBranchesExt,
27-
};
28-
2930
#[derive(Clone, Copy, Default)]
3031
pub struct VirtualBranchActions;
3132

@@ -81,6 +82,24 @@ impl VirtualBranchActions {
8182
.map_err(Into::into)
8283
}
8384

85+
pub fn list_virtual_branches_cached(
86+
&self,
87+
project: &Project,
88+
worktree_changes: Option<DiffByPathMap>,
89+
) -> Result<(Vec<branch::VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
90+
let ctx = open_with_verify(project)?;
91+
92+
assure_open_workspace_mode(&ctx)
93+
.context("Listing virtual branches requires open workspace mode")?;
94+
95+
branch::list_virtual_branches_cached(
96+
&ctx,
97+
project.exclusive_worktree_access().write_permission(),
98+
worktree_changes,
99+
)
100+
.map_err(Into::into)
101+
}
102+
84103
pub fn create_virtual_branch(
85104
&self,
86105
project: &Project,
@@ -569,11 +588,17 @@ impl VirtualBranchActions {
569588

570589
pub fn get_uncommited_files(&self, project: &Project) -> Result<Vec<RemoteBranchFile>> {
571590
let context = CommandContext::open(project)?;
572-
573591
let guard = project.exclusive_worktree_access();
574-
575592
get_uncommited_files(&context, guard.read_permission())
576593
}
594+
595+
/// Like [`get_uncommited_files()`], but returns a type that can be re-used with
596+
/// [`crate::list_virtual_branches()`].
597+
pub fn get_uncommited_files_reusable(&self, project: &Project) -> Result<DiffByPathMap> {
598+
let context = CommandContext::open(project)?;
599+
let guard = project.exclusive_worktree_access();
600+
get_uncommited_files_raw(&context, guard.read_permission())
601+
}
577602
}
578603

579604
fn open_with_verify(project: &Project) -> Result<CommandContext> {

crates/gitbutler-branch-actions/src/branch.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use gitbutler_branch::{
66
Branch as GitButlerBranch, BranchId, BranchIdentity, ReferenceExtGix, Target,
77
};
88
use gitbutler_command_context::{CommandContext, GixRepositoryExt};
9+
use gitbutler_diff::DiffByPathMap;
910
use gitbutler_project::access::WorktreeReadPermission;
1011
use gitbutler_reference::normalize_branch_name;
1112
use gitbutler_repo::RepositoryExt;
@@ -23,14 +24,21 @@ use std::{
2324
vec,
2425
};
2526

26-
pub(crate) fn get_uncommited_files(
27+
pub(crate) fn get_uncommited_files_raw(
2728
context: &CommandContext,
2829
_permission: &WorktreeReadPermission,
29-
) -> Result<Vec<RemoteBranchFile>> {
30+
) -> Result<DiffByPathMap> {
3031
let repository = context.repository();
3132
let head_commit = repository.head_commit()?;
32-
let files = gitbutler_diff::workdir(repository, &head_commit.id())
33-
.context("Failed to list uncommited files")?
33+
Ok(gitbutler_diff::workdir(repository, &head_commit.id())
34+
.context("Failed to list uncommited files")?)
35+
}
36+
37+
pub(crate) fn get_uncommited_files(
38+
context: &CommandContext,
39+
_permission: &WorktreeReadPermission,
40+
) -> Result<Vec<RemoteBranchFile>> {
41+
let files = get_uncommited_files_raw(context, _permission)?
3442
.into_iter()
3543
.map(|(path, file)| {
3644
let binary = file.hunks.iter().any(|h| h.binary);

crates/gitbutler-branch-actions/src/status.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,24 @@ pub struct VirtualBranchesStatus {
2727
pub skipped_files: Vec<gitbutler_diff::FileDiff>,
2828
}
2929

30+
pub fn get_applied_status(
31+
ctx: &CommandContext,
32+
perm: Option<&mut WorktreeWritePermission>,
33+
) -> Result<VirtualBranchesStatus> {
34+
get_applied_status_cached(ctx, perm, None)
35+
}
36+
3037
/// Returns branches and their associated file changes, in addition to a list
3138
/// of skipped files.
39+
/// `worktree_changes` are all changed files against the current `HEAD^{tree}` and index
40+
/// against the current working tree directory, and it's used to avoid double-computing
41+
/// this expensive information.
3242
// TODO(kv): make this side effect free
33-
#[instrument(level = tracing::Level::DEBUG, skip(ctx, perm))]
34-
pub fn get_applied_status(
43+
#[instrument(level = tracing::Level::DEBUG, skip(ctx, perm, worktree_changes))]
44+
pub fn get_applied_status_cached(
3545
ctx: &CommandContext,
3646
perm: Option<&mut WorktreeWritePermission>,
47+
worktree_changes: Option<gitbutler_diff::DiffByPathMap>,
3748
) -> Result<VirtualBranchesStatus> {
3849
assure_open_workspace_mode(ctx).context("ng applied status requires open workspace mode")?;
3950
// TODO(ST): this was `get_workspace_head()`, which is slow and ideally, we don't dynamically
@@ -45,9 +56,10 @@ pub fn get_applied_status(
4556
.project()
4657
.virtual_branches()
4758
.list_branches_in_workspace()?;
48-
let base_file_diffs =
59+
let base_file_diffs = worktree_changes.map(Ok).unwrap_or_else(|| {
4960
gitbutler_diff::workdir(ctx.repository(), &integration_commit_id.to_owned())
50-
.context("failed to diff workdir")?;
61+
.context("failed to diff workdir")
62+
})?;
5163

5264
let mut skipped_files: Vec<gitbutler_diff::FileDiff> = Vec::new();
5365
for file_diff in base_file_diffs.values() {

crates/gitbutler-branch-actions/src/virtual.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
hunk::VirtualBranchHunk,
1414
integration::get_workspace_head,
1515
remote::{branch_to_remote_branch, RemoteBranch},
16-
status::get_applied_status,
16+
status::{get_applied_status, get_applied_status_cached},
1717
Get, VirtualBranchesExt,
1818
};
1919
use anyhow::{anyhow, bail, Context, Result};
@@ -254,13 +254,23 @@ fn resolve_old_applied_state(
254254

255255
Ok(())
256256
}
257-
258-
#[instrument(level = tracing::Level::DEBUG, skip(ctx, perm))]
259257
pub fn list_virtual_branches(
258+
ctx: &CommandContext,
259+
perm: &mut WorktreeWritePermission,
260+
) -> Result<(Vec<VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
261+
list_virtual_branches_cached(ctx, perm, None)
262+
}
263+
264+
/// `worktree_changes` are all changed files against the current `HEAD^{tree}` and index
265+
/// against the current working tree directory, and it's used to avoid double-computing
266+
/// this expensive information.
267+
#[instrument(level = tracing::Level::DEBUG, skip(ctx, perm, worktree_changes))]
268+
pub fn list_virtual_branches_cached(
260269
ctx: &CommandContext,
261270
// TODO(ST): this should really only shared access, but there is some internals
262271
// that conditionally write things.
263272
perm: &mut WorktreeWritePermission,
273+
worktree_changes: Option<gitbutler_diff::DiffByPathMap>,
264274
) -> Result<(Vec<VirtualBranch>, Vec<gitbutler_diff::FileDiff>)> {
265275
assure_open_workspace_mode(ctx)
266276
.context("Listing virtual branches requires open workspace mode")?;
@@ -274,7 +284,7 @@ pub fn list_virtual_branches(
274284
.get_default_target()
275285
.context("failed to get default target")?;
276286

277-
let status = get_applied_status(ctx, Some(perm))?;
287+
let status = get_applied_status_cached(ctx, Some(perm), worktree_changes)?;
278288
let max_selected_for_changes = status
279289
.branches
280290
.iter()

crates/gitbutler-diff/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod diff;
22
mod hunk;
33
pub mod write;
44
pub use diff::{
5-
diff_files_into_hunks, hunks_by_filepath, reverse_hunk, trees, workdir, ChangeType, FileDiff,
6-
GitHunk,
5+
diff_files_into_hunks, hunks_by_filepath, reverse_hunk, trees, workdir, ChangeType,
6+
DiffByPathMap, FileDiff, GitHunk,
77
};
88
pub use hunk::{Hunk, HunkHash};

crates/gitbutler-watcher/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ gix = { workspace = true, features = [
2424
] }
2525
gitbutler-command-context.workspace = true
2626
gitbutler-project.workspace = true
27+
gitbutler-diff.workspace = true
2728
gitbutler-user.workspace = true
2829
gitbutler-reference.workspace = true
2930
gitbutler-error.workspace = true

crates/gitbutler-watcher/src/handler.rs

+28-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::{path::PathBuf, sync::Arc};
22

3+
use super::{events, Change};
34
use anyhow::{Context, Result};
4-
use gitbutler_branch_actions::{VirtualBranchActions, VirtualBranches};
5+
use gitbutler_branch_actions::{RemoteBranchFile, VirtualBranchActions, VirtualBranches};
56
use gitbutler_command_context::CommandContext;
7+
use gitbutler_diff::DiffByPathMap;
68
use gitbutler_error::error::Marker;
79
use gitbutler_operating_modes::{
810
in_open_workspace_mode, in_outside_workspace_mode, operating_mode,
@@ -18,8 +20,6 @@ use gitbutler_sync::cloud::{push_oplog, push_repo};
1820
use gitbutler_user as users;
1921
use tracing::instrument;
2022

21-
use super::{events, Change};
22-
2323
/// A type that contains enough state to make decisions based on changes in the filesystem, which themselves
2424
/// may trigger [Changes](Change)
2525
// NOTE: This is `Clone` as each incoming event is spawned onto a thread for processing.
@@ -71,7 +71,7 @@ impl Handler {
7171

7272
// This is only produced at the end of mutating Tauri commands to trigger a fresh state being served to the UI.
7373
events::InternalEvent::CalculateVirtualBranches(project_id) => self
74-
.calculate_virtual_branches(project_id)
74+
.calculate_virtual_branches(project_id, None)
7575
.context("failed to handle virtual branch event"),
7676
}
7777
}
@@ -90,8 +90,12 @@ impl Handler {
9090
CommandContext::open(&project).context("Failed to create a command context")
9191
}
9292

93-
#[instrument(skip(self, project_id))]
94-
fn calculate_virtual_branches(&self, project_id: ProjectId) -> Result<()> {
93+
#[instrument(skip(self, project_id, worktree_changes))]
94+
fn calculate_virtual_branches(
95+
&self,
96+
project_id: ProjectId,
97+
worktree_changes: Option<DiffByPathMap>,
98+
) -> Result<()> {
9599
let ctx = self.open_command_context(project_id)?;
96100
// Skip if we're not on the open workspace mode
97101
if !in_open_workspace_mode(&ctx) {
@@ -102,7 +106,7 @@ impl Handler {
102106
.projects
103107
.get(project_id)
104108
.context("failed to get project")?;
105-
match VirtualBranchActions.list_virtual_branches(&project) {
109+
match VirtualBranchActions.list_virtual_branches_cached(&project, worktree_changes) {
106110
Ok((branches, skipped_files)) => self.emit_app_event(Change::VirtualBranches {
107111
project_id: project.id,
108112
virtual_branches: VirtualBranches {
@@ -126,26 +130,36 @@ impl Handler {
126130
fn recalculate_everything(&self, paths: Vec<PathBuf>, project_id: ProjectId) -> Result<()> {
127131
let ctx = self.open_command_context(project_id)?;
128132

129-
self.emit_uncommited_files(ctx.project());
133+
let worktree_changes = self.emit_uncommited_files(ctx.project()).ok();
130134

131135
if in_open_workspace_mode(&ctx) {
132136
self.maybe_create_snapshot(project_id).ok();
133-
self.calculate_virtual_branches(project_id)?;
137+
self.calculate_virtual_branches(project_id, worktree_changes)?;
134138
}
135139

136140
Ok(())
137141
}
138142

139143
/// Try to emit uncommited files. Swollow errors if they arrise.
140-
fn emit_uncommited_files(&self, project: &Project) {
141-
let Ok(files) = VirtualBranchActions.get_uncommited_files(project) else {
142-
return;
143-
};
144+
fn emit_uncommited_files(&self, project: &Project) -> Result<DiffByPathMap> {
145+
let files = VirtualBranchActions.get_uncommited_files_reusable(project)?;
144146

145147
let _ = self.emit_app_event(Change::UncommitedFiles {
146148
project_id: project.id,
147-
files,
149+
files: files
150+
.clone()
151+
.into_iter()
152+
.map(|(path, file)| {
153+
let binary = file.hunks.iter().any(|h| h.binary);
154+
RemoteBranchFile {
155+
path,
156+
hunks: file.hunks,
157+
binary,
158+
}
159+
})
160+
.collect(),
148161
});
162+
Ok(files)
149163
}
150164

151165
fn maybe_create_snapshot(&self, project_id: ProjectId) -> anyhow::Result<()> {

0 commit comments

Comments
 (0)