Load history only where no file graph is needed 57eaac9107df
Bookmark create, set and merge, pull, tags, identities, bisect, doctor and most history rewrites loaded every file graph of a state only to ask which changes it holds, its heads or its tree. They now load its history alone, which on the Linux tree is the difference between a fraction of a second and running out of memory. A history-only state panics rather than answer a graph it has not built, so a caller that does need graphs cannot quietly get a wrong one. refs #35
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-29 22:08 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
57eaac9107df33c2461b70d04bf3333a8607fda241e779719978c704cede25d6- Depends on
- 05e71ed6cdf1 132b884b9a8d 23b217d75848 3e5772ccf9da 614541c3cebd 728da0f92161 75167672ebcd 7b9809e67c3d 957fee30826e 982864068a59 b837d2b55853 c07e0e7d2d85 c863c7a7a2d1
- Depended on by
- c2e1d09047db 14edbaaf5291
- 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.1.0 v0.10.0 v0.11.0 v0.2.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
- 29 atoms, 0 tree operations, 7 files
Browse the tree at this change
modified crates/oxy-core/src/repo/bookmarks.rs +4 −4
| … | 84 unchanged lines |
||
| 85 | 85 | return fail(format!("bookmark {name} already exists")); |
|
| 86 | 86 | } |
|
| 87 | 87 | let heads = match heads { |
|
| 88 | - | Some(h) => self.load_state(h)?.maximal(h), |
|
| 88 | + | Some(h) => self.load_state_history(h)?.maximal(h), |
|
| 89 | 89 | None => { |
|
| 90 | 90 | let (_, wc) = self.working_copy()?; |
|
| 91 | - | self.load_state(&wc.deps)?.maximal(&wc.deps) |
|
| 91 | + | self.load_state_history(&wc.deps)?.maximal(&wc.deps) |
|
| 92 | 92 | } |
|
| 93 | 93 | }; |
|
| 94 | 94 | let mut view = op.view.clone(); |
|
| … | 13 unchanged lines |
||
| 108 | 108 | "no bookmark {name}; create it with `oxy bookmark create`" |
|
| 109 | 109 | )); |
|
| 110 | 110 | } |
|
| 111 | - | let heads = self.load_state(heads)?.maximal(heads); |
|
| 111 | + | let heads = self.load_state_history(heads)?.maximal(heads); |
|
| 112 | 112 | let mut view = op.view.clone(); |
|
| 113 | 113 | view.bookmarks.insert( |
|
| 114 | 114 | name.to_string(), |
|
| … | 79 unchanged lines |
||
| 194 | 194 | let local = self.heads_of(&op.view, ¤t)?; |
|
| 195 | 195 | let mut all = local.clone(); |
|
| 196 | 196 | all.extend(other); |
|
| 197 | - | let mut heads = self.load_state(&all)?.maximal(&all); |
|
| 197 | + | let mut heads = self.load_state_history(&all)?.maximal(&all); |
|
| 198 | 198 | heads.sort(); |
|
| 199 | 199 | let mut sorted_local = local.clone(); |
|
| 200 | 200 | sorted_local.sort(); |
|
| … | 29 unchanged lines |
modified crates/oxy-core/src/repo/doctor.rs +1 −1
| … | 189 unchanged lines |
||
| 190 | 190 | let Ok(heads) = self.bookmark_heads() else { |
|
| 191 | 191 | return; |
|
| 192 | 192 | }; |
|
| 193 | - | let Ok(state) = self.load_state(&heads) else { |
|
| 193 | + | let Ok(state) = self.load_state_history(&heads) else { |
|
| 194 | 194 | return; |
|
| 195 | 195 | }; |
|
| 196 | 196 | let view = state.tree().view(); |
|
| … | 128 unchanged lines |
modified crates/oxy-core/src/repo/history.rs +11 −11
| … | 51 unchanged lines |
||
| 52 | 52 | out |
|
| 53 | 53 | }; |
|
| 54 | 54 | let candidates = replace(&heads); |
|
| 55 | - | let st = self.load_state(&candidates)?; |
|
| 55 | + | let st = self.load_state_history(&candidates)?; |
|
| 56 | 56 | let new_heads = st.maximal(&candidates); |
|
| 57 | 57 | ||
| 58 | 58 | let (_, wc) = self.working_copy()?; |
|
| 59 | - | let base = self.load_state(&wc.deps)?; |
|
| 59 | + | let base = self.load_state_history(&wc.deps)?; |
|
| 60 | 60 | let wc_base = base.maximal(&wc.deps); |
|
| 61 | 61 | let wc_base = if wc_base.contains(old) { |
|
| 62 | 62 | let c = replace(&wc_base); |
|
| 63 | - | let st = self.load_state(&c)?; |
|
| 63 | + | let st = self.load_state_history(&c)?; |
|
| 64 | 64 | st.maximal(&c) |
|
| 65 | 65 | } else { |
|
| 66 | 66 | wc_base |
|
| … | 40 unchanged lines |
||
| 107 | 107 | let (wc_hash, _) = self.working_copy()?; |
|
| 108 | 108 | let mut all = heads.clone(); |
|
| 109 | 109 | all.push(wc_hash); |
|
| 110 | - | let state = self.load_state(&all)?; |
|
| 110 | + | let state = self.load_state_history(&all)?; |
|
| 111 | 111 | if !state.closure(&wc_hash).contains(old) |
|
| 112 | 112 | && !heads.contains(old) |
|
| 113 | 113 | && !state.heads().contains(old) |
|
| … | 43 unchanged lines |
||
| 157 | 157 | out |
|
| 158 | 158 | }; |
|
| 159 | 159 | let candidates = remap(&heads); |
|
| 160 | - | let st = self.load_state(&candidates)?; |
|
| 160 | + | let st = self.load_state_history(&candidates)?; |
|
| 161 | 161 | let new_heads = st.maximal(&candidates); |
|
| 162 | 162 | let new_wc = mapping.get(&wc_hash).copied().unwrap_or(wc_hash); |
|
| 163 | 163 | ||
| … | 171 unchanged lines |
||
| 335 | 335 | return fail("that is the working copy; commit it instead"); |
|
| 336 | 336 | } |
|
| 337 | 337 | // Every reference must live in the target's closure (or be the target). |
|
| 338 | - | let closure = self.load_state(&[*target])?; |
|
| 338 | + | let closure = self.load_state_history(&[*target])?; |
|
| 339 | 339 | let ok_change = |h: &ChangeHash| *h == *target || closure.contains(h); |
|
| 340 | 340 | let ok_node = |n: &NodeId| n.is_root() || closure.tree().introducer(n).is_some(); |
|
| 341 | 341 | let too_new = |what: &str| { |
|
| … | 325 unchanged lines |
||
| 667 | 667 | let bookmark = self.config.bookmark.clone(); |
|
| 668 | 668 | let mut heads = self.heads_of(&op.view, &bookmark)?; |
|
| 669 | 669 | heads.push(h); |
|
| 670 | - | let st = self.load_state(&heads)?; |
|
| 670 | + | let st = self.load_state_history(&heads)?; |
|
| 671 | 671 | let heads = st.maximal(&heads); |
|
| 672 | 672 | let mut view = op.view.clone(); |
|
| 673 | 673 | view.bookmarks.insert( |
|
| … | 3 unchanged lines |
||
| 677 | 677 | let (_, wc) = self.working_copy()?; |
|
| 678 | 678 | let mut base = wc.deps.clone(); |
|
| 679 | 679 | base.push(h); |
|
| 680 | - | let wc_base = self.load_state(&base)?.maximal(&base); |
|
| 680 | + | let wc_base = self.load_state_history(&base)?.maximal(&base); |
|
| 681 | 681 | let fresh = self.new_working_copy(wc_base, &wc.message); |
|
| 682 | 682 | view.workspaces |
|
| 683 | 683 | .insert(self.workspace, self.store.put_change(&fresh)?); |
|
| … | 47 unchanged lines |
||
| 731 | 731 | let (wc_hash, _) = self.working_copy()?; |
|
| 732 | 732 | let mut all = heads.clone(); |
|
| 733 | 733 | all.push(wc_hash); |
|
| 734 | - | let state = self.load_state(&all)?; |
|
| 734 | + | let state = self.load_state_history(&all)?; |
|
| 735 | 735 | for (h, meta) in state.changes() { |
|
| 736 | 736 | if !meta.deps.contains(hash) { |
|
| 737 | 737 | continue; |
|
| … | 95 unchanged lines |
||
| 833 | 833 | if dst.kind == ChangeKind::WorkingCopy { |
|
| 834 | 834 | return fail("cannot squash into the working copy"); |
|
| 835 | 835 | } |
|
| 836 | - | let closure = self.load_state(&[*into])?; |
|
| 836 | + | let closure = self.load_state_history(&[*into])?; |
|
| 837 | 837 | let outside: Vec<String> = src |
|
| 838 | 838 | .deps |
|
| 839 | 839 | .iter() |
|
| … | 206 unchanged lines |
||
| 1046 | 1046 | let (wc_hash, _) = self.working_copy()?; |
|
| 1047 | 1047 | let mut all = self.bookmark_heads()?; |
|
| 1048 | 1048 | all.push(wc_hash); |
|
| 1049 | - | let state = self.load_state(&all)?; |
|
| 1049 | + | let state = self.load_state_history(&all)?; |
|
| 1050 | 1050 | let src_now = state |
|
| 1051 | 1051 | .changes() |
|
| 1052 | 1052 | .find(|(_, m)| m.change_id == src.change_id) |
|
| … | 143 unchanged lines |
modified crates/oxy-core/src/repo/identity.rs +1 −1
| … | 452 unchanged lines |
||
| 453 | 453 | return fail("no identity: run oxy identity create"); |
|
| 454 | 454 | }; |
|
| 455 | 455 | let heads = self.bookmark_heads()?; |
|
| 456 | - | let state = self.load_state(&heads)?; |
|
| 456 | + | let state = self.load_state_history(&heads)?; |
|
| 457 | 457 | let mut out = Vec::new(); |
|
| 458 | 458 | for (h, meta) in state.changes() { |
|
| 459 | 459 | if (meta.author == key || meta.author.is_zero()) |
|
| … | 27 unchanged lines |
modified crates/oxy-core/src/repo/inspect.rs +4 −4
| … | 182 unchanged lines |
||
| 183 | 183 | return fail("a bisect is in progress; finish it or run `oxy bisect reset`"); |
|
| 184 | 184 | } |
|
| 185 | 185 | let (_, wc) = self.working_copy()?; |
|
| 186 | - | let start = self.load_state(&wc.deps)?.maximal(&wc.deps); |
|
| 186 | + | let start = self.load_state_history(&wc.deps)?.maximal(&wc.deps); |
|
| 187 | 187 | self.bisect_save(&BisectState { |
|
| 188 | 188 | start, |
|
| 189 | 189 | ..Default::default() |
|
| … | 47 unchanged lines |
||
| 237 | 237 | fn bisect_heads(&self, st: &BisectState, k: usize) -> Vec<ChangeHash> { |
|
| 238 | 238 | let mut all = st.good.clone(); |
|
| 239 | 239 | all.extend(st.candidates[..k].iter().copied()); |
|
| 240 | - | self.load_state(&all) |
|
| 240 | + | self.load_state_history(&all) |
|
| 241 | 241 | .map(|s| s.maximal(&all)) |
|
| 242 | 242 | .unwrap_or(all) |
|
| 243 | 243 | } |
|
| … | 65 unchanged lines |
||
| 309 | 309 | bad: st.bad.len(), |
|
| 310 | 310 | }); |
|
| 311 | 311 | } |
|
| 312 | - | let good_heads = self.load_state(&st.good)?.maximal(&st.good); |
|
| 313 | - | let bad_heads = self.load_state(&st.bad)?.maximal(&st.bad); |
|
| 312 | + | let good_heads = self.load_state_history(&st.good)?.maximal(&st.good); |
|
| 313 | + | let bad_heads = self.load_state_history(&st.bad)?.maximal(&st.bad); |
|
| 314 | 314 | st.good = good_heads.clone(); |
|
| 315 | 315 | st.bad = bad_heads.clone(); |
|
| 316 | 316 | st.candidates = self.negotiate(&good_heads, &bad_heads)?; |
|
| … | 45 unchanged lines |
modified crates/oxy-core/src/repo/sync.rs +1 −1
| … | 1188 unchanged lines |
||
| 1189 | 1189 | let remote_heads = self.store.get_state(&remote_state)?.heads; |
|
| 1190 | 1190 | let mut all = local.clone(); |
|
| 1191 | 1191 | all.extend(remote_heads); |
|
| 1192 | - | let st = self.load_state(&all)?; |
|
| 1192 | + | let st = self.load_state_history(&all)?; |
|
| 1193 | 1193 | let mut heads = st.maximal(&all); |
|
| 1194 | 1194 | heads.sort(); |
|
| 1195 | 1195 | let mut sorted_local = local.clone(); |
|
| … | 307 unchanged lines |
modified crates/oxy-core/src/repo/tags.rs +1 −1
| … | 51 unchanged lines |
||
| 52 | 52 | if op.view.tags.contains_key(name) { |
|
| 53 | 53 | return fail(format!("tag {name:?} already exists; tags are immutable")); |
|
| 54 | 54 | } |
|
| 55 | - | let st = self.load_state(heads)?; |
|
| 55 | + | let st = self.load_state_history(heads)?; |
|
| 56 | 56 | let heads = st.maximal(heads); |
|
| 57 | 57 | if heads.is_empty() { |
|
| 58 | 58 | return fail("a tag needs at least one change"); |
|
| … | 183 unchanged lines |