Repo::union_resolution: a host settles a state whose conflicts are all union (refs #145) ac4ec124b9f2
For OxygenHub's land: the kept-both text of every conflicted file when each is a merge=union text file and each conflict an adjacent insertion; None when any conflict needs a person.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key b62ca20efc42
- When
- · 2026-10-26 10:15 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
ac4ec124b9f2fa52ea27d682b54225cf95a771daf8fb4ab7f5ab53410ef19286- Depends on
- 157d02e95586 4afe288b7946 982864068a59 9bd98cfb4f7e
- Depended on by
- c0d4075cadc0
- In bookmarks
- identical-sides main push-notes subrepo-pin-state
- In tags
- v0.10.0 v0.11.0 v0.8.0 v0.9.0
- Edits
- 2 atoms, 0 tree operations, 2 files
Browse the tree at this change
modified crates/oxy-core/src/repo/snapshot.rs +33 −0
| … | 639 unchanged lines |
||
| 640 | 640 | Ok(kept.into_iter().filter(|k| !k.settled.is_empty()).collect()) |
|
| 641 | 641 | } |
|
| 642 | 642 | ||
| 643 | + | /// Every conflict of `snap`, `state`'s materialization, settled by |
|
| 644 | + | /// keeping both sides (oxygen#145): the new text of each conflicted |
|
| 645 | + | /// file, when each is a text file its `.oxyattributes` mark |
|
| 646 | + | /// `merge=union` and each of its conflicts is an adjacent insertion; |
|
| 647 | + | /// `None` when any conflict needs a person, or there is none. A host |
|
| 648 | + | /// lands a proposal with these as a resolution, as a checkout settles |
|
| 649 | + | /// them in a working copy. |
|
| 650 | + | pub fn union_resolution(state: &State, snap: &Snapshot) -> Option<Vec<(String, Vec<u8>)>> { |
|
| 651 | + | if snap.tree.is_conflicted() { |
|
| 652 | + | return None; |
|
| 653 | + | } |
|
| 654 | + | let attrs = Repo::state_attributes(snap); |
|
| 655 | + | let mut out = Vec::new(); |
|
| 656 | + | for (p, f) in &snap.files { |
|
| 657 | + | if !f.is_conflicted() { |
|
| 658 | + | continue; |
|
| 659 | + | } |
|
| 660 | + | let FileContent::Text { bytes, .. } = &f.content else { |
|
| 661 | + | return None; |
|
| 662 | + | }; |
|
| 663 | + | if !attrs.lookup(p).union { |
|
| 664 | + | return None; |
|
| 665 | + | } |
|
| 666 | + | let sides = state.both_sides(&f.node); |
|
| 667 | + | let (text, _, left) = keep_both_text(bytes, &sides, false); |
|
| 668 | + | if !left.is_empty() || sides.iter().any(Option::is_none) { |
|
| 669 | + | return None; |
|
| 670 | + | } |
|
| 671 | + | out.push((p.clone(), text)); |
|
| 672 | + | } |
|
| 673 | + | (!out.is_empty()).then_some(out) |
|
| 674 | + | } |
|
| 675 | + | ||
| 643 | 676 | /// The chosen resolutions `base` still needs; the rest are forgotten. |
|
| 644 | 677 | fn resolutions_for(&self, base: &Snapshot) -> Result<HashSet<String>> { |
|
| 645 | 678 | let all = self.resolutions()?; |
|
| … | 2768 unchanged lines |
modified crates/oxy-core/src/repo/tests.rs +33 −0
| … | 334 unchanged lines |
||
| 335 | 335 | ); |
|
| 336 | 336 | } |
|
| 337 | 337 | ||
| 338 | + | /// oxygen#145: a host (OxygenHub landing a proposal) settles a state whose |
|
| 339 | + | /// only conflicts are adjacent insertions in union files; any other |
|
| 340 | + | /// conflict needs a person. |
|
| 341 | + | #[test] |
|
| 342 | + | fn a_host_settles_a_state_whose_conflicts_are_all_union() { |
|
| 343 | + | hermetic(); |
|
| 344 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 345 | + | let root = dir.path(); |
|
| 346 | + | let repo = Repo::init(root).unwrap(); |
|
| 347 | + | write(root, ".oxyattributes", b"*.md merge=union\n"); |
|
| 348 | + | write(root, "A.md", b"a\nb\n"); |
|
| 349 | + | write(root, "B.md", b"x\ny\n"); |
|
| 350 | + | let h1 = repo.commit("base", &[]).unwrap(); |
|
| 351 | + | write(root, "A.md", b"a\none\nb\n"); |
|
| 352 | + | write(root, "B.md", b"x\nY1\n"); |
|
| 353 | + | let h2 = repo.commit("one", &[]).unwrap(); |
|
| 354 | + | repo.checkout(&[h1], false, "checkout").unwrap(); |
|
| 355 | + | write(root, "A.md", b"a\ntwo\nb\n"); |
|
| 356 | + | let h3 = repo.commit("two", &[]).unwrap(); |
|
| 357 | + | let st = repo.load_state(&[h2, h3]).unwrap(); |
|
| 358 | + | assert_eq!( |
|
| 359 | + | Repo::union_resolution(&st, &st.materialize()), |
|
| 360 | + | Some(vec![("A.md".into(), b"a\none\ntwo\nb\n".to_vec())]) |
|
| 361 | + | ); |
|
| 362 | + | // B.md's line replaced two ways: a person decides. |
|
| 363 | + | repo.checkout(&[h3], false, "checkout").unwrap(); |
|
| 364 | + | write(root, "B.md", b"x\nY2\n"); |
|
| 365 | + | let h4 = repo.commit("Y2", &[]).unwrap(); |
|
| 366 | + | let st = repo.load_state(&[h2, h4]).unwrap(); |
|
| 367 | + | assert!(st.materialize().is_conflicted()); |
|
| 368 | + | assert_eq!(Repo::union_resolution(&st, &st.materialize()), None); |
|
| 369 | + | } |
|
| 370 | + | ||
| 338 | 371 | /// oxygen#121: two changes that each replaced the same line are not an |
|
| 339 | 372 | /// adjacent insertion: keeping both would keep two versions of the line, |
|
| 340 | 373 | /// so `--both` leaves the markers, as it does for markers already edited. |
|
| … | 10211 unchanged lines |