Diff from the status cache: the working-copy change rendered against its indexed base (refs #41) 77445673174b
While the status cache holds, oxy diff and diff --json render the stored working-copy change against its base loaded from the state index, with no snapshot; Repo::working_copy_with_base picks that or a snapshot, and change_base's rendering is shared as render_base_for. On the Linux tree, one file edited: 0.34 s (from 0.41 s), 250 MB (from 360 MB). A test checks the cached diff renders exactly as a snapshot's.
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-10-01 19:48 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
77445673174be3dcd5b8e13a15d5402d2bb828e549e183533f8eb36cf8d931b4- Depends on
- 1d360c91a289 3604179679eb 75167672ebcd 86e1824f2d0b 8ab265dc4a51 982864068a59 c6aa5bd9d1c8 fce44ae9ad29
- Depended on by
- 74b4a3c62ea4 dd6147dcf9c2 e226a6cc2f71 10b4fd430eab 1ec3f008e96a
- In bookmarks
- agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
- In tags
- v0.10.0 v0.11.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
- Edits
- 9 atoms, 0 tree operations, 5 files
Browse the tree at this change
modified crates/oxy-core/src/repo/mod.rs +11 −1
| … | 1942 unchanged lines |
||
| 1943 | 1943 | /// contents of the files the change edits. Other text files are left |
|
| 1944 | 1944 | /// unrendered ([`Snapshot::unrendered`]). |
|
| 1945 | 1945 | pub fn change_base(&self, change: &Change) -> Result<(State, Snapshot)> { |
|
| 1946 | - | let mut base = self.load_state_history(&change.deps)?; |
|
| 1946 | + | let base = self.load_state_history(&change.deps)?; |
|
| 1947 | + | self.render_base_for(change, base) |
|
| 1948 | + | } |
|
| 1949 | + | ||
| 1950 | + | /// [`Repo::change_base`] with the base already loaded (the working |
|
| 1951 | + | /// copy's comes from the state index). |
|
| 1952 | + | pub(crate) fn render_base_for( |
|
| 1953 | + | &self, |
|
| 1954 | + | change: &Change, |
|
| 1955 | + | mut base: State, |
|
| 1956 | + | ) -> Result<(State, Snapshot)> { |
|
| 1947 | 1957 | let edited: std::collections::HashSet<NodeId> = |
|
| 1948 | 1958 | change.edits.iter().map(|e| e.node()).collect(); |
|
| 1949 | 1959 | let nodes: Vec<NodeId> = edited.iter().copied().collect(); |
|
| … | 129 unchanged lines |
modified crates/oxy-core/src/repo/snapshot.rs +17 −4
| … | 1413 unchanged lines |
||
| 1414 | 1414 | let (base, snapshot) = self.change_base(&c)?; |
|
| 1415 | 1415 | (c, base, snapshot) |
|
| 1416 | 1416 | } |
|
| 1417 | - | None => { |
|
| 1418 | - | let snap = self.snapshot_status("oxy diff")?; |
|
| 1419 | - | (snap.wc, snap.base, snap.base_snapshot) |
|
| 1420 | - | } |
|
| 1417 | + | None => self.working_copy_with_base()?, |
|
| 1421 | 1418 | }; |
|
| 1422 | 1419 | Ok(diff_files_of(&c, &base, &base_snapshot)) |
|
| 1420 | + | } |
|
| 1421 | + | ||
| 1422 | + | /// The working-copy change with its base state and the base's |
|
| 1423 | + | /// rendering, for showing what the working copy changes. While the |
|
| 1424 | + | /// status cache holds (every file as the last snapshot left it), the |
|
| 1425 | + | /// stored working-copy change is the answer and no snapshot is taken; |
|
| 1426 | + | /// otherwise a snapshot makes it. |
|
| 1427 | + | pub fn working_copy_with_base(&self) -> Result<(Change, State, Snapshot)> { |
|
| 1428 | + | if self.status_by_manifest()?.is_some() { |
|
| 1429 | + | let (_, wc) = self.working_copy()?; |
|
| 1430 | + | let base = self.load_state_indexed(&wc.deps)?; |
|
| 1431 | + | let (base, snapshot) = self.render_base_for(&wc, base)?; |
|
| 1432 | + | return Ok((wc, base, snapshot)); |
|
| 1433 | + | } |
|
| 1434 | + | let snap = self.snapshot_status("oxy diff")?; |
|
| 1435 | + | Ok((snap.wc, snap.base, snap.base_snapshot)) |
|
| 1423 | 1436 | } |
|
| 1424 | 1437 | ||
| 1425 | 1438 | /// The log restricted to changes touching `path` (its node, so renames |
|
| … | 1191 unchanged lines |
modified crates/oxy-core/src/repo/tests.rs +26 −0
| … | 5954 unchanged lines |
||
| 5955 | 5955 | assert_eq!(repo.status_by_manifest().unwrap(), Some(vec![])); |
|
| 5956 | 5956 | } |
|
| 5957 | 5957 | ||
| 5958 | + | /// oxygen#41: while the status cache holds, the working copy's diff is |
|
| 5959 | + | /// rendered from the stored working-copy change, and says exactly what a |
|
| 5960 | + | /// snapshot's diff says. |
|
| 5961 | + | #[test] |
|
| 5962 | + | fn the_working_copy_diff_from_the_cache_matches_a_snapshot() { |
|
| 5963 | + | hermetic(); |
|
| 5964 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 5965 | + | let root = dir.path(); |
|
| 5966 | + | let repo = Repo::init(root).unwrap(); |
|
| 5967 | + | write(root, "a.txt", b"one\ntwo\nthree\n"); |
|
| 5968 | + | write(root, "gone.txt", b"bye\n"); |
|
| 5969 | + | repo.commit("base", &[]).unwrap(); |
|
| 5970 | + | write(root, "a.txt", b"one\n2\nthree\nfour\n"); |
|
| 5971 | + | write(root, "new.txt", b"hello\n"); |
|
| 5972 | + | fs::remove_file(root.join("gone.txt")).unwrap(); |
|
| 5973 | + | let snap = repo.snapshot_status("oxy status").unwrap(); |
|
| 5974 | + | let slow = render_change(&snap.wc, &snap.base, &snap.base_snapshot); |
|
| 5975 | + | assert!( |
|
| 5976 | + | repo.status_by_manifest().unwrap().is_some(), |
|
| 5977 | + | "the cache holds" |
|
| 5978 | + | ); |
|
| 5979 | + | let (wc, base, snapshot) = repo.working_copy_with_base().unwrap(); |
|
| 5980 | + | assert_eq!(render_change(&wc, &base, &snapshot), slow); |
|
| 5981 | + | assert_eq!(repo.diff_files(None).unwrap().len(), 3); |
|
| 5982 | + | } |
|
| 5983 | + | ||
| 5958 | 5984 | /// oxygen#35: a history-only state keeps no inserted text. It reads a |
|
| 5959 | 5985 | /// stored insert's text blob only when it builds that file's graph, and |
|
| 5960 | 5986 | /// builds the same graph a full load does. |
|
| … | 708 unchanged lines |
modified docs/benchmarks.md +9 −0
| … | 769 unchanged lines |
||
| 770 | 770 | A later status that finds every file as the manifest says answers from |
|
| 771 | 771 | the cache. Test builds check that answer against a snapshot. Diff still |
|
| 772 | 772 | renders the base; it is next. |
|
| 773 | + | ||
| 774 | + | Then diff (same day, load average 4.2, Git 0.10 s throughout): while the |
|
| 775 | + | status cache holds, `oxy diff` renders the stored working-copy change |
|
| 776 | + | against its base loaded from the state index, rather than snapshotting. |
|
| 777 | + | 0.34 to 0.35 s and 250 MB (from 0.41 s and 360 MB). Rendering it through |
|
| 778 | + | `change_base`, which reads the base's changes rather than the index, was |
|
| 779 | + | slower than a snapshot (0.9 s) and was not kept. What remains is the |
|
| 780 | + | status check's listing (about 130 ms), the index (60 ms) and rendering. |
|
| 781 | + |
modified src/cli/workspace.rs +2 −5
| … | 883 unchanged lines |
||
| 884 | 884 | print!("{}", render_change(&c, &base, &snapshot)); |
|
| 885 | 885 | } |
|
| 886 | 886 | None => { |
|
| 887 | - | let snap = repo.snapshot_status("oxy diff")?; |
|
| 888 | - | print!( |
|
| 889 | - | "{}", |
|
| 890 | - | render_change(&snap.wc, &snap.base, &snap.base_snapshot) |
|
| 891 | - | ); |
|
| 887 | + | let (wc, base, snapshot) = repo.working_copy_with_base()?; |
|
| 888 | + | print!("{}", render_change(&wc, &base, &snapshot)); |
|
| 892 | 889 | } |
|
| 893 | 890 | } |
|
| 894 | 891 | Ok(0) |
|
| … | 1472 unchanged lines |