Revsets: independent() and conflicts() 543bbf64d127
independent(X, IN) is what could ship without X: the members of IN that neither depend on X nor are depended on by it. conflicts(S) is the changes that contributed atoms to a conflict in the state S names (the working copy's base when S is left out): text regions through conflict_regions, opaque replace conflicts through blob_contributors. Tests: c and d are independent of b; two edits of one line make b and e the contributors of the conflict. refs #65
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-10-04 03:21 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
543bbf64d127d15e7be2d6692d8f5c8e155f48cc555c4f53dbaf89f07957e749- Depends on
- 2c9ddac3c387 47ce8b8e2d4b 8ad9dfe16303 982864068a59
- 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.10.0 v0.11.0 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, 3 files
Browse the tree at this change
modified crates/oxy-core/src/repo/revset.rs +61 −0
| … | 367 unchanged lines |
||
| 368 | 368 | let x = self.eval(&args[0], None)?; |
|
| 369 | 369 | self.roots(&x) |
|
| 370 | 370 | } |
|
| 371 | + | "independent" => { |
|
| 372 | + | self.arity(name, args, 2)?; |
|
| 373 | + | let x = self.eval(&args[0], None)?; |
|
| 374 | + | let inn = self.eval(&args[1], None)?; |
|
| 375 | + | let below = self.ancestors(&x)?; |
|
| 376 | + | let above = self.descendants(&x)?; |
|
| 377 | + | Ok(inn |
|
| 378 | + | .into_iter() |
|
| 379 | + | .filter(|h| !below.contains(h) && !above.contains(h)) |
|
| 380 | + | .collect()) |
|
| 381 | + | } |
|
| 382 | + | "conflicts" => { |
|
| 383 | + | if args.len() > 1 { |
|
| 384 | + | return Err(err( |
|
| 385 | + | "conflicts() takes a state, or nothing for the working copy's", |
|
| 386 | + | )); |
|
| 387 | + | } |
|
| 388 | + | let heads: Vec<ChangeHash> = match args.first() { |
|
| 389 | + | Some(e) => self.eval(e, None)?.into_iter().collect(), |
|
| 390 | + | None => match self.working_copy() { |
|
| 391 | + | Some(wc) => { |
|
| 392 | + | self.ensure(&[wc])?; |
|
| 393 | + | self.graph |
|
| 394 | + | .get(&wc) |
|
| 395 | + | .map(|m| m.deps.clone()) |
|
| 396 | + | .unwrap_or_default() |
|
| 397 | + | } |
|
| 398 | + | None => self.repo.bookmark_heads()?, |
|
| 399 | + | }, |
|
| 400 | + | }; |
|
| 401 | + | self.conflicts(within, &heads) |
|
| 402 | + | } |
|
| 371 | 403 | "latest" => { |
|
| 372 | 404 | self.arity(name, args, 2)?; |
|
| 373 | 405 | let x = self.eval(&args[0], None)?; |
|
| … | 202 unchanged lines |
||
| 576 | 608 | self.tick()?; |
|
| 577 | 609 | } |
|
| 578 | 610 | self.closure_of(heads, within) |
|
| 611 | + | } |
|
| 612 | + | ||
| 613 | + | /// `conflicts(S)`: the changes that contributed atoms to a conflict in |
|
| 614 | + | /// the state `heads` name (conflicts belong to states, not changes). |
|
| 615 | + | fn conflicts(&mut self, within: Option<&Set>, heads: &[ChangeHash]) -> Result<Set> { |
|
| 616 | + | let state = self.repo.load_state(heads)?; |
|
| 617 | + | let snapshot = state.materialize(); |
|
| 618 | + | let mut out = Set::new(); |
|
| 619 | + | for f in snapshot.files.values() { |
|
| 620 | + | if !f.is_conflicted() { |
|
| 621 | + | continue; |
|
| 622 | + | } |
|
| 623 | + | match &f.content { |
|
| 624 | + | crate::state::FileContent::Text { .. } => { |
|
| 625 | + | out.extend(state.conflict_regions(&f.node).contributors); |
|
| 626 | + | } |
|
| 627 | + | crate::state::FileContent::Opaque { blobs } => { |
|
| 628 | + | out.extend(state.blob_contributors(&f.node, blobs)); |
|
| 629 | + | } |
|
| 630 | + | crate::state::FileContent::Dir => {} |
|
| 631 | + | } |
|
| 632 | + | self.tick()?; |
|
| 633 | + | } |
|
| 634 | + | self.ensure(heads)?; |
|
| 635 | + | Ok(match within { |
|
| 636 | + | Some(w) => out.intersection(w).copied().collect(), |
|
| 637 | + | None => out, |
|
| 638 | + | }) |
|
| 579 | 639 | } |
|
| 580 | 640 | ||
| 581 | 641 | /// `filter_metas` for a test that needs the evaluator (identity lookups). |
|
| … | 35 unchanged lines |
||
| 617 | 677 | | "revoked" |
|
| 618 | 678 | | "attested" |
|
| 619 | 679 | | "built" |
|
| 680 | + | | "conflicts" |
|
| 620 | 681 | ), |
|
| 621 | 682 | Expr::Intersection(a, b) => Self::is_predicate(a) || Self::is_predicate(b), |
|
| 622 | 683 | Expr::Union(a, b) => Self::is_predicate(a) && Self::is_predicate(b), |
|
| … | 123 unchanged lines |
modified crates/oxy-core/src/repo/tests.rs +30 −0
| … | 7146 unchanged lines |
||
| 7147 | 7147 | repo.revoke_identity(&key, "test").unwrap(); |
|
| 7148 | 7148 | assert_eq!(set("revoked()"), vec![d]); |
|
| 7149 | 7149 | assert_eq!(set("signed_by(me) ~ revoked()"), vec![]); |
|
| 7150 | + | // independent(X, IN): what could ship without X. c and d neither |
|
| 7151 | + | // depend on b nor are depended on by it; a is below, the working copy |
|
| 7152 | + | // above. |
|
| 7153 | + | assert_eq!( |
|
| 7154 | + | set(&format!("independent(#{}, ::@)", b.short(12))), |
|
| 7155 | + | sorted(&[c, d]) |
|
| 7156 | + | ); |
|
| 7157 | + | assert_eq!( |
|
| 7158 | + | set(&format!("independent(#{}, ::main)", a.short(12))), |
|
| 7159 | + | vec![c] |
|
| 7160 | + | ); |
|
| 7161 | + | // conflicts(S): nothing conflicts yet; then two edits of one line. |
|
| 7162 | + | assert_eq!(set("conflicts()"), vec![]); |
|
| 7163 | + | assert_eq!(set("conflicts(main)"), vec![]); |
|
| 7164 | + | // (The saved revset above edited the user config under the root: |
|
| 7165 | + | // discard it.) |
|
| 7166 | + | repo.checkout(&[a], true, "checkout a").unwrap(); |
|
| 7167 | + | write(root, "src/a.rs", b"one\nTWO\n"); |
|
| 7168 | + | let e = repo |
|
| 7169 | + | .commit("fifth: the other edit of line two", &[]) |
|
| 7170 | + | .unwrap(); |
|
| 7171 | + | repo.checkout(&repo.bookmark_heads().unwrap(), true, "checkout main") |
|
| 7172 | + | .unwrap(); |
|
| 7173 | + | let conflicted = repo.revset("conflicts()").unwrap().hashes; |
|
| 7174 | + | assert!( |
|
| 7175 | + | conflicted.contains(&e) && conflicted.contains(&b), |
|
| 7176 | + | "{conflicted:?} should hold b and e" |
|
| 7177 | + | ); |
|
| 7178 | + | assert!(!conflicted.contains(&c)); |
|
| 7179 | + | assert_eq!(set("conflicts() & author(nobody)"), vec![]); |
|
| 7150 | 7180 | } |
modified docs/revset_query_language.md +3 −3
| … | 26 unchanged lines |
||
| 27 | 27 | **Implemented (2026-10-04, oxygen#65):** `oxy log -r`, `oxy show -r`, |
|
| 28 | 28 | `oxy verify -r` and `oxy abandon -r` with the symbols, relations, |
|
| 29 | 29 | operators, `author`, `description` (substring), `date`, `kind`, |
|
| 30 | - | `touches`, `latest` and the trust predicates of §2.4 below, saved |
|
| 31 | - | revsets, and a time budget. Not yet: `independent()`, `conflicts()`, |
|
| 30 | + | `touches`, `latest`, `independent`, `conflicts` and the trust predicates |
|
| 31 | + | of §2.4 below, saved revsets, and a time budget. Not yet: |
|
| 32 | 32 | `description(re:…)`, `diff -r`, `push -r`, `fold`, and the hub's `?r=`. |
|
| 33 | 33 | Other commands take a single change (a ChangeId prefix, a `#hash` |
|
| 34 | 34 | prefix or `@`) or a bookmark or tag name; symbols in a revset resolve |
|
| … | 45 unchanged lines |
||
| 80 | 80 | | `date(RANGE)` | changes by HLC wall time: `2026-10-01..`, `-7d..`, `..2026-09-30` | |
|
| 81 | 81 | | `kind(K)` | `normal`, `resolution` or `working-copy` | |
|
| 82 | 82 | | `touches(GLOB)` | changes that edit any node whose path matches GLOB in the evaluation state. Nodes keep their identity through renames, so this follows a file's whole history under any name. | |
|
| 83 | - | | `conflicts(S)` | changes that contributed atoms to a conflict in state S (default `@`). Conflicts belong to states, not changes. | |
|
| 83 | + | | `conflicts(S)` | changes that contributed atoms to a conflict in state S (default: the working copy's base). Conflicts belong to states, not changes. | |
|
| 84 | 84 | ||
| 85 | 85 | ### 2.4 Trust: unique to Oxygen |
|
| 86 | 86 | ||
| … | 76 unchanged lines |