Resolutions depend only on contributors; candidate-order-independent name conflict test b288cd254343
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-26 18:18 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
b288cd254343a8c981e7c891d9cbd4472d208a1cb643e9be69c71c479b4c3fa8- Depends on
- 20247a03a596 ff0af1ee299d
- Depended on by
- nothing yet
- 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
- 7 atoms, 0 tree operations, 1 file
Browse the tree at this change
modified crates/oxy-core/src/repo.rs +16 −18
| … | 167 unchanged lines |
||
| 168 | 168 | pub changed: bool, |
|
| 169 | 169 | /// Whether the working copy resolves conflicts of its base (spec §9.6). |
|
| 170 | 170 | pub resolves: bool, |
|
| 171 | + | /// Changes that contributed to the conflicts being resolved. |
|
| 172 | + | pub contributors: Vec<ChangeHash>, |
|
| 171 | 173 | } |
|
| 172 | 174 | ||
| 173 | 175 | pub struct LogEntry { |
|
| … | 421 unchanged lines |
||
| 595 | 597 | wc.deps = deps.into_iter().collect(); |
|
| 596 | 598 | wc.normalize(); |
|
| 597 | 599 | let resolves = b.resolves; |
|
| 600 | + | let contributors: Vec<ChangeHash> = b.contributors.iter().copied().collect(); |
|
| 598 | 601 | ||
| 599 | 602 | let mut statuses = b.statuses; |
|
| 600 | 603 | statuses.sort_by(|a, b| a.path.cmp(&b.path)); |
|
| … | 6 unchanged lines |
||
| 607 | 610 | view.workspaces.insert(self.workspace, new_hash); |
|
| 608 | 611 | self.record_op(&[op_ref], &format!("{command}{SNAPSHOT_SUFFIX}"), view)?; |
|
| 609 | 612 | } |
|
| 610 | - | Ok(SnapshotResult { wc_hash: new_hash, wc, base, base_snapshot, statuses, changed, resolves }) |
|
| 613 | + | Ok(SnapshotResult { wc_hash: new_hash, wc, base, base_snapshot, statuses, changed, resolves, contributors }) |
|
| 611 | 614 | } |
|
| 612 | 615 | ||
| 613 | 616 | /// Turn the working-copy change into a normal change on the bookmark (spec §12.3). |
|
| … | 10 unchanged lines |
||
| 624 | 627 | } |
|
| 625 | 628 | let (op_ref, op) = self.head_op()?; |
|
| 626 | 629 | let mut deps: BTreeSet<ChangeHash> = referenced_changes(&snap.wc, &snap.base); |
|
| 627 | - | if snap.resolves { |
|
| 628 | - | // A resolution depends on every change that contributed to the |
|
| 629 | - | // conflict (spec §8.1); those are already among the working copy's |
|
| 630 | - | // dependencies, so keep every one that is not merely a base head. |
|
| 631 | - | let heads: BTreeSet<ChangeHash> = snap.base.maximal(&snap.wc.deps).into_iter().collect(); |
|
| 632 | - | deps.extend(snap.wc.deps.iter().filter(|d| !heads.contains(d) || touches_conflict(&snap)).copied()); |
|
| 633 | - | } |
|
| 630 | + | // A resolution depends on every change that contributed to the |
|
| 631 | + | // conflict (spec §8.1). Tree and opaque conflicts are resolved through |
|
| 632 | + | // ordinary operations whose references already cover the contributors. |
|
| 633 | + | deps.extend(snap.contributors.iter().copied()); |
|
| 634 | 634 | for d in extra_deps { |
|
| 635 | 635 | if !snap.base.contains(d) { |
|
| 636 | 636 | return fail(format!("--depends-on {d}: not in the working copy's base state")); |
|
| … | 489 unchanged lines |
||
| 1126 | 1126 | } |
|
| 1127 | 1127 | Ok(()) |
|
| 1128 | 1128 | } |
|
| 1129 | - | } |
|
| 1130 | - | ||
| 1131 | - | /// Whether a working copy touches a node that is conflicted in its base. |
|
| 1132 | - | fn touches_conflict(snap: &SnapshotResult) -> bool { |
|
| 1133 | - | let conflicted: BTreeSet<NodeId> = snap.base_snapshot.files.values().filter(|f| f.is_conflicted()).map(|f| f.node).collect(); |
|
| 1134 | - | snap.wc.tree_ops.iter().any(|op| conflicted.contains(&op.node())) || snap.wc.edits.iter().any(|e| conflicted.contains(&e.node())) |
|
| 1135 | 1129 | } |
|
| 1136 | 1130 | ||
| 1137 | 1131 | fn repo_live_keys(base: &State, node: &NodeId) -> Vec<crate::graph::AtomKey> { |
|
| … | 340 unchanged lines |
||
| 1478 | 1472 | let h4 = repo.commit("resolve", &[]).unwrap(); |
|
| 1479 | 1473 | let c4 = repo.store().get_change(&h4).unwrap(); |
|
| 1480 | 1474 | assert_eq!(c4.kind, ChangeKind::Resolution); |
|
| 1481 | - | assert!(c4.deps.contains(&h2) && c4.deps.contains(&h3) && c4.deps.contains(&h1), "{:?}", c4.deps); |
|
| 1475 | + | let mut want = vec![h1, h2, h3]; |
|
| 1476 | + | want.sort(); |
|
| 1477 | + | assert_eq!(c4.deps, want, "a resolution depends on exactly the contributors and what it references"); |
|
| 1482 | 1478 | assert_eq!(repo.bookmark_heads().unwrap(), vec![h4]); |
|
| 1483 | 1479 | let snap = repo.load_state(&[h4]).unwrap().materialize(); |
|
| 1484 | 1480 | assert!(!snap.is_conflicted()); |
|
| … | 45 unchanged lines |
||
| 1530 | 1526 | assert!(r.conflicted); |
|
| 1531 | 1527 | assert!(root.join("new.txt.oxy-conflict-1").exists() && root.join("new.txt.oxy-conflict-2").exists()); |
|
| 1532 | 1528 | assert!(!root.join("new.txt").exists()); |
|
| 1533 | - | // Keep candidate 2 under the real name. |
|
| 1534 | - | fs::remove_file(root.join("new.txt.oxy-conflict-1")).unwrap(); |
|
| 1535 | - | fs::rename(root.join("new.txt.oxy-conflict-2"), root.join("new.txt")).unwrap(); |
|
| 1529 | + | // Candidate numbering follows change-hash order, so pick by content: |
|
| 1530 | + | // keep the "two" candidate under the real name and drop the other. |
|
| 1531 | + | let (keep, drop) = if fs::read(root.join("new.txt.oxy-conflict-1")).unwrap() == b"two\n" { (1, 2) } else { (2, 1) }; |
|
| 1532 | + | fs::remove_file(root.join(format!("new.txt.oxy-conflict-{drop}"))).unwrap(); |
|
| 1533 | + | fs::rename(root.join(format!("new.txt.oxy-conflict-{keep}")), root.join("new.txt")).unwrap(); |
|
| 1536 | 1534 | let h = repo.commit("pick two", &[]).unwrap(); |
|
| 1537 | 1535 | assert_eq!(repo.store().get_change(&h).unwrap().kind, ChangeKind::Resolution); |
|
| 1538 | 1536 | // The resolution does not touch keep.txt, so it does not depend on h1; |
|
| … | 203 unchanged lines |