OXYGENHUB

A full change hash resolves without its # aa4939e07a09

A change named by its full 64-digit hash without the #, as bookmark show
--json and the hub's API print heads, resolves wherever a change is
named; 64 hex digits cannot be a ChangeId, which has 32. JSON output
now feeds back into bookmark set, bookmark create and checkout.

closes #103
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-12 00:18 UTC
Signature
✔ signed by Andy McCall
Hash
aa4939e07a092789f94655dab78135c3cb4f7dda9626ce2baebf51a63611782e
Depends on
34fad4a92451 75167672ebcd 8fd78eb52ad2 982864068a59 b689779e9e74
Depended on by
f2f91ceb0e9f 31d3e932e97a
In bookmarks
bare-hashes carried-not-removed 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
In tags
v0.10.0 v0.11.0 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
4 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified CHANGELOG.md +2 −0
…
1 unchanged lines
2 2
3 3
## Unreleased
4 4
5 +
- **Full hashes as `--json` prints them (#103).** A change named by its full 64-digit hash without the `#`, as `oxy bookmark show --json` and the hub's API print heads, resolves wherever a change is named (`bookmark set`, `bookmark create`, `checkout`, …), so JSON output feeds back into commands.
6 +
5 7
- **Large new files are not snapshotted unseen, and past snapshots expire (#98).** A 1 GB image left untracked in a working tree was taken in by the next `status` and kept by the operation log for good: deleting the file and running `gc --now` freed nothing. Now a new, untracked file over `snapshot.max-new-file-size` (default 100 MiB) is left out and named by `status`, with how to ignore it or raise the limit; tracked files and files already in the working copy are unaffected. Past working-copy snapshots keep their files only for `core.snapshot-retention` (default 7 days), so `gc` prunes what only they held, and `oxy gc --expire-snapshots --now` does it at once. `undo` and `op restore` to an operation whose working copy has expired keep the files as they are.
6 8
7 9
- **`oxy undo` undoes this workspace's last command (#99).** It undid the repository's newest operation, whichever workspace made it: in a repository with several workspaces an undo could take back another workspace's commit or merge, and it said only which operation it went back to. Operations now record their workspace, and undo takes back this workspace's last command, leaving other workspaces' work as it is. On a bookmark both share, only its own heads go; a change another workspace built on it, or the workspace it would take away, refuses the undo and names it. Undo now says which command it undid and what that changed, passes over pushes (the remote keeps what it was sent), and warns when an undone commit is already on a remote. `oxy amend` warns when it rewrites a change a remote already has, so the next push will need `--force`.
…
172 unchanged lines
modified crates/oxy-core/src/repo/mod.rs +5 −1
…
2479 unchanged lines
2480 2480
            return fail("empty change specifier");
2481 2481
        }
2482 2482
        let entries = self.log()?;
2483 -
        let matches: Vec<&LogEntry> = if let Some(hp) = spec.strip_prefix('#') {
2483 +
        // A full hash as `--json` prints it, without its `#`: 64 hex digits
2484 +
        // cannot be a ChangeId, which has 32 (oxygen#103).
2485 +
        let full_hash =
2486 +
            (spec.len() == 64 && spec.bytes().all(|b| b.is_ascii_hexdigit())).then_some(spec);
2487 +
        let matches: Vec<&LogEntry> = if let Some(hp) = spec.strip_prefix('#').or(full_hash) {
2484 2488
            entries
2485 2489
                .iter()
2486 2490
                .filter(|e| e.hash.starts_with_hex(hp))
…
219 unchanged lines
modified crates/oxy-core/src/repo/tests.rs +19 −0
…
8518 unchanged lines
8519 8519
        "committed files stay"
8520 8520
    );
8521 8521
}
8522 +
8523 +
/// oxygen#103: a full change hash as `--json` prints it, without `#`,
8524 +
/// resolves, so JSON output feeds back into commands.
8525 +
#[test]
8526 +
fn a_full_hash_resolves_without_its_hash_sign() {
8527 +
    hermetic();
8528 +
    let dir = tempfile::tempdir().unwrap();
8529 +
    let root = dir.path();
8530 +
    let repo = Repo::init(root).unwrap();
8531 +
    write(root, "a.txt", b"a\n");
8532 +
    let h = repo.commit("a", &[]).unwrap();
8533 +
    assert_eq!(repo.resolve_change(&h.to_hex()).unwrap(), h);
8534 +
    assert_eq!(repo.resolve_change(&format!("#{}", h.to_hex())).unwrap(), h);
8535 +
    assert!(repo
8536 +
        .resolve_change(&"0".repeat(64))
8537 +
        .unwrap_err()
8538 +
        .to_string()
8539 +
        .contains("no change matches"));
8540 +
}