git verify: hold only the open frontier of ancestor sets ee6aa3e81bc8
Verify kept, for every Git commit, the set of changes of it and all its ancestors, and never let one go: memory grew with the square of the history, and on the VS Code import (140k commits) even --last 60 was killed for memory. A commit's set is now dropped after its last child has used it, and that child takes it over rather than copying it. refs #81
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-10-07 19:57 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
ee6aa3e81bc86069b2497a22e1be65df9408fd39190031cf9c27aeb57e4a8fdb- Depends on
- 57d8dc05c9d4 62794ecc7f74 79ab8528ff62
- Depended on by
- 8b7986dd3cb0
- In bookmarks
- 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.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
- 6 atoms, 0 tree operations, 1 file
Browse the tree at this change
modified crates/oxy-core/src/repo/git.rs +30 −4
| … | 1127 unchanged lines |
||
| 1128 | 1128 | // come, so a child adds its own changes instead of loading all its |
|
| 1129 | 1129 | // history again (as the import does). |
|
| 1130 | 1130 | let mut waiting: HashMap<&str, usize> = HashMap::new(); |
|
| 1131 | + | // Children still to come per commit, through any parent: a commit's |
|
| 1132 | + | // closure is dropped after its last child, so only the open |
|
| 1133 | + | // frontier is held. Kept for every commit, the closures grew with |
|
| 1134 | + | // the square of the history and VS Code's verify ran out of memory |
|
| 1135 | + | // (oxygen#81). |
|
| 1136 | + | let mut children: HashMap<&str, usize> = HashMap::new(); |
|
| 1131 | 1137 | for c in &commits { |
|
| 1132 | 1138 | if let Some(p1) = c.parents.first() { |
|
| 1133 | 1139 | *waiting.entry(p1.as_str()).or_default() += 1; |
|
| 1134 | 1140 | } |
|
| 1141 | + | for p in &c.parents { |
|
| 1142 | + | *children.entry(p.as_str()).or_default() += 1; |
|
| 1143 | + | } |
|
| 1135 | 1144 | } |
|
| 1136 | 1145 | let mut kept: HashMap<String, State> = HashMap::new(); |
|
| 1137 | 1146 | let mut kept_order: std::collections::VecDeque<String> = std::collections::VecDeque::new(); |
|
| 1138 | 1147 | for c in &commits { |
|
| 1139 | 1148 | rep.commits += 1; |
|
| 1149 | + | // The first parent's closure, taken over when this is its last |
|
| 1150 | + | // child (no copy), then the other parents' added. |
|
| 1140 | 1151 | let mut set: BTreeSet<ChangeHash> = BTreeSet::new(); |
|
| 1141 | - | for p in &c.parents { |
|
| 1142 | - | if let Some(ps) = closure.get(p) { |
|
| 1143 | - | set.extend(ps.iter().copied()); |
|
| 1152 | + | for (i, p) in c.parents.iter().enumerate() { |
|
| 1153 | + | let left = children.get_mut(p.as_str()).map(|n| { |
|
| 1154 | + | *n -= 1; |
|
| 1155 | + | *n |
|
| 1156 | + | }); |
|
| 1157 | + | let ps = if left == Some(0) { |
|
| 1158 | + | closure.remove(p) |
|
| 1159 | + | } else { |
|
| 1160 | + | closure.get(p).cloned() |
|
| 1161 | + | }; |
|
| 1162 | + | if let Some(ps) = ps { |
|
| 1163 | + | if i == 0 && set.is_empty() { |
|
| 1164 | + | set = Arc::try_unwrap(ps).unwrap_or_else(|a| (*a).clone()); |
|
| 1165 | + | } else { |
|
| 1166 | + | set.extend(ps.iter().copied()); |
|
| 1167 | + | } |
|
| 1144 | 1168 | } |
|
| 1145 | 1169 | } |
|
| 1146 | 1170 | match imported.get(&c.sha) { |
|
| … | 8 unchanged lines |
||
| 1155 | 1179 | } |
|
| 1156 | 1180 | } |
|
| 1157 | 1181 | let set = Arc::new(set); |
|
| 1158 | - | closure.insert(c.sha.clone(), set.clone()); |
|
| 1182 | + | if children.get(c.sha.as_str()).is_some_and(|n| *n > 0) { |
|
| 1183 | + | closure.insert(c.sha.clone(), set.clone()); |
|
| 1184 | + | } |
|
| 1159 | 1185 | if rep.mismatches.len() >= 50 { |
|
| 1160 | 1186 | break; |
|
| 1161 | 1187 | } |
|
| … | 218 unchanged lines |