Skip to content

Commit ef5b2b9

Browse files
Merge pull request #9864 from gitbutlerapp/fix-broken-history-previews
Fix broken history previews
2 parents ca775b5 + b26d1c3 commit ef5b2b9

File tree

8 files changed

+79
-146
lines changed

8 files changed

+79
-146
lines changed

apps/desktop/src/components/FeedItem.svelte

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import FeedActionDiff from '$components/FeedActionDiff.svelte';
33
import FeedItemKind from '$components/FeedItemKind.svelte';
44
import FeedStreamMessage from '$components/FeedStreamMessage.svelte';
5+
import ReduxResult from '$components/ReduxResult.svelte';
56
import {
67
allCommitsUpdated,
78
ButlerAction,
@@ -14,6 +15,7 @@
1415
} from '$lib/actions/types';
1516
import butbotSvg from '$lib/assets/butbot-actions.svg?raw';
1617
import { isFeedMessage, isInProgressAssistantMessage, type FeedEntry } from '$lib/feed/feed';
18+
import { HISTORY_SERVICE } from '$lib/history/history';
1719
import { Snapshot } from '$lib/history/types';
1820
import { USER } from '$lib/user/user';
1921
import { inject } from '@gitbutler/shared/context';
@@ -27,6 +29,14 @@
2729
const { action, projectId }: Props = $props();
2830
2931
const user = inject(USER);
32+
const historyService = inject(HISTORY_SERVICE);
33+
34+
// Get snapshot diff for Snapshot feed items
35+
const snapshotDiff = $derived(
36+
action instanceof Snapshot
37+
? historyService.snapshotDiff({ projectId, snapshotId: action.id })
38+
: null
39+
);
3040
let failedToLoadImage = $state(false);
3141
3242
function workflowTriggerTooltip(workflow: Workflow): string {
@@ -142,9 +152,15 @@
142152
{#if action.details?.body}
143153
<Markdown content={action.details?.body} />
144154
{/if}
145-
{#each action.filesChanged as file}
146-
<span class="text-greyer">{file}</span>
147-
{/each}
155+
{#if snapshotDiff}
156+
<ReduxResult result={snapshotDiff.current} {projectId}>
157+
{#snippet children(files)}
158+
{#each files as file}
159+
<span class="text-greyer">{file.path}</span>
160+
{/each}
161+
{/snippet}
162+
</ReduxResult>
163+
{/if}
148164
</span>
149165
</div>
150166
{:else if action instanceof Workflow}

apps/desktop/src/components/SnapshotCard.svelte

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
2+
import ReduxResult from '$components/ReduxResult.svelte';
23
import SnapshotAttachment from '$components/SnapshotAttachment.svelte';
3-
import { createdOnDay } from '$lib/history/history';
4+
import { createdOnDay, HISTORY_SERVICE } from '$lib/history/history';
45
import { MODE_SERVICE } from '$lib/mode/modeService';
56
import { toHumanReadableTime } from '$lib/utils/time';
67
import { inject } from '@gitbutler/shared/context';
@@ -168,6 +169,9 @@
168169
169170
const modeService = inject(MODE_SERVICE);
170171
const mode = $derived(modeService.mode({ projectId }));
172+
173+
const historyService = inject(HISTORY_SERVICE);
174+
const snapshotDiff = $derived(historyService.snapshotDiff({ projectId, snapshotId: entry.id }));
171175
</script>
172176

173177
<div
@@ -214,25 +218,25 @@
214218
{/if}
215219
</div>
216220

217-
{#if entry.filesChanged.length > 0 && !isRestoreSnapshot}
218-
{@const files = entry.filesChanged}
219-
<SnapshotAttachment
220-
foldable={entry.filesChanged.length > 2}
221-
foldedAmount={entry.filesChanged.length}
222-
>
223-
<div class="files-attacment">
224-
{#each files as filePath}
225-
<FileListItem
226-
listMode="list"
227-
{filePath}
228-
onclick={() => onDiffClick(filePath)}
229-
selected={selectedFile?.path === filePath && selectedFile?.entryId === entry.id}
230-
hideBorder={filePath === files[files.length - 1]}
231-
/>
232-
{/each}
233-
</div>
234-
</SnapshotAttachment>
235-
{/if}
221+
<ReduxResult result={snapshotDiff.current} {projectId}>
222+
{#snippet children(files)}
223+
{#if files.length > 0 && !isRestoreSnapshot}
224+
<SnapshotAttachment foldable={files.length > 2} foldedAmount={files.length}>
225+
<div class="files-attacment">
226+
{#each files as file, idx}
227+
<FileListItem
228+
listMode="list"
229+
filePath={file.path}
230+
onclick={() => onDiffClick(file.path)}
231+
selected={selectedFile?.path === file.path && selectedFile?.entryId === entry.id}
232+
hideBorder={idx === files.length - 1}
233+
/>
234+
{/each}
235+
</div>
236+
</SnapshotAttachment>
237+
{/if}
238+
{/snippet}
239+
</ReduxResult>
236240

237241
{#if isRestoreSnapshot}
238242
<SnapshotAttachment>

apps/desktop/src/lib/history/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ export class SnapshotDetails {
5656

5757
export class Snapshot {
5858
id!: string;
59-
linesAdded!: number;
60-
linesRemoved!: number;
61-
filesChanged!: string[];
6259
@Type(() => SnapshotDetails)
6360
details?: SnapshotDetails;
6461
@Transform((obj) => new Date(obj.value * 1000))

apps/desktop/src/routes/[projectId]/history/+page.svelte

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
1212
import { HISTORY_SERVICE, createdOnDay } from '$lib/history/history';
1313
import { ID_SELECTION } from '$lib/selection/idSelection.svelte';
14-
import { createSnapshotSelection } from '$lib/selection/key';
14+
import { createSnapshotSelection, type SelectionId } from '$lib/selection/key';
1515
import { inject } from '@gitbutler/shared/context';
1616
import { EmptyStatePlaceholder, Icon, FileViewHeader } from '@gitbutler/ui';
1717
import { stickyHeader } from '@gitbutler/ui/utils/stickyHeader';
1818
import type { Snapshot } from '$lib/history/types';
19-
import type { TreeChange } from '$lib/hunks/change';
20-
import type { SelectedFile } from '$lib/selection/key';
2119
2220
// TODO: Refactor so we don't need non-null assertion.
2321
const projectId = $derived(page.params.projectId!);
@@ -37,9 +35,14 @@
3735
3836
const withinRestoreItems = $derived(findRestorationRanges($snapshots));
3937
40-
let snapshotFilesTempStore: { entryId: string; diffs: TreeChange[] } | undefined =
41-
$state(undefined);
42-
let selectedFile: (SelectedFile & { type: 'snapshot' }) | undefined = $state(undefined);
38+
let currentSelectionId: SelectionId | undefined = $state(undefined);
39+
40+
// Derive selectedFile from the selection service
41+
const selectedFile = $derived.by(() => {
42+
if (!currentSelectionId) return undefined;
43+
const selections = idSelection.values(currentSelectionId);
44+
return selections.length > 0 ? selections[0] : undefined;
45+
});
4346
4447
async function onLastInView() {
4548
if (!$loading && !$isAllLoaded) await snapshotManager.loadMore();
@@ -67,16 +70,15 @@
6770
return ranges.map((snapshot) => snapshot.id);
6871
}
6972
70-
function updateFilePreview(entry: Snapshot, path: string) {
71-
if (!snapshotFilesTempStore) return;
72-
73-
const file = snapshotFilesTempStore.diffs
74-
.map((tc, i) => [tc, i] as const)
75-
.find(([tc]) => tc.path === path);
76-
if (!file) return;
77-
73+
async function updateFilePreview(entry: Snapshot, path: string) {
7874
const selectionId = createSnapshotSelection({ snapshotId: entry.id });
79-
idSelection.set(path, selectionId, file[1]);
75+
// Get the diff data to find the file index
76+
const diffs = await historyService.getSnapshotDiff(projectId, entry.id);
77+
const fileIndex = diffs.findIndex((tc) => tc.path === path);
78+
if (fileIndex === -1) return;
79+
80+
idSelection.set(path, selectionId, fileIndex);
81+
currentSelectionId = selectionId;
8082
}
8183
</script>
8284

@@ -130,21 +132,20 @@
130132
// Until we have that figured out, we need to reload the page.
131133
location.reload();
132134
}}
133-
onDiffClick={async (path) => {
134-
if (snapshotFilesTempStore?.entryId === entry.id) {
135-
if (selectedFile?.path === path) {
136-
selectedFile = undefined;
137-
} else {
138-
updateFilePreview(entry, path);
139-
}
135+
onDiffClick={(path) => {
136+
if (
137+
selectedFile?.path === path &&
138+
selectedFile?.type === 'snapshot' &&
139+
selectedFile.snapshotId === entry.id
140+
) {
141+
currentSelectionId = undefined;
140142
} else {
141-
snapshotFilesTempStore = {
142-
entryId: entry.id,
143-
diffs: await historyService.getSnapshotDiff(projectId, entry.id)
144-
};
145143
updateFilePreview(entry, path);
146144
}
147145
}}
146+
selectedFile={currentSelectionId && selectedFile && selectedFile.type === 'snapshot'
147+
? { entryId: selectedFile.snapshotId, path: selectedFile.path }
148+
: undefined}
148149
/>
149150
{/if}
150151
{/each}
@@ -204,16 +205,12 @@
204205
filePath={selectedFile.path}
205206
draggable={false}
206207
oncloseclick={() => {
207-
selectedFile = undefined;
208+
currentSelectionId = undefined;
208209
}}
209210
/>
210211
</div>
211212

212-
<SelectionView
213-
{projectId}
214-
diffOnly
215-
selectionId={createSnapshotSelection({ snapshotId: selectedFile.snapshotId })}
216-
/>
213+
<SelectionView {projectId} diffOnly selectionId={currentSelectionId} />
217214
</ConfigurableScrollableContainer>
218215
</div>
219216
{:else}

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,16 +364,6 @@ fn restores_gitbutler_workspace() -> anyhow::Result<()> {
364364
&all_snapshots[3..],
365365
);
366366

367-
let first_snapshot = all_snapshots.last().unwrap();
368-
assert_eq!(
369-
(
370-
first_snapshot.lines_added,
371-
first_snapshot.lines_removed,
372-
first_snapshot.files_changed.len()
373-
),
374-
(0, 0, 0),
375-
"The first snapshot is intentionally not listing everything as changed"
376-
);
377367
Ok(())
378368
}
379369

crates/gitbutler-oplog/src/entry.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{
22
fmt,
33
fmt::{Debug, Display, Formatter},
4-
path::PathBuf,
54
str::FromStr,
65
};
76

@@ -21,12 +20,6 @@ pub struct Snapshot {
2120
/// Snapshot creation time in seconds from Unix epoch seconds, based on a commit as `commit_id`.
2221
#[serde(serialize_with = "gitbutler_serde::as_time_seconds_from_unix_epoch")]
2322
pub created_at: git2::Time,
24-
/// The number of working directory lines added in the snapshot
25-
pub lines_added: usize,
26-
/// The number of working directory lines removed in the snapshot
27-
pub lines_removed: usize,
28-
/// The list of working directory files that were changed in the snapshot
29-
pub files_changed: Vec<PathBuf>,
3023
/// Snapshot details as persisted in the commit message, or `None` if the details couldn't be parsed.
3124
pub details: Option<SnapshotDetails>,
3225
}

crates/gitbutler-oplog/src/oplog.rs

Lines changed: 6 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ use gitbutler_project::{
2929
use gitbutler_repo::RepositoryExt;
3030
use gitbutler_repo::SignaturePurpose;
3131
use gitbutler_stack::{Stack, VirtualBranchesHandle, VirtualBranchesState};
32-
use gix::object::tree::diff::Change;
3332
use gix::prelude::ObjectIdExt;
3433
use gix::{bstr::ByteSlice, ObjectId};
3534
use tracing::instrument;
@@ -197,7 +196,6 @@ impl OplogExt for CommandContext {
197196
.attach(&repo);
198197

199198
let mut snapshots = Vec::new();
200-
let mut wd_trees_cache: HashMap<gix::ObjectId, gix::ObjectId> = HashMap::new();
201199

202200
for commit_info in traversal_root_id.ancestors().all()? {
203201
if snapshots.len() == limit {
@@ -224,14 +222,6 @@ impl OplogExt for CommandContext {
224222
continue;
225223
}
226224

227-
// Get tree id from cache or calculate it
228-
let wd_tree = repo.find_tree(get_workdir_tree(
229-
Some(&mut wd_trees_cache),
230-
commit_id,
231-
&repo,
232-
self,
233-
)?)?;
234-
235225
let commit_id = gix_to_git2_oid(commit_id);
236226
let details = commit
237227
.message_raw()?
@@ -245,63 +235,12 @@ impl OplogExt for CommandContext {
245235
}
246236
}
247237

248-
if let Some(parent_id) = first_parent {
249-
// Get tree id from cache or calculate it
250-
251-
let mut files_changed = Vec::new();
252-
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
253-
let (mut lines_added, mut lines_removed) = (0, 0);
254-
let parent_tree = repo.find_tree(get_workdir_tree(
255-
Some(&mut wd_trees_cache),
256-
parent_id,
257-
&repo,
258-
self,
259-
)?)?;
260-
parent_tree
261-
.changes()?
262-
.options(|opts| {
263-
opts.track_rewrites(None).track_path();
264-
})
265-
.for_each_to_obtain_tree(&wd_tree, |change| -> Result<_> {
266-
match change {
267-
Change::Addition { location, .. } => {
268-
files_changed.push(gix::path::from_bstr(location).into_owned());
269-
}
270-
Change::Deletion { .. }
271-
| Change::Modification { .. }
272-
| Change::Rewrite { .. } => {}
273-
}
274-
if let Some(counts) = change
275-
.diff(&mut resource_cache)
276-
.ok()
277-
.and_then(|mut platform| platform.line_counts().ok().flatten())
278-
{
279-
lines_added += u64::from(counts.insertions);
280-
lines_removed += u64::from(counts.removals);
281-
}
282-
resource_cache.clear_resource_cache_keep_allocation();
283-
284-
Ok(gix::object::tree::diff::Action::Continue)
285-
})?;
286-
287-
snapshots.push(Snapshot {
288-
commit_id,
289-
details,
290-
lines_added: lines_added as usize,
291-
lines_removed: lines_removed as usize,
292-
files_changed,
293-
created_at: commit_time,
294-
});
295-
} else {
296-
// this is the very first snapshot
297-
snapshots.push(Snapshot {
298-
commit_id,
299-
details,
300-
lines_added: 0,
301-
lines_removed: 0,
302-
files_changed: Vec::new(),
303-
created_at: commit_time,
304-
});
238+
snapshots.push(Snapshot {
239+
commit_id,
240+
details,
241+
created_at: commit_time,
242+
});
243+
if first_parent.is_none() {
305244
break;
306245
}
307246
}

crates/gitbutler-oplog/tests/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ mod operation_kind {
9494
}
9595

9696
mod snapshot_details {
97-
use std::{path::PathBuf, str::FromStr};
97+
use std::str::FromStr;
9898

9999
use gitbutler_oplog::entry::{OperationKind, Snapshot, SnapshotDetails, Trailer, Version};
100100

@@ -109,9 +109,6 @@ mod snapshot_details {
109109
let snapshot = Snapshot {
110110
commit_id: commit_sha,
111111
created_at,
112-
lines_added: 1,
113-
lines_removed: 1,
114-
files_changed: vec![PathBuf::from("foo.txt")],
115112
details: Some(details),
116113
};
117114
assert_eq!(snapshot.commit_id, commit_sha);

0 commit comments

Comments
 (0)