Say which bookmark pull pulled, and which are left behind 7d9dab131937
pull acts on the current bookmark. On rstar-api, pulls run while on a feature bookmark said only "Already up to date" while main, which the fetch had just found one behind, was what the user meant; the next pull after switching took it. pull now names the bookmark it pulled and each other bookmark behind its remote, with the command that takes it; --json carries the bookmark. A core test covers a remote main moving to changes already here, as a hub landing does. closes #29
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-29 21:31 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
7d9dab131937b35c1c03543ee2346d3821d80c879936bbcb8666f65ec466a9c4- Depends on
- 4fe7d6045860 6f8aeb996558 75167672ebcd 982864068a59 c6aa5bd9d1c8 fce44ae9ad29
- Depended on by
- d36ba2f79f95 767ac6cd0545 f6dccc4533c8 14edbaaf5291 da868d5d2646 86e1824f2d0b
- 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
- 8 atoms, 0 tree operations, 3 files
Browse the tree at this change
modified crates/oxy-core/src/repo/tests.rs +44 −0
| … | 5665 unchanged lines |
||
| 5666 | 5666 | assert_eq!(s.wc.deps, repo.bookmark_heads().unwrap()); |
|
| 5667 | 5667 | assert!(root.join("many/f299.txt").exists()); |
|
| 5668 | 5668 | } |
|
| 5669 | + | ||
| 5670 | + | /// oxygen#29 (metalgear, rstar-api): after a proposal landed on the hub, |
|
| 5671 | + | /// moving `main` to a change this clone had pushed on another bookmark, |
|
| 5672 | + | /// the first `pull` said "Already up to date" while `main` was behind. |
|
| 5673 | + | #[test] |
|
| 5674 | + | fn pull_takes_a_remote_move_to_changes_already_here() { |
|
| 5675 | + | hermetic(); |
|
| 5676 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 5677 | + | let root = dir.path(); |
|
| 5678 | + | let origin_path = root.join("origin"); |
|
| 5679 | + | let origin = Repo::init(&origin_path).unwrap(); |
|
| 5680 | + | let url = origin_path.to_string_lossy().to_string(); |
|
| 5681 | + | let mut b = Repo::clone(&url, &root.join("b")).unwrap(); |
|
| 5682 | + | write(&root.join("b"), "base.txt", b"base\n"); |
|
| 5683 | + | b.commit("base", &[]).unwrap(); |
|
| 5684 | + | assert!(b.push("origin", false).unwrap()[0].ok); |
|
| 5685 | + | ||
| 5686 | + | // A feature bookmark with one change, pushed. |
|
| 5687 | + | b.bookmark_create("feat", None).unwrap(); |
|
| 5688 | + | b.bookmark_switch("feat", false).unwrap(); |
|
| 5689 | + | write(&root.join("b"), "x.txt", b"x\n"); |
|
| 5690 | + | let x = b.commit("x", &[]).unwrap(); |
|
| 5691 | + | assert!(b.push("origin", false).unwrap()[0].ok); |
|
| 5692 | + | b.bookmark_switch("main", false).unwrap(); |
|
| 5693 | + | ||
| 5694 | + | // The remote lands it: its main moves to the feature's state. |
|
| 5695 | + | let feat = origin |
|
| 5696 | + | .store() |
|
| 5697 | + | .get_state(&origin.view().unwrap().bookmarks["feat"]) |
|
| 5698 | + | .unwrap() |
|
| 5699 | + | .heads; |
|
| 5700 | + | origin.bookmark_set("main", &feat).unwrap(); |
|
| 5701 | + | ||
| 5702 | + | let r = b.pull("origin").unwrap(); |
|
| 5703 | + | assert!(r.changed, "{r:?}"); |
|
| 5704 | + | assert!(r.heads.contains(&x) || b.load_state(&r.heads).unwrap().contains(&x)); |
|
| 5705 | + | let main = b |
|
| 5706 | + | .bookmarks() |
|
| 5707 | + | .unwrap() |
|
| 5708 | + | .into_iter() |
|
| 5709 | + | .find(|m| m.name == "main") |
|
| 5710 | + | .unwrap(); |
|
| 5711 | + | assert_eq!(main.behind, Some(0), "{main:?}"); |
|
| 5712 | + | } |
modified src/cli/sync.rs +36 −1
| … | 194 unchanged lines |
||
| 195 | 195 | Ok(0) |
|
| 196 | 196 | } |
|
| 197 | 197 | ||
| 198 | + | /// Pull acts on the current bookmark only: name the others the fetch |
|
| 199 | + | /// found behind their remote, which is easy to take for the current one |
|
| 200 | + | /// (oxygen#29). |
|
| 201 | + | fn others_behind(repo: &oxy_core::repo::Repo, remote: &str) { |
|
| 202 | + | let Ok(bookmarks) = repo.bookmarks() else { |
|
| 203 | + | return; |
|
| 204 | + | }; |
|
| 205 | + | for b in bookmarks { |
|
| 206 | + | let Some(n) = b.behind.filter(|n| *n > 0) else { |
|
| 207 | + | continue; |
|
| 208 | + | }; |
|
| 209 | + | if b.current { |
|
| 210 | + | continue; |
|
| 211 | + | } |
|
| 212 | + | println!( |
|
| 213 | + | " {} {}", |
|
| 214 | + | ui::note(), |
|
| 215 | + | ui::dim(format!( |
|
| 216 | + | "{} is {} behind {remote}/{}: oxy bookmark switch {} then oxy pull to take {}", |
|
| 217 | + | b.name, |
|
| 218 | + | plural(n, "change"), |
|
| 219 | + | b.name, |
|
| 220 | + | b.name, |
|
| 221 | + | if n == 1 { "it" } else { "them" } |
|
| 222 | + | )) |
|
| 223 | + | ); |
|
| 224 | + | } |
|
| 225 | + | } |
|
| 226 | + | ||
| 198 | 227 | pub fn pull(remote: Option<&str>, as_json: bool) -> Result<i32> { |
|
| 199 | 228 | let repo = open()?; |
|
| 200 | 229 | let remote = remote_name(remote); |
|
| … | 9 unchanged lines |
||
| 210 | 239 | "{}", |
|
| 211 | 240 | json::obj([ |
|
| 212 | 241 | ("remote", json::s(&remote)), |
|
| 242 | + | ("bookmark", json::s(&repo.config.bookmark)), |
|
| 213 | 243 | ("heads", json::hashes(&r.heads)), |
|
| 214 | 244 | ("changed", json::b(r.changed)), |
|
| 215 | 245 | ("checked_out", json::b(r.checked_out)), |
|
| … | 6 unchanged lines |
||
| 222 | 252 | sp.done(&format!( |
|
| 223 | 253 | "{} {}", |
|
| 224 | 254 | ui::ok(ui::glyph("✔", "*")), |
|
| 225 | - | ui::dim("Already up to date.") |
|
| 255 | + | ui::dim(format!( |
|
| 256 | + | "{} is up to date with {remote}.", |
|
| 257 | + | repo.config.bookmark |
|
| 258 | + | )) |
|
| 226 | 259 | )); |
|
| 260 | + | others_behind(&repo, &remote); |
|
| 227 | 261 | return Ok(0); |
|
| 228 | 262 | } |
|
| 229 | 263 | sp.done(&format!( |
|
| … | 15 unchanged lines |
||
| 245 | 279 | ui::warn("the result has conflicts; see oxy status") |
|
| 246 | 280 | ); |
|
| 247 | 281 | } |
|
| 282 | + | others_behind(&repo, &remote); |
|
| 248 | 283 | Ok(0) |
|
| 249 | 284 | } |
|
| 250 | 285 | ||
| … | 462 unchanged lines |
modified tests/cli_json.rs +42 −0
| … | 445 unchanged lines |
||
| 446 | 446 | assert!(out.contains("only lacks 2 changes that main has"), "{out}"); |
|
| 447 | 447 | assert!(!r.join("a.txt").exists() && r.join("b.txt").exists()); |
|
| 448 | 448 | } |
|
| 449 | + | ||
| 450 | + | /// oxygen#29: pull acts on the current bookmark; a pull on a feature |
|
| 451 | + | /// bookmark said only "Already up to date" while main was behind. |
|
| 452 | + | #[test] |
|
| 453 | + | fn pull_names_its_bookmark_and_the_others_left_behind() { |
|
| 454 | + | let tmp = tempfile::tempdir().unwrap(); |
|
| 455 | + | let home = tmp.path().join("home"); |
|
| 456 | + | std::fs::create_dir_all(&home).unwrap(); |
|
| 457 | + | let origin = tmp.path().join("origin"); |
|
| 458 | + | assert_eq!( |
|
| 459 | + | oxy(tmp.path(), &home, &["init", origin.to_str().unwrap()]).0, |
|
| 460 | + | 0 |
|
| 461 | + | ); |
|
| 462 | + | let (a, b) = (tmp.path().join("a"), tmp.path().join("b")); |
|
| 463 | + | for d in [&a, &b] { |
|
| 464 | + | assert_eq!( |
|
| 465 | + | oxy( |
|
| 466 | + | tmp.path(), |
|
| 467 | + | &home, |
|
| 468 | + | &["clone", origin.to_str().unwrap(), d.to_str().unwrap()] |
|
| 469 | + | ) |
|
| 470 | + | .0, |
|
| 471 | + | 0 |
|
| 472 | + | ); |
|
| 473 | + | } |
|
| 474 | + | write(&a, "one.txt", "one\n"); |
|
| 475 | + | assert_eq!(oxy(&a, &home, &["commit", "-m", "one"]).0, 0); |
|
| 476 | + | assert_eq!(oxy(&a, &home, &["push"]).0, 0); |
|
| 477 | + | assert_eq!(oxy(&b, &home, &["pull"]).0, 0); |
|
| 478 | + | assert_eq!(oxy(&b, &home, &["bookmark", "create", "feat"]).0, 0); |
|
| 479 | + | assert_eq!(oxy(&b, &home, &["bookmark", "switch", "feat"]).0, 0); |
|
| 480 | + | assert_eq!(oxy(&b, &home, &["push", "-b", "feat"]).0, 0); |
|
| 481 | + | write(&a, "two.txt", "two\n"); |
|
| 482 | + | assert_eq!(oxy(&a, &home, &["commit", "-m", "two"]).0, 0); |
|
| 483 | + | assert_eq!(oxy(&a, &home, &["push"]).0, 0); |
|
| 484 | + | ||
| 485 | + | let (code, out, err) = oxy(&b, &home, &["pull"]); |
|
| 486 | + | assert_eq!(code, 0, "{err}"); |
|
| 487 | + | let out = String::from_utf8_lossy(&out); |
|
| 488 | + | assert!(out.contains("feat is up to date with origin"), "{out}"); |
|
| 489 | + | assert!(out.contains("main is 1 change behind origin/main"), "{out}"); |
|
| 490 | + | } |