OXYGENHUB

Compare main … push-notes

⇄

0 changes in push-notes not in main; 31 the other way. 59 files differ between the two states.

hide whitespace changes
removed .flow/checks.wasm +0 −0

No line changes.

modified .flow/pipeline.kdl +0 −20
…
2 unchanged lines
3 3
// Earth; the jobs appear as checks on the change.
4 4
schema-version 1
5 5
6 -
// Cheap checks on the WASM tier (oxygenflow#96): OxygenFlow's checks module,
7 -
// copied from oxygenflow/.flow/checks.wasm (built there from
8 -
// crates/flow-checks; copy it again when it changes). No image and no KVM,
9 -
// so any runner with the WASM tier takes it: a phone, plex, windows-1.
10 -
sandbox "wasm" {
11 -
    runtime "wasm"
12 -
    memory-limit "64MiB"
13 -
}
14 -
15 -
// The changelog's shape, and a section for the version Cargo.toml says, so a
16 -
// release never finds its notes missing.
17 -
job "changelog" {
18 -
    sandbox "wasm"
19 -
    scope "CHANGELOG.md" "Cargo.toml" ".flow/checks.wasm"
20 -
    exec ".flow/checks.wasm" "changelog" "CHANGELOG.md" "Cargo.toml"
21 -
}
22 -
23 6
// The OxygenFlow build image: Rust with rustfmt and clippy, Node, and every
24 7
// crate of Cargo.lock vendored (oxygenflow/images/rust). Rebuild and re-pin
25 8
// it when Cargo.lock changes.
…
170 unchanged lines
196 179
        }
197 180
        artifact "oxy_riscv64.deb" {
198 181
            path "/src/target/dist-flat/oxy_riscv64.deb"
199 -
        }
200 -
        artifact "oxy-src.tar.gz" {
201 -
            path "/src/target/dist-flat/oxy-src.tar.gz"
202 182
        }
203 183
        artifact "SHA256SUMS" {
204 184
            path "/src/target/dist-flat/SHA256SUMS"
…
31 unchanged lines
modified CHANGELOG.md +1 −34
1 1
# Changelog
2 2
3 -
## 0.11.0 (2026-11-04)
4 -
5 -
- **Changed: the changelog is checked on the WASM tier.** A `changelog` job in the pipeline runs OxygenFlow's checks module on any runner with the WASM tier, failing when the changelog's sections are out of shape or Cargo.toml's version has none.
6 -
- **Changed: moved subrepo pins are named (#162).** A pin moved by work inside a subrepo is a pin bump the parent's next commit records, and it used to go in unsaid. `oxy status` now lists it under *Subrepo pins* (old → new), and a subrepo's own uncommitted edits, which the parent does not record; a bare `oxy commit` that records a moved pin says so and how to leave it out (`oxy commit PATHS`).
7 -
- **Added: `oxy submodule remove --delete` (#164).** It deletes the subrepo's directory too, read-only object files and all, and refuses while the subrepo has uncommitted edits (`--force` to delete them anyway); without it, removal says the directory is left and how to delete it. A directory moved by hand used to fail halfway, after the copy, on the read-only files.
8 -
- **Changed: an agent's commit says "awaiting seal", not "unsigned" (#165).** In a checkout that signs keyless under an agent grant, a change of its own author that the next push will seal shows `◌ awaiting seal` in `oxy log` and `oxy show`; "unsigned" is kept for changes nothing will sign.
9 -
- **Fixed: a stale `author.key` can no longer fork your identity (#163).** A checkout that pinned `author.key` to a key a rotation made elsewhere had already succeeded built a second successor of it on `oxy identity delegate add`, splitting the identity in two. `rotate`, `renew` and `delegate add`/`remove` now refuse to start from a key that already has a successor in your home, naming the newest key and the `oxy config` that points at it; their report says which key they started from and which config named it; and `oxy identity show` warns when the configured key is not the newest, and names the key where an identity forks.
10 -
11 -
## 0.10.0 (2026-11-01)
12 -
13 -
- **Fixed: fetch, pull and clone are far faster with many bookmarks (#158).** They fetched each bookmark in its own exchange with the hub, and changes that bookmarks share were sent again for each one; a pull with nothing new still asked for a bundle per bookmark. Now every bookmark's heads are fetched in one exchange, and none when nothing is new. On a 47 MB repository with 160 bookmarks: a pull with nothing new 169 s → 3.5 s, a clone 324 s → 26 s. `OXY_TIMINGS` now also shows the refs, transparency log and per-bookmark checks of a fetch.
14 -
- **Added: `oxy git colocate`, a `.git` beside `.oxy` (oxygenhub#355).** Your editor's Git features (blame, history, gutter marks) and other Git tools now work on an Oxygen checkout: oxy keeps a `.git` there with the current bookmark exported after every command, Git's index at its tree so `git status` shows only your uncommitted work, and `.oxy/` excluded. One way: commit with oxy. `--off` stops it.
15 -
- **Changed: `.git` is never content.** oxy no longer picks up a `.git` directory at a checkout's top, and checkout refuses a path with a `.git` component at any depth (as Git does), so a change cannot plant Git hooks.
16 -
- **Added: a Git export from the change graph (oxygenhub#355).** oxy-core's `git_export_graph` writes every bookmark and tag to a bare Git repository so that the same changes give the same commits wherever it runs: each change a commit whose parents are its dependencies, each bookmark a branch (a merge of its heads when it has several), tags as tags, incrementally. It is what OxygenHub serves to `git clone`. (`oxy git export` still follows the checkout's own history.)
17 -
- **Added: `oxy mcp`, Oxygen for AI agents (#159).** An MCP server over stdio for a repository: an agent calls typed tools (status, diff, log, show, cat, blame, bookmarks, tags, conflicts, op log, identity, commit, amend, describe, fetch, pull, push, tlog verify) and gets each command's JSON back instead of reading a shell. It signs as the identity its environment configures, so started with an agent home every change is sealed via the agent with nothing to forget per command, and says who in its `initialize` reply. Commits name their paths (or `all: true`); undo and forced pushes are off unless started with `--allow-destructive`. The protocol itself is oxy-core's `mcp` module, for `oh mcp` to reuse. See docs/mcp.md.
18 -
- **Icons for HaikuDepot (#157).** `packaging/haikuports/icons/` has oxy's and oh's icons as HVIF (Haiku's vector format) with 16, 32 and 64 px PNGs, written by `make-icons.py`: the Oxygen mark on a pale tile for oxy, on OxygenHub's dark tile for oh, the same family as the VS Code extensions.
19 -
20 -
## 0.9.0 (2026-10-29)
21 -
22 -
- **Releases carry their source (#157).** Each release now has `oxy-VERSION-src.tar.gz`, the source as released, reproducible (sorted, fixed times and owners, gzip without a timestamp), listed in SHA256SUMS, for packagers to fetch at a fixed checksum. `packaging/haikuports/make-recipe.py oxy|oh TARBALL VERSION` writes the haikuports recipe from it (oh's from OxygenHub's source tarball); the 0.8.0 recipe, which used the on-demand archive, is gone.
23 -
- **Added: a haikuports recipe for oxy (#156).** `packaging/haikuports/` holds the recipe that builds oxy for HaikuDepot from the release source and every crate in `Cargo.lock`, and the script that writes it for a new release; the 0.8.0 recipe builds with haikuporter on Haiku R1/beta6.
24 -
- **Fixed: an expired checkout token no longer hides your sign-in (#155).** A checkout's own token for a remote (`remote.NAME.token`) is used first, so when it expired every fetch, pull and push answered 401 although `oxy auth login` had a good sign-in for the same hub. A checkout token the hub no longer knows (401) now falls back to that sign-in, with a warning naming `oxy config --unset remote.NAME.token` to drop the stale one; never to another checkout's token, and not when the token is refused for permission (403, 404), so a narrowly scoped token is never swapped for a wider sign-in. When both are refused, the error says to sign in again and unset it.
25 -
- **Added: the `artifact` object type (Cylinder C1, #108).** An artifact is now an object of its own in the store (oxy-core's `artifact` module), named by its object hash as every object is, and `oxy fsck` checks it; its tree stays a blob. Stores written before read as they did: the type is a new one at the end of the list.
26 -
- **Added: artifacts (Cylinder, step C1, #108).** `oxy artifact pack DIR --kind archive|package|container|runner-image|report [--arch --os --entrypoint --env K=V --label K=V]` packs a directory into an artifact: each file an ordinary blob of the repository's store, so files and chunks it holds already cost nothing, under a tree and an artifact encoding in canonical CBOR, named by the BLAKE3 of that encoding (docs/cylinder.md §4.1). `oxy artifact list | show NAME | unpack NAME DEST | verify NAME` (any unique prefix of six characters or more; `--json` on list, show and pack), and `oxy gc` keeps every artifact's blobs. The new `cylinder` crate holds the model.
27 -
- **Added: oxy on Haiku (#154).** oxy builds and runs on Haiku R1/beta6 (x86_64) with HaikuPorts' Rust; `oxy-VERSION-haiku-x86_64.tar.gz` is on the releases page from 0.8.0, with its own `SHA256SUMS-haiku`. The README says how to install it and how to build it (`ZSTD_SYS_USE_PKG_CONFIG=1`).
28 -
29 -
- **Fixed: a subrepo put back after its removal is pinned in the change that registers it (#152).** `oxy submodule remove` leaves a marker so the next snapshot drops the node while the directory stays; the snapshot cleared it only after reading it, so when nothing looked at the working copy between the removal's commit and the subrepo's return, the change that registered it again in `.oxymodules` left it out and a second commit was needed. A marker is now dropped before it is read once the removal is recorded or the path is registered again.
30 -
- **Fixed: `oxy identity create` needs no repository (#144).** Outside one it makes a user identity: the secret and identity go to the oxy home's keys and the user file names it as the author, so a new user or machine sets up an identity first; a repository made or cloned afterwards signs with it. Only `--local` needs a repository, and inside one it is kept there too, as before.
31 -
32 -
## 0.8.0 (2026-10-27)
3 +
## Unreleased
33 4
34 5
- **A server can add a note to a reference a push moved (#148).** `oxy-server`'s `Backend` has a `push_notes` step: each note is added to that accepted reference's result, which `oxy push` shows in place of "updated". OxygenHub will use it to say when a push moves the bookmark of a proposal that has already landed (oxygenhub#329).
35 -
36 -
- **Identical sides settle by themselves (#146).** Two bookmarks that each resolved the same conflict to the same text conflicted again when merged, both sides the identical lines. A conflict whose sides are byte-identical now settles as `merge=union` adjacent insertions do (#145), in any file and keeping one side: a checkout writes it once ("kept one of its identical sides") and the next commit records the resolution, and a host landing a proposal settles it too.
37 -
38 -
- **A subrepo at its pinned state is unmodified on any bookmark (#147).** A subrepo whose heads were exactly its pin's but which sat on another bookmark than the pin named counted as modified, so `oxy bookmark switch` refused ("the working copy has changes"). Only the state counts now: the same repository at the same heads, or the same Git commit; the recorded pin stands.
39 6
40 7
- **A shallow clone fetches its checkpoint in parts (#137).** The checkpoint of a big state came as one answer, the whole state's graph blobs in one body (about 2 GB for the VS Code history), built and held whole on both sides. A server now answers `checkpoint-index` (the checkpoint and its history, no graphs), and the client fetches the graph blobs it lacks with `objects`, 2,048 at a time; `objects` reads them in pack order. An older server answers 404 and gets the one-piece request as before.
41 8
…
284 unchanged lines
modified Cargo.lock +4 −15
…
571 unchanged lines
572 572
]
573 573
574 574
[[package]]
575 -
name = "cylinder"
576 -
version = "0.11.0"
577 -
dependencies = [
578 -
 "blake3",
579 -
 "oxy-core",
580 -
 "tempfile",
581 -
 "thiserror 1.0.69",
582 -
]
583 -
584 -
[[package]]
585 575
name = "data-encoding"
586 576
version = "2.11.1"
587 577
source = "registry+https://github.com/rust-lang/crates.io-index"
…
911 unchanged lines
1499 1489
1500 1490
[[package]]
1501 1491
name = "oxy"
1502 -
version = "0.11.0"
1492 +
version = "0.7.0"
1503 1493
dependencies = [
1504 1494
 "anyhow",
1505 1495
 "clap",
1506 1496
 "clap_complete",
1507 1497
 "clap_mangen",
1508 1498
 "console",
1509 -
 "cylinder",
1510 1499
 "indicatif",
1511 1500
 "libc",
1512 1501
 "mimalloc",
…
6 unchanged lines
1519 1508
1520 1509
[[package]]
1521 1510
name = "oxy-core"
1522 -
version = "0.11.0"
1511 +
version = "0.7.0"
1523 1512
dependencies = [
1524 1513
 "blake3",
1525 1514
 "ed25519-dalek",
…
20 unchanged lines
1546 1535
1547 1536
[[package]]
1548 1537
name = "oxy-server"
1549 -
version = "0.11.0"
1538 +
version = "0.7.0"
1550 1539
dependencies = [
1551 1540
 "axum",
1552 1541
 "axum-server",
…
7 unchanged lines
1560 1549
1561 1550
[[package]]
1562 1551
name = "oxy-strata"
1563 -
version = "0.11.0"
1552 +
version = "0.7.0"
1564 1553
dependencies = [
1565 1554
 "async-trait",
1566 1555
 "bytes",
…
1419 unchanged lines
modified Cargo.toml +2 −3
1 1
[workspace]
2 -
members = ["crates/oxy-core", "crates/oxy-server", "crates/oxy-strata", "crates/cylinder", "."]
2 +
members = ["crates/oxy-core", "crates/oxy-server", "crates/oxy-strata", "."]
3 3
resolver = "2"
4 4
5 5
[workspace.package]
6 -
version = "0.11.0"
6 +
version = "0.7.0"
7 7
edition = "2021"
8 8
license = "MIT OR Apache-2.0"
9 9
…
13 unchanged lines
23 23
oxy-core = { path = "crates/oxy-core" }
24 24
oxy-server = { path = "crates/oxy-server" }
25 25
oxy-strata = { path = "crates/oxy-strata" }
26 -
cylinder = { path = "crates/cylinder" }
27 26
clap = { version = "4", features = ["derive"] }
28 27
anyhow = "1"
29 28
libc = "0.2"
…
19 unchanged lines
modified README.md +1 −7
…
15 unchanged lines
16 16
17 17
Every change the agent makes is attributed to you and signed by the agent. `oxy log`, `oxy verify` and the hub therefore show who did the work, such as "Andy McCall · via Claude Code", and a policy can require that a person has approved it before it lands.
18 18
19 -
An agent can also work through `oxy mcp`, an MCP server for the repository: typed tools instead of a shell, signing as the identity it was started with, and nothing destructive unless you allow it (see [docs/mcp.md](docs/mcp.md)).
20 -
21 19
See [docs/ai_native_agent_workflows.md](docs/ai_native_agent_workflows.md) for the design and [docs/guide.md](docs/guide.md) §9 for the setup.
22 20
23 21
## What is different
…
13 unchanged lines
37 35
38 36
## Install
39 37
40 -
Builds for Linux, macOS, Windows and Haiku are on the [releases page](https://oxygenhub.io/andymccall/oxygen/releases). These are early releases: the macOS and Windows builds are not signed and have had little testing.
38 +
Builds for Linux, macOS and Windows are on the [releases page](https://oxygenhub.io/andymccall/oxygen/releases). These are early releases: the macOS and Windows builds are not signed and have had little testing.
41 39
42 40
- **Debian, Ubuntu and other Linux with apt** (x86_64, arm64 or riscv64): add the repository at https://oxygenhub.io/apt and install with apt, so `apt upgrade` keeps oxy current:
43 41
…
13 unchanged lines
57 55
- **Other Linux:** unpack `oxy-VERSION-linux-x86_64.tar.gz` (or `-linux-arm64`) and put `oxy` on your `PATH`.
58 56
- **macOS** (Apple Silicon or Intel): unpack `oxy-VERSION-macos-arm64.tar.gz` (or `-macos-x86_64`), then `xattr -d com.apple.quarantine oxy`, since the build is not notarised, and put it on your `PATH`.
59 57
- **Windows:** unzip `oxy-VERSION-windows-x86_64.zip` and put the folder on your `PATH`. SmartScreen may warn on first run, since the build is not signed.
60 -
- **Haiku** (R1/beta6, x86_64): unpack `oxy-VERSION-haiku-x86_64.tar.gz` and copy `oxy` to `~/config/non-packaged/bin`. It is built in a Haiku guest rather than by OxygenFlow, so it has its own `SHA256SUMS-haiku` and no build attestation.
61 58
62 59
`oh`, the OxygenHub command line, is released the same way from [OxygenHub's releases](https://oxygenhub.io/andymccall/oxygenhub/releases).
63 60
…
7 unchanged lines
71 68
```
72 69
73 70
The binary is `target/release/oxy`.
74 -
75 -
On Haiku, install `rust_bin` and `zstd_devel` with pkgman and build with `ZSTD_SYS_USE_PKG_CONFIG=1 cargo build --release`: zstd-sys then links Haiku's libzstd, since a system library also references zstd and ld refuses the bundled copy's hidden symbols.
76 71
77 72
**Changelog:** a commit that changes code adds a line under `## Unreleased` in CHANGELOG.md (Added, Changed, Fixed or Security, with the issue number); versions change only at release. `scripts/changelog-check.sh [PATH...]` checks it for the paths a commit takes (the working copy's changes without any) and runs in the gate; `NO_CHANGELOG=1` for code changes users won't notice.
78 73
…
78 unchanged lines
157 152
| `docs/strata.md` | Keeping a repository on your own storage: S3, GCS, Azure, a directory |
158 153
| `docs/protocol.md` | Wire protocol for other implementers |
159 154
| `docs/prompt.md` | Shell prompt integration |
160 -
| `docs/mcp.md` | `oxy mcp`: the repository as tools for AI agents |
161 155
162 156
## Licence
163 157
…
1 unchanged lines
removed crates/cylinder/Cargo.toml +0 −14
1 -
[package]
2 -
name = "cylinder"
3 -
version.workspace = true
4 -
edition.workspace = true
5 -
license.workspace = true
6 -
description = "Cylinder: artifacts as trees of Oxygen chunks (docs/cylinder.md)"
7 -
8 -
[dependencies]
9 -
oxy-core = { path = "../oxy-core" }
10 -
blake3 = "1"
11 -
thiserror = "1"
12 -
13 -
[dev-dependencies]
14 -
tempfile = "3"
removed crates/cylinder/src/lib.rs +0 −454
1 -
//! Cylinder, step C1 (docs/cylinder.md §4.1): an **artifact** is an
2 -
//! immutable, content-addressed object naming a tree of files, each kept as
3 -
//! an ordinary Oxygen blob of FastCDC BLAKE3 chunks. An image rebuilt with
4 -
//! one changed file moves only that file's changed chunks, and a binary in
5 -
//! a release archive and in a runner image is the same chunks.
6 -
//!
7 -
//! The model is oxy-core's ([`oxy_core::artifact`]): an `artifact` object
8 -
//! in canonical CBOR, named by its object hash, whose tree is a blob. This
9 -
//! crate packs a directory into one, opens, verifies and unpacks it, and
10 -
//! keeps it: `.oxy/artifacts/NAME` records each artifact this repository
11 -
//! keeps, which garbage collection keeps with its tree and files
12 -
//! ([`roots`]).
13 -
14 -
use oxy_core::hash::{ObjectRef, ObjectType};
15 -
use oxy_core::repo::Repo;
16 -
use std::collections::BTreeMap;
17 -
use std::path::{Path, PathBuf};
18 -
19 -
pub use oxy_core::artifact::{
20 -
    Artifact, DirTree, Entry, Kind, MODE_EXEC, MODE_FILE, MODE_LINK, VERSION,
21 -
};
22 -
23 -
#[derive(Debug, thiserror::Error)]
24 -
pub enum Error {
25 -
    #[error(transparent)]
26 -
    Repo(#[from] oxy_core::repo::RepoError),
27 -
    #[error("io: {0}")]
28 -
    Io(#[from] std::io::Error),
29 -
    #[error("not an artifact: {0}")]
30 -
    Object(#[from] oxy_core::objects::ObjectError),
31 -
    #[error("store: {0}")]
32 -
    Store(#[from] oxy_core::store::StoreError),
33 -
    #[error("{0}")]
34 -
    Bad(String),
35 -
}
36 -
37 -
pub type Result<T> = std::result::Result<T, Error>;
38 -
39 -
fn bad<T>(msg: impl Into<String>) -> Result<T> {
40 -
    Err(Error::Bad(msg.into()))
41 -
}
42 -
43 -
/// What to pack a directory as.
44 -
#[derive(Clone, Debug)]
45 -
pub struct Spec {
46 -
    pub kind: Kind,
47 -
    pub arch: Option<String>,
48 -
    pub os: Option<String>,
49 -
    pub entrypoint: Option<Vec<String>>,
50 -
    pub env: BTreeMap<String, String>,
51 -
    pub labels: BTreeMap<String, String>,
52 -
}
53 -
54 -
impl Spec {
55 -
    pub fn new(kind: Kind) -> Spec {
56 -
        Spec {
57 -
            kind,
58 -
            arch: None,
59 -
            os: None,
60 -
            entrypoint: None,
61 -
            env: BTreeMap::new(),
62 -
            labels: BTreeMap::new(),
63 -
        }
64 -
    }
65 -
}
66 -
67 -
/// An artifact kept in a repository, with its name.
68 -
#[derive(Clone, Debug)]
69 -
pub struct Kept {
70 -
    pub name: String,
71 -
    pub artifact: Artifact,
72 -
    /// Its object (its name, as a reference).
73 -
    pub object: ObjectRef,
74 -
}
75 -
76 -
fn kept_dir(repo: &Repo) -> PathBuf {
77 -
    repo.oxy_dir().join("artifacts")
78 -
}
79 -
80 -
/// Every file under `dir`, by relative path: regular files and links, in
81 -
/// path order. `.oxy` and `.git` directories are not part of an artifact.
82 -
fn walk(dir: &Path) -> Result<Vec<(String, PathBuf, std::fs::Metadata)>> {
83 -
    let mut out = Vec::new();
84 -
    let mut stack = vec![(dir.to_path_buf(), String::new())];
85 -
    while let Some((d, rel)) = stack.pop() {
86 -
        for e in std::fs::read_dir(&d)? {
87 -
            let e = e?;
88 -
            let name = e
89 -
                .file_name()
90 -
                .into_string()
91 -
                .map_err(|n| Error::Bad(format!("{n:?} is not UTF-8")))?;
92 -
            let path = if rel.is_empty() {
93 -
                name.clone()
94 -
            } else {
95 -
                format!("{rel}/{name}")
96 -
            };
97 -
            let md = std::fs::symlink_metadata(e.path())?;
98 -
            if md.is_dir() {
99 -
                if name != ".oxy" && name != ".git" {
100 -
                    stack.push((e.path(), path));
101 -
                }
102 -
            } else if md.is_file() || md.file_type().is_symlink() {
103 -
                out.push((path, e.path(), md));
104 -
            }
105 -
        }
106 -
    }
107 -
    out.sort_by(|a, b| a.0.cmp(&b.0));
108 -
    Ok(out)
109 -
}
110 -
111 -
#[cfg(unix)]
112 -
fn mode_of(md: &std::fs::Metadata) -> u32 {
113 -
    use std::os::unix::fs::PermissionsExt;
114 -
    if md.file_type().is_symlink() {
115 -
        MODE_LINK
116 -
    } else if md.permissions().mode() & 0o111 != 0 {
117 -
        MODE_EXEC
118 -
    } else {
119 -
        MODE_FILE
120 -
    }
121 -
}
122 -
123 -
#[cfg(not(unix))]
124 -
fn mode_of(md: &std::fs::Metadata) -> u32 {
125 -
    if md.file_type().is_symlink() {
126 -
        MODE_LINK
127 -
    } else {
128 -
        MODE_FILE
129 -
    }
130 -
}
131 -
132 -
/// Pack the files under `dir` into an artifact of `spec`'s kind in
133 -
/// `repo`'s store: each file a blob, so files and chunks the store holds
134 -
/// already cost nothing; then its tree and its encoding, kept under its
135 -
/// name.
136 -
pub fn pack(repo: &Repo, dir: &Path, spec: &Spec) -> Result<Kept> {
137 -
    if !dir.is_dir() {
138 -
        return bad(format!("{} is not a directory", dir.display()));
139 -
    }
140 -
    let mut tree = DirTree::default();
141 -
    for (path, full, md) in walk(dir)? {
142 -
        let mode = mode_of(&md);
143 -
        let bytes = if mode == MODE_LINK {
144 -
            std::fs::read_link(&full)?
145 -
                .to_string_lossy()
146 -
                .into_owned()
147 -
                .into_bytes()
148 -
        } else {
149 -
            std::fs::read(&full)?
150 -
        };
151 -
        let blob = repo.store_blob(&bytes)?;
152 -
        tree.entries.push(Entry {
153 -
            path,
154 -
            mode,
155 -
            blob,
156 -
            size: bytes.len() as u64,
157 -
        });
158 -
    }
159 -
    let artifact = Artifact {
160 -
        kind: spec.kind,
161 -
        arch: spec.arch.clone(),
162 -
        os: spec.os.clone(),
163 -
        tree: repo.store_blob(&tree.encode())?,
164 -
        entrypoint: spec.entrypoint.clone(),
165 -
        env: spec.env.clone(),
166 -
        labels: spec.labels.clone(),
167 -
        total_size: tree.entries.iter().map(|e| e.size).sum(),
168 -
        receipt: None,
169 -
    };
170 -
    let object = repo.store().put(ObjectType::Artifact, &artifact.encode())?;
171 -
    let name = object.to_hex();
172 -
    std::fs::create_dir_all(kept_dir(repo))?;
173 -
    std::fs::write(kept_dir(repo).join(&name), b"")?;
174 -
    Ok(Kept {
175 -
        name,
176 -
        artifact,
177 -
        object,
178 -
    })
179 -
}
180 -
181 -
/// The names of the artifacts kept in `repo`, sorted.
182 -
pub fn list(repo: &Repo) -> Result<Vec<String>> {
183 -
    let mut out = Vec::new();
184 -
    match std::fs::read_dir(kept_dir(repo)) {
185 -
        Ok(rd) => {
186 -
            for e in rd {
187 -
                let n = e?.file_name().to_string_lossy().into_owned();
188 -
                if n.len() == 64 && n.bytes().all(|b| b.is_ascii_hexdigit()) {
189 -
                    out.push(n);
190 -
                }
191 -
            }
192 -
        }
193 -
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
194 -
        Err(e) => return Err(e.into()),
195 -
    }
196 -
    out.sort();
197 -
    Ok(out)
198 -
}
199 -
200 -
/// The kept artifact `name` names, or a unique prefix of it (at least six
201 -
/// characters), read from the store, which checks it against its name.
202 -
pub fn open(repo: &Repo, name: &str) -> Result<Kept> {
203 -
    let name = name.to_ascii_lowercase();
204 -
    if name.len() < 6 {
205 -
        return bad("name an artifact by at least six characters of its name");
206 -
    }
207 -
    let found: Vec<String> = list(repo)?
208 -
        .into_iter()
209 -
        .filter(|n| n.starts_with(&name))
210 -
        .collect();
211 -
    let full = match found.as_slice() {
212 -
        [one] => one.clone(),
213 -
        [] => {
214 -
            return bad(format!(
215 -
                "no artifact {name} here; `oxy artifact list` shows them"
216 -
            ))
217 -
        }
218 -
        _ => {
219 -
            return bad(format!(
220 -
                "{name} names {} artifacts: give more of the name",
221 -
                found.len()
222 -
            ))
223 -
        }
224 -
    };
225 -
    let object = ObjectRef::from_hex(&full).map_or_else(
226 -
        || bad(format!("artifact {full}: its record is damaged")),
227 -
        Ok,
228 -
    )?;
229 -
    // The store checks the bytes against the object's name as it reads.
230 -
    let bytes = repo.store().get_typed(&object, ObjectType::Artifact)?;
231 -
    Ok(Kept {
232 -
        name: full,
233 -
        artifact: Artifact::decode(&bytes)?,
234 -
        object,
235 -
    })
236 -
}
237 -
238 -
/// An artifact's tree.
239 -
pub fn tree(repo: &Repo, a: &Artifact) -> Result<DirTree> {
240 -
    Ok(DirTree::decode(&repo.read_blob(&a.tree)?)?)
241 -
}
242 -
243 -
/// Check that every file of `k` is in the store whole: its bytes read, at
244 -
/// the size its tree says. Answers how many files were read.
245 -
pub fn verify(repo: &Repo, k: &Kept) -> Result<usize> {
246 -
    let t = tree(repo, &k.artifact)?;
247 -
    let mut total = 0;
248 -
    for e in &t.entries {
249 -
        let n = repo.read_blob(&e.blob)?.len() as u64;
250 -
        if n != e.size {
251 -
            return bad(format!(
252 -
                "{}: {n} bytes, but the tree says {}",
253 -
                e.path, e.size
254 -
            ));
255 -
        }
256 -
        total += n;
257 -
    }
258 -
    if total != k.artifact.total_size {
259 -
        return bad(format!(
260 -
            "the files are {total} bytes together, but the artifact says {}",
261 -
            k.artifact.total_size
262 -
        ));
263 -
    }
264 -
    Ok(t.entries.len())
265 -
}
266 -
267 -
/// Write `k`'s files under `dest`, which must not exist or be empty.
268 -
pub fn unpack(repo: &Repo, k: &Kept, dest: &Path) -> Result<usize> {
269 -
    if dest.exists() && std::fs::read_dir(dest)?.next().is_some() {
270 -
        return bad(format!("{} is not empty", dest.display()));
271 -
    }
272 -
    let t = tree(repo, &k.artifact)?;
273 -
    for e in &t.entries {
274 -
        let out = dest.join(&e.path);
275 -
        if let Some(p) = out.parent() {
276 -
            std::fs::create_dir_all(p)?;
277 -
        }
278 -
        let bytes = repo.read_blob(&e.blob)?;
279 -
        if e.mode == MODE_LINK {
280 -
            #[cfg(unix)]
281 -
            std::os::unix::fs::symlink(String::from_utf8_lossy(&bytes).as_ref(), &out)?;
282 -
            #[cfg(not(unix))]
283 -
            std::fs::write(&out, &bytes)?;
284 -
            continue;
285 -
        }
286 -
        std::fs::write(&out, &bytes)?;
287 -
        #[cfg(unix)]
288 -
        if e.mode == MODE_EXEC {
289 -
            use std::os::unix::fs::PermissionsExt;
290 -
            std::fs::set_permissions(&out, std::fs::Permissions::from_mode(0o755))?;
291 -
        }
292 -
    }
293 -
    Ok(t.entries.len())
294 -
}
295 -
296 -
/// The blobs garbage collection must keep for the kept artifacts: each
297 -
/// one's encoding, its tree and its files (`Repo::gc_keeping`).
298 -
pub fn roots(repo: &Repo) -> Result<Vec<ObjectRef>> {
299 -
    let mut out = Vec::new();
300 -
    for name in list(repo)? {
301 -
        let k = open(repo, &name)?;
302 -
        out.push(k.object);
303 -
        out.push(k.artifact.tree);
304 -
        out.extend(tree(repo, &k.artifact)?.entries.iter().map(|e| e.blob));
305 -
    }
306 -
    Ok(out)
307 -
}
308 -
309 -
#[cfg(test)]
310 -
mod tests {
311 -
    use super::*;
312 -
    use oxy_core::cbor::{self, Value};
313 -
314 -
    fn repo(dir: &Path) -> Repo {
315 -
        Repo::init(&dir.join("repo")).unwrap()
316 -
    }
317 -
318 -
    #[test]
319 -
    fn a_directory_packs_into_an_artifact_and_back() {
320 -
        let t = tempfile::tempdir().unwrap();
321 -
        let r = repo(t.path());
322 -
        let src = t.path().join("out");
323 -
        std::fs::create_dir_all(src.join("bin")).unwrap();
324 -
        std::fs::write(src.join("README"), b"hello\n").unwrap();
325 -
        std::fs::write(src.join("bin/tool"), vec![7u8; 300_000]).unwrap();
326 -
        #[cfg(unix)]
327 -
        {
328 -
            use std::os::unix::fs::PermissionsExt;
329 -
            std::fs::set_permissions(src.join("bin/tool"), std::fs::Permissions::from_mode(0o755))
330 -
                .unwrap();
331 -
            std::os::unix::fs::symlink("bin/tool", src.join("tool")).unwrap();
332 -
        }
333 -
        let mut spec = Spec::new(Kind::Archive);
334 -
        spec.arch = Some("x86_64".into());
335 -
        spec.labels.insert("version".into(), "0.1.0".into());
336 -
        let k = pack(&r, &src, &spec).unwrap();
337 -
        assert_eq!(k.name, k.artifact.name().to_hex());
338 -
        assert_eq!(
339 -
            k.artifact.total_size,
340 -
            300_006 + if cfg!(unix) { 8 } else { 0 }
341 -
        );
342 -
        assert_eq!(list(&r).unwrap(), vec![k.name.clone()]);
343 -
344 -
        let again = open(&r, &k.name[..10]).unwrap();
345 -
        assert_eq!(again.artifact, k.artifact);
346 -
        let paths: Vec<(String, u32)> = tree(&r, &again.artifact)
347 -
            .unwrap()
348 -
            .entries
349 -
            .into_iter()
350 -
            .map(|e| (e.path, e.mode))
351 -
            .collect();
352 -
        assert_eq!(paths[0], ("README".into(), MODE_FILE));
353 -
        assert_eq!(
354 -
            paths[1],
355 -
            (
356 -
                "bin/tool".into(),
357 -
                if cfg!(unix) { MODE_EXEC } else { MODE_FILE }
358 -
            )
359 -
        );
360 -
        assert_eq!(verify(&r, &again).unwrap(), paths.len());
361 -
362 -
        let dest = t.path().join("back");
363 -
        unpack(&r, &again, &dest).unwrap();
364 -
        assert_eq!(
365 -
            std::fs::read(dest.join("bin/tool")).unwrap(),
366 -
            vec![7u8; 300_000]
367 -
        );
368 -
        #[cfg(unix)]
369 -
        assert_eq!(
370 -
            std::fs::read_link(dest.join("tool")).unwrap(),
371 -
            Path::new("bin/tool")
372 -
        );
373 -
        assert!(unpack(&r, &again, &dest)
374 -
            .unwrap_err()
375 -
            .to_string()
376 -
            .contains("not empty"));
377 -
378 -
        // The same files pack to the same name; garbage collection keeps it.
379 -
        assert_eq!(pack(&r, &src, &spec).unwrap().name, k.name);
380 -
        let keep = roots(&r).unwrap();
381 -
        assert!(keep.contains(&k.object) && keep.contains(&k.artifact.tree));
382 -
        r.gc_keeping(true, &keep, &mut |_| {}).unwrap();
383 -
        // An artifact is an object of its own, which fsck reads and accepts.
384 -
        let report = r.fsck().unwrap();
385 -
        let errors: Vec<_> = report
386 -
            .problems
387 -
            .iter()
388 -
            .filter(|p| p.severity == oxy_core::repo::Severity::Error)
389 -
            .collect();
390 -
        assert!(errors.is_empty(), "{errors:?}");
391 -
        assert_eq!(
392 -
            verify(&r, &open(&r, &k.name).unwrap()).unwrap(),
393 -
            paths.len()
394 -
        );
395 -
    }
396 -
397 -
    #[test]
398 -
    fn encodings_are_canonical_and_checked() {
399 -
        let a = Artifact {
400 -
            kind: Kind::RunnerImage,
401 -
            arch: Some("riscv64".into()),
402 -
            os: Some("linux".into()),
403 -
            tree: ObjectRef([1; 32]),
404 -
            entrypoint: Some(vec!["/sbin/init".into()]),
405 -
            env: [("PATH".to_string(), "/bin".to_string())].into(),
406 -
            labels: BTreeMap::new(),
407 -
            total_size: 42,
408 -
            receipt: Some(ObjectRef([2; 32])),
409 -
        };
410 -
        let bytes = a.encode();
411 -
        assert_eq!(Artifact::decode(&bytes).unwrap(), a);
412 -
        assert_eq!(
413 -
            a.name(),
414 -
            oxy_core::hash::hash_object(ObjectType::Artifact, &bytes)
415 -
        );
416 -
        assert!(Kind::parse("tarball")
417 -
            .unwrap_err()
418 -
            .to_string()
419 -
            .contains("runner-image"));
420 -
421 -
        let wrong = |v: Value| Artifact::decode(&cbor::encode(&v)).unwrap_err().to_string();
422 -
        assert!(wrong(Value::map([(0, Value::Uint(2))])).contains("version"));
423 -
        assert!(wrong(Value::map([(0, Value::Uint(1)), (10, Value::Null)])).contains("key 10"));
424 -
425 -
        let t = DirTree {
426 -
            entries: vec![Entry {
427 -
                path: "a".into(),
428 -
                mode: MODE_FILE,
429 -
                blob: ObjectRef([3; 32]),
430 -
                size: 1,
431 -
            }],
432 -
        };
433 -
        assert_eq!(DirTree::decode(&t.encode()).unwrap(), t);
434 -
        let entry = |p: &str, mode: u32| {
435 -
            Value::Array(vec![
436 -
                Value::Text(p.into()),
437 -
                Value::Uint(mode as u64),
438 -
                Value::Bytes(vec![0; 32]),
439 -
                Value::Uint(0),
440 -
            ])
441 -
        };
442 -
        let tree = |items: Vec<Value>| {
443 -
            DirTree::decode(&cbor::encode(&Value::map([
444 -
                (0, Value::Uint(1)),
445 -
                (1, Value::Array(items)),
446 -
            ])))
447 -
            .unwrap_err()
448 -
            .to_string()
449 -
        };
450 -
        assert!(tree(vec![entry("../x", MODE_FILE)]).contains("clean"));
451 -
        assert!(tree(vec![entry("b", MODE_FILE), entry("a", MODE_FILE)]).contains("order"));
452 -
        assert!(tree(vec![entry("a", 0o040000)]).contains("mode"));
453 -
    }
454 -
}
removed crates/oxy-core/src/artifact.rs +0 −303
1 -
//! Artifacts (Cylinder, docs/cylinder.md §4.1, oxygen#108): an immutable
2 -
//! object naming a tree of files, each an ordinary blob of FastCDC BLAKE3
3 -
//! chunks, so an image rebuilt with one changed file moves only that file's
4 -
//! changed chunks, and a binary in an archive and in an image is the same
5 -
//! chunks. An artifact is an object of its own (`artifact`) in canonical
6 -
//! CBOR; its tree is a blob. The `cylinder` crate packs, unpacks and keeps
7 -
//! them.
8 -
9 -
use crate::cbor::{self, Value};
10 -
use crate::hash::{hash_object, ObjectRef, ObjectType};
11 -
use crate::objects::{schema, ObjectError};
12 -
use std::collections::BTreeMap;
13 -
14 -
type R<T> = std::result::Result<T, ObjectError>;
15 -
16 -
/// The encoding's version this crate writes and reads.
17 -
pub const VERSION: u64 = 1;
18 -
19 -
/// What an artifact is for.
20 -
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21 -
pub enum Kind {
22 -
    Container,
23 -
    RunnerImage,
24 -
    Package,
25 -
    Archive,
26 -
    Report,
27 -
}
28 -
29 -
impl Kind {
30 -
    pub const ALL: [Kind; 5] = [
31 -
        Kind::Container,
32 -
        Kind::RunnerImage,
33 -
        Kind::Package,
34 -
        Kind::Archive,
35 -
        Kind::Report,
36 -
    ];
37 -
38 -
    pub fn as_str(self) -> &'static str {
39 -
        match self {
40 -
            Kind::Container => "container",
41 -
            Kind::RunnerImage => "runner-image",
42 -
            Kind::Package => "package",
43 -
            Kind::Archive => "archive",
44 -
            Kind::Report => "report",
45 -
        }
46 -
    }
47 -
48 -
    pub fn parse(s: &str) -> R<Kind> {
49 -
        Kind::ALL.into_iter().find(|k| k.as_str() == s).map_or_else(
50 -
            || {
51 -
                schema(format!(
52 -
                    "{s:?} is not a kind of artifact: {}",
53 -
                    Kind::ALL.map(|k| k.as_str()).join(", ")
54 -
                ))
55 -
            },
56 -
            Ok,
57 -
        )
58 -
    }
59 -
}
60 -
61 -
/// A file's mode in a tree: a regular file, an executable one, or a
62 -
/// symbolic link (whose blob is its target).
63 -
pub const MODE_FILE: u32 = 0o100644;
64 -
pub const MODE_EXEC: u32 = 0o100755;
65 -
pub const MODE_LINK: u32 = 0o120000;
66 -
67 -
/// A file of an artifact's tree.
68 -
#[derive(Clone, Debug, PartialEq, Eq)]
69 -
pub struct Entry {
70 -
    /// `/`-separated, relative, never `.` or `..` parts.
71 -
    pub path: String,
72 -
    pub mode: u32,
73 -
    pub blob: ObjectRef,
74 -
    pub size: u64,
75 -
}
76 -
77 -
/// An artifact's files, in path order: paths, modes, blobs and sizes.
78 -
#[derive(Clone, Debug, Default, PartialEq, Eq)]
79 -
pub struct DirTree {
80 -
    pub entries: Vec<Entry>,
81 -
}
82 -
83 -
fn path_ok(p: &str) -> bool {
84 -
    !p.is_empty()
85 -
        && !p.starts_with('/')
86 -
        && p.split('/').all(|s| !s.is_empty() && s != "." && s != "..")
87 -
}
88 -
89 -
fn obj(r: &ObjectRef) -> Value {
90 -
    Value::Bytes(r.0.to_vec())
91 -
}
92 -
93 -
fn get_obj(v: &Value, what: &str) -> R<ObjectRef> {
94 -
    match v.as_bytes() {
95 -
        Some(b) if b.len() == 32 => {
96 -
            let mut a = [0u8; 32];
97 -
            a.copy_from_slice(b);
98 -
            Ok(ObjectRef(a))
99 -
        }
100 -
        _ => schema(format!("{what} is not an object reference")),
101 -
    }
102 -
}
103 -
104 -
fn get_text(v: &Value, what: &str) -> R<String> {
105 -
    v.as_text()
106 -
        .map(str::to_string)
107 -
        .map_or_else(|| schema(format!("{what} is not text")), Ok)
108 -
}
109 -
110 -
fn get_uint(v: &Value, what: &str) -> R<u64> {
111 -
    v.as_uint()
112 -
        .map_or_else(|| schema(format!("{what} is not a number")), Ok)
113 -
}
114 -
115 -
impl DirTree {
116 -
    pub fn encode(&self) -> Vec<u8> {
117 -
        let entries = self
118 -
            .entries
119 -
            .iter()
120 -
            .map(|e| {
121 -
                Value::Array(vec![
122 -
                    Value::Text(e.path.clone()),
123 -
                    Value::Uint(e.mode as u64),
124 -
                    obj(&e.blob),
125 -
                    Value::Uint(e.size),
126 -
                ])
127 -
            })
128 -
            .collect();
129 -
        cbor::encode(&Value::map([
130 -
            (0, Value::Uint(VERSION)),
131 -
            (1, Value::Array(entries)),
132 -
        ]))
133 -
    }
134 -
135 -
    /// Read and check a tree: a known version, clean paths, each once, in
136 -
    /// order, with a known mode.
137 -
    pub fn decode(bytes: &[u8]) -> R<DirTree> {
138 -
        let v = cbor::decode(bytes)?;
139 -
        let m = v.as_map().map_or_else(|| schema("a tree is a map"), Ok)?;
140 -
        if m.get(&0).and_then(Value::as_uint) != Some(VERSION) {
141 -
            return schema(format!(
142 -
                "tree version {:?} is not known; this reads {VERSION}",
143 -
                m.get(&0)
144 -
            ));
145 -
        }
146 -
        let mut entries = Vec::new();
147 -
        for item in m.get(&1).and_then(Value::as_array).unwrap_or_default() {
148 -
            let [p, mode, blob, size] = item.as_array().unwrap_or_default() else {
149 -
                return schema("a tree entry is [path, mode, blob, size]");
150 -
            };
151 -
            let path = get_text(p, "a path")?;
152 -
            let mode = get_uint(mode, "a mode")? as u32;
153 -
            if !path_ok(&path) {
154 -
                return schema(format!("{path:?} is not a clean relative path"));
155 -
            }
156 -
            if ![MODE_FILE, MODE_EXEC, MODE_LINK].contains(&mode) {
157 -
                return schema(format!(
158 -
                    "{path}: mode {mode:o} is not a file, an executable or a link"
159 -
                ));
160 -
            }
161 -
            if entries.last().is_some_and(|l: &Entry| l.path >= path) {
162 -
                return schema(format!(
163 -
                    "{path}: entries are not in path order, or one is there twice"
164 -
                ));
165 -
            }
166 -
            entries.push(Entry {
167 -
                path,
168 -
                mode,
169 -
                blob: get_obj(blob, "a blob")?,
170 -
                size: get_uint(size, "a size")?,
171 -
            });
172 -
        }
173 -
        Ok(DirTree { entries })
174 -
    }
175 -
}
176 -
177 -
/// An artifact (docs/cylinder.md §4.1).
178 -
#[derive(Clone, Debug, PartialEq, Eq)]
179 -
pub struct Artifact {
180 -
    pub kind: Kind,
181 -
    /// `x86_64`, `riscv64`, `aarch64`.
182 -
    pub arch: Option<String>,
183 -
    /// `linux`, `darwin`, `none`.
184 -
    pub os: Option<String>,
185 -
    /// The [`DirTree`]'s blob.
186 -
    pub tree: ObjectRef,
187 -
    /// The default command, for a container or an image.
188 -
    pub entrypoint: Option<Vec<String>>,
189 -
    pub env: BTreeMap<String, String>,
190 -
    pub labels: BTreeMap<String, String>,
191 -
    /// The files' bytes together.
192 -
    pub total_size: u64,
193 -
    /// The receipt of the Flow job that built it, when one did.
194 -
    pub receipt: Option<ObjectRef>,
195 -
}
196 -
197 -
fn pairs(m: &BTreeMap<String, String>) -> Value {
198 -
    if m.is_empty() {
199 -
        return Value::Null;
200 -
    }
201 -
    Value::Array(
202 -
        m.iter()
203 -
            .map(|(k, v)| Value::Array(vec![Value::Text(k.clone()), Value::Text(v.clone())]))
204 -
            .collect(),
205 -
    )
206 -
}
207 -
208 -
fn get_pairs(v: Option<&Value>, what: &str) -> R<BTreeMap<String, String>> {
209 -
    let mut out = BTreeMap::new();
210 -
    let Some(v) = v.filter(|v| !v.is_null()) else {
211 -
        return Ok(out);
212 -
    };
213 -
    for p in v
214 -
        .as_array()
215 -
        .map_or_else(|| schema(format!("{what} is a list of pairs")), Ok)?
216 -
    {
217 -
        let [k, val] = p.as_array().unwrap_or_default() else {
218 -
            return schema(format!("{what}: a pair is [key, value]"));
219 -
        };
220 -
        let k = get_text(k, what)?;
221 -
        if out.insert(k.clone(), get_text(val, what)?).is_some() {
222 -
            return schema(format!("{what}: {k} is there twice"));
223 -
        }
224 -
    }
225 -
    Ok(out)
226 -
}
227 -
228 -
fn opt_text(v: &Option<String>) -> Value {
229 -
    v.as_ref().map_or(Value::Null, |s| Value::Text(s.clone()))
230 -
}
231 -
232 -
impl Artifact {
233 -
    /// Its canonical CBOR, keys as in docs/cylinder.md §4.1.
234 -
    pub fn encode(&self) -> Vec<u8> {
235 -
        let entry = self.entrypoint.as_ref().map_or(Value::Null, |a| {
236 -
            Value::Array(a.iter().cloned().map(Value::Text).collect())
237 -
        });
238 -
        cbor::encode(&Value::map([
239 -
            (0, Value::Uint(VERSION)),
240 -
            (1, Value::Text(self.kind.as_str().into())),
241 -
            (2, opt_text(&self.arch)),
242 -
            (3, opt_text(&self.os)),
243 -
            (4, obj(&self.tree)),
244 -
            (5, entry),
245 -
            (6, pairs(&self.env)),
246 -
            (7, pairs(&self.labels)),
247 -
            (8, Value::Uint(self.total_size)),
248 -
            (9, self.receipt.as_ref().map_or(Value::Null, obj)),
249 -
        ]))
250 -
    }
251 -
252 -
    pub fn decode(bytes: &[u8]) -> R<Artifact> {
253 -
        let v = cbor::decode(bytes)?;
254 -
        let m = v
255 -
            .as_map()
256 -
            .map_or_else(|| schema("an artifact is a map"), Ok)?;
257 -
        if m.get(&0).and_then(Value::as_uint) != Some(VERSION) {
258 -
            return schema(format!(
259 -
                "artifact version {:?} is not known; this reads {VERSION}",
260 -
                m.get(&0)
261 -
            ));
262 -
        }
263 -
        if let Some(k) = m.keys().find(|k| **k > 9) {
264 -
            return schema(format!("key {k} is not part of an artifact"));
265 -
        }
266 -
        let text = |k: u64, what: &str| -> R<Option<String>> {
267 -
            match m.get(&k) {
268 -
                None | Some(Value::Null) => Ok(None),
269 -
                Some(v) => get_text(v, what).map(Some),
270 -
            }
271 -
        };
272 -
        let entrypoint = match m.get(&5) {
273 -
            None | Some(Value::Null) => None,
274 -
            Some(v) => Some(
275 -
                v.as_array()
276 -
                    .unwrap_or_default()
277 -
                    .iter()
278 -
                    .map(|a| get_text(a, "the entrypoint"))
279 -
                    .collect::<R<_>>()?,
280 -
            ),
281 -
        };
282 -
        let receipt = match m.get(&9) {
283 -
            None | Some(Value::Null) => None,
284 -
            Some(v) => Some(get_obj(v, "the receipt")?),
285 -
        };
286 -
        Ok(Artifact {
287 -
            kind: Kind::parse(&text(1, "the kind")?.unwrap_or_default())?,
288 -
            arch: text(2, "the architecture")?,
289 -
            os: text(3, "the system")?,
290 -
            tree: get_obj(m.get(&4).unwrap_or(&Value::Null), "the tree")?,
291 -
            entrypoint,
292 -
            env: get_pairs(m.get(&6), "env")?,
293 -
            labels: get_pairs(m.get(&7), "labels")?,
294 -
            total_size: get_uint(m.get(&8).unwrap_or(&Value::Null), "the size")?,
295 -
            receipt,
296 -
        })
297 -
    }
298 -
299 -
    /// Its name: its object's hash, as every Oxygen object is named.
300 -
    pub fn name(&self) -> ObjectRef {
301 -
        hash_object(ObjectType::Artifact, &self.encode())
302 -
    }
303 -
}
modified crates/oxy-core/src/hash.rs +1 −6
…
67 unchanged lines
68 68
    Certificate,
69 69
    /// An author's grant to a certified subject (§14.7).
70 70
    Grant,
71 -
    /// A tree of files as one immutable thing: an image, a package, an
72 -
    /// archive (Cylinder, docs/cylinder.md §4.1).
73 -
    Artifact,
74 71
}
75 72
76 73
impl ObjectType {
77 -
    pub const ALL: [ObjectType; 13] = [
74 +
    pub const ALL: [ObjectType; 12] = [
78 75
        ObjectType::Chunk,
79 76
        ObjectType::Blob,
80 77
        ObjectType::Change,
…
6 unchanged lines
87 84
        ObjectType::Receipt,
88 85
        ObjectType::Certificate,
89 86
        ObjectType::Grant,
90 -
        ObjectType::Artifact,
91 87
    ];
92 88
93 89
    pub fn name(self) -> &'static str {
…
10 unchanged lines
104 100
            ObjectType::Receipt => "receipt",
105 101
            ObjectType::Certificate => "certificate",
106 102
            ObjectType::Grant => "grant",
107 -
            ObjectType::Artifact => "artifact",
108 103
        }
109 104
    }
110 105
…
59 unchanged lines
modified crates/oxy-core/src/lib.rs +0 −2
…
6 unchanged lines
7 7
8 8
pub mod alloc_stats;
9 9
pub mod approval;
10 -
pub mod artifact;
11 10
pub mod attest;
12 11
pub mod attributes;
13 12
pub mod cbor;
…
11 unchanged lines
25 24
pub mod ids;
26 25
pub mod lanes;
27 26
pub mod lines;
28 -
pub mod mcp;
29 27
pub mod objects;
30 28
pub mod oplog;
31 29
pub mod policy;
…
13 unchanged lines
removed crates/oxy-core/src/mcp.rs +0 −353
1 -
//! A Model Context Protocol server over stdio (oxygen#159): JSON-RPC 2.0,
2 -
//! one message per line, as MCP's stdio transport has it. This is only the
3 -
//! protocol: `initialize`, `ping`, `tools/list` and `tools/call`, with the
4 -
//! tools themselves supplied by the caller (`oxy mcp`, and `oh mcp` in the
5 -
//! hub, which reaches this module through its pinned oxygen).
6 -
//!
7 -
//! Nothing is written to stdout but protocol messages: a tool's own
8 -
//! output goes into its result.
9 -
10 -
use serde_json::{json, Value};
11 -
use std::io::{BufRead, Write};
12 -
13 -
/// The protocol versions this server speaks, newest first. A client asking
14 -
/// for one of them gets it back; any other gets the newest.
15 -
pub const PROTOCOL_VERSIONS: &[&str] = &["2025-06-18", "2025-03-26", "2024-11-05"];
16 -
17 -
/// One tool as `tools/list` describes it.
18 -
#[derive(Clone, Debug)]
19 -
pub struct Tool {
20 -
    pub name: String,
21 -
    pub description: String,
22 -
    /// A JSON Schema object for the arguments.
23 -
    pub input_schema: Value,
24 -
    /// Reads only: no change to the repository, the remote or the hub.
25 -
    pub read_only: bool,
26 -
    /// May lose work or history (undo, abandon, deletes).
27 -
    pub destructive: bool,
28 -
}
29 -
30 -
impl Tool {
31 -
    fn describe(&self) -> Value {
32 -
        json!({
33 -
            "name": self.name,
34 -
            "description": self.description,
35 -
            "inputSchema": self.input_schema,
36 -
            "annotations": {
37 -
                "readOnlyHint": self.read_only,
38 -
                "destructiveHint": self.destructive,
39 -
            },
40 -
        })
41 -
    }
42 -
}
43 -
44 -
/// What a tool call produced: text for the model to read, the same as JSON
45 -
/// when there is some, and whether it failed. A failed tool is a result, not
46 -
/// a protocol error, so the model sees why.
47 -
#[derive(Clone, Debug, Default, PartialEq)]
48 -
pub struct ToolOutput {
49 -
    pub text: String,
50 -
    pub structured: Option<Value>,
51 -
    pub is_error: bool,
52 -
}
53 -
54 -
impl ToolOutput {
55 -
    pub fn ok(text: impl Into<String>) -> ToolOutput {
56 -
        ToolOutput {
57 -
            text: text.into(),
58 -
            structured: None,
59 -
            is_error: false,
60 -
        }
61 -
    }
62 -
63 -
    pub fn error(text: impl Into<String>) -> ToolOutput {
64 -
        ToolOutput {
65 -
            text: text.into(),
66 -
            structured: None,
67 -
            is_error: true,
68 -
        }
69 -
    }
70 -
71 -
    fn to_json(&self) -> Value {
72 -
        let mut out = json!({
73 -
            "content": [{ "type": "text", "text": self.text }],
74 -
            "isError": self.is_error,
75 -
        });
76 -
        // Structured content must be an object; an array is wrapped.
77 -
        if let Some(s) = &self.structured {
78 -
            let obj = if s.is_object() {
79 -
                s.clone()
80 -
            } else {
81 -
                json!({ "result": s })
82 -
            };
83 -
            out["structuredContent"] = obj;
84 -
        }
85 -
        out
86 -
    }
87 -
}
88 -
89 -
/// The tools a server offers.
90 -
pub trait Tools {
91 -
    fn list(&self) -> Vec<Tool>;
92 -
    /// Run a tool. `None` when no tool has that name.
93 -
    fn call(&self, name: &str, args: &Value) -> Option<ToolOutput>;
94 -
}
95 -
96 -
/// Who the server is, for `initialize`.
97 -
#[derive(Clone, Debug)]
98 -
pub struct ServerInfo {
99 -
    pub name: String,
100 -
    pub version: String,
101 -
    /// Shown to the model: what the server is for and how to use it safely.
102 -
    pub instructions: String,
103 -
}
104 -
105 -
const PARSE_ERROR: i64 = -32700;
106 -
const INVALID_REQUEST: i64 = -32600;
107 -
const METHOD_NOT_FOUND: i64 = -32601;
108 -
const INVALID_PARAMS: i64 = -32602;
109 -
110 -
fn error(id: Value, code: i64, message: &str) -> Value {
111 -
    json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } })
112 -
}
113 -
114 -
fn result(id: Value, result: Value) -> Value {
115 -
    json!({ "jsonrpc": "2.0", "id": id, "result": result })
116 -
}
117 -
118 -
/// Answer one message; `None` for a notification (no reply is due).
119 -
pub fn handle(info: &ServerInfo, tools: &dyn Tools, message: &Value) -> Option<Value> {
120 -
    let Some(obj) = message.as_object() else {
121 -
        return Some(error(
122 -
            Value::Null,
123 -
            INVALID_REQUEST,
124 -
            "a message is a JSON object",
125 -
        ));
126 -
    };
127 -
    let method = obj.get("method").and_then(Value::as_str);
128 -
    let id = obj.get("id").cloned();
129 -
    let Some(method) = method else {
130 -
        // A response to something we never sent, or nonsense: a request
131 -
        // without a method gets an error, anything else is ignored.
132 -
        return id.map(|id| error(id, INVALID_REQUEST, "a request needs a method"));
133 -
    };
134 -
    // Notifications (initialized, cancelled, …) carry no id and get no reply.
135 -
    let id = id?;
136 -
    let params = obj.get("params").cloned().unwrap_or(Value::Null);
137 -
    Some(match method {
138 -
        "initialize" => {
139 -
            let asked = params.get("protocolVersion").and_then(Value::as_str);
140 -
            let version = asked
141 -
                .filter(|v| PROTOCOL_VERSIONS.contains(v))
142 -
                .unwrap_or(PROTOCOL_VERSIONS[0]);
143 -
            result(
144 -
                id,
145 -
                json!({
146 -
                    "protocolVersion": version,
147 -
                    "capabilities": { "tools": { "listChanged": false } },
148 -
                    "serverInfo": { "name": info.name, "version": info.version },
149 -
                    "instructions": info.instructions,
150 -
                }),
151 -
            )
152 -
        }
153 -
        "ping" => result(id, json!({})),
154 -
        "tools/list" => result(
155 -
            id,
156 -
            json!({ "tools": tools.list().iter().map(Tool::describe).collect::<Vec<_>>() }),
157 -
        ),
158 -
        "tools/call" => {
159 -
            let Some(name) = params.get("name").and_then(Value::as_str) else {
160 -
                return Some(error(
161 -
                    id,
162 -
                    INVALID_PARAMS,
163 -
                    "tools/call needs the tool's name",
164 -
                ));
165 -
            };
166 -
            let args = params
167 -
                .get("arguments")
168 -
                .cloned()
169 -
                .unwrap_or_else(|| json!({}));
170 -
            if !args.is_object() {
171 -
                return Some(error(
172 -
                    id,
173 -
                    INVALID_PARAMS,
174 -
                    "a tool's arguments are a JSON object",
175 -
                ));
176 -
            }
177 -
            match tools.call(name, &args) {
178 -
                Some(out) => result(id, out.to_json()),
179 -
                None => error(id, INVALID_PARAMS, &format!("no tool named {name}")),
180 -
            }
181 -
        }
182 -
        other => error(id, METHOD_NOT_FOUND, &format!("{other} is not supported")),
183 -
    })
184 -
}
185 -
186 -
/// Serve until the input ends: read a message per line, write a reply per
187 -
/// request. A line that is not JSON gets a parse error; blank lines are
188 -
/// skipped.
189 -
pub fn serve(
190 -
    info: &ServerInfo,
191 -
    tools: &dyn Tools,
192 -
    input: impl BufRead,
193 -
    mut output: impl Write,
194 -
) -> std::io::Result<()> {
195 -
    for line in input.lines() {
196 -
        let line = line?;
197 -
        if line.trim().is_empty() {
198 -
            continue;
199 -
        }
200 -
        let reply = match serde_json::from_str::<Value>(&line) {
201 -
            Ok(Value::Array(batch)) => {
202 -
                let replies: Vec<Value> = batch
203 -
                    .iter()
204 -
                    .filter_map(|m| handle(info, tools, m))
205 -
                    .collect();
206 -
                (!replies.is_empty()).then_some(Value::Array(replies))
207 -
            }
208 -
            Ok(message) => handle(info, tools, &message),
209 -
            Err(e) => Some(error(Value::Null, PARSE_ERROR, &format!("not JSON: {e}"))),
210 -
        };
211 -
        if let Some(reply) = reply {
212 -
            writeln!(output, "{reply}")?;
213 -
            output.flush()?;
214 -
        }
215 -
    }
216 -
    Ok(())
217 -
}
218 -
219 -
/// A string argument, if given and a string.
220 -
pub fn arg_str<'a>(args: &'a Value, key: &str) -> Option<&'a str> {
221 -
    args.get(key).and_then(Value::as_str)
222 -
}
223 -
224 -
/// A list of strings, if given; a single string counts as a list of one.
225 -
pub fn arg_strings(args: &Value, key: &str) -> Result<Vec<String>, String> {
226 -
    match args.get(key) {
227 -
        None | Some(Value::Null) => Ok(vec![]),
228 -
        Some(Value::String(s)) => Ok(vec![s.clone()]),
229 -
        Some(Value::Array(a)) => a
230 -
            .iter()
231 -
            .map(|v| {
232 -
                v.as_str()
233 -
                    .map(str::to_string)
234 -
                    .ok_or_else(|| format!("{key} must be a list of strings"))
235 -
            })
236 -
            .collect(),
237 -
        Some(_) => Err(format!("{key} must be a list of strings")),
238 -
    }
239 -
}
240 -
241 -
#[cfg(test)]
242 -
mod tests {
243 -
    use super::*;
244 -
245 -
    struct Echo;
246 -
247 -
    impl Tools for Echo {
248 -
        fn list(&self) -> Vec<Tool> {
249 -
            vec![Tool {
250 -
                name: "echo".into(),
251 -
                description: "says it back".into(),
252 -
                input_schema: json!({"type": "object"}),
253 -
                read_only: true,
254 -
                destructive: false,
255 -
            }]
256 -
        }
257 -
        fn call(&self, name: &str, args: &Value) -> Option<ToolOutput> {
258 -
            (name == "echo").then(|| ToolOutput {
259 -
                text: args.to_string(),
260 -
                structured: Some(json!([1, 2])),
261 -
                is_error: false,
262 -
            })
263 -
        }
264 -
    }
265 -
266 -
    fn info() -> ServerInfo {
267 -
        ServerInfo {
268 -
            name: "t".into(),
269 -
            version: "1".into(),
270 -
            instructions: "be careful".into(),
271 -
        }
272 -
    }
273 -
274 -
    fn talk(lines: &str) -> Vec<Value> {
275 -
        let mut out = Vec::new();
276 -
        serve(&info(), &Echo, lines.as_bytes(), &mut out).unwrap();
277 -
        String::from_utf8(out)
278 -
            .unwrap()
279 -
            .lines()
280 -
            .map(|l| serde_json::from_str(l).unwrap())
281 -
            .collect()
282 -
    }
283 -
284 -
    #[test]
285 -
    fn a_session_initializes_lists_and_calls() {
286 -
        let r = talk(concat!(
287 -
            r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"c","version":"0"}}}"#,
288 -
            "\n",
289 -
            r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#,
290 -
            "\n\n",
291 -
            r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#,
292 -
            "\n",
293 -
            r#"{"jsonrpc":"2.0","id":"three","method":"tools/call","params":{"name":"echo","arguments":{"a":1}}}"#,
294 -
            "\n",
295 -
        ));
296 -
        assert_eq!(r.len(), 3, "the notification gets no reply: {r:?}");
297 -
        assert_eq!(
298 -
            r[0]["result"]["protocolVersion"], "2025-03-26",
299 -
            "a version we speak is kept"
300 -
        );
301 -
        assert_eq!(r[0]["result"]["instructions"], "be careful");
302 -
        assert_eq!(r[1]["result"]["tools"][0]["name"], "echo");
303 -
        assert_eq!(
304 -
            r[1]["result"]["tools"][0]["annotations"]["readOnlyHint"],
305 -
            true
306 -
        );
307 -
        assert_eq!(r[2]["id"], "three");
308 -
        assert_eq!(r[2]["result"]["content"][0]["text"], r#"{"a":1}"#);
309 -
        assert_eq!(
310 -
            r[2]["result"]["structuredContent"],
311 -
            json!({"result": [1, 2]}),
312 -
            "an array is wrapped in an object"
313 -
        );
314 -
        assert_eq!(r[2]["result"]["isError"], false);
315 -
    }
316 -
317 -
    #[test]
318 -
    fn errors_are_protocol_errors_and_unknown_versions_get_the_newest() {
319 -
        let r = talk(concat!(
320 -
            "not json\n",
321 -
            r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"1999-01-01"}}"#,
322 -
            "\n",
323 -
            r#"{"jsonrpc":"2.0","id":2,"method":"resources/list"}"#,
324 -
            "\n",
325 -
            r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"nope"}}"#,
326 -
            "\n",
327 -
            r#"{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"echo","arguments":[1]}}"#,
328 -
            "\n",
329 -
            r#"[{"jsonrpc":"2.0","id":5,"method":"ping"},{"jsonrpc":"2.0","method":"notifications/cancelled"}]"#,
330 -
            "\n",
331 -
        ));
332 -
        assert_eq!(r[0]["error"]["code"], PARSE_ERROR);
333 -
        assert_eq!(r[1]["result"]["protocolVersion"], PROTOCOL_VERSIONS[0]);
334 -
        assert_eq!(r[2]["error"]["code"], METHOD_NOT_FOUND);
335 -
        assert_eq!(r[3]["error"]["code"], INVALID_PARAMS);
336 -
        assert_eq!(r[4]["error"]["code"], INVALID_PARAMS);
337 -
        assert_eq!(
338 -
            r[5],
339 -
            json!([{"jsonrpc": "2.0", "id": 5, "result": {}}]),
340 -
            "a batch is answered as a batch"
341 -
        );
342 -
    }
343 -
344 -
    #[test]
345 -
    fn string_arguments_and_lists() {
346 -
        let a = json!({"one": "x", "many": ["a", "b"], "bad": [1]});
347 -
        assert_eq!(arg_str(&a, "one"), Some("x"));
348 -
        assert_eq!(arg_strings(&a, "one").unwrap(), vec!["x"]);
349 -
        assert_eq!(arg_strings(&a, "many").unwrap(), vec!["a", "b"]);
350 -
        assert_eq!(arg_strings(&a, "missing").unwrap(), Vec::<String>::new());
351 -
        assert!(arg_strings(&a, "bad").is_err());
352 -
    }
353 -
}
modified crates/oxy-core/src/protocol.rs +5 −26
…
261 unchanged lines
262 262
    }
263 263
}
264 264
265 -
// Bundles a local remote served on this thread, for tests that count
266 -
// round trips (oxygen#158).
267 -
#[cfg(test)]
268 -
thread_local! {
269 -
    pub(crate) static LOCAL_FETCHES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
270 -
}
271 -
272 265
impl Remote for LocalRemote {
273 266
    fn repo_id(&self) -> Result<[u8; 16]> {
274 267
        self.repo.id()
…
5 unchanged lines
280 273
        self.repo.negotiate(have, want)
281 274
    }
282 275
    fn fetch(&self, changes: &[ChangeHash], max_blob: Option<u64>) -> Result<Bundle> {
283 -
        #[cfg(test)]
284 -
        LOCAL_FETCHES.with(|n| n.set(n.get() + 1));
285 276
        self.repo.bundle_raw_for(changes, max_blob)
286 277
    }
287 278
    fn objects(&self, refs: &[ObjectRef]) -> Result<Vec<(ObjectType, Vec<u8>)>> {
…
49 unchanged lines
337 328
    /// a temporary directory.
338 329
    pub cache_dir: Option<PathBuf>,
339 330
    /// Other tokens for the same hub, tried in turn when `token` is
340 -
    /// refused (401, 403 or 404): the credential store's sign-in when
341 -
    /// `token` is the remote's own (oxygen#155), or those of enclosing
342 -
    /// checkouts when it came from the credential store (oxygen#112).
331 +
    /// refused (401, 403 or 404): those of enclosing checkouts, when
332 +
    /// `token` came from the credential store rather than from this
333 +
    /// remote's own configuration (oxygen#112).
343 334
    pub fallback_tokens: Vec<String>,
344 -
    /// `token` is the remote's own: fall back only when the hub says it is
345 -
    /// not a token at all (HTTP 401: expired or unknown), never when it is
346 -
    /// one that may not do this (403, 404), so a narrowly scoped token is
347 -
    /// not quietly swapped for a wider sign-in (oxygen#155).
348 -
    pub fallback_on_401_only: bool,
349 335
}
350 336
351 337
/// Opens a store for a URL it understands, or passes (`None`). Stores that
…
65 unchanged lines
417 403
    /// The token in use, and those still to try when it is refused.
418 404
    token: Mutex<Option<String>>,
419 405
    fallbacks: Mutex<Vec<String>>,
420 -
    fallback_on_401_only: bool,
421 406
    repo_id: Option<[u8; 16]>,
422 407
    agent: ureq::Agent,
423 408
}
…
42 unchanged lines
466 451
            base: base.trim_end_matches('/').to_string(),
467 452
            token: Mutex::new(options.token.clone()),
468 453
            fallbacks: Mutex::new(options.fallback_tokens.iter().rev().cloned().collect()),
469 -
            fallback_on_401_only: options.fallback_on_401_only,
470 454
            repo_id: options.repo_id,
471 455
            agent,
472 456
        })
…
5 unchanged lines
478 462
        loop {
479 463
            let token = self.token.lock().unwrap_or_else(|p| p.into_inner()).clone();
480 464
            match self.call_once(post, endpoint, body, token.as_deref()) {
481 -
                Err(RepoError::Msg(m))
482 -
                    if token.is_some()
483 -
                        && refused(&m)
484 -
                        && (!self.fallback_on_401_only || m.contains(": HTTP 401")) =>
485 -
                {
465 +
                Err(RepoError::Msg(m)) if token.is_some() && refused(&m) => {
486 466
                    let next = self
487 467
                        .fallbacks
488 468
                        .lock()
…
3 unchanged lines
492 472
                        return Err(RepoError::Msg(m));
493 473
                    };
494 474
                    crate::credentials::warn_once(&format!(
495 -
                        "{}: the token in use was refused; trying another this computer has for the hub \
496 -
                         (a stale remote.NAME.token goes with `oxy config --unset remote.NAME.token`)",
475 +
                        "{}: the stored token was refused; using the one an enclosing checkout has for this hub",
497 476
                        self.base
498 477
                    ));
499 478
                    *self.token.lock().unwrap_or_else(|p| p.into_inner()) = Some(next);
…
1341 unchanged lines
modified crates/oxy-core/src/repo/bookmarks.rs +1 −1
…
497 unchanged lines
498 498
                .get(p)
499 499
                .and_then(|m| m.bookmark.clone());
500 500
            super::subrepos::read_pin(&self.root.join(p), hint.as_deref())
501 -
                .is_ok_and(|have| have.same_state(&want))
501 +
                .is_ok_and(|have| have == want)
502 502
        };
503 503
        touched.retain(|p| !at_target_pin(p));
504 504
        let same = |p: &String| {
…
216 unchanged lines
modified crates/oxy-core/src/repo/checkout.rs +2 −38
…
455 unchanged lines
456 456
        }
457 457
        self.write_manifest_entries(report.conflicted, false, &entries, &HashMap::new())?;
458 458
        if report.conflicted && !self.bare {
459 -
            report.kept_both = self.settle_conflicts(command)?;
459 +
            report.kept_both = self.settle_unions(command)?;
460 460
        }
461 461
        let _ = self.run_hook(
462 462
            "post-checkout",
…
62 unchanged lines
525 525
            }
526 526
            if i == 0 && *comp == OXY_DIR {
527 527
                return fail(format!("refusing to write inside {OXY_DIR}: {rel:?}"));
528 -
            }
529 -
            // A `.git` path could plant hooks a side-by-side Git would run;
530 -
            // Git refuses them at any depth and in any case, and so does oxy.
531 -
            if comp.eq_ignore_ascii_case(".git") {
532 -
                return fail(format!("refusing to write a .git path: {rel:?}"));
533 528
            }
534 529
            if i + 1 < comps.len() {
535 530
                full.push(comp);
…
65 unchanged lines
601 596
}
602 597
603 598
/// Refuse a path that could escape the workspace (spec §7.7): an empty,
604 -
/// `.` or `..` component, a backslash, a first component `.oxy`, or a
605 -
/// `.git` component anywhere (it could plant Git hooks).
599 +
/// `.` or `..` component, a backslash, or a first component `.oxy`.
606 600
fn check_path_name(rel: &str) -> Result<()> {
607 601
    for (i, comp) in rel.split('/').enumerate() {
608 602
        if comp.is_empty() || comp == "." || comp == ".." || comp.contains('\\') {
…
2 unchanged lines
611 605
        if i == 0 && comp == OXY_DIR {
612 606
            return fail(format!("refusing to write inside {OXY_DIR}: {rel:?}"));
613 607
        }
614 -
        if comp.eq_ignore_ascii_case(".git") {
615 -
            return fail(format!("refusing to write a .git path: {rel:?}"));
616 -
        }
617 608
    }
618 609
    Ok(())
619 610
}
…
36 unchanged lines
656 647
    }
657 648
    fs::write(full, bytes)?;
658 649
    set_executable(full, exec)
659 -
}
660 -
661 -
#[cfg(test)]
662 -
mod path_name_tests {
663 -
    use super::check_path_name;
664 -
665 -
    #[test]
666 -
    fn git_paths_are_refused_at_any_depth_and_case() {
667 -
        for bad in [
668 -
            ".git/hooks/post-checkout",
669 -
            "sub/.git/config",
670 -
            ".GIT/hooks/x",
671 -
            "a/.Git",
672 -
            ".oxy/x",
673 -
            "../x",
674 -
        ] {
675 -
            assert!(check_path_name(bad).is_err(), "{bad}");
676 -
        }
677 -
        for good in [
678 -
            "src/main.rs",
679 -
            ".gitignore",
680 -
            ".github/workflows/ci.yml",
681 -
            "a/.gitmodules",
682 -
        ] {
683 -
            assert!(check_path_name(good).is_ok(), "{good}");
684 -
        }
685 -
    }
686 650
}
modified crates/oxy-core/src/repo/config.rs +0 −6
…
45 unchanged lines
46 46
    /// `git.claim`: a Git import authors the commits whose Git author email
47 47
    /// is this repository's identity's with its key (oxygen#97).
48 48
    pub git_claim: bool,
49 -
    /// `git.colocate`: keep a `.git` beside `.oxy` with the current
50 -
    /// bookmark exported after every operation, so editors' Git features
51 -
    /// work on an Oxygen checkout (one way: oxy to Git).
52 -
    pub git_colocate: bool,
53 49
    /// `core.op-retention`: days for which operations keep their states reachable (spec §12.1, §21).
54 50
    pub op_retention_days: u64,
55 51
    /// `core.gc-grace`: days an unreachable object survives, protecting concurrent writers (spec §21).
…
56 unchanged lines
112 108
            remote_cas: BTreeMap::new(),
113 109
            default_remote: None,
114 110
            git_claim: false,
115 -
            git_colocate: false,
116 111
            op_retention_days: 90,
117 112
            gc_grace_days: 14,
118 113
            max_new_file: 100 << 20,
…
102 unchanged lines
221 216
            }
222 217
            "hooks.semantic-deps" => self.semantic_deps = matches!(value, "true" | "1" | "yes"),
223 218
            "git.claim" => self.git_claim = matches!(value, "true" | "1" | "yes"),
224 -
            "git.colocate" => self.git_colocate = matches!(value, "true" | "1" | "yes"),
225 219
            "filter.paths" => {
226 220
                self.sparse_paths = value
227 221
                    .split(',')
…
163 unchanged lines
modified crates/oxy-core/src/repo/fsck.rs +0 −1
…
102 unchanged lines
103 103
        ObjectType::Receipt => crate::receipt::Receipt::decode(body).map(|_| ()),
104 104
        ObjectType::Certificate => crate::cert::Certificate::decode(body).map(|_| ()),
105 105
        ObjectType::Grant => crate::cert::Grant::decode(body).map(|_| ()),
106 -
        ObjectType::Artifact => crate::artifact::Artifact::decode(body).map(|_| ()),
107 106
        ObjectType::State => crate::objects::StateObject::decode(body).map(|_| ()),
108 107
    };
109 108
    r.map_err(|e| e.to_string())
…
408 unchanged lines
modified crates/oxy-core/src/repo/git.rs +8 −358
…
1740 unchanged lines
1741 1741
    }
1742 1742
1743 1743
    fn git_author(&self, c: &Change) -> String {
1744 -
        self.git_author_key(&c.author)
1744 +
        match self.store.get_identity(&c.author) {
1745 +
            Ok(id) if !id.name.is_empty() => format!(
1746 +
                "{} <{}>",
1747 +
                id.name.replace(['<', '>', '\n'], " "),
1748 +
                id.email.replace(['<', '>', '\n', ' '], "")
1749 +
            ),
1750 +
            _ => "oxy <oxy@localhost>".to_string(),
1751 +
        }
1745 1752
    }
1746 1753
1747 1754
    #[allow(clippy::too_many_arguments)]
…
421 unchanged lines
2169 2176
        h.len(),
2170 2177
        w.len()
2171 2178
    )
2172 -
}
2173 -
2174 -
/// What `git_export_graph` wrote.
2175 -
#[derive(Debug, Clone, Default, PartialEq, Eq)]
2176 -
pub struct GitGraphReport {
2177 -
    /// Changes written as commits this time (the rest were exported before).
2178 -
    pub commits: usize,
2179 -
    /// Branches and tags pointed at their commits.
2180 -
    pub branches: usize,
2181 -
    pub tags: usize,
2182 -
    /// Bookmarks or tags whose names Git does not take as refs.
2183 -
    pub skipped_refs: Vec<String>,
2184 -
    /// Subrepo nodes with Oxygen pins, which Git cannot represent.
2185 -
    pub skipped_subrepos: usize,
2186 -
}
2187 -
2188 -
/// A bookmark or tag name Git takes as a ref without escaping.
2189 -
fn git_ref_ok(name: &str) -> bool {
2190 -
    !name.is_empty()
2191 -
        && !name.starts_with(['-', '/', '.'])
2192 -
        && !name.ends_with(['/', '.'])
2193 -
        && !name.contains("..")
2194 -
        && !name.contains("//")
2195 -
        && !name.ends_with(".lock")
2196 -
        && name
2197 -
            .bytes()
2198 -
            .all(|b| b.is_ascii_alphanumeric() || b"-_./+".contains(&b))
2199 -
}
2200 -
2201 -
impl Repo {
2202 -
    /// Export every bookmark and tag to the bare Git repository at
2203 -
    /// `git_dir` from the change graph (spec §23, oxygenhub D1), for a hub
2204 -
    /// to serve to Git clients. Unlike `git_export`, which follows one
2205 -
    /// checkout's operation log, this depends only on the changes, so the
2206 -
    /// same changes give the same Git commits wherever it runs:
2207 -
    ///
2208 -
    /// - each change is a commit whose tree is the state of the change and
2209 -
    ///   everything it depends on, whose parents are its dependencies'
2210 -
    ///   commits, with the change's author, time and message and its
2211 -
    ///   `Change-Id:` and `Oxy-Hash:` trailers (a change that depends on
2212 -
    ///   nothing is a root, so Git sees Oxygen's dependencies, not the order
2213 -
    ///   changes were made in);
2214 -
    /// - a bookmark with one head is a branch at that head's commit; with
2215 -
    ///   several, a merge of their commits whose tree is the bookmark's
2216 -
    ///   state, at the newest head's time;
2217 -
    /// - a tag points at its heads' commit, or such a merge.
2218 -
    ///
2219 -
    /// Commits already exported are remembered (marks), so a later run
2220 -
    /// writes only new changes. Branches for bookmarks that are gone are
2221 -
    /// deleted.
2222 -
    pub fn git_export_graph(&self, git_dir: &Path) -> Result<GitGraphReport> {
2223 -
        if !git_dir.join("HEAD").exists() {
2224 -
            fs::create_dir_all(git_dir)?;
2225 -
            git(git_dir, &["init", "--bare", "-q"])?;
2226 -
        }
2227 -
        let marks_dir = self.git_marks_dir(git_dir)?;
2228 -
        let git_marks = marks_dir.join("git-marks");
2229 -
        let graph_marks = marks_dir.join("graph-marks");
2230 -
        let mut marks = Self::read_oxy_marks(&graph_marks)?;
2231 -
        let mut next = marks.values().copied().max().unwrap_or(0) + 1;
2232 -
        let mut report = GitGraphReport::default();
2233 -
        let mut stream: Vec<u8> = Vec::new();
2234 -
2235 -
        let mut refs: Vec<(String, Vec<ChangeHash>)> = Vec::new();
2236 -
        for b in self.bookmarks()? {
2237 -
            if git_ref_ok(&b.name) {
2238 -
                refs.push((format!("refs/heads/{}", b.name), b.heads));
2239 -
            } else {
2240 -
                report.skipped_refs.push(b.name);
2241 -
            }
2242 -
        }
2243 -
        let tags = self.tags()?;
2244 -
        for t in &tags {
2245 -
            if git_ref_ok(&t.name) {
2246 -
                refs.push((format!("refs/tags/{}", t.name), t.heads.clone()));
2247 -
            } else {
2248 -
                report.skipped_refs.push(t.name.clone());
2249 -
            }
2250 -
        }
2251 -
2252 -
        // Every change the refs reach, dependencies before dependents: a
2253 -
        // depth-first walk in hash order, so the order is the same everywhere.
2254 -
        let mut order: Vec<ChangeHash> = Vec::new();
2255 -
        let mut seen: HashSet<ChangeHash> = HashSet::new();
2256 -
        let mut metas: HashMap<ChangeHash, crate::state::ChangeMeta> = HashMap::new();
2257 -
        let mut roots: Vec<ChangeHash> = refs.iter().flat_map(|(_, h)| h.iter().copied()).collect();
2258 -
        roots.sort();
2259 -
        roots.dedup();
2260 -
        for root in roots {
2261 -
            if !seen.insert(root) {
2262 -
                continue;
2263 -
            }
2264 -
            let mut stack: Vec<(ChangeHash, bool)> = vec![(root, false)];
2265 -
            while let Some((h, done)) = stack.pop() {
2266 -
                if done {
2267 -
                    order.push(h);
2268 -
                    continue;
2269 -
                }
2270 -
                stack.push((h, true));
2271 -
                let meta = self.store.get_change_meta(&h)?;
2272 -
                let mut deps = meta.deps.clone();
2273 -
                deps.sort();
2274 -
                for d in deps.into_iter().rev() {
2275 -
                    if seen.insert(d) {
2276 -
                        stack.push((d, false));
2277 -
                    }
2278 -
                }
2279 -
                metas.insert(h, meta);
2280 -
            }
2281 -
        }
2282 -
2283 -
        for h in &order {
2284 -
            if marks.contains_key(h) {
2285 -
                continue;
2286 -
            }
2287 -
            let meta = &metas[h];
2288 -
            let snapshot = self.load_state(&[*h])?.materialize();
2289 -
            let mut message = meta.message.clone();
2290 -
            if !message.ends_with('\n') {
2291 -
                message.push('\n');
2292 -
            }
2293 -
            message.push_str(&format!(
2294 -
                "\nChange-Id: {}\nOxy-Hash: {}\n",
2295 -
                meta.change_id.to_hex(),
2296 -
                h.to_hex()
2297 -
            ));
2298 -
            if snapshot.is_conflicted() {
2299 -
                message.push_str("Oxy-Conflicted: true\n");
2300 -
            }
2301 -
            let mut deps = meta.deps.clone();
2302 -
            deps.sort();
2303 -
            let parents: Vec<String> = deps
2304 -
                .iter()
2305 -
                .filter_map(|d| marks.get(d))
2306 -
                .map(|m| format!(":{m}"))
2307 -
                .collect();
2308 -
            let mark = next;
2309 -
            next += 1;
2310 -
            if parents.is_empty() {
2311 -
                // Without `from`, fast-import would make the scratch ref's
2312 -
                // tip its parent; a change that depends on nothing is a root.
2313 -
                stream.extend_from_slice(b"reset refs/oxy/export\n\n");
2314 -
            }
2315 -
            self.write_commit(
2316 -
                &mut stream,
2317 -
                "refs/oxy/export",
2318 -
                mark,
2319 -
                &self.git_author_key(&meta.author),
2320 -
                meta.hlc.wall_ms / 1000,
2321 -
                &message,
2322 -
                &parents,
2323 -
                &snapshot,
2324 -
            )?;
2325 -
            marks.insert(*h, mark);
2326 -
            report.commits += 1;
2327 -
        }
2328 -
2329 -
        let mut wanted: HashSet<String> = HashSet::new();
2330 -
        for (name, heads) in &refs {
2331 -
            let mut heads = heads.clone();
2332 -
            heads.sort();
2333 -
            heads.dedup();
2334 -
            let target = match heads.as_slice() {
2335 -
                [] => continue,
2336 -
                [one] => format!(":{}", marks[one]),
2337 -
                many => {
2338 -
                    // A merge of the heads, named by them so it is made once.
2339 -
                    let key = hash_object(
2340 -
                        ObjectType::Chunk,
2341 -
                        many.iter()
2342 -
                            .map(|h| h.to_hex())
2343 -
                            .collect::<Vec<_>>()
2344 -
                            .join(" ")
2345 -
                            .as_bytes(),
2346 -
                    );
2347 -
                    if let Some(m) = marks.get(&key) {
2348 -
                        format!(":{m}")
2349 -
                    } else {
2350 -
                        let snapshot = self.load_state(many)?.materialize();
2351 -
                        let newest = many.iter().map(|h| metas[h].hlc.wall_ms).max().unwrap_or(0);
2352 -
                        let mut message = format!(
2353 -
                            "Merge of {} heads\n\nOxy-Heads: {}\n",
2354 -
                            many.len(),
2355 -
                            many.iter()
2356 -
                                .map(|h| h.to_hex())
2357 -
                                .collect::<Vec<_>>()
2358 -
                                .join(" ")
2359 -
                        );
2360 -
                        if snapshot.is_conflicted() {
2361 -
                            message.push_str("Oxy-Conflicted: true\n");
2362 -
                        }
2363 -
                        let parents: Vec<String> =
2364 -
                            many.iter().map(|h| format!(":{}", marks[h])).collect();
2365 -
                        let mark = next;
2366 -
                        next += 1;
2367 -
                        self.write_commit(
2368 -
                            &mut stream,
2369 -
                            "refs/oxy/export",
2370 -
                            mark,
2371 -
                            "oxy <oxy@localhost>",
2372 -
                            newest / 1000,
2373 -
                            &message,
2374 -
                            &parents,
2375 -
                            &snapshot,
2376 -
                        )?;
2377 -
                        marks.insert(key, mark);
2378 -
                        format!(":{mark}")
2379 -
                    }
2380 -
                }
2381 -
            };
2382 -
            stream.extend_from_slice(format!("reset {name}\nfrom {target}\n\n").as_bytes());
2383 -
            wanted.insert(name.clone());
2384 -
            if name.starts_with("refs/tags/") {
2385 -
                report.tags += 1;
2386 -
            } else {
2387 -
                report.branches += 1;
2388 -
            }
2389 -
        }
2390 -
        stream.extend_from_slice(b"done\n");
2391 -
        run_fast_import(git_dir, &stream, &git_marks)?;
2392 -
        let text: String = marks
2393 -
            .iter()
2394 -
            .map(|(h, m)| format!("{} {m}\n", h.to_hex()))
2395 -
            .collect();
2396 -
        write_atomic(&graph_marks, text.as_bytes())?;
2397 -
        let _ = git(git_dir, &["update-ref", "-d", "refs/oxy/export"]);
2398 -
        // Branches and tags of bookmarks and tags that are gone.
2399 -
        let listed = git(
2400 -
            git_dir,
2401 -
            &[
2402 -
                "for-each-ref",
2403 -
                "--format=%(refname)",
2404 -
                "refs/heads",
2405 -
                "refs/tags",
2406 -
            ],
2407 -
        )?;
2408 -
        for r in listed
2409 -
            .lines()
2410 -
            .map(str::trim)
2411 -
            .filter(|r| !r.is_empty() && !wanted.contains(*r))
2412 -
        {
2413 -
            git(git_dir, &["update-ref", "-d", r])?;
2414 -
        }
2415 -
        // HEAD names the main bookmark when there is one, so a clone checks it out.
2416 -
        let main = ["main", "master", "trunk", "develop"]
2417 -
            .into_iter()
2418 -
            .find(|b| wanted.contains(&format!("refs/heads/{b}")));
2419 -
        if let Some(b) = main {
2420 -
            git(
2421 -
                git_dir,
2422 -
                &["symbolic-ref", "HEAD", &format!("refs/heads/{b}")],
2423 -
            )?;
2424 -
        }
2425 -
        report.skipped_subrepos = self.git_export_skipped.replace(0);
2426 -
        Ok(report)
2427 -
    }
2428 -
2429 -
    /// `Name <email>` for a Git commit, from the author's identity.
2430 -
    fn git_author_key(&self, key: &KeyId) -> String {
2431 -
        match self.store.get_identity(key) {
2432 -
            Ok(id) if !id.name.is_empty() => format!(
2433 -
                "{} <{}>",
2434 -
                id.name.replace(['<', '>', '\n'], " "),
2435 -
                id.email.replace(['<', '>', '\n', ' '], "")
2436 -
            ),
2437 -
            _ => "oxy <oxy@localhost>".to_string(),
2438 -
        }
2439 -
    }
2440 -
}
2441 -
2442 -
/// Feed a fast-import stream to `git fast-import` in `git_dir`, keeping its
2443 -
/// marks in `git_marks` so a later run can refer to commits it wrote.
2444 -
fn run_fast_import(git_dir: &Path, stream: &[u8], git_marks: &Path) -> Result<()> {
2445 -
    let mut cmd = Command::new("git");
2446 -
    // `--force`: a ref may move to an unrelated commit (a bookmark rewritten).
2447 -
    cmd.arg("-C")
2448 -
        .arg(git_dir)
2449 -
        .args(["fast-import", "--quiet", "--done", "--force"])
2450 -
        .arg(format!("--export-marks={}", git_marks.display()));
2451 -
    if git_marks.exists() {
2452 -
        cmd.arg(format!("--import-marks={}", git_marks.display()));
2453 -
    }
2454 -
    let mut child = cmd
2455 -
        .stdin(Stdio::piped())
2456 -
        .stdout(Stdio::null())
2457 -
        .stderr(Stdio::piped())
2458 -
        .spawn()
2459 -
        .map_err(|e| RepoError::Msg(format!("running git fast-import: {e}")))?;
2460 -
    child.stdin.take().unwrap().write_all(stream)?;
2461 -
    let out = child.wait_with_output()?;
2462 -
    if !out.status.success() {
2463 -
        return fail(format!(
2464 -
            "git fast-import failed: {}",
2465 -
            String::from_utf8_lossy(&out.stderr).trim()
2466 -
        ));
2467 -
    }
2468 -
    Ok(())
2469 -
}
2470 -
2471 -
impl Repo {
2472 -
    /// Bring the side-by-side Git repository up to date (`git.colocate`):
2473 -
    /// a `.git` beside `.oxy`, made with `git init` when missing and told to
2474 -
    /// leave `.oxy/` alone, holding the current bookmark as exported by
2475 -
    /// `git_export_as` (one commit per state the bookmark passed through),
2476 -
    /// with HEAD on that branch and Git's index at its tree, so `git status`
2477 -
    /// shows only the working copy's uncommitted work. Files on disk are
2478 -
    /// never touched. Does nothing when the bookmark has not moved since the
2479 -
    /// last time; `Ok(true)` when it exported.
2480 -
    pub fn git_colocate_sync(&self) -> Result<bool> {
2481 -
        if self.bare {
2482 -
            return fail("a bare repository has no working copy for a .git beside it");
2483 -
        }
2484 -
        let git_dir = self.root.join(".git");
2485 -
        if git_dir.is_file() {
2486 -
            return fail(".git is a file (a Git worktree or submodule link); remove it to keep a side-by-side Git repository here");
2487 -
        }
2488 -
        if !git_dir.join("HEAD").exists() {
2489 -
            git(&self.root, &["init", "-q"])?;
2490 -
        }
2491 -
        let exclude = git_dir.join("info").join("exclude");
2492 -
        let have = fs::read_to_string(&exclude).unwrap_or_default();
2493 -
        if !have.lines().any(|l| l.trim() == "/.oxy/") {
2494 -
            fs::create_dir_all(exclude.parent().expect("info/"))?;
2495 -
            let sep = if have.is_empty() || have.ends_with('\n') {
2496 -
                ""
2497 -
            } else {
2498 -
                "\n"
2499 -
            };
2500 -
            fs::write(
2501 -
                &exclude,
2502 -
                format!("{have}{sep}# Oxygen's own repository (oxy git colocate)\n/.oxy/\n"),
2503 -
            )?;
2504 -
        }
2505 -
        let bookmark = self.config.bookmark.clone();
2506 -
        let state = self
2507 -
            .view()?
2508 -
            .bookmarks
2509 -
            .get(&bookmark)
2510 -
            .map(|s| s.to_hex())
2511 -
            .unwrap_or_default();
2512 -
        let stamp_file = self.oxy.join("git").join("colocate-stamp");
2513 -
        let stamp = format!("{bookmark} {state}");
2514 -
        if fs::read_to_string(&stamp_file).is_ok_and(|s| s.trim() == stamp) {
2515 -
            return Ok(false);
2516 -
        }
2517 -
        if !state.is_empty() {
2518 -
            self.git_export_as(&git_dir, &bookmark)?;
2519 -
            git(
2520 -
                &self.root,
2521 -
                &["symbolic-ref", "HEAD", &format!("refs/heads/{bookmark}")],
2522 -
            )?;
2523 -
            git(&self.root, &["read-tree", "HEAD"])?;
2524 -
        }
2525 -
        fs::create_dir_all(stamp_file.parent().expect("git/"))?;
2526 -
        fs::write(&stamp_file, stamp)?;
2527 -
        Ok(!state.is_empty())
2528 -
    }
2529 2179
}
modified crates/oxy-core/src/repo/identity.rs +0 −100
…
17 unchanged lines
18 18
    Invalid(String),
19 19
}
20 20
21 -
/// Make a user identity with no repository (oxygen#144), so a first
22 -
/// identity comes before any repository: its secret and a copy of it go to
23 -
/// this home's keys, and the user file names it as the author, as `oxy
24 -
/// identity create` does inside one. A repository reads it from the keys
25 -
/// when it first signs with it. `name` and `email` default to the user
26 -
/// file's author, then to `$USER` and `OXY_AUTHOR_*`.
27 -
pub fn create_user_identity(
28 -
    name: Option<&str>,
29 -
    email: Option<&str>,
30 -
    expires: Option<u64>,
31 -
) -> Result<(KeyId, Identity)> {
32 -
    let user = default_user_config();
33 -
    let defaults = Config::load(Some(&user), Path::new(""))?;
34 -
    let name = name.map(str::to_string).unwrap_or(defaults.author_name);
35 -
    let email = email.map(str::to_string).unwrap_or(defaults.author_email);
36 -
    let sk = SecretKey::generate();
37 -
    let mut id = sk.identity(&name, &email, Hlc::now().wall_ms);
38 -
    id.expires = expires;
39 -
    let key = id.hash();
40 -
    let keys = default_keys_dir();
41 -
    fs::create_dir_all(&keys)?;
42 -
    crate::durable::write_atomic_private(
43 -
        &keys.join(format!("{}.secret", key.to_hex())),
44 -
        &sk.encode(&key),
45 -
    )?;
46 -
    write_atomic(
47 -
        &keys.join(format!("{}.identity", key.to_hex())),
48 -
        &id.encode(),
49 -
    )?;
50 -
    Config::set_user(&user, "author.name", &id.name)?;
51 -
    Config::set_user(&user, "author.email", &id.email)?;
52 -
    Config::set_user(&user, "author.key", &key.to_hex())?;
53 -
    Ok((key, id))
54 -
}
55 -
56 21
/// The outcome of replacing the configured identity (spec §14.2).
57 22
#[derive(Clone, Debug)]
58 23
pub struct Rotation {
…
112 unchanged lines
171 136
        };
172 137
        if new.previous != Some(old) {
173 138
            return fail("the successor identity must name the current key as previous");
174 -
        }
175 -
        // A key that already has a successor is not the identity's newest:
176 -
        // building another from it forks the identity into two branches
177 -
        // (oxygen#163), as a checkout whose stale `author.key` pin outlived
178 -
        // a rotation made elsewhere once did.
179 -
        if let Some(newer) = self.successors(&old)?.into_iter().next() {
180 -
            let newest = self.newest(&newer)?;
181 -
            let fix = if self.config.is_repo_set("author.key") {
182 -
                format!(
183 -
                    "This repository's config pins author.key to the older key: `oxy config author.key {}` here \
184 -
                     (or `oxy config --unset author.key` to follow the user config), then run it again.",
185 -
                    newest.to_hex()
186 -
                )
187 -
            } else {
188 -
                format!(
189 -
                    "The user config names the older key: `oxy config --user author.key {}`, then run it again.",
190 -
                    newest.to_hex()
191 -
                )
192 -
            };
193 -
            return fail(format!(
194 -
                "`{command}` would start from key {}, which is not your identity's newest ({} succeeds it, and {} is the newest). \
195 -
                 Another successor would fork the identity into two branches.\n{fix}",
196 -
                old.short(12),
197 -
                newer.short(12),
198 -
                newest.short(12),
199 -
            ));
200 139
        }
201 140
        let new_key = self.install_key(&new, &sk)?;
202 141
        let attest = Attest {
…
19 unchanged lines
222 161
            identity: new,
223 162
            policy,
224 163
        })
225 -
    }
226 -
227 -
    /// Keys in this home whose identity names `key` as `previous`: the
228 -
    /// successors already made from it. More than one means a fork.
229 -
    pub fn successors(&self, key: &KeyId) -> Result<Vec<KeyId>> {
230 -
        let mut found = Vec::new();
231 -
        let entries = match fs::read_dir(&self.keys_dir) {
232 -
            Ok(e) => e,
233 -
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(found),
234 -
            Err(e) => return Err(e.into()),
235 -
        };
236 -
        for entry in entries {
237 -
            let path = entry?.path();
238 -
            if path.extension().and_then(|e| e.to_str()) != Some("identity") {
239 -
                continue;
240 -
            }
241 -
            let Ok(bytes) = fs::read(&path) else { continue };
242 -
            let Ok(id) = Identity::decode(&bytes) else {
243 -
                continue;
244 -
            };
245 -
            if id.previous == Some(*key) {
246 -
                found.push(id.hash());
247 -
            }
248 -
        }
249 -
        found.sort();
250 -
        Ok(found)
251 -
    }
252 -
253 -
    /// The newest key of the line through `key` in this home: its
254 -
    /// successors followed to the end (the first, where there is a fork).
255 -
    pub fn newest(&self, key: &KeyId) -> Result<KeyId> {
256 -
        let mut cur = *key;
257 -
        for _ in 0..256 {
258 -
            match self.successors(&cur)?.into_iter().next() {
259 -
                Some(next) => cur = next,
260 -
                None => break,
261 -
            }
262 -
        }
263 -
        Ok(cur)
264 164
    }
265 165
266 166
    /// Rotate to a fresh key pair; the new identity names the old one as
…
613 unchanged lines
modified crates/oxy-core/src/repo/mod.rs +10 −37
…
142 unchanged lines
143 143
pub use git::{GitExportReport, GitImportReport, GitVerifyReport};
144 144
pub use hooks::{json_array, json_str, HookOutcome};
145 145
pub use hosted::FileEdit;
146 -
pub use identity::{create_user_identity, Rotation, SignatureStatus};
146 +
pub use identity::{Rotation, SignatureStatus};
147 147
pub use inspect::{BisectStep, BlameLine, Description};
148 148
pub use keyless::home_key;
149 149
pub use ops::{RestoreReport, RevertReport, Undone, ViewChange};
…
549 unchanged lines
699 699
700 700
    pub fn root(&self) -> &Path {
701 701
        &self.root
702 -
    }
703 -
    /// The repository's `.oxy` directory (shared by its workspaces), for
704 -
    /// what other crates keep beside the store (Cylinder's artifacts).
705 -
    pub fn oxy_dir(&self) -> &Path {
706 -
        &self.oxy
707 702
    }
708 703
    pub fn store(&self) -> &Store {
709 704
        &self.store
…
1517 unchanged lines
2227 2222
        // already tracked as subrepo nodes, they contribute their pin;
2228 2223
        // unregistered ones are left alone.
2229 2224
        let modules = self.modules_on_disk();
2230 -
        // A subrepo at the state its recorded pin names is unmodified,
2231 -
        // whatever bookmark it sits on (#147): the recorded pin stands.
2232 -
        let subrepo_entry = |rel: &str, pin: crate::subrepo::Pin| -> FsEntry {
2233 -
            if let Some((NodeKind::Subrepo, Some(b))) = tracked.get(rel) {
2234 -
                let recorded = self
2235 -
                    .read_blob(b)
2236 -
                    .ok()
2237 -
                    .and_then(|x| crate::subrepo::Pin::decode(&x).ok());
2238 -
                if recorded.is_some_and(|r| r.same_state(&pin)) {
2239 -
                    return FsEntry {
2240 -
                        blob: Some(*b),
2241 -
                        ..FsEntry::new(NodeKind::Subrepo, false, vec![])
2242 -
                    };
2243 -
                }
2244 -
            }
2245 -
            FsEntry::new(NodeKind::Subrepo, false, pin.encode())
2246 -
        };
2247 -
        // A removal's marker is spent once the removal is recorded (no
2248 -
        // subrepo node is tracked there) or the path is registered again:
2249 -
        // dropped before it is read, or the change registering it again
2250 -
        // would leave it out (oxygen#152).
2251 -
        let mut removed = self.removed_subrepos();
2252 -
        removed.retain(|rel| {
2253 -
            let spent = tracked.get(rel).map(|(k, _)| *k) != Some(NodeKind::Subrepo)
2254 -
                || modules.entries.contains_key(rel);
2255 -
            if spent {
2225 +
        let removed = self.removed_subrepos();
2226 +
        for rel in &removed {
2227 +
            if tracked.get(rel).map(|(k, _)| *k) != Some(NodeKind::Subrepo) {
2256 2228
                self.clear_removed_subrepo(rel);
2257 2229
            }
2258 -
            !spent
2259 -
        });
2230 +
        }
2260 2231
        let mut untracked_nested = Vec::new();
2261 2232
        for dir in nested {
2262 2233
            let rel = self.rel_path(&dir);
…
7 unchanged lines
2270 2241
                continue;
2271 2242
            }
2272 2243
            let pin = subrepos::read_pin(&dir, registered.and_then(|m| m.bookmark.as_deref()))?;
2273 -
            let entry = subrepo_entry(&rel, pin);
2274 -
            out.insert(rel, entry);
2244 +
            out.insert(rel, FsEntry::new(NodeKind::Subrepo, false, pin.encode()));
2275 2245
        }
2276 2246
        untracked_nested.sort();
2277 2247
        *self.untracked_nested.borrow_mut() = untracked_nested;
…
14 unchanged lines
2292 2262
                        &path,
2293 2263
                        modules.entries.get(rel).and_then(|m| m.bookmark.as_deref()),
2294 2264
                    )?;
2295 -
                    out.insert(rel.clone(), subrepo_entry(rel, pin));
2265 +
                    out.insert(
2266 +
                        rel.clone(),
2267 +
                        FsEntry::new(NodeKind::Subrepo, false, pin.encode()),
2268 +
                    );
2296 2269
                } else if let Some(b) = blob {
2297 2270
                    // Not cloned (or emptied): the pin stays as recorded, as a
2298 2271
                    // Git gitlink does when its directory is absent.
…
650 unchanged lines
modified crates/oxy-core/src/repo/snapshot.rs +37 −80
…
116 unchanged lines
117 117
    pub path: String,
118 118
    /// The conflicts settled by keeping every side, by number.
119 119
    pub settled: Vec<usize>,
120 -
    /// Of those, the ones whose sides were identical, settled by keeping
121 -
    /// one (oxygen#146).
122 -
    pub identical: Vec<usize>,
123 120
    /// The conflicts that keep their markers, by number, and why.
124 121
    pub left: Vec<(usize, &'static str)>,
125 122
}
…
4 unchanged lines
130 127
pub(crate) fn keep_both_text(
131 128
    text: &[u8],
132 129
    sides: &[Option<Vec<Vec<u8>>>],
133 -
    reverse: bool,
134 -
) -> (Vec<u8>, Vec<usize>, Vec<(usize, &'static str)>) {
135 -
    let plans: Vec<Option<crate::state::Settle>> = sides
136 -
        .iter()
137 -
        .map(|s| {
138 -
            s.as_ref().map(|w| crate::state::Settle {
139 -
                sides: w.clone(),
140 -
                kept: w.clone(),
141 -
                identical: false,
142 -
            })
143 -
        })
144 -
        .collect();
145 -
    settle_text(text, &plans, reverse)
146 -
}
147 -
148 -
/// `text` (a working-copy file) with the marker block of each conflict
149 -
/// that `plans` (from [`State::settle_plans`]) settle replaced by its
150 -
/// kept text, the sides in order (`reverse`d on request); the conflicts
151 -
/// settled, and those left and why. A block whose text between the
152 -
/// markers is not its sides' keeps its markers.
153 -
pub(crate) fn settle_text(
154 -
    text: &[u8],
155 -
    plans: &[Option<crate::state::Settle>],
156 130
    reverse: bool,
157 131
) -> (Vec<u8>, Vec<usize>, Vec<(usize, &'static str)>) {
158 132
    // A side as the markers hold it: the renderer ends each with a newline.
…
31 unchanged lines
190 164
                found.last_mut().unwrap().extend_from_slice(line);
191 165
            }
192 166
        }
193 -
        let wanted = match plans.get(n.wrapping_sub(1)) {
194 -
            _ if total != plans.len() => {
167 +
        let wanted = match sides.get(n.wrapping_sub(1)) {
168 +
            _ if total != sides.len() => {
195 169
                Err("the markers are not the file's conflicts now; see `oxy status`")
196 170
            }
197 171
            Some(Some(w)) => Ok(w),
198 172
            _ => Err("both sides changed these lines; edit the markers"),
199 173
        };
200 174
        let keep = wanted.and_then(|w| {
201 -
            let mut a: Vec<Vec<u8>> = w.sides.iter().map(|s| with_newline(s)).collect();
175 +
            let mut a: Vec<Vec<u8>> = w.iter().map(|s| with_newline(s)).collect();
202 176
            let mut b = found.clone();
203 177
            a.sort();
204 178
            b.sort();
205 179
            if a == b {
206 -
                Ok(&w.kept)
180 +
                Ok(w)
207 181
            } else {
208 182
                Err("the lines between its markers were edited; finish it by hand")
209 183
            }
…
427 unchanged lines
637 611
            out.push(KeptBoth {
638 612
                path: p,
639 613
                settled,
640 -
                identical: Vec::new(),
641 614
                left,
642 615
            });
643 616
        }
644 617
        Ok(out)
645 618
    }
646 619
647 -
    /// Settle what a checkout may settle by itself in each conflicted text
648 -
    /// file ([`State::settle_plans`]): conflicts whose sides are identical,
649 -
    /// keeping one (oxygen#146), and in files whose `.oxyattributes` say
650 -
    /// `merge=union`, adjacent insertions, keeping both sides earliest
651 -
    /// change first (oxygen#145). The next commit records the resolution.
652 -
    /// The files it settled something in.
653 -
    pub(crate) fn settle_conflicts(&self, command: &str) -> Result<Vec<KeptBoth>> {
620 +
    /// Settle the adjacent insertions of each conflicted text file whose
621 +
    /// `.oxyattributes` say `merge=union` (oxygen#145) by keeping both
622 +
    /// sides, earliest change first, as [`Repo::keep_both`] does: a
623 +
    /// checkout calls it, and the next commit records the resolution. The
624 +
    /// files it settled something in.
625 +
    pub(crate) fn settle_unions(&self, command: &str) -> Result<Vec<KeptBoth>> {
654 626
        let snap = self.snapshot(command)?;
655 -
        let mut out = Vec::new();
656 -
        for (p, f) in &snap.base_snapshot.files {
657 -
            if !matches!(&f.content, FileContent::Text { materialized, .. } if materialized.is_conflicted())
658 -
            {
659 -
                continue;
660 -
            }
661 -
            let plans = snap
662 -
                .base
663 -
                .settle_plans(&f.node, snap.attributes.lookup(p).union);
664 -
            if plans.iter().all(Option::is_none) {
665 -
                continue;
666 -
            }
667 -
            let disk = self.root().join(p);
668 -
            let Ok(text) = fs::read(&disk) else { continue };
669 -
            let (bytes, settled, left) = settle_text(&text, &plans, false);
670 -
            if settled.is_empty() {
671 -
                continue;
672 -
            }
673 -
            fs::write(&disk, bytes)?;
674 -
            let identical = settled
675 -
                .iter()
676 -
                .copied()
677 -
                .filter(|n| plans[n - 1].as_ref().is_some_and(|w| w.identical))
678 -
                .collect();
679 -
            out.push(KeptBoth {
680 -
                path: p.clone(),
681 -
                settled,
682 -
                identical,
683 -
                left,
684 -
            });
627 +
        let paths: Vec<String> = snap
628 +
            .base_snapshot
629 +
            .files
630 +
            .iter()
631 +
            .filter(|(p, f)| {
632 +
                matches!(&f.content, FileContent::Text { materialized, .. } if materialized.is_conflicted())
633 +
                    && snap.attributes.lookup(p).union
634 +
            })
635 +
            .map(|(p, _)| p.clone())
636 +
            .collect();
637 +
        if paths.is_empty() {
638 +
            return Ok(Vec::new());
685 639
        }
686 -
        Ok(out)
640 +
        let kept = self.keep_both(&snap, &paths, false)?;
641 +
        Ok(kept.into_iter().filter(|k| !k.settled.is_empty()).collect())
687 642
    }
688 643
689 -
    /// Every conflict of `snap`, `state`'s materialization, settled as a
690 -
    /// checkout settles them ([`State::settle_plans`]): the new text of
691 -
    /// each conflicted file, when each is a text file whose every conflict
692 -
    /// has identical sides (one kept, oxygen#146) or, in a file its
693 -
    /// `.oxyattributes` mark `merge=union`, is an adjacent insertion (both
694 -
    /// kept, oxygen#145); `None` when any conflict needs a person, or there
695 -
    /// is none. A host lands a proposal with these as a resolution, as a
696 -
    /// checkout settles them in a working copy.
644 +
    /// Every conflict of `snap`, `state`'s materialization, settled by
645 +
    /// keeping both sides (oxygen#145): the new text of each conflicted
646 +
    /// file, when each is a text file its `.oxyattributes` mark
647 +
    /// `merge=union` and each of its conflicts is an adjacent insertion;
648 +
    /// `None` when any conflict needs a person, or there is none. A host
649 +
    /// lands a proposal with these as a resolution, as a checkout settles
650 +
    /// them in a working copy.
697 651
    pub fn union_resolution(state: &State, snap: &Snapshot) -> Option<Vec<(String, Vec<u8>)>> {
698 652
        if snap.tree.is_conflicted() {
699 653
            return None;
…
7 unchanged lines
707 661
            let FileContent::Text { bytes, .. } = &f.content else {
708 662
                return None;
709 663
            };
710 -
            let plans = state.settle_plans(&f.node, attrs.lookup(p).union);
711 -
            let (text, _, left) = settle_text(bytes, &plans, false);
712 -
            if !left.is_empty() || plans.iter().any(Option::is_none) {
664 +
            if !attrs.lookup(p).union {
665 +
                return None;
666 +
            }
667 +
            let sides = state.both_sides(&f.node);
668 +
            let (text, _, left) = keep_both_text(bytes, &sides, false);
669 +
            if !left.is_empty() || sides.iter().any(Option::is_none) {
713 670
                return None;
714 671
            }
715 672
            out.push((p.clone(), text));
…
2916 unchanged lines
modified crates/oxy-core/src/repo/subrepos.rs +10 −1
…
181 unchanged lines
182 182
            }
183 183
        }
184 184
        let actual = read_pin(&dir, module.as_ref().and_then(|m| m.bookmark.as_deref()))?;
185 -
        let same = pin.same_state(&actual);
185 +
        let same = match (pin, &actual) {
186 +
            (Pin::Oxy { heads: a, .. }, Pin::Oxy { heads: b, .. }) => {
187 +
                let (mut a, mut b) = (a.clone(), b.clone());
188 +
                a.sort();
189 +
                b.sort();
190 +
                a == b
191 +
            }
192 +
            (Pin::Git { sha: a, .. }, Pin::Git { sha: b, .. }) => a == b,
193 +
            _ => false,
194 +
        };
186 195
        if same {
187 196
            return Ok(if cloned {
188 197
                SubrepoSync::Updated
…
242 unchanged lines
modified crates/oxy-core/src/repo/sync.rs +24 −121
…
1474 unchanged lines
1475 1475
        if options.token.is_some() && options.ca.is_some() {
1476 1476
            return;
1477 1477
        }
1478 -
        let own = options.token.is_some();
1479 -
        let env = std::env::var("OXY_TOKEN").is_ok_and(|t| !t.is_empty());
1480 -
        let explicit = own || env;
1478 +
        let explicit =
1479 +
            options.token.is_some() || std::env::var("OXY_TOKEN").is_ok_and(|t| !t.is_empty());
1481 1480
        let stored = self.credentials().options_for(url);
1482 -
        // A checkout's own token refused as expired or unknown (401) falls
1483 -
        // back to this computer's sign-in for the hub, never to another
1484 -
        // checkout's, and never when it is refused for permission (#155).
1485 -
        if own && !env {
1486 -
            if let Some(t) = stored
1487 -
                .token
1488 -
                .clone()
1489 -
                .filter(|t| options.token.as_ref() != Some(t))
1490 -
            {
1491 -
                options.fallback_tokens.push(t);
1492 -
                options.fallback_on_401_only = true;
1493 -
            }
1494 -
        }
1495 1481
        options.token = options.token.take().or(stored.token);
1496 1482
        options.ca = options.ca.take().or(stored.ca);
1497 1483
        let enclosing = self.enclosing_credentials(url);
…
12 unchanged lines
1510 1496
        }
1511 1497
    }
1512 1498
1513 -
    /// What to do when `remote`'s own token was refused (`message` says
1514 -
    /// HTTP 401) and so was this computer's sign-in for its hub, which it
1515 -
    /// falls back to (oxygen#155).
1516 -
    pub fn stale_token_note(&self, remote: &str, message: &str) -> Option<String> {
1517 -
        if !message.contains("HTTP 401") {
1518 -
            return None;
1519 -
        }
1520 -
        let own = self.config.remote_tokens.get(remote)?;
1521 -
        let url = self.config.remotes.get(remote)?;
1522 -
        let stored = self.credentials().options_for(url).token?;
1523 -
        (stored != *own).then(|| {
1524 -
            format!(
1525 -
                "the hub refused both this checkout's own token for {remote} (remote.{remote}.token) and your sign-in: \
1526 -
                 `oxy auth login HUB`, then `oxy config --unset remote.{remote}.token` so the sign-in is used"
1527 -
            )
1528 -
        })
1529 -
    }
1530 -
1531 1499
    /// The tokens and CAs of remotes on `url`'s hub in enclosing
1532 1500
    /// checkouts, nearest first.
1533 1501
    fn enclosing_credentials(&self, url: &str) -> Vec<(Option<String>, Option<PathBuf>)> {
…
200 unchanged lines
1734 1702
    pub fn fetch(&self, remote_name: &str) -> Result<FetchReport> {
1735 1703
        let remote = self.open_named_remote(remote_name)?;
1736 1704
        self.check_remote_id(remote.as_ref(), remote_name, false)?;
1737 -
        let resp = {
1738 -
            let _t = crate::timing::phase("fetch: refs");
1739 -
            remote.refs()?
1740 -
        };
1705 +
        let resp = remote.refs()?;
1741 1706
        // The host's transparency log (spec §14.8): a fork fails here.
1742 -
        let log_phase = crate::timing::phase("fetch: transparency log");
1743 1707
        let log = self.check_log(
1744 1708
            &self.remote_url(remote_name)?,
1745 1709
            self.config.remote_cas.get(remote_name).map(Path::new),
1746 1710
            &resp,
1747 1711
        )?;
1748 -
        drop(log_phase);
1749 1712
        let mut log_notes = log.notes.clone();
1750 1713
        // Trust policy (spec §14.5). Adopt the remote's policy when it is newer
1751 1714
        // and signed by roots we trust, or on first contact. A remote that
…
47 unchanged lines
1799 1762
        let mut report = Vec::new();
1800 1763
        let mut skipped = Vec::new();
1801 1764
        let max_blob = self.config.max_blob;
1802 -
        // One negotiation and one bundle for every bookmark's heads at
1803 -
        // once (oxygen#158): a bookmark at a time cost a round trip each and
1804 -
        // sent again the changes bookmarks share, as `have` did not grow
1805 -
        // between them (160 bookmarks: 160 bundles, 49k changes encoded for
1806 -
        // a few hundred). Heads already here, with every attestation's
1807 -
        // signer known, need no round trip at all.
1808 -
        let mut wanted: Vec<ChangeHash> = resp
1809 -
            .refs
1810 -
            .iter()
1811 -
            .flat_map(|r| r.heads.iter().copied())
1812 -
            .filter(|h| !self.store.has(h))
1813 -
            .collect();
1814 -
        wanted.sort();
1815 -
        wanted.dedup();
1816 -
        // A bundle also carries the identities and certificates of the keys
1817 -
        // that signed the remote's attestations (spec §16.2): with no change
1818 -
        // wanted, ask anyway when one of those signers is unknown here.
1819 -
        let unknown_signer = resp
1820 -
            .refs
1821 -
            .iter()
1822 -
            .filter_map(|r| r.attestation.as_ref())
1823 -
            .chain(resp.tags.iter().filter_map(|t| t.attestation.as_ref()))
1824 -
            .flat_map(|a| a.sigs.iter().map(|s| s.key))
1825 -
            .any(|k| {
1826 -
                self.store.get_identity(&k).is_err()
1827 -
                    && !matches!(self.store.certificate_of(&k), Ok(Some(_)))
1828 -
            });
1829 -
        let shallow = self.is_shallow();
1830 -
        let imported: Vec<ChangeHash> = if wanted.is_empty() && !unknown_signer {
1831 -
            Vec::new()
1832 -
        } else {
1765 +
        for r in resp.refs {
1833 1766
            let needed = {
1834 1767
                let _t = crate::timing::phase("fetch: negotiate");
1835 -
                remote.negotiate(&have, &wanted)?
1768 +
                remote.negotiate(&have, &r.heads)?
1836 1769
            };
1837 1770
            let bundle = {
1838 1771
                let _t = crate::timing::phase("fetch: bundle");
1839 1772
                remote.fetch(&needed, max_blob)?
1840 1773
            };
1841 -
            // Changes are not individually re-verified here: each bookmark's
1774 +
            // Changes are not individually re-verified here: the bookmark's
1842 1775
            // attestation (checked below) binds its whole state, and the
1843 1776
            // receiver of the push verified the added changes at that time.
1844 1777
            // A shallow repository takes changes whose dependencies it never
1845 1778
            // held (below what it cloned), as its clone did (#137).
1846 -
            let _t = crate::timing::phase("fetch: import");
1779 +
            let shallow = self.is_shallow();
1780 +
            let imported = {
1781 +
                let _t = crate::timing::phase("fetch: import");
1782 +
                if shallow {
1783 +
                    self.import_bundle_shallow(&bundle, false)?
1784 +
                } else {
1785 +
                    self.import_bundle(&bundle, false)?
1786 +
                }
1787 +
            };
1847 1788
            if shallow {
1848 -
                self.import_bundle_shallow(&bundle, false)?
1849 -
            } else {
1850 -
                self.import_bundle(&bundle, false)?
1789 +
                self.refresh_boundary(&imported)?;
1851 1790
            }
1852 -
        };
1853 -
        if shallow {
1854 -
            self.refresh_boundary(&imported)?;
1855 -
        }
1856 -
        if max_blob.is_some() {
1857 -
            self.register_missing_blobs(&imported, remote_name)?;
1858 -
        }
1859 -
        let boundary = if shallow {
1860 -
            self.boundary()
1861 -
        } else {
1862 -
            BTreeSet::new()
1863 -
        };
1864 -
        // Each bookmark's new changes: those just imported that its heads
1865 -
        // reach, for the report.
1866 -
        let mut new_deps: HashMap<ChangeHash, Vec<ChangeHash>> = HashMap::new();
1867 -
        for h in &imported {
1868 -
            if let Ok(m) = self.store.get_change_meta(h) {
1869 -
                new_deps.insert(*h, m.deps);
1791 +
            if max_blob.is_some() {
1792 +
                self.register_missing_blobs(&imported, remote_name)?;
1870 1793
            }
1871 -
        }
1872 -
        let new_in = |heads: &[ChangeHash]| -> usize {
1873 -
            let mut seen: HashSet<ChangeHash> = HashSet::new();
1874 -
            let mut stack: Vec<ChangeHash> = heads
1875 -
                .iter()
1876 -
                .filter(|h| new_deps.contains_key(*h))
1877 -
                .copied()
1878 -
                .collect();
1879 -
            while let Some(h) = stack.pop() {
1880 -
                if seen.insert(h) {
1881 -
                    stack.extend(
1882 -
                        new_deps[&h]
1883 -
                            .iter()
1884 -
                            .filter(|d| new_deps.contains_key(*d) && !seen.contains(*d)),
1885 -
                    );
1886 -
                }
1887 -
            }
1888 -
            seen.len()
1889 -
        };
1890 -
        let checks = crate::timing::phase("fetch: check each bookmark");
1891 -
        for r in resp.refs {
1794 +
            let boundary = if shallow {
1795 +
                self.boundary()
1796 +
            } else {
1797 +
                BTreeSet::new()
1798 +
            };
1892 1799
            for h in &r.heads {
1893 1800
                if !self.store.has(h) && !boundary.contains(h) {
1894 1801
                    return fail(format!("remote did not send head {}", h.short(12)));
…
74 unchanged lines
1969 1876
                .entry(remote_name.to_string())
1970 1877
                .or_default()
1971 1878
                .insert(r.name.clone(), state);
1972 -
            let n = new_in(&r.heads);
1973 -
            report.push((r.name, r.heads, n));
1879 +
            report.push((r.name, r.heads, imported.len()));
1974 1880
        }
1975 -
        drop(checks);
1976 -
        let tags_phase = crate::timing::phase("fetch: tags");
1977 1881
        let mut new_tags = 0;
1978 1882
        for t in &resp.tags {
1979 1883
            if view.tags.contains_key(&t.name) {
…
34 unchanged lines
2014 1918
        // The remote withdrew a tag this repository has: so does this one,
2015 1919
        // once the withdrawal is checked as a push's would be; one that is
2016 1920
        // not is left as it is, said so (oxygenhub#301).
2017 -
        drop(tags_phase);
2018 1921
        let mut withdrawn_tags = Vec::new();
2019 1922
        for t in &resp.withdrawn {
2020 1923
            if !view.tags.contains_key(&t.name) {
…
1151 unchanged lines
modified crates/oxy-core/src/repo/tests.rs +0 −414
…
269 unchanged lines
270 270
            vec![KeptBoth {
271 271
                path: "f.txt".into(),
272 272
                settled: vec![1],
273 -
                identical: vec![],
274 273
                left: vec![]
275 274
            }]
276 275
        );
…
61 unchanged lines
338 337
    assert_eq!(
339 338
        statuses(&s),
340 339
        vec![("CHANGELOG.md".into(), ChangeStatus::Resolved)]
341 -
    );
342 -
}
343 -
344 -
/// oxygen#146: two bookmarks that resolved the same conflict to the same
345 -
/// text agree. Merged, their resolutions are a conflict whose sides are
346 -
/// identical: a checkout keeps one, in any file, and a host settles it so
347 -
/// too; the next commit records it.
348 -
#[test]
349 -
fn identical_resolutions_on_two_bookmarks_settle_by_themselves() {
350 -
    hermetic();
351 -
    let dir = tempfile::tempdir().unwrap();
352 -
    let root = dir.path();
353 -
    let repo = Repo::init(root).unwrap();
354 -
    write(root, "f.txt", b"a\nx\nb\n");
355 -
    let base = repo.commit("base", &[]).unwrap();
356 -
    write(root, "f.txt", b"a\nX1\nb\n");
357 -
    let c1 = repo.commit("X1", &[]).unwrap();
358 -
    repo.checkout(&[base], false, "checkout").unwrap();
359 -
    write(root, "f.txt", b"a\nX2\nb\n");
360 -
    let c2 = repo.commit("X2", &[]).unwrap();
361 -
    // Each bookmark merges both and resolves the conflict the same way.
362 -
    repo.checkout(&[c1, c2], false, "checkout").unwrap();
363 -
    assert!(has_conflict_markers(&fs::read(root.join("f.txt")).unwrap()));
364 -
    write(root, "f.txt", b"a\nX\nb\n");
365 -
    let r1 = repo.commit("resolve on one", &[]).unwrap();
366 -
    repo.checkout(&[c1, c2], false, "checkout").unwrap();
367 -
    write(root, "f.txt", b"a\nX\nb\n");
368 -
    let r2 = repo.commit("resolve on two", &[]).unwrap();
369 -
    assert_ne!(r1, r2);
370 -
371 -
    let st = repo.load_state(&[r1, r2]).unwrap();
372 -
    assert!(
373 -
        st.materialize().is_conflicted(),
374 -
        "two resolutions, one place"
375 -
    );
376 -
    assert_eq!(
377 -
        Repo::union_resolution(&st, &st.materialize()),
378 -
        Some(vec![("f.txt".into(), b"a\nX\nb\n".to_vec())]),
379 -
        "a host settles it"
380 340
    );
381 -
    let r = repo.checkout(&[r1, r2], false, "checkout").unwrap();
382 -
    assert!(r.conflicted);
383 -
    assert_eq!(r.kept_both.len(), 1, "{:?}", r.kept_both);
384 -
    assert_eq!(
385 -
        (r.kept_both[0].path.as_str(), &r.kept_both[0].identical),
386 -
        ("f.txt", &vec![1])
387 -
    );
388 -
    assert_eq!(fs::read_to_string(root.join("f.txt")).unwrap(), "a\nX\nb\n");
389 -
    let s = repo.snapshot("status").unwrap();
390 -
    assert_eq!(statuses(&s), vec![("f.txt".into(), ChangeStatus::Resolved)]);
391 -
    let merged = repo.commit("merge", &[]).unwrap();
392 -
    let st = repo.load_state(&[merged]).unwrap();
393 -
    assert!(!st.materialize().is_conflicted());
394 341
}
395 342
396 343
/// oxygen#145: a host (OxygenHub landing a proposal) settles a state whose
…
4974 unchanged lines
5371 5318
    assert!(
5372 5319
        !clone.snapshot("status").unwrap().changed,
5373 5320
        "an unregistered nested repository is ignored"
5374 -
    );
5375 -
5376 -
    // Registered again, it is pinned again; removed and committed, then
5377 -
    // registered again with no snapshot between, the change that adds it
5378 -
    // back pins it too: the removal's marker is spent (oxygen#152).
5379 -
    let modules = format!("[libs/lib]\nurl = {lib_url}\n");
5380 -
    let pinned = |c: ChangeHash| {
5381 -
        clone
5382 -
            .load_state(&[c])
5383 -
            .unwrap()
5384 -
            .materialize()
5385 -
            .files
5386 -
            .get("libs/lib")
5387 -
            .map(|n| n.kind)
5388 -
    };
5389 -
    write(&clone_dir, ".oxymodules", modules.as_bytes());
5390 -
    let c = clone.commit("lib again", &[]).unwrap();
5391 -
    assert_eq!(pinned(c), Some(NodeKind::Subrepo));
5392 -
    clone.submodule_remove("libs/lib").unwrap();
5393 -
    let c = clone.commit("drop lib again", &[]).unwrap();
5394 -
    assert_eq!(pinned(c), None);
5395 -
    write(&clone_dir, ".oxymodules", modules.as_bytes());
5396 -
    let c = clone.commit("lib back", &[]).unwrap();
5397 -
    assert_eq!(
5398 -
        pinned(c),
5399 -
        Some(NodeKind::Subrepo),
5400 -
        "pinned in the change that registers it"
5401 5321
    );
5402 -
    assert!(!clone.snapshot("status").unwrap().changed);
5403 5322
}
5404 5323
5405 5324
#[test]
…
5603 unchanged lines
11009 10928
    assert!(format!("{:?}", e.unwrap_err()).contains("does not exist"));
11010 10929
}
11011 10930
11012 -
/// oxygen#147: a subrepo at its pinned state is unmodified whatever
11013 -
/// bookmark it sits on, so the parent's status is clean and a bookmark
11014 -
/// switch goes ahead; a new state in it is still a change.
11015 -
#[test]
11016 -
fn a_subrepo_at_its_pinned_state_on_another_bookmark_is_unmodified() {
11017 -
    hermetic();
11018 -
    let dir = tempfile::tempdir().unwrap();
11019 -
    let root = dir.path();
11020 -
    let _lib_origin = Repo::init(&root.join("lib-origin")).unwrap();
11021 -
    let lib_url = root.join("lib-origin").to_string_lossy().to_string();
11022 -
    let lib = Repo::clone(&lib_url, &root.join("lib"))
11023 -
        .unwrap()
11024 -
        .with_home(root.join("home"));
11025 -
    write(&root.join("lib"), "lib.txt", b"v1\n");
11026 -
    lib.commit("lib v1", &[]).unwrap();
11027 -
    assert!(lib.push("origin", false).unwrap()[0].ok);
11028 -
    let _app_origin = Repo::init(&root.join("app-origin")).unwrap();
11029 -
    let app_dir = root.join("app");
11030 -
    let mut app = Repo::clone(&root.join("app-origin").to_string_lossy(), &app_dir)
11031 -
        .unwrap()
11032 -
        .with_home(root.join("home"));
11033 -
    write(&app_dir, "main.txt", b"app\n");
11034 -
    app.submodule_add(&lib_url, "libs/lib", None).unwrap();
11035 -
    let a1 = app.commit("app with lib", &[]).unwrap();
11036 -
11037 -
    // The nested repository moves to another bookmark at the same heads.
11038 -
    let mut nested = Repo::open(&app_dir.join("libs/lib"))
11039 -
        .unwrap()
11040 -
        .with_home(root.join("home"));
11041 -
    let heads = nested.bookmark_heads().unwrap();
11042 -
    nested.bookmark_create("feature", Some(&heads)).unwrap();
11043 -
    nested.bookmark_switch("feature", false).unwrap();
11044 -
    let snap = app.snapshot("status").unwrap();
11045 -
    assert!(
11046 -
        !snap.changed,
11047 -
        "only the bookmark differs: {:?}",
11048 -
        snap.statuses
11049 -
    );
11050 -
    app.bookmark_create("other", Some(&[a1])).unwrap();
11051 -
    app.bookmark_switch("other", false).unwrap();
11052 -
11053 -
    // A new state in it is a change, as before.
11054 -
    let nested = Repo::open(&app_dir.join("libs/lib"))
11055 -
        .unwrap()
11056 -
        .with_home(root.join("home"));
11057 -
    write(&app_dir.join("libs/lib"), "lib.txt", b"v2\n");
11058 -
    nested.commit("lib v2", &[]).unwrap();
11059 -
    let snap = app.snapshot("status").unwrap();
11060 -
    assert_eq!(
11061 -
        snap.statuses
11062 -
            .iter()
11063 -
            .map(|s| (s.path.as_str(), s.status))
11064 -
            .collect::<Vec<_>>(),
11065 -
        vec![("libs/lib", ChangeStatus::Modified)]
11066 -
    );
11067 -
}
11068 -
11069 10931
/// oxygenhub#249: a handle reads the operation head once and keeps it,
11070 10932
/// so one command or request never mixes two heads; another handle's
11071 10933
/// operation shows only after `refresh`. Its own operations move it, and
…
199 unchanged lines
11271 11133
        Config::load(Some(&user), &repo_cfg).unwrap().author_key,
11272 11134
        Some(pin),
11273 11135
        "a repository's pin wins"
11274 -
    );
11275 -
}
11276 -
11277 -
#[test]
11278 -
fn git_export_graph_is_deterministic_and_merges_heads() {
11279 -
    hermetic();
11280 -
    if std::process::Command::new("git")
11281 -
        .arg("--version")
11282 -
        .output()
11283 -
        .is_err()
11284 -
    {
11285 -
        eprintln!("git not installed; skipping");
11286 -
        return;
11287 -
    }
11288 -
    let dir = tempfile::tempdir().unwrap();
11289 -
    let root = dir.path();
11290 -
    let mut repo = Repo::init(&root.join("r"))
11291 -
        .unwrap()
11292 -
        .with_home(root.join("home"));
11293 -
    repo.create_identity("Ada Lovelace", "[email protected]", IdentityScope::User)
11294 -
        .unwrap();
11295 -
    let r = root.join("r");
11296 -
    write(&r, "README.md", b"hello\n");
11297 -
    let h1 = repo.commit("first", &[]).unwrap();
11298 -
    write(&r, "README.md", b"hello\nworld\n");
11299 -
    write(&r, "src/main.rs", b"fn main() {}\n");
11300 -
    let h2 = repo.commit("second", &[]).unwrap();
11301 -
    repo.tag_create("v1", &[h2], "the first release").unwrap();
11302 -
    // An independent change: main gets two heads.
11303 -
    repo.checkout(&[h1], false, "checkout").unwrap();
11304 -
    write(&r, "other.txt", b"o\n");
11305 -
    let h3 = repo.commit("third", &[]).unwrap();
11306 -
    repo.bookmark_create("gone", Some(&[h1])).unwrap();
11307 -
11308 -
    let git = |git_dir: &Path, args: &[&str]| -> String {
11309 -
        let out = std::process::Command::new("git")
11310 -
            .arg("-C")
11311 -
            .arg(git_dir)
11312 -
            .args(args)
11313 -
            .output()
11314 -
            .unwrap();
11315 -
        assert!(
11316 -
            out.status.success(),
11317 -
            "git {args:?}: {}",
11318 -
            String::from_utf8_lossy(&out.stderr)
11319 -
        );
11320 -
        String::from_utf8_lossy(&out.stdout).trim().to_string()
11321 -
    };
11322 -
    let a = root.join("a.git");
11323 -
    let rep = repo.git_export_graph(&a).unwrap();
11324 -
    assert_eq!((rep.commits, rep.tags), (3, 1), "{rep:?}");
11325 -
    assert_eq!(rep.branches, 2, "main and gone: {rep:?}");
11326 -
11327 -
    // Each change is a commit whose parents are exactly its dependencies'
11328 -
    // commits (Oxygen's dependencies, not the order changes were made in).
11329 -
    let by_hash = |hash: ChangeHash| {
11330 -
        git(
11331 -
            &a,
11332 -
            &[
11333 -
                "log",
11334 -
                "--all",
11335 -
                "--format=%H",
11336 -
                &format!("--grep=Oxy-Hash: {}", hash.to_hex()),
11337 -
            ],
11338 -
        )
11339 -
    };
11340 -
    let (c1, c2, c3) = (by_hash(h1), by_hash(h2), by_hash(h3));
11341 -
    for (h, c) in [(h1, &c1), (h2, &c2), (h3, &c3)] {
11342 -
        let mut want: Vec<String> = repo
11343 -
            .store()
11344 -
            .get_change_meta(&h)
11345 -
            .unwrap()
11346 -
            .deps
11347 -
            .iter()
11348 -
            .map(|d| by_hash(*d))
11349 -
            .collect();
11350 -
        want.sort();
11351 -
        let mut have: Vec<String> = git(&a, &["rev-list", "--parents", "-1", c])
11352 -
            .split(' ')
11353 -
            .skip(1)
11354 -
            .map(String::from)
11355 -
            .collect();
11356 -
        have.sort();
11357 -
        assert_eq!(have, want, "{h:?}'s commit's parents");
11358 -
    }
11359 -
    assert!(
11360 -
        !repo.store().get_change_meta(&h2).unwrap().deps.is_empty(),
11361 -
        "second edits first's README"
11362 -
    );
11363 -
    assert_eq!(
11364 -
        git(&a, &["show", &format!("{c2}:src/main.rs")]),
11365 -
        "fn main() {}"
11366 -
    );
11367 -
    assert!(
11368 -
        git(&a, &["ls-tree", "--name-only", &c3])
11369 -
            .lines()
11370 -
            .all(|l| l != "src"),
11371 -
        "a change's tree is its own closure, not the bookmark's"
11372 -
    );
11373 -
    assert_eq!(
11374 -
        git(&a, &["log", "-1", "--format=%an <%ae>", &c1]),
11375 -
        "Ada Lovelace <[email protected]>"
11376 -
    );
11377 -
11378 -
    // main has two heads: a merge of both, whose tree is the union.
11379 -
    let main = git(&a, &["rev-parse", "main"]);
11380 -
    let mut parents: Vec<String> = git(&a, &["rev-list", "--parents", "-1", "main"])
11381 -
        .split(' ')
11382 -
        .skip(1)
11383 -
        .map(String::from)
11384 -
        .collect();
11385 -
    parents.sort();
11386 -
    let mut want = vec![c2.clone(), c3.clone()];
11387 -
    want.sort();
11388 -
    assert_eq!(parents, want, "the merge's parents are the heads");
11389 -
    assert_eq!(
11390 -
        git(&a, &["ls-tree", "--name-only", "main"]),
11391 -
        "README.md\nother.txt\nsrc"
11392 -
    );
11393 -
    assert!(git(&a, &["log", "-1", "--format=%B", "main"]).contains("Merge of 2 heads"));
11394 -
    assert_eq!(
11395 -
        git(&a, &["rev-parse", "v1^{commit}"]),
11396 -
        c2,
11397 -
        "the tag is its head's commit"
11398 -
    );
11399 -
    assert_eq!(git(&a, &["symbolic-ref", "HEAD"]), "refs/heads/main");
11400 -
11401 -
    // Again: nothing new. A bookmark deleted takes its branch with it.
11402 -
    repo.bookmark_delete("gone").unwrap();
11403 -
    let rep = repo.git_export_graph(&a).unwrap();
11404 -
    assert_eq!(rep.commits, 0, "{rep:?}");
11405 -
    assert!(
11406 -
        git(&a, &["for-each-ref", "--format=%(refname)", "refs/heads"])
11407 -
            .lines()
11408 -
            .all(|r| r != "refs/heads/gone")
11409 -
    );
11410 -
    assert_eq!(
11411 -
        git(&a, &["rev-parse", "main"]),
11412 -
        main,
11413 -
        "the merge is made once"
11414 -
    );
11415 -
11416 -
    // The same changes elsewhere give the same commits.
11417 -
    let other = Repo::clone(r.to_str().unwrap(), &root.join("clone"))
11418 -
        .unwrap()
11419 -
        .with_home(root.join("home"));
11420 -
    let b = root.join("b.git");
11421 -
    other.git_export_graph(&b).unwrap();
11422 -
    assert_eq!(
11423 -
        git(&b, &["rev-parse", "main"]),
11424 -
        main,
11425 -
        "deterministic across repositories"
11426 11136
    );
11427 -
    assert_eq!(git(&b, &["rev-parse", "v1^{commit}"]), c2);
11428 -
}
11429 -
11430 -
#[test]
11431 -
fn a_fetch_of_many_bookmarks_takes_shared_changes_once() {
11432 -
    // oxygen#158: every bookmark's heads are fetched in one exchange, so
11433 -
    // changes bookmarks share are not sent again for each; each bookmark
11434 -
    // still says how many of its changes were new, and a fetch with
11435 -
    // nothing new imports nothing.
11436 -
    hermetic();
11437 -
    let dir = tempfile::tempdir().unwrap();
11438 -
    let root = dir.path();
11439 -
    drop(Repo::init(&root.join("origin")).unwrap());
11440 -
    let url = root.join("origin").to_string_lossy().to_string();
11441 -
    let mut a = Repo::clone(&url, &root.join("a"))
11442 -
        .unwrap()
11443 -
        .with_home(root.join("home-a"));
11444 -
    let w = root.join("a");
11445 -
    write(&w, "f.txt", b"one\n");
11446 -
    a.commit("one", &[]).unwrap();
11447 -
    write(&w, "f.txt", b"one\ntwo\n");
11448 -
    a.commit("two", &[]).unwrap();
11449 -
    a.push("origin", false).unwrap();
11450 -
    // Five bookmarks on main's history, each one change of its own.
11451 -
    for i in 0..5 {
11452 -
        a.bookmark_create(&format!("feat{i}"), None).unwrap();
11453 -
        a.bookmark_switch(&format!("feat{i}"), false).unwrap();
11454 -
        write(&w, &format!("feat{i}.txt"), format!("{i}\n").as_bytes());
11455 -
        a.commit(&format!("feat {i}"), &[]).unwrap();
11456 -
        a.push("origin", false).unwrap();
11457 -
        a.bookmark_switch("main", false).unwrap();
11458 -
    }
11459 -
11460 -
    let b = Repo::clone(&url, &root.join("b"))
11461 -
        .unwrap()
11462 -
        .with_home(root.join("home-b"));
11463 -
    let report = b.fetch("origin").unwrap();
11464 -
    assert_eq!(report.refs.len(), 6, "every bookmark: {:?}", report.refs);
11465 -
    assert!(
11466 -
        report.refs.iter().all(|(_, _, n)| *n == 0),
11467 -
        "the clone brought them all: {:?}",
11468 -
        report.refs
11469 -
    );
11470 -
11471 -
    // One new change on one bookmark: counted there, nowhere else.
11472 -
    a.bookmark_switch("feat3", false).unwrap();
11473 -
    write(&w, "feat3.txt", b"3\nmore\n");
11474 -
    a.commit("feat 3 again", &[]).unwrap();
11475 -
    a.push("origin", false).unwrap();
11476 -
    let fetches = || crate::protocol::LOCAL_FETCHES.with(|n| n.get());
11477 -
    let at = fetches();
11478 -
    let report = b.fetch("origin").unwrap();
11479 -
    assert_eq!(fetches() - at, 1, "one bundle for all six bookmarks");
11480 -
    let counts: Vec<(String, usize)> = report
11481 -
        .refs
11482 -
        .iter()
11483 -
        .map(|(n, _, c)| (n.clone(), *c))
11484 -
        .collect();
11485 -
    assert!(counts.contains(&("feat3".to_string(), 1)), "{counts:?}");
11486 -
    assert_eq!(
11487 -
        counts.iter().filter(|(_, c)| *c > 0).count(),
11488 -
        1,
11489 -
        "only feat3 had anything new: {counts:?}"
11490 -
    );
11491 -
11492 -
    // Nothing new: no bundle at all, every bookmark still listed.
11493 -
    let at = fetches();
11494 -
    let report = b.fetch("origin").unwrap();
11495 -
    assert_eq!(fetches() - at, 0, "nothing to fetch, no round trip");
11496 -
    assert_eq!(report.refs.len(), 6);
11497 -
    assert!(
11498 -
        report.refs.iter().all(|(_, _, n)| *n == 0),
11499 -
        "{:?}",
11500 -
        report.refs
11501 -
    );
11502 -
}
11503 -
11504 -
#[test]
11505 -
fn a_stale_pinned_key_cannot_fork_the_identity() {
11506 -
    // oxygen#163: a checkout pinned author.key to a key that a rotation made
11507 -
    // elsewhere had already succeeded; delegating there built a second
11508 -
    // successor of it, splitting the identity in two.
11509 -
    hermetic();
11510 -
    let dir = tempfile::tempdir().unwrap();
11511 -
    let root = dir.path();
11512 -
    let home = root.join("home");
11513 -
    let mut a = Repo::init(&root.join("a")).unwrap().with_home(&home);
11514 -
    let (first, _) = a
11515 -
        .create_identity("Ada", "[email protected]", IdentityScope::User)
11516 -
        .unwrap();
11517 -
    let r = a.rotate_identity().unwrap();
11518 -
    assert_eq!((r.old, a.config.author_key), (first, Some(r.new)));
11519 -
    assert_eq!(a.successors(&first).unwrap(), vec![r.new]);
11520 -
    assert_eq!(a.newest(&first).unwrap(), r.new);
11521 -
11522 -
    // Another checkout still pins the first key.
11523 -
    let mut b = Repo::init(&root.join("b")).unwrap().with_home(&home);
11524 -
    b.config.set("author.key", &first.to_hex()).unwrap();
11525 -
    b.save_config().unwrap();
11526 -
    let mut b = Repo::open(&root.join("b")).unwrap().with_home(&home);
11527 -
    assert_eq!(b.config.author_key, Some(first));
11528 -
    for e in [
11529 -
        b.rotate_identity().unwrap_err().to_string(),
11530 -
        b.renew_identity(None).unwrap_err().to_string(),
11531 -
    ] {
11532 -
        assert!(e.contains("fork") && e.contains(&r.new.short(12)), "{e}");
11533 -
        assert!(
11534 -
            e.contains(&format!("oxy config author.key {}", r.new.to_hex())),
11535 -
            "{e}"
11536 -
        );
11537 -
    }
11538 -
    assert_eq!(
11539 -
        b.successors(&first).unwrap(),
11540 -
        vec![r.new],
11541 -
        "nothing was made"
11542 -
    );
11543 -
11544 -
    // Pointed at the newest key, it rotates as usual.
11545 -
    b.config.set("author.key", &r.new.to_hex()).unwrap();
11546 -
    b.save_config().unwrap();
11547 -
    let mut b = Repo::open(&root.join("b")).unwrap().with_home(&home);
11548 -
    let r2 = b.rotate_identity().unwrap();
11549 -
    assert_eq!(r2.old, r.new);
11550 -
    assert_eq!(b.newest(&first).unwrap(), r2.new);
11551 11137
}
modified crates/oxy-core/src/repo/walk.rs +1 −3
…
184 unchanged lines
185 185
    let mut mine = Listing::default();
186 186
    mine.dirs.push((rel.clone(), mtime));
187 187
    for (e, name) in entries.into_iter().zip(names) {
188 -
        // `.git` is never content: a side-by-side Git repository
189 -
        // (`git.colocate`), or Git's own beside an import.
190 -
        if name == OXY_DIR || name.eq_ignore_ascii_case(".git") {
188 +
        if name == OXY_DIR {
191 189
            continue;
192 190
        }
193 191
        let Ok(ft) = e.file_type() else { continue };
…
136 unchanged lines
modified crates/oxy-core/src/state.rs +0 −55
…
749 unchanged lines
750 750
    }
751 751
}
752 752
753 -
/// How one conflict settles without a person ([`State::settle_plans`]).
754 -
#[derive(Clone, Debug, PartialEq, Eq)]
755 -
pub struct Settle {
756 -
    /// Its sides' text, as its markers show them.
757 -
    pub sides: Vec<Vec<u8>>,
758 -
    /// The text that takes the markers' place, side by side, in order.
759 -
    pub kept: Vec<Vec<u8>>,
760 -
    /// Its sides are identical, and one is kept.
761 -
    pub identical: bool,
762 -
}
763 -
764 753
/// See `State::conflict_regions`.
765 754
#[derive(Clone, Debug, Default, PartialEq, Eq)]
766 755
pub struct ConflictRegions {
…
1401 unchanged lines
2168 2157
                            })
2169 2158
                            .collect(),
2170 2159
                    ));
2171 -
                }
2172 -
                _ => out.push(None),
2173 -
            }
2174 -
        }
2175 -
        out
2176 -
    }
2177 -
2178 -
    /// How each conflict of a text node settles without a person, in
2179 -
    /// marker order (spec §9.5); `None` for one that needs a person. A
2180 -
    /// conflict whose sides are identical keeps one of them, in any file
2181 -
    /// (oxygen#146): two bookmarks that resolved the same conflict the same
2182 -
    /// way agree. With `union` (the file is `merge=union`, oxygen#145), an
2183 -
    /// adjacent insertion keeps every side, earliest change first.
2184 -
    pub fn settle_plans(&self, node: &NodeId, union: bool) -> Vec<Option<Settle>> {
2185 -
        let Some(g) = self.graph(node) else {
2186 -
            return Vec::new();
2187 -
        };
2188 -
        let m = self.materialize_ordered(g);
2189 -
        let text = |s: &[AtomKey]| -> Vec<u8> {
2190 -
            s.iter()
2191 -
                .flat_map(|a| g.content(a).unwrap_or_default().to_vec())
2192 -
                .collect()
2193 -
        };
2194 -
        let mut out = Vec::new();
2195 -
        for (i, seg) in m.segments.iter().enumerate() {
2196 -
            match seg {
2197 -
                crate::graph::Segment::Lines(_) => {}
2198 -
                crate::graph::Segment::Conflict(sides) => {
2199 -
                    let texts: Vec<Vec<u8>> = sides.iter().map(|s| text(s)).collect();
2200 -
                    out.push(if texts.len() > 1 && texts.iter().all(|t| *t == texts[0]) {
2201 -
                        Some(Settle {
2202 -
                            kept: vec![texts[0].clone()],
2203 -
                            sides: texts,
2204 -
                            identical: true,
2205 -
                        })
2206 -
                    } else if union && g.adjacent_insertion(&m, i, self) {
2207 -
                        Some(Settle {
2208 -
                            kept: texts.clone(),
2209 -
                            sides: texts,
2210 -
                            identical: false,
2211 -
                        })
2212 -
                    } else {
2213 -
                        None
2214 -
                    });
2215 2160
                }
2216 2161
                _ => out.push(None),
2217 2162
            }
…
1000 unchanged lines
modified crates/oxy-core/src/subrepo.rs +0 −30
…
25 unchanged lines
26 26
}
27 27
28 28
impl Pin {
29 -
    /// Whether two pins name the same state: the same repository at the
30 -
    /// same heads, in any order, or the same Git commit. The bookmark or
31 -
    /// branch each names does not count (#147): a subrepo at its pinned
32 -
    /// state is at it whatever bookmark it sits on.
33 -
    pub fn same_state(&self, other: &Pin) -> bool {
34 -
        match (self, other) {
35 -
            (
36 -
                Pin::Oxy {
37 -
                    repo: r1,
38 -
                    heads: h1,
39 -
                    ..
40 -
                },
41 -
                Pin::Oxy {
42 -
                    repo: r2,
43 -
                    heads: h2,
44 -
                    ..
45 -
                },
46 -
            ) => {
47 -
                let (mut a, mut b) = (h1.clone(), h2.clone());
48 -
                a.sort();
49 -
                a.dedup();
50 -
                b.sort();
51 -
                b.dedup();
52 -
                r1 == r2 && a == b
53 -
            }
54 -
            (Pin::Git { sha: a, .. }, Pin::Git { sha: b, .. }) => a == b,
55 -
            _ => false,
56 -
        }
57 -
    }
58 -
59 29
    pub fn encode(&self) -> Vec<u8> {
60 30
        let v = match self {
61 31
            Pin::Oxy {
…
278 unchanged lines
modified crates/oxy-server/tests/http.rs +1 −27
…
757 unchanged lines
758 758
        vec!["good".to_string()],
759 759
        "the parent's, to fall back on"
760 760
    );
761 -
    assert!(!options.fallback_on_401_only);
762 761
    write(&nested, "c.txt", b"child\n");
763 762
    child.commit("child work", &[]).unwrap();
764 763
    let pushed = child.push("origin", false).unwrap();
…
7 unchanged lines
772 771
        1
773 772
    );
774 773
775 -
    // Its own token, if refused, falls back to this computer's sign-in,
776 -
    // never to the parent's (#155): stale too here, so the push fails and
777 -
    // says what to do.
774 +
    // Its own token, if wrong, stays wrong: what a checkout sets is its choice.
778 775
    child.config.set("remote.origin.token", "wrong").unwrap();
779 776
    child.save_config().unwrap();
780 -
    let options = child.options_for_url(&format!("{base}/child"));
781 -
    assert_eq!(
782 -
        options.fallback_tokens,
783 -
        vec!["stale".to_string()],
784 -
        "the sign-in only"
785 -
    );
786 -
    assert!(
787 -
        options.fallback_on_401_only,
788 -
        "and only for a token the hub does not know: one refused for permission is not swapped"
789 -
    );
790 777
    write(&nested, "d.txt", b"more\n");
791 778
    child.commit("more", &[]).unwrap();
792 779
    let err = child.push("origin", false).err().unwrap().to_string();
793 780
    assert!(err.contains("401"), "{err}");
794 -
    let note = child.stale_token_note("origin", &err).expect("a note");
795 -
    assert!(
796 -
        note.contains("oxy auth login") && note.contains("oxy config --unset remote.origin.token"),
797 -
        "{note}"
798 -
    );
799 -
    assert!(child.stale_token_note("origin", "HTTP 500").is_none());
800 -
    // With a good sign-in, an expired checkout token no longer blocks.
801 -
    oxy_core::credentials::Credentials::at(&home)
802 -
        .save(&base, "good", Some("andy"), None)
803 -
        .unwrap();
804 -
    let child = Repo::open(&nested).unwrap().with_home(&home);
805 -
    let pushed = child.push("origin", false).unwrap();
806 -
    assert!(pushed[0].ok, "{}", pushed[0].reason);
807 781
}
808 782
809 783
/// A host refuses a push by what it adds (`Backend::vet_push`): the client
…
450 unchanged lines
modified docs/cylinder.md +0 −11
…
395 unchanged lines
396 396
- **C1. The service boundary and artifact model.** Implement the
397 397
  `Artifact` CBOR object type and FastCDC chunk engine in `cylinder`;
398 398
  unify runner images with artifacts.
399 -
  *Started 2026-10-28 (oxygen#108):* `crates/cylinder` has the
400 -
  `Artifact` and `DirTree` encodings of §4.1 (canonical CBOR; a name is
401 -
  the BLAKE3 of the encoding; `env` and `labels` as sorted pairs, since
402 -
  the profile's maps take integer keys), packing a directory into the
403 -
  repository's own blobs and chunks, verifying and unpacking, and the
404 -
  roots garbage collection keeps; `oxy artifact pack | list | show |
405 -
  unpack | verify`. The artifact is an object type of its own
406 -
  (`artifact`, in oxy-core's `artifact` module, checked by `oxy fsck`),
407 -
  named by its object hash like every object; its tree is a blob, and
408 -
  `.oxy/artifacts/NAME` records the artifacts a repository keeps. Next:
409 -
  runner images as artifacts, then moving them between repositories (C2).
410 399
- **C2. Channels, provenance and consensus.** Flow publishes artifacts;
411 400
  signed channel move attestations; multi-party `reproduced(N)` consensus;
412 401
  revset predicates.
…
44 unchanged lines
removed docs/mcp.md +0 −78
1 -
# oxy mcp: Oxygen for AI agents
2 -
3 -
`oxy mcp` serves one repository to an AI agent over the
4 -
[Model Context Protocol](https://modelcontextprotocol.io) (MCP), on stdio
5 -
(oxygen#159). The agent calls typed tools (`status`, `log`, `commit`, …)
6 -
and gets JSON back, instead of running `oxy` in a shell and reading its
7 -
output.
8 -
9 -
## Who changes are signed as
10 -
11 -
The server signs as whatever identity its environment configures, exactly
12 -
as `oxy` would: it runs each tool as this same `oxy` binary, in the
13 -
repository, with the server's environment. Start it from an agent home and
14 -
every change the agent makes is sealed **via the agent**, with no
15 -
`OXY_HOME` to remember on each command:
16 -
17 -
```sh
18 -
OXY_HOME=~/.oxy-agents/claude oxy mcp ~/development/myproject
19 -
```
20 -
21 -
The server says who it signs as in its `initialize` reply (the first line
22 -
of `oxy identity show`), and the `identity` tool shows the rest. Without an
23 -
identity, reading works and committing is refused with oxy's own message.
24 -
25 -
## Tools
26 -
27 -
Each tool returns the command's `--json` output as structured content (an
28 -
array comes wrapped as `{"result": […]}`) and as text. A failed command is a
29 -
tool result with `isError: true` and oxy's error, so the agent sees why.
30 -
31 -
| Tool | Runs | Arguments |
32 -
|---|---|---|
33 -
| `status` | `oxy status --json` | |
34 -
| `diff` | `oxy diff [CHANGE] --json` | `change` |
35 -
| `log` | `oxy log --json` | `revset`, `path`, `limit` (default 20, at most 1000) |
36 -
| `show` | `oxy show CHANGE --json` | `change` (default `@`), `stat` |
37 -
| `cat` | `oxy cat PATH [-r CHANGE]` | `path`, `change` |
38 -
| `blame` | `oxy blame PATH --json` | `path` |
39 -
| `bookmarks` | `oxy bookmark list --json` | `all` |
40 -
| `tags` | `oxy tag list --json` | |
41 -
| `conflicts` | `oxy resolve --json` (report only) | |
42 -
| `op_log` | `oxy op log --json` | `limit` |
43 -
| `identity` | `oxy identity show` | |
44 -
| `commit` | `oxy commit -m MESSAGE PATHS…` | `message`, and `paths` or `all: true` |
45 -
| `amend` | `oxy amend [-m] PATHS…` | `paths` or `all: true`, `message` |
46 -
| `describe` | `oxy describe [CHANGE] -m MESSAGE` | `message`, `change` |
47 -
| `fetch`, `pull` | `oxy fetch\|pull [REMOTE] --json` | `remote` |
48 -
| `push` | `oxy push [REMOTE] --json` | `remote`, `bookmark`, `force` |
49 -
| `tlog_verify` | `oxy tlog verify --json` | |
50 -
| `undo` | `oxy undo` | only with `--allow-destructive` |
51 -
52 -
## Safety
53 -
54 -
- **Commits are deliberate.** `commit` and `amend` take the paths to
55 -
  include, or `all: true`; never neither.
56 -
- **Nothing destructive by default.** `undo` is not offered, and `push`
57 -
  refuses `force`, unless the server is started with `--allow-destructive`.
58 -
  Tag withdrawal, bookmark deletion and history rewriting are not tools at
59 -
  all. Each tool carries MCP's `readOnlyHint` and `destructiveHint`.
60 -
- **Values are never flags.** Arguments are passed as `--flag=value` and
61 -
  after `--`, so a path or message starting with `-` stays a value.
62 -
- **Your policy still applies.** Pushes go through the remote's checks and
63 -
  the repository's trust policy like any other; an agent's delegation
64 -
  scope limits what its seals may do.
65 -
- **Output is bounded.** A tool's text stops at 256 KiB, saying to narrow
66 -
  the request.
67 -
68 -
## Adding it to an agent
69 -
70 -
Claude Code, for one project (run in the project's root):
71 -
72 -
```sh
73 -
claude mcp add oxy -- env OXY_HOME=$HOME/.oxy-agents/claude oxy mcp
74 -
```
75 -
76 -
Any MCP client that starts stdio servers takes the same command: `oxy mcp
77 -
[DIR] [--allow-destructive]`, with `OXY_HOME` set for an agent's identity.
78 -
`DIR` defaults to the current directory and must be a repository's root.
modified docs/spec.md +1 −6
…
389 unchanged lines
390 390
391 391
`oxy status` MUST list every conflict, MUST exit with status 2 when any conflict exists in the working-copy state, MUST exit with status 3 when only workspace conflicts (§9.7) exist, and MUST support `--json` output enumerating conflicts by node, kind, and contributing ChangeIds. The JSON object has `bookmark`, `working_copy` (ChangeId), `heads`, `clean`, `conflicted`, `paths` (`[{path, status}]`) and `conflicts`: for a text or replace conflict `{kind, path, node, changes: [ChangeId], resolved: true | false | "markers"}`, for a tree conflict `{kind: name | multi-placement | orphan | cycle | mode, nodes, path?}`.
392 392
393 -
An **adjacent insertion** is an order conflict whose sides only add lines between two lines that were next to each other: no line between them was deleted by a change that not every side builds on (two changes that each replaced the same line conflict the same way, and are not one). Keeping every side settles it without losing or repeating a line, though either order can be wrong for code, so it is never done silently: `oxy resolve --both [PATH]..` replaces the markers of each adjacent insertion in the working copy with its sides, earliest first (`--reverse`: latest first), and leaves the markers of any other conflict, or of one whose lines between the markers were edited. `oxy status` says which conflicts qualify. A file whose `.oxyattributes` say `merge=union` (§19; changelogs, lists where order does not matter) needs no command: a checkout settles its adjacent insertions the same way at once, and the next commit records the resolution. The state is still conflicted until then; nothing about what a conflict is changes. A conflict whose sides are **identical**, byte for byte, settles the same way in any file, with one side kept: two bookmarks that resolved the same conflict to the same text, then merged, agree (oxygen#146; Git merges identical changes cleanly too). A host landing a proposal settles both kinds, and only them, as a checkout does.
393 +
An **adjacent insertion** is an order conflict whose sides only add lines between two lines that were next to each other: no line between them was deleted by a change that not every side builds on (two changes that each replaced the same line conflict the same way, and are not one). Keeping every side settles it without losing or repeating a line, though either order can be wrong for code, so it is never done silently: `oxy resolve --both [PATH]..` replaces the markers of each adjacent insertion in the working copy with its sides, earliest first (`--reverse`: latest first), and leaves the markers of any other conflict, or of one whose lines between the markers were edited. `oxy status` says which conflicts qualify. A file whose `.oxyattributes` say `merge=union` (§19; changelogs, lists where order does not matter) needs no command: a checkout settles its adjacent insertions the same way at once, and the next commit records the resolution. The state is still conflicted until then; nothing about what a conflict is changes.
394 394
395 395
### 9.6 Resolution
396 396
…
616 unchanged lines
1013 1013
- **Fetch and push against a Git remote.** `oxy git fetch URL` keeps a bare mirror under `.oxy/git/` for the remote, fetches the branch into it, and imports the commits the bookmark does not have yet, seeding the import with the commits already known: an imported commit maps to its change, and a commit this repository exported maps to the bookmark state it was made from (so a Git commit on top of exported work is based on everything that work contained, including changes Git never saw). The bookmark moves to the union of its heads and the new changes, like a pull; the working copy follows when it was clean. A fetched state whose previous state was itself a Git tree is remembered as equal to the fetched tip. `oxy git push URL` refuses until the remote's tip is known, exports the bookmark's later states as commits chained onto that tip, and pushes the branch, so the push is always a fast-forward.
1014 1014
- **Submodules.** A Git gitlink entry (mode `160000`) imports as a `subrepo` node with a Git pin (§7.8) and the matching `.gitmodules` entry becomes a `.oxymodules` entry with `branch` set to the submodule's configured branch when there is one. Export writes a Git pin back as a gitlink, keeps a tracked `.gitmodules` as is, and otherwise derives `.gitmodules` from `.oxymodules`; an Oxygen pin has no Git form and is omitted from the exported tree, which the export report counts.
1015 1015
- **Merges of opaque files.** When the union of a merge commit's parents holds several live blobs for a file or submodule, the merge's change replaces each of them with the commit's blob and is a resolution: Git's merge is the authority, unlike a working copy, where a candidate on disk is not yet a decision.
1016 -
- **A side-by-side `.git`.** `oxy git colocate` (config `git.colocate`) keeps a `.git` beside `.oxy` so editors' and other tools' Git features work on an Oxygen checkout: made with `git init` when missing, `/.oxy/` added to its `info/exclude`, the current bookmark exported into it (as `oxy git export` does) after every oxy command that leaves the bookmark somewhere new, HEAD on that branch and the index at its tree, so `git status` shows exactly the working copy's uncommitted work. It is one way: commits made with Git are not read back. oxy never takes a `.git` directory as content, and checkout refuses to write a path with a `.git` component at any depth or case (as Git does), so a change cannot plant hooks there. `oxy git colocate --off` stops the updates and leaves `.git` in place.
1017 -
- **Export by change graph.** For a server serving Git clients (a hub's Git view), `git_export_graph` exports every bookmark and tag from the changes alone, so the same changes give the same commits wherever it runs: each change is a commit whose tree is the state of the change and its dependency closure and whose parents are its dependencies' commits (a change that depends on nothing is a root), with the change's author, time, message and `Change-Id:`/`Oxy-Hash:` trailers; a bookmark with one head is a branch at its commit, with several a merge of their commits (the bookmark's state, at the newest head's time, `Oxy-Heads:`); tags likewise. Marks make it incremental; branches of deleted bookmarks are removed.
1018 1016
- **Round trip.** Import followed by export MUST reproduce every tree hash. Export followed by import MUST reproduce every ChangeId. `oxy git verify GIT_DIR` checks an import commit by commit: for each commit, the state formed by the changes imported for it and all its ancestors must materialize to exactly that commit's tree (content, executable bits, symlink targets, submodule pins; `.oxymodules` is allowed beside submodules). `--last N` rebuilds only the last N commits, following the others, to look again at where an import went wrong.
1019 1017
1020 1018
## 24. Command-line interface
…
26 unchanged lines
1047 1045
oxy doctor [--offline] [--json]     check identity, config and remotes (§21.2)
1048 1046
oxy completions SHELL               shell completion script     oxy man [--dir DIR]   man pages
1049 1047
oxy hunks                           oxy discard PATH.. | --hunks N,M
1050 -
oxy mcp [DIR] [--allow-destructive] serve the repository to an AI agent (MCP, stdio)
1051 1048
```
1052 1049
1053 1050
**Machine interface.** `status`, `log` (with `--path`), `show`, `diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`, `fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`, `policy show`, `root` and `version` take `--json` and then print exactly one JSON document on stdout, with ids as hex strings and times in milliseconds since the epoch; errors go to stderr with a non-zero status. `commit --hunks N,M` and `discard --hunks N,M` take hunk numbers as `hunks` prints them. Editors and other tools MUST use these rather than reading `.oxy/`.
1054 -
1055 -
**Agents.** `oxy mcp` serves the repository to an AI agent over the Model Context Protocol on stdio (docs/mcp.md): each tool runs one of the commands above with `--json`, in the repository and with the server's environment, so it signs as the identity `OXY_HOME` configures (an agent home seals every change via the agent). `commit` and `amend` take explicit paths or `all: true`; `undo` and a forced push are offered only with `--allow-destructive`.
1056 1051
1057 1052
Change arguments accept a ChangeId prefix, a ChangeHash prefix (with `#`), a bookmark or tag name, and `@` for the working-copy change. Ambiguous prefixes are an error.
1058 1053
…
141 unchanged lines
removed packaging/haikuports/README.md +0 −40
1 -
# oxy and oh on HaikuDepot
2 -
3 -
[haikuports](https://github.com/haikuports/haikuports) recipes for oxy
4 -
(`dev-vcs/oxy`) and OxygenHub's `oh` (`dev-vcs/oh`) are written by
5 -
`make-recipe.py` from a release's source tarball (oxygen#156, #157).
6 -
7 -
Each release's dist job attaches `NAME-VERSION-src.tar.gz`: the source as
8 -
released, reproducible (sorted, fixed times and owners, gzip without a
9 -
timestamp). oh's holds the oxygenhub tree with its subrepos inside, so it
10 -
builds alone. The recipe fetches it from the release's stable address,
11 -
`https://oxygenhub.io/andymccall/REPO/releases/download/vVERSION/NAME-VERSION-src.tar.gz`,
12 -
and every crates.io crate in its `Cargo.lock` as a source of its own with its
13 -
checksum, building offline as haikuports' other Rust recipes (ripgrep) do.
14 -
15 -
For a release (oxy shown; oh is the same with `oh` and `oxygenhub`):
16 -
17 -
```sh
18 -
curl -LO https://oxygenhub.io/andymccall/oxygen/releases/download/vX.Y.Z/oxy-X.Y.Z-src.tar.gz
19 -
python3 packaging/haikuports/make-recipe.py oxy oxy-X.Y.Z-src.tar.gz X.Y.Z > oxy-X.Y.Z.recipe
20 -
```
21 -
22 -
Test it on Haiku R1/beta6 with haikuports checked out and `haikuporter`
23 -
installed: copy the recipe to `haikuports/dev-vcs/oxy/` and run
24 -
`haikuporter --no-source-packages -j1 -y oxy`. Submit it as a pull request
25 -
to haikuports (`oxy: new recipe`, then `oxy: bump to X.Y.Z`).
26 -
27 -
Haiku needs the system's zstd (`ZSTD_SYS_USE_PKG_CONFIG=1`: the bundled
28 -
zstd's hidden symbols break the link), which the recipe sets. oh's tests
29 -
pull in the hub, whose pipeline parser needs a newer Rust than HaikuPorts'
30 -
1.94, so its recipe tests `hub-api` only.
31 -
32 -
## Icons
33 -
34 -
HaikuDepot takes a package's icon from HaikuDepotServer, not from the
35 -
recipe. `icons/` holds them, written by `make-icons.py icons/` (the PNGs
36 -
rendered from the SVGs): Oxygen (`oxy`) is the mark on a pale tile,
37 -
OxygenHub (`oh`) the mark on the hub's dark tile, as the VS Code
38 -
extensions' icons are. Once the packages are listed on
39 -
depot.haiku-os.org, upload `NAME.hvif` with `NAME-16.png`, `-32` and `-64`
40 -
as each package's icon.
removed packaging/haikuports/dev-vcs/oh/oh-0.9.16.recipe +0 −1472
1 -
SUMMARY="The command line of OxygenHub, the forge for Oxygen repositories"
2 -
DESCRIPTION="oh talks to an OxygenHub (such as https://oxygenhub.io): issues, proposals \
3 -
and their reviews, releases, Flow runs, their logs and reports, and checks, \
4 -
from the terminal. It works alongside oxy, the Oxygen version control \
5 -
client."
6 -
HOMEPAGE="https://oxygenhub.io/andymccall/oxygenhub"
7 -
COPYRIGHT="2026 Andy McCall"
8 -
LICENSE="MIT
9 -
	Apache v2"
10 -
REVISION="1"
11 -
SOURCE_URI="$HOMEPAGE/releases/download/v$portVersion/oh-$portVersion-src.tar.gz"
12 -
CHECKSUM_SHA256="55770a8668647a44e57ed085315dc11bd97028d2a5c1a1640cd85af87b461200"
13 -
SOURCE_DIR="oxygenhub-$portVersion"
14 -
SOURCE_URI_2="https://static.crates.io/crates/adler2/adler2-2.0.1.crate"
15 -
CHECKSUM_SHA256_2="320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
16 -
SOURCE_FILENAME_2="adler2-2.0.1.tar.gz"
17 -
SOURCE_URI_3="https://static.crates.io/crates/aho-corasick/aho-corasick-1.1.5.crate"
18 -
CHECKSUM_SHA256_3="c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
19 -
SOURCE_FILENAME_3="aho-corasick-1.1.5.tar.gz"
20 -
SOURCE_URI_4="https://static.crates.io/crates/allocator-api2/allocator-api2-0.2.21.crate"
21 -
CHECKSUM_SHA256_4="683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
22 -
SOURCE_FILENAME_4="allocator-api2-0.2.21.tar.gz"
23 -
SOURCE_URI_5="https://static.crates.io/crates/ammonia/ammonia-4.2.0.crate"
24 -
CHECKSUM_SHA256_5="3f7ed3ac4252be4d3e36fb589ed5659356842587b21a31596a9dc89d3b2f5e2a"
25 -
SOURCE_FILENAME_5="ammonia-4.2.0.tar.gz"
26 -
SOURCE_URI_6="https://static.crates.io/crates/android_system_properties/android_system_properties-0.1.6.crate"
27 -
CHECKSUM_SHA256_6="ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
28 -
SOURCE_FILENAME_6="android_system_properties-0.1.6.tar.gz"
29 -
SOURCE_URI_7="https://static.crates.io/crates/anstream/anstream-1.0.0.crate"
30 -
CHECKSUM_SHA256_7="824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
31 -
SOURCE_FILENAME_7="anstream-1.0.0.tar.gz"
32 -
SOURCE_URI_8="https://static.crates.io/crates/anstyle/anstyle-1.0.14.crate"
33 -
CHECKSUM_SHA256_8="940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
34 -
SOURCE_FILENAME_8="anstyle-1.0.14.tar.gz"
35 -
SOURCE_URI_9="https://static.crates.io/crates/anstyle-parse/anstyle-parse-1.0.0.crate"
36 -
CHECKSUM_SHA256_9="52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
37 -
SOURCE_FILENAME_9="anstyle-parse-1.0.0.tar.gz"
38 -
SOURCE_URI_10="https://static.crates.io/crates/anstyle-query/anstyle-query-1.1.5.crate"
39 -
CHECKSUM_SHA256_10="40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
40 -
SOURCE_FILENAME_10="anstyle-query-1.1.5.tar.gz"
41 -
SOURCE_URI_11="https://static.crates.io/crates/anstyle-wincon/anstyle-wincon-3.0.11.crate"
42 -
CHECKSUM_SHA256_11="291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
43 -
SOURCE_FILENAME_11="anstyle-wincon-3.0.11.tar.gz"
44 -
SOURCE_URI_12="https://static.crates.io/crates/anyhow/anyhow-1.0.104.crate"
45 -
CHECKSUM_SHA256_12="330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
46 -
SOURCE_FILENAME_12="anyhow-1.0.104.tar.gz"
47 -
SOURCE_URI_13="https://static.crates.io/crates/arbitrary/arbitrary-1.4.2.crate"
48 -
CHECKSUM_SHA256_13="c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
49 -
SOURCE_FILENAME_13="arbitrary-1.4.2.tar.gz"
50 -
SOURCE_URI_14="https://static.crates.io/crates/arc-swap/arc-swap-1.9.2.crate"
51 -
CHECKSUM_SHA256_14="c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
52 -
SOURCE_FILENAME_14="arc-swap-1.9.2.tar.gz"
53 -
SOURCE_URI_15="https://static.crates.io/crates/argon2/argon2-0.5.3.crate"
54 -
CHECKSUM_SHA256_15="3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
55 -
SOURCE_FILENAME_15="argon2-0.5.3.tar.gz"
56 -
SOURCE_URI_16="https://static.crates.io/crates/arrayvec/arrayvec-0.7.8.crate"
57 -
CHECKSUM_SHA256_16="d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
58 -
SOURCE_FILENAME_16="arrayvec-0.7.8.tar.gz"
59 -
SOURCE_URI_17="https://static.crates.io/crates/askama/askama-0.14.0.crate"
60 -
CHECKSUM_SHA256_17="f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
61 -
SOURCE_FILENAME_17="askama-0.14.0.tar.gz"
62 -
SOURCE_URI_18="https://static.crates.io/crates/askama_derive/askama_derive-0.14.0.crate"
63 -
CHECKSUM_SHA256_18="129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
64 -
SOURCE_FILENAME_18="askama_derive-0.14.0.tar.gz"
65 -
SOURCE_URI_19="https://static.crates.io/crates/askama_parser/askama_parser-0.14.0.crate"
66 -
CHECKSUM_SHA256_19="d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
67 -
SOURCE_FILENAME_19="askama_parser-0.14.0.tar.gz"
68 -
SOURCE_URI_20="https://static.crates.io/crates/asn1-rs/asn1-rs-0.7.2.crate"
69 -
CHECKSUM_SHA256_20="b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
70 -
SOURCE_FILENAME_20="asn1-rs-0.7.2.tar.gz"
71 -
SOURCE_URI_21="https://static.crates.io/crates/asn1-rs-derive/asn1-rs-derive-0.6.0.crate"
72 -
CHECKSUM_SHA256_21="3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
73 -
SOURCE_FILENAME_21="asn1-rs-derive-0.6.0.tar.gz"
74 -
SOURCE_URI_22="https://static.crates.io/crates/asn1-rs-impl/asn1-rs-impl-0.2.0.crate"
75 -
CHECKSUM_SHA256_22="7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
76 -
SOURCE_FILENAME_22="asn1-rs-impl-0.2.0.tar.gz"
77 -
SOURCE_URI_23="https://static.crates.io/crates/async-compression/async-compression-0.4.48.crate"
78 -
CHECKSUM_SHA256_23="fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd"
79 -
SOURCE_FILENAME_23="async-compression-0.4.48.tar.gz"
80 -
SOURCE_URI_24="https://static.crates.io/crates/async-trait/async-trait-0.1.92.crate"
81 -
CHECKSUM_SHA256_24="82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
82 -
SOURCE_FILENAME_24="async-trait-0.1.92.tar.gz"
83 -
SOURCE_URI_25="https://static.crates.io/crates/atoi/atoi-2.0.0.crate"
84 -
CHECKSUM_SHA256_25="f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
85 -
SOURCE_FILENAME_25="atoi-2.0.0.tar.gz"
86 -
SOURCE_URI_26="https://static.crates.io/crates/atomic-waker/atomic-waker-1.1.2.crate"
87 -
CHECKSUM_SHA256_26="1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
88 -
SOURCE_FILENAME_26="atomic-waker-1.1.2.tar.gz"
89 -
SOURCE_URI_27="https://static.crates.io/crates/autocfg/autocfg-1.5.1.crate"
90 -
CHECKSUM_SHA256_27="f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
91 -
SOURCE_FILENAME_27="autocfg-1.5.1.tar.gz"
92 -
SOURCE_URI_28="https://static.crates.io/crates/axum/axum-0.8.9.crate"
93 -
CHECKSUM_SHA256_28="31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
94 -
SOURCE_FILENAME_28="axum-0.8.9.tar.gz"
95 -
SOURCE_URI_29="https://static.crates.io/crates/axum-core/axum-core-0.5.6.crate"
96 -
CHECKSUM_SHA256_29="08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
97 -
SOURCE_FILENAME_29="axum-core-0.5.6.tar.gz"
98 -
SOURCE_URI_30="https://static.crates.io/crates/axum-extra/axum-extra-0.10.3.crate"
99 -
CHECKSUM_SHA256_30="9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
100 -
SOURCE_FILENAME_30="axum-extra-0.10.3.tar.gz"
101 -
SOURCE_URI_31="https://static.crates.io/crates/axum-macros/axum-macros-0.5.1.crate"
102 -
CHECKSUM_SHA256_31="7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
103 -
SOURCE_FILENAME_31="axum-macros-0.5.1.tar.gz"
104 -
SOURCE_URI_32="https://static.crates.io/crates/axum-server/axum-server-0.8.0.crate"
105 -
CHECKSUM_SHA256_32="b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc"
106 -
SOURCE_FILENAME_32="axum-server-0.8.0.tar.gz"
107 -
SOURCE_URI_33="https://static.crates.io/crates/base32/base32-0.5.1.crate"
108 -
CHECKSUM_SHA256_33="022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
109 -
SOURCE_FILENAME_33="base32-0.5.1.tar.gz"
110 -
SOURCE_URI_34="https://static.crates.io/crates/base64/base64-0.22.1.crate"
111 -
CHECKSUM_SHA256_34="72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
112 -
SOURCE_FILENAME_34="base64-0.22.1.tar.gz"
113 -
SOURCE_URI_35="https://static.crates.io/crates/base64/base64-0.23.1.crate"
114 -
CHECKSUM_SHA256_35="ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
115 -
SOURCE_FILENAME_35="base64-0.23.1.tar.gz"
116 -
SOURCE_URI_36="https://static.crates.io/crates/base64ct/base64ct-1.8.3.crate"
117 -
CHECKSUM_SHA256_36="2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
118 -
SOURCE_FILENAME_36="base64ct-1.8.3.tar.gz"
119 -
SOURCE_URI_37="https://static.crates.io/crates/basic-toml/basic-toml-0.1.10.crate"
120 -
CHECKSUM_SHA256_37="ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
121 -
SOURCE_FILENAME_37="basic-toml-0.1.10.tar.gz"
122 -
SOURCE_URI_38="https://static.crates.io/crates/bincode/bincode-1.3.3.crate"
123 -
CHECKSUM_SHA256_38="b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
124 -
SOURCE_FILENAME_38="bincode-1.3.3.tar.gz"
125 -
SOURCE_URI_39="https://static.crates.io/crates/bit-set/bit-set-0.8.0.crate"
126 -
CHECKSUM_SHA256_39="08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
127 -
SOURCE_FILENAME_39="bit-set-0.8.0.tar.gz"
128 -
SOURCE_URI_40="https://static.crates.io/crates/bit-vec/bit-vec-0.8.0.crate"
129 -
CHECKSUM_SHA256_40="5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
130 -
SOURCE_FILENAME_40="bit-vec-0.8.0.tar.gz"
131 -
SOURCE_URI_41="https://static.crates.io/crates/bit-vec/bit-vec-0.9.1.crate"
132 -
CHECKSUM_SHA256_41="b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
133 -
SOURCE_FILENAME_41="bit-vec-0.9.1.tar.gz"
134 -
SOURCE_URI_42="https://static.crates.io/crates/bitflags/bitflags-2.13.2.crate"
135 -
CHECKSUM_SHA256_42="3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
136 -
SOURCE_FILENAME_42="bitflags-2.13.2.tar.gz"
137 -
SOURCE_URI_43="https://static.crates.io/crates/blake2/blake2-0.10.6.crate"
138 -
CHECKSUM_SHA256_43="46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
139 -
SOURCE_FILENAME_43="blake2-0.10.6.tar.gz"
140 -
SOURCE_URI_44="https://static.crates.io/crates/blake3/blake3-1.8.7.crate"
141 -
CHECKSUM_SHA256_44="6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
142 -
SOURCE_FILENAME_44="blake3-1.8.7.tar.gz"
143 -
SOURCE_URI_45="https://static.crates.io/crates/block-buffer/block-buffer-0.10.4.crate"
144 -
CHECKSUM_SHA256_45="3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
145 -
SOURCE_FILENAME_45="block-buffer-0.10.4.tar.gz"
146 -
SOURCE_URI_46="https://static.crates.io/crates/block-buffer/block-buffer-0.12.1.crate"
147 -
CHECKSUM_SHA256_46="d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
148 -
SOURCE_FILENAME_46="block-buffer-0.12.1.tar.gz"
149 -
SOURCE_URI_47="https://static.crates.io/crates/bstr/bstr-1.13.1.crate"
150 -
CHECKSUM_SHA256_47="6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
151 -
SOURCE_FILENAME_47="bstr-1.13.1.tar.gz"
152 -
SOURCE_URI_48="https://static.crates.io/crates/bumpalo/bumpalo-3.20.3.crate"
153 -
CHECKSUM_SHA256_48="72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
154 -
SOURCE_FILENAME_48="bumpalo-3.20.3.tar.gz"
155 -
SOURCE_URI_49="https://static.crates.io/crates/bytemuck/bytemuck-1.25.2.crate"
156 -
CHECKSUM_SHA256_49="95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
157 -
SOURCE_FILENAME_49="bytemuck-1.25.2.tar.gz"
158 -
SOURCE_URI_50="https://static.crates.io/crates/byteorder/byteorder-1.5.0.crate"
159 -
CHECKSUM_SHA256_50="1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
160 -
SOURCE_FILENAME_50="byteorder-1.5.0.tar.gz"
161 -
SOURCE_URI_51="https://static.crates.io/crates/byteorder-lite/byteorder-lite-0.1.0.crate"
162 -
CHECKSUM_SHA256_51="8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
163 -
SOURCE_FILENAME_51="byteorder-lite-0.1.0.tar.gz"
164 -
SOURCE_URI_52="https://static.crates.io/crates/bytes/bytes-1.12.1.crate"
165 -
CHECKSUM_SHA256_52="fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
166 -
SOURCE_FILENAME_52="bytes-1.12.1.tar.gz"
167 -
SOURCE_URI_53="https://static.crates.io/crates/cc/cc-1.5.1.crate"
168 -
CHECKSUM_SHA256_53="f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
169 -
SOURCE_FILENAME_53="cc-1.5.1.tar.gz"
170 -
SOURCE_URI_54="https://static.crates.io/crates/cfg-if/cfg-if-1.0.5.crate"
171 -
CHECKSUM_SHA256_54="4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
172 -
SOURCE_FILENAME_54="cfg-if-1.0.5.tar.gz"
173 -
SOURCE_URI_55="https://static.crates.io/crates/cfg_aliases/cfg_aliases-0.2.2.crate"
174 -
CHECKSUM_SHA256_55="f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
175 -
SOURCE_FILENAME_55="cfg_aliases-0.2.2.tar.gz"
176 -
SOURCE_URI_56="https://static.crates.io/crates/chacha20/chacha20-0.10.2.crate"
177 -
CHECKSUM_SHA256_56="65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
178 -
SOURCE_FILENAME_56="chacha20-0.10.2.tar.gz"
179 -
SOURCE_URI_57="https://static.crates.io/crates/chrono/chrono-0.4.45.crate"
180 -
CHECKSUM_SHA256_57="1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
181 -
SOURCE_FILENAME_57="chrono-0.4.45.tar.gz"
182 -
SOURCE_URI_58="https://static.crates.io/crates/clap/clap-4.6.7.crate"
183 -
CHECKSUM_SHA256_58="aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
184 -
SOURCE_FILENAME_58="clap-4.6.7.tar.gz"
185 -
SOURCE_URI_59="https://static.crates.io/crates/clap_builder/clap_builder-4.6.7.crate"
186 -
CHECKSUM_SHA256_59="ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
187 -
SOURCE_FILENAME_59="clap_builder-4.6.7.tar.gz"
188 -
SOURCE_URI_60="https://static.crates.io/crates/clap_derive/clap_derive-4.6.7.crate"
189 -
CHECKSUM_SHA256_60="f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
190 -
SOURCE_FILENAME_60="clap_derive-4.6.7.tar.gz"
191 -
SOURCE_URI_61="https://static.crates.io/crates/clap_lex/clap_lex-1.1.1.crate"
192 -
CHECKSUM_SHA256_61="1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
193 -
SOURCE_FILENAME_61="clap_lex-1.1.1.tar.gz"
194 -
SOURCE_URI_62="https://static.crates.io/crates/cmov/cmov-0.5.4.crate"
195 -
CHECKSUM_SHA256_62="0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
196 -
SOURCE_FILENAME_62="cmov-0.5.4.tar.gz"
197 -
SOURCE_URI_63="https://static.crates.io/crates/colorchoice/colorchoice-1.0.5.crate"
198 -
CHECKSUM_SHA256_63="1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
199 -
SOURCE_FILENAME_63="colorchoice-1.0.5.tar.gz"
200 -
SOURCE_URI_64="https://static.crates.io/crates/compression-codecs/compression-codecs-0.4.43.crate"
201 -
CHECKSUM_SHA256_64="bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
202 -
SOURCE_FILENAME_64="compression-codecs-0.4.43.tar.gz"
203 -
SOURCE_URI_65="https://static.crates.io/crates/compression-core/compression-core-0.4.33.crate"
204 -
CHECKSUM_SHA256_65="6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414"
205 -
SOURCE_FILENAME_65="compression-core-0.4.33.tar.gz"
206 -
SOURCE_URI_66="https://static.crates.io/crates/const-oid/const-oid-0.10.2.crate"
207 -
CHECKSUM_SHA256_66="a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
208 -
SOURCE_FILENAME_66="const-oid-0.10.2.tar.gz"
209 -
SOURCE_URI_67="https://static.crates.io/crates/const-oid/const-oid-0.9.6.crate"
210 -
CHECKSUM_SHA256_67="c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
211 -
SOURCE_FILENAME_67="const-oid-0.9.6.tar.gz"
212 -
SOURCE_URI_68="https://static.crates.io/crates/constant_time_eq/constant_time_eq-0.4.2.crate"
213 -
CHECKSUM_SHA256_68="3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
214 -
SOURCE_FILENAME_68="constant_time_eq-0.4.2.tar.gz"
215 -
SOURCE_URI_69="https://static.crates.io/crates/cookie/cookie-0.18.2.crate"
216 -
CHECKSUM_SHA256_69="1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
217 -
SOURCE_FILENAME_69="cookie-0.18.2.tar.gz"
218 -
SOURCE_URI_70="https://static.crates.io/crates/core_detect/core_detect-1.0.0.crate"
219 -
CHECKSUM_SHA256_70="7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
220 -
SOURCE_FILENAME_70="core_detect-1.0.0.tar.gz"
221 -
SOURCE_URI_71="https://static.crates.io/crates/cpufeatures/cpufeatures-0.2.17.crate"
222 -
CHECKSUM_SHA256_71="59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
223 -
SOURCE_FILENAME_71="cpufeatures-0.2.17.tar.gz"
224 -
SOURCE_URI_72="https://static.crates.io/crates/cpufeatures/cpufeatures-0.3.1.crate"
225 -
CHECKSUM_SHA256_72="5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
226 -
SOURCE_FILENAME_72="cpufeatures-0.3.1.tar.gz"
227 -
SOURCE_URI_73="https://static.crates.io/crates/crc/crc-3.4.0.crate"
228 -
CHECKSUM_SHA256_73="5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
229 -
SOURCE_FILENAME_73="crc-3.4.0.tar.gz"
230 -
SOURCE_URI_74="https://static.crates.io/crates/crc-catalog/crc-catalog-2.5.0.crate"
231 -
CHECKSUM_SHA256_74="217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
232 -
SOURCE_FILENAME_74="crc-catalog-2.5.0.tar.gz"
233 -
SOURCE_URI_75="https://static.crates.io/crates/crc32fast/crc32fast-1.5.2.crate"
234 -
CHECKSUM_SHA256_75="01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
235 -
SOURCE_FILENAME_75="crc32fast-1.5.2.tar.gz"
236 -
SOURCE_URI_76="https://static.crates.io/crates/crossbeam-deque/crossbeam-deque-0.8.8.crate"
237 -
CHECKSUM_SHA256_76="622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
238 -
SOURCE_FILENAME_76="crossbeam-deque-0.8.8.tar.gz"
239 -
SOURCE_URI_77="https://static.crates.io/crates/crossbeam-epoch/crossbeam-epoch-0.9.21.crate"
240 -
CHECKSUM_SHA256_77="dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
241 -
SOURCE_FILENAME_77="crossbeam-epoch-0.9.21.tar.gz"
242 -
SOURCE_URI_78="https://static.crates.io/crates/crossbeam-queue/crossbeam-queue-0.3.14.crate"
243 -
CHECKSUM_SHA256_78="03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae"
244 -
SOURCE_FILENAME_78="crossbeam-queue-0.3.14.tar.gz"
245 -
SOURCE_URI_79="https://static.crates.io/crates/crossbeam-utils/crossbeam-utils-0.8.23.crate"
246 -
CHECKSUM_SHA256_79="a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
247 -
SOURCE_FILENAME_79="crossbeam-utils-0.8.23.tar.gz"
248 -
SOURCE_URI_80="https://static.crates.io/crates/crypto-common/crypto-common-0.1.7.crate"
249 -
CHECKSUM_SHA256_80="78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
250 -
SOURCE_FILENAME_80="crypto-common-0.1.7.tar.gz"
251 -
SOURCE_URI_81="https://static.crates.io/crates/crypto-common/crypto-common-0.2.2.crate"
252 -
CHECKSUM_SHA256_81="ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
253 -
SOURCE_FILENAME_81="crypto-common-0.2.2.tar.gz"
254 -
SOURCE_URI_82="https://static.crates.io/crates/cssparser/cssparser-0.38.0.crate"
255 -
CHECKSUM_SHA256_82="11119743ad110e8c1bdccd930d7f5c30c99e5fc76a7b63ec9807e84eef0c5f59"
256 -
SOURCE_FILENAME_82="cssparser-0.38.0.tar.gz"
257 -
SOURCE_URI_83="https://static.crates.io/crates/ctutils/ctutils-0.4.2.crate"
258 -
CHECKSUM_SHA256_83="7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
259 -
SOURCE_FILENAME_83="ctutils-0.4.2.tar.gz"
260 -
SOURCE_URI_84="https://static.crates.io/crates/curve25519-dalek/curve25519-dalek-5.0.0.crate"
261 -
CHECKSUM_SHA256_84="b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
262 -
SOURCE_FILENAME_84="curve25519-dalek-5.0.0.tar.gz"
263 -
SOURCE_URI_85="https://static.crates.io/crates/curve25519-dalek-derive/curve25519-dalek-derive-0.1.1.crate"
264 -
CHECKSUM_SHA256_85="f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
265 -
SOURCE_FILENAME_85="curve25519-dalek-derive-0.1.1.tar.gz"
266 -
SOURCE_URI_86="https://static.crates.io/crates/data-encoding/data-encoding-2.11.1.crate"
267 -
CHECKSUM_SHA256_86="4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
268 -
SOURCE_FILENAME_86="data-encoding-2.11.1.tar.gz"
269 -
SOURCE_URI_87="https://static.crates.io/crates/der/der-0.7.10.crate"
270 -
CHECKSUM_SHA256_87="e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
271 -
SOURCE_FILENAME_87="der-0.7.10.tar.gz"
272 -
SOURCE_URI_88="https://static.crates.io/crates/der/der-0.8.2.crate"
273 -
CHECKSUM_SHA256_88="a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
274 -
SOURCE_FILENAME_88="der-0.8.2.tar.gz"
275 -
SOURCE_URI_89="https://static.crates.io/crates/der-parser/der-parser-10.0.0.crate"
276 -
CHECKSUM_SHA256_89="07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
277 -
SOURCE_FILENAME_89="der-parser-10.0.0.tar.gz"
278 -
SOURCE_URI_90="https://static.crates.io/crates/deranged/deranged-0.5.8.crate"
279 -
CHECKSUM_SHA256_90="7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
280 -
SOURCE_FILENAME_90="deranged-0.5.8.tar.gz"
281 -
SOURCE_URI_91="https://static.crates.io/crates/derive_arbitrary/derive_arbitrary-1.4.2.crate"
282 -
CHECKSUM_SHA256_91="1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
283 -
SOURCE_FILENAME_91="derive_arbitrary-1.4.2.tar.gz"
284 -
SOURCE_URI_92="https://static.crates.io/crates/digest/digest-0.10.7.crate"
285 -
CHECKSUM_SHA256_92="9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
286 -
SOURCE_FILENAME_92="digest-0.10.7.tar.gz"
287 -
SOURCE_URI_93="https://static.crates.io/crates/digest/digest-0.11.3.crate"
288 -
CHECKSUM_SHA256_93="f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
289 -
SOURCE_FILENAME_93="digest-0.11.3.tar.gz"
290 -
SOURCE_URI_94="https://static.crates.io/crates/displaydoc/displaydoc-0.2.7.crate"
291 -
CHECKSUM_SHA256_94="c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
292 -
SOURCE_FILENAME_94="displaydoc-0.2.7.tar.gz"
293 -
SOURCE_URI_95="https://static.crates.io/crates/dotenvy/dotenvy-0.15.7.crate"
294 -
CHECKSUM_SHA256_95="1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
295 -
SOURCE_FILENAME_95="dotenvy-0.15.7.tar.gz"
296 -
SOURCE_URI_96="https://static.crates.io/crates/dtoa/dtoa-1.0.11.crate"
297 -
CHECKSUM_SHA256_96="4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
298 -
SOURCE_FILENAME_96="dtoa-1.0.11.tar.gz"
299 -
SOURCE_URI_97="https://static.crates.io/crates/dtoa-short/dtoa-short-0.3.5.crate"
300 -
CHECKSUM_SHA256_97="cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
301 -
SOURCE_FILENAME_97="dtoa-short-0.3.5.tar.gz"
302 -
SOURCE_URI_98="https://static.crates.io/crates/dyn-clone/dyn-clone-1.0.20.crate"
303 -
CHECKSUM_SHA256_98="d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
304 -
SOURCE_FILENAME_98="dyn-clone-1.0.20.tar.gz"
305 -
SOURCE_URI_99="https://static.crates.io/crates/ed25519/ed25519-3.0.0.crate"
306 -
CHECKSUM_SHA256_99="29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
307 -
SOURCE_FILENAME_99="ed25519-3.0.0.tar.gz"
308 -
SOURCE_URI_100="https://static.crates.io/crates/ed25519-dalek/ed25519-dalek-3.0.0.crate"
309 -
CHECKSUM_SHA256_100="6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
310 -
SOURCE_FILENAME_100="ed25519-dalek-3.0.0.tar.gz"
311 -
SOURCE_URI_101="https://static.crates.io/crates/either/either-1.18.0.crate"
312 -
CHECKSUM_SHA256_101="252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
313 -
SOURCE_FILENAME_101="either-1.18.0.tar.gz"
314 -
SOURCE_URI_102="https://static.crates.io/crates/email-encoding/email-encoding-0.4.2.crate"
315 -
CHECKSUM_SHA256_102="420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd"
316 -
SOURCE_FILENAME_102="email-encoding-0.4.2.tar.gz"
317 -
SOURCE_URI_103="https://static.crates.io/crates/email_address/email_address-0.2.9.crate"
318 -
CHECKSUM_SHA256_103="e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
319 -
SOURCE_FILENAME_103="email_address-0.2.9.tar.gz"
320 -
SOURCE_URI_104="https://static.crates.io/crates/encoding_rs/encoding_rs-0.8.41.crate"
321 -
CHECKSUM_SHA256_104="7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a"
322 -
SOURCE_FILENAME_104="encoding_rs-0.8.41.tar.gz"
323 -
SOURCE_URI_105="https://static.crates.io/crates/equivalent/equivalent-1.0.2.crate"
324 -
CHECKSUM_SHA256_105="877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
325 -
SOURCE_FILENAME_105="equivalent-1.0.2.tar.gz"
326 -
SOURCE_URI_106="https://static.crates.io/crates/errno/errno-0.3.14.crate"
327 -
CHECKSUM_SHA256_106="39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
328 -
SOURCE_FILENAME_106="errno-0.3.14.tar.gz"
329 -
SOURCE_URI_107="https://static.crates.io/crates/etcetera/etcetera-0.8.0.crate"
330 -
CHECKSUM_SHA256_107="136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
331 -
SOURCE_FILENAME_107="etcetera-0.8.0.tar.gz"
332 -
SOURCE_URI_108="https://static.crates.io/crates/event-listener/event-listener-5.4.2.crate"
333 -
CHECKSUM_SHA256_108="5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
334 -
SOURCE_FILENAME_108="event-listener-5.4.2.tar.gz"
335 -
SOURCE_URI_109="https://static.crates.io/crates/fancy-regex/fancy-regex-0.16.2.crate"
336 -
CHECKSUM_SHA256_109="998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
337 -
SOURCE_FILENAME_109="fancy-regex-0.16.2.tar.gz"
338 -
SOURCE_URI_110="https://static.crates.io/crates/fastrand/fastrand-2.5.0.crate"
339 -
CHECKSUM_SHA256_110="da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
340 -
SOURCE_FILENAME_110="fastrand-2.5.0.tar.gz"
341 -
SOURCE_URI_111="https://static.crates.io/crates/fdeflate/fdeflate-0.3.7.crate"
342 -
CHECKSUM_SHA256_111="1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
343 -
SOURCE_FILENAME_111="fdeflate-0.3.7.tar.gz"
344 -
SOURCE_URI_112="https://static.crates.io/crates/fiat-crypto/fiat-crypto-0.3.0.crate"
345 -
CHECKSUM_SHA256_112="64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
346 -
SOURCE_FILENAME_112="fiat-crypto-0.3.0.tar.gz"
347 -
SOURCE_URI_113="https://static.crates.io/crates/filetime/filetime-0.2.29.crate"
348 -
CHECKSUM_SHA256_113="5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
349 -
SOURCE_FILENAME_113="filetime-0.2.29.tar.gz"
350 -
SOURCE_URI_114="https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.14.crate"
351 -
CHECKSUM_SHA256_114="aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
352 -
SOURCE_FILENAME_114="find-msvc-tools-0.1.14.tar.gz"
353 -
SOURCE_URI_115="https://static.crates.io/crates/flate2/flate2-1.1.10.crate"
354 -
CHECKSUM_SHA256_115="6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
355 -
SOURCE_FILENAME_115="flate2-1.1.10.tar.gz"
356 -
SOURCE_URI_116="https://static.crates.io/crates/flume/flume-0.11.1.crate"
357 -
CHECKSUM_SHA256_116="da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
358 -
SOURCE_FILENAME_116="flume-0.11.1.tar.gz"
359 -
SOURCE_URI_117="https://static.crates.io/crates/fnv/fnv-1.0.7.crate"
360 -
CHECKSUM_SHA256_117="3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
361 -
SOURCE_FILENAME_117="fnv-1.0.7.tar.gz"
362 -
SOURCE_URI_118="https://static.crates.io/crates/foldhash/foldhash-0.1.5.crate"
363 -
CHECKSUM_SHA256_118="d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
364 -
SOURCE_FILENAME_118="foldhash-0.1.5.tar.gz"
365 -
SOURCE_URI_119="https://static.crates.io/crates/form_urlencoded/form_urlencoded-1.2.2.crate"
366 -
CHECKSUM_SHA256_119="cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
367 -
SOURCE_FILENAME_119="form_urlencoded-1.2.2.tar.gz"
368 -
SOURCE_URI_120="https://static.crates.io/crates/fs-err/fs-err-3.3.1.crate"
369 -
CHECKSUM_SHA256_120="b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
370 -
SOURCE_FILENAME_120="fs-err-3.3.1.tar.gz"
371 -
SOURCE_URI_121="https://static.crates.io/crates/futures/futures-0.3.34.crate"
372 -
CHECKSUM_SHA256_121="9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
373 -
SOURCE_FILENAME_121="futures-0.3.34.tar.gz"
374 -
SOURCE_URI_122="https://static.crates.io/crates/futures-channel/futures-channel-0.3.34.crate"
375 -
CHECKSUM_SHA256_122="b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
376 -
SOURCE_FILENAME_122="futures-channel-0.3.34.tar.gz"
377 -
SOURCE_URI_123="https://static.crates.io/crates/futures-core/futures-core-0.3.34.crate"
378 -
CHECKSUM_SHA256_123="92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
379 -
SOURCE_FILENAME_123="futures-core-0.3.34.tar.gz"
380 -
SOURCE_URI_124="https://static.crates.io/crates/futures-executor/futures-executor-0.3.34.crate"
381 -
CHECKSUM_SHA256_124="031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
382 -
SOURCE_FILENAME_124="futures-executor-0.3.34.tar.gz"
383 -
SOURCE_URI_125="https://static.crates.io/crates/futures-intrusive/futures-intrusive-0.5.0.crate"
384 -
CHECKSUM_SHA256_125="1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
385 -
SOURCE_FILENAME_125="futures-intrusive-0.5.0.tar.gz"
386 -
SOURCE_URI_126="https://static.crates.io/crates/futures-io/futures-io-0.3.34.crate"
387 -
CHECKSUM_SHA256_126="53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
388 -
SOURCE_FILENAME_126="futures-io-0.3.34.tar.gz"
389 -
SOURCE_URI_127="https://static.crates.io/crates/futures-macro/futures-macro-0.3.34.crate"
390 -
CHECKSUM_SHA256_127="9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
391 -
SOURCE_FILENAME_127="futures-macro-0.3.34.tar.gz"
392 -
SOURCE_URI_128="https://static.crates.io/crates/futures-sink/futures-sink-0.3.34.crate"
393 -
CHECKSUM_SHA256_128="1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
394 -
SOURCE_FILENAME_128="futures-sink-0.3.34.tar.gz"
395 -
SOURCE_URI_129="https://static.crates.io/crates/futures-task/futures-task-0.3.34.crate"
396 -
CHECKSUM_SHA256_129="cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
397 -
SOURCE_FILENAME_129="futures-task-0.3.34.tar.gz"
398 -
SOURCE_URI_130="https://static.crates.io/crates/futures-util/futures-util-0.3.34.crate"
399 -
CHECKSUM_SHA256_130="0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
400 -
SOURCE_FILENAME_130="futures-util-0.3.34.tar.gz"
401 -
SOURCE_URI_131="https://static.crates.io/crates/generic-array/generic-array-0.14.7.crate"
402 -
CHECKSUM_SHA256_131="85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
403 -
SOURCE_FILENAME_131="generic-array-0.14.7.tar.gz"
404 -
SOURCE_URI_132="https://static.crates.io/crates/getrandom/getrandom-0.2.17.crate"
405 -
CHECKSUM_SHA256_132="ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
406 -
SOURCE_FILENAME_132="getrandom-0.2.17.tar.gz"
407 -
SOURCE_URI_133="https://static.crates.io/crates/getrandom/getrandom-0.4.3.crate"
408 -
CHECKSUM_SHA256_133="300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
409 -
SOURCE_FILENAME_133="getrandom-0.4.3.tar.gz"
410 -
SOURCE_URI_134="https://static.crates.io/crates/globset/globset-0.4.20.crate"
411 -
CHECKSUM_SHA256_134="07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
412 -
SOURCE_FILENAME_134="globset-0.4.20.tar.gz"
413 -
SOURCE_URI_135="https://static.crates.io/crates/h2/h2-0.4.19.crate"
414 -
CHECKSUM_SHA256_135="ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
415 -
SOURCE_FILENAME_135="h2-0.4.19.tar.gz"
416 -
SOURCE_URI_136="https://static.crates.io/crates/hashbrown/hashbrown-0.15.5.crate"
417 -
CHECKSUM_SHA256_136="9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
418 -
SOURCE_FILENAME_136="hashbrown-0.15.5.tar.gz"
419 -
SOURCE_URI_137="https://static.crates.io/crates/hashbrown/hashbrown-0.17.1.crate"
420 -
CHECKSUM_SHA256_137="ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
421 -
SOURCE_FILENAME_137="hashbrown-0.17.1.tar.gz"
422 -
SOURCE_URI_138="https://static.crates.io/crates/hashlink/hashlink-0.10.0.crate"
423 -
CHECKSUM_SHA256_138="7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
424 -
SOURCE_FILENAME_138="hashlink-0.10.0.tar.gz"
425 -
SOURCE_URI_139="https://static.crates.io/crates/heck/heck-0.5.0.crate"
426 -
CHECKSUM_SHA256_139="2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
427 -
SOURCE_FILENAME_139="heck-0.5.0.tar.gz"
428 -
SOURCE_URI_140="https://static.crates.io/crates/hex/hex-0.4.3.crate"
429 -
CHECKSUM_SHA256_140="7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
430 -
SOURCE_FILENAME_140="hex-0.4.3.tar.gz"
431 -
SOURCE_URI_141="https://static.crates.io/crates/hkdf/hkdf-0.12.4.crate"
432 -
CHECKSUM_SHA256_141="7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
433 -
SOURCE_FILENAME_141="hkdf-0.12.4.tar.gz"
434 -
SOURCE_URI_142="https://static.crates.io/crates/hmac/hmac-0.12.1.crate"
435 -
CHECKSUM_SHA256_142="6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
436 -
SOURCE_FILENAME_142="hmac-0.12.1.tar.gz"
437 -
SOURCE_URI_143="https://static.crates.io/crates/home/home-0.5.12.crate"
438 -
CHECKSUM_SHA256_143="cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
439 -
SOURCE_FILENAME_143="home-0.5.12.tar.gz"
440 -
SOURCE_URI_144="https://static.crates.io/crates/html5ever/html5ever-0.40.1.crate"
441 -
CHECKSUM_SHA256_144="456a1a377e608e555d22ddab27ac0114bc7a7b4199078108e34c2aeae6c9b130"
442 -
SOURCE_FILENAME_144="html5ever-0.40.1.tar.gz"
443 -
SOURCE_URI_145="https://static.crates.io/crates/http/http-1.5.0.crate"
444 -
CHECKSUM_SHA256_145="918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
445 -
SOURCE_FILENAME_145="http-1.5.0.tar.gz"
446 -
SOURCE_URI_146="https://static.crates.io/crates/http-body/http-body-1.1.0.crate"
447 -
CHECKSUM_SHA256_146="ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
448 -
SOURCE_FILENAME_146="http-body-1.1.0.tar.gz"
449 -
SOURCE_URI_147="https://static.crates.io/crates/http-body-util/http-body-util-0.1.5.crate"
450 -
CHECKSUM_SHA256_147="23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
451 -
SOURCE_FILENAME_147="http-body-util-0.1.5.tar.gz"
452 -
SOURCE_URI_148="https://static.crates.io/crates/httparse/httparse-1.10.1.crate"
453 -
CHECKSUM_SHA256_148="6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
454 -
SOURCE_FILENAME_148="httparse-1.10.1.tar.gz"
455 -
SOURCE_URI_149="https://static.crates.io/crates/httpdate/httpdate-1.0.3.crate"
456 -
CHECKSUM_SHA256_149="df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
457 -
SOURCE_FILENAME_149="httpdate-1.0.3.tar.gz"
458 -
SOURCE_URI_150="https://static.crates.io/crates/humantime/humantime-2.4.0.crate"
459 -
CHECKSUM_SHA256_150="15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"
460 -
SOURCE_FILENAME_150="humantime-2.4.0.tar.gz"
461 -
SOURCE_URI_151="https://static.crates.io/crates/hybrid-array/hybrid-array-0.4.15.crate"
462 -
CHECKSUM_SHA256_151="27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
463 -
SOURCE_FILENAME_151="hybrid-array-0.4.15.tar.gz"
464 -
SOURCE_URI_152="https://static.crates.io/crates/hyper/hyper-1.11.1.crate"
465 -
CHECKSUM_SHA256_152="27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
466 -
SOURCE_FILENAME_152="hyper-1.11.1.tar.gz"
467 -
SOURCE_URI_153="https://static.crates.io/crates/hyper-rustls/hyper-rustls-0.27.10.crate"
468 -
CHECKSUM_SHA256_153="dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
469 -
SOURCE_FILENAME_153="hyper-rustls-0.27.10.tar.gz"
470 -
SOURCE_URI_154="https://static.crates.io/crates/hyper-util/hyper-util-0.1.21.crate"
471 -
CHECKSUM_SHA256_154="ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
472 -
SOURCE_FILENAME_154="hyper-util-0.1.21.tar.gz"
473 -
SOURCE_URI_155="https://static.crates.io/crates/iana-time-zone-haiku/iana-time-zone-haiku-0.1.2.crate"
474 -
CHECKSUM_SHA256_155="f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
475 -
SOURCE_FILENAME_155="iana-time-zone-haiku-0.1.2.tar.gz"
476 -
SOURCE_URI_156="https://static.crates.io/crates/icu_collections/icu_collections-2.3.0.crate"
477 -
CHECKSUM_SHA256_156="fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
478 -
SOURCE_FILENAME_156="icu_collections-2.3.0.tar.gz"
479 -
SOURCE_URI_157="https://static.crates.io/crates/icu_locale_core/icu_locale_core-2.3.0.crate"
480 -
CHECKSUM_SHA256_157="d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
481 -
SOURCE_FILENAME_157="icu_locale_core-2.3.0.tar.gz"
482 -
SOURCE_URI_158="https://static.crates.io/crates/icu_normalizer/icu_normalizer-2.3.0.crate"
483 -
CHECKSUM_SHA256_158="12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
484 -
SOURCE_FILENAME_158="icu_normalizer-2.3.0.tar.gz"
485 -
SOURCE_URI_159="https://static.crates.io/crates/icu_normalizer_data/icu_normalizer_data-2.3.0.crate"
486 -
CHECKSUM_SHA256_159="1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
487 -
SOURCE_FILENAME_159="icu_normalizer_data-2.3.0.tar.gz"
488 -
SOURCE_URI_160="https://static.crates.io/crates/icu_properties/icu_properties-2.3.0.crate"
489 -
CHECKSUM_SHA256_160="7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
490 -
SOURCE_FILENAME_160="icu_properties-2.3.0.tar.gz"
491 -
SOURCE_URI_161="https://static.crates.io/crates/icu_properties_data/icu_properties_data-2.3.0.crate"
492 -
CHECKSUM_SHA256_161="e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
493 -
SOURCE_FILENAME_161="icu_properties_data-2.3.0.tar.gz"
494 -
SOURCE_URI_162="https://static.crates.io/crates/icu_provider/icu_provider-2.3.1.crate"
495 -
CHECKSUM_SHA256_162="d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
496 -
SOURCE_FILENAME_162="icu_provider-2.3.1.tar.gz"
497 -
SOURCE_URI_163="https://static.crates.io/crates/idna/idna-1.1.0.crate"
498 -
CHECKSUM_SHA256_163="3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
499 -
SOURCE_FILENAME_163="idna-1.1.0.tar.gz"
500 -
SOURCE_URI_164="https://static.crates.io/crates/idna_adapter/idna_adapter-1.2.2.crate"
501 -
CHECKSUM_SHA256_164="cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
502 -
SOURCE_FILENAME_164="idna_adapter-1.2.2.tar.gz"
503 -
SOURCE_URI_165="https://static.crates.io/crates/ignore/ignore-0.4.33.crate"
504 -
CHECKSUM_SHA256_165="00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f"
505 -
SOURCE_FILENAME_165="ignore-0.4.33.tar.gz"
506 -
SOURCE_URI_166="https://static.crates.io/crates/image/image-0.25.10.crate"
507 -
CHECKSUM_SHA256_166="85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
508 -
SOURCE_FILENAME_166="image-0.25.10.tar.gz"
509 -
SOURCE_URI_167="https://static.crates.io/crates/image-webp/image-webp-0.2.4.crate"
510 -
CHECKSUM_SHA256_167="525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
511 -
SOURCE_FILENAME_167="image-webp-0.2.4.tar.gz"
512 -
SOURCE_URI_168="https://static.crates.io/crates/indexmap/indexmap-2.14.2.crate"
513 -
CHECKSUM_SHA256_168="cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
514 -
SOURCE_FILENAME_168="indexmap-2.14.2.tar.gz"
515 -
SOURCE_URI_169="https://static.crates.io/crates/ipnet/ipnet-2.12.2.crate"
516 -
CHECKSUM_SHA256_169="791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
517 -
SOURCE_FILENAME_169="ipnet-2.12.2.tar.gz"
518 -
SOURCE_URI_170="https://static.crates.io/crates/is_terminal_polyfill/is_terminal_polyfill-1.70.2.crate"
519 -
CHECKSUM_SHA256_170="a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
520 -
SOURCE_FILENAME_170="is_terminal_polyfill-1.70.2.tar.gz"
521 -
SOURCE_URI_171="https://static.crates.io/crates/itertools/itertools-0.15.0.crate"
522 -
CHECKSUM_SHA256_171="8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
523 -
SOURCE_FILENAME_171="itertools-0.15.0.tar.gz"
524 -
SOURCE_URI_172="https://static.crates.io/crates/itoa/itoa-1.0.18.crate"
525 -
CHECKSUM_SHA256_172="8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
526 -
SOURCE_FILENAME_172="itoa-1.0.18.tar.gz"
527 -
SOURCE_URI_173="https://static.crates.io/crates/jobserver/jobserver-0.1.35.crate"
528 -
CHECKSUM_SHA256_173="1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
529 -
SOURCE_FILENAME_173="jobserver-0.1.35.tar.gz"
530 -
SOURCE_URI_174="https://static.crates.io/crates/js-sys/js-sys-0.3.106.crate"
531 -
CHECKSUM_SHA256_174="7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
532 -
SOURCE_FILENAME_174="js-sys-0.3.106.tar.gz"
533 -
SOURCE_URI_175="https://static.crates.io/crates/kdl/kdl-6.7.1.crate"
534 -
CHECKSUM_SHA256_175="082ddf81b2acd76fe04412655d17befedfff1837db772f8e74c38050d25ed670"
535 -
SOURCE_FILENAME_175="kdl-6.7.1.tar.gz"
536 -
SOURCE_URI_176="https://static.crates.io/crates/keccak/keccak-0.2.2.crate"
537 -
CHECKSUM_SHA256_176="d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598"
538 -
SOURCE_FILENAME_176="keccak-0.2.2.tar.gz"
539 -
SOURCE_URI_177="https://static.crates.io/crates/lazy_static/lazy_static-1.5.0.crate"
540 -
CHECKSUM_SHA256_177="bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
541 -
SOURCE_FILENAME_177="lazy_static-1.5.0.tar.gz"
542 -
SOURCE_URI_178="https://static.crates.io/crates/lettre/lettre-0.11.23.crate"
543 -
CHECKSUM_SHA256_178="f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae"
544 -
SOURCE_FILENAME_178="lettre-0.11.23.tar.gz"
545 -
SOURCE_URI_179="https://static.crates.io/crates/libc/libc-0.2.189.crate"
546 -
CHECKSUM_SHA256_179="3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
547 -
SOURCE_FILENAME_179="libc-0.2.189.tar.gz"
548 -
SOURCE_URI_180="https://static.crates.io/crates/libm/libm-0.2.16.crate"
549 -
CHECKSUM_SHA256_180="b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
550 -
SOURCE_FILENAME_180="libm-0.2.16.tar.gz"
551 -
SOURCE_URI_181="https://static.crates.io/crates/libredox/libredox-0.1.25.crate"
552 -
CHECKSUM_SHA256_181="61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
553 -
SOURCE_FILENAME_181="libredox-0.1.25.tar.gz"
554 -
SOURCE_URI_182="https://static.crates.io/crates/libsqlite3-sys/libsqlite3-sys-0.30.1.crate"
555 -
CHECKSUM_SHA256_182="2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
556 -
SOURCE_FILENAME_182="libsqlite3-sys-0.30.1.tar.gz"
557 -
SOURCE_URI_183="https://static.crates.io/crates/linked-hash-map/linked-hash-map-0.5.6.crate"
558 -
CHECKSUM_SHA256_183="0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
559 -
SOURCE_FILENAME_183="linked-hash-map-0.5.6.tar.gz"
560 -
SOURCE_URI_184="https://static.crates.io/crates/linux-raw-sys/linux-raw-sys-0.12.1.crate"
561 -
CHECKSUM_SHA256_184="32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
562 -
SOURCE_FILENAME_184="linux-raw-sys-0.12.1.tar.gz"
563 -
SOURCE_URI_185="https://static.crates.io/crates/litemap/litemap-0.8.3.crate"
564 -
CHECKSUM_SHA256_185="47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
565 -
SOURCE_FILENAME_185="litemap-0.8.3.tar.gz"
566 -
SOURCE_URI_186="https://static.crates.io/crates/lock_api/lock_api-0.4.14.crate"
567 -
CHECKSUM_SHA256_186="224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
568 -
SOURCE_FILENAME_186="lock_api-0.4.14.tar.gz"
569 -
SOURCE_URI_187="https://static.crates.io/crates/log/log-0.4.34.crate"
570 -
CHECKSUM_SHA256_187="f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
571 -
SOURCE_FILENAME_187="log-0.4.34.tar.gz"
572 -
SOURCE_URI_188="https://static.crates.io/crates/lru-slab/lru-slab-0.1.3.crate"
573 -
CHECKSUM_SHA256_188="4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
574 -
SOURCE_FILENAME_188="lru-slab-0.1.3.tar.gz"
575 -
SOURCE_URI_189="https://static.crates.io/crates/lzma-rs/lzma-rs-0.3.0.crate"
576 -
CHECKSUM_SHA256_189="297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
577 -
SOURCE_FILENAME_189="lzma-rs-0.3.0.tar.gz"
578 -
SOURCE_URI_190="https://static.crates.io/crates/maplit/maplit-1.0.2.crate"
579 -
CHECKSUM_SHA256_190="3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
580 -
SOURCE_FILENAME_190="maplit-1.0.2.tar.gz"
581 -
SOURCE_URI_191="https://static.crates.io/crates/markup5ever/markup5ever-0.40.0.crate"
582 -
CHECKSUM_SHA256_191="0ab3dc68ac4a0f5719e560136778c1ee716e296030d75dbd4484e37e39e3a842"
583 -
SOURCE_FILENAME_191="markup5ever-0.40.0.tar.gz"
584 -
SOURCE_URI_192="https://static.crates.io/crates/matchers/matchers-0.2.0.crate"
585 -
CHECKSUM_SHA256_192="d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
586 -
SOURCE_FILENAME_192="matchers-0.2.0.tar.gz"
587 -
SOURCE_URI_193="https://static.crates.io/crates/matchit/matchit-0.8.4.crate"
588 -
CHECKSUM_SHA256_193="47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
589 -
SOURCE_FILENAME_193="matchit-0.8.4.tar.gz"
590 -
SOURCE_URI_194="https://static.crates.io/crates/md-5/md-5-0.10.6.crate"
591 -
CHECKSUM_SHA256_194="d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
592 -
SOURCE_FILENAME_194="md-5-0.10.6.tar.gz"
593 -
SOURCE_URI_195="https://static.crates.io/crates/md-5/md-5-0.11.0.crate"
594 -
CHECKSUM_SHA256_195="69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
595 -
SOURCE_FILENAME_195="md-5-0.11.0.tar.gz"
596 -
SOURCE_URI_196="https://static.crates.io/crates/memchr/memchr-2.8.3.crate"
597 -
CHECKSUM_SHA256_196="cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
598 -
SOURCE_FILENAME_196="memchr-2.8.3.tar.gz"
599 -
SOURCE_URI_197="https://static.crates.io/crates/miette/miette-7.6.0.crate"
600 -
CHECKSUM_SHA256_197="5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
601 -
SOURCE_FILENAME_197="miette-7.6.0.tar.gz"
602 -
SOURCE_URI_198="https://static.crates.io/crates/miette-derive/miette-derive-7.6.0.crate"
603 -
CHECKSUM_SHA256_198="db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
604 -
SOURCE_FILENAME_198="miette-derive-7.6.0.tar.gz"
605 -
SOURCE_URI_199="https://static.crates.io/crates/mime/mime-0.3.17.crate"
606 -
CHECKSUM_SHA256_199="6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
607 -
SOURCE_FILENAME_199="mime-0.3.17.tar.gz"
608 -
SOURCE_URI_200="https://static.crates.io/crates/minimal-lexical/minimal-lexical-0.2.1.crate"
609 -
CHECKSUM_SHA256_200="68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
610 -
SOURCE_FILENAME_200="minimal-lexical-0.2.1.tar.gz"
611 -
SOURCE_URI_201="https://static.crates.io/crates/miniz_oxide/miniz_oxide-0.8.9.crate"
612 -
CHECKSUM_SHA256_201="1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
613 -
SOURCE_FILENAME_201="miniz_oxide-0.8.9.tar.gz"
614 -
SOURCE_URI_202="https://static.crates.io/crates/miniz_oxide/miniz_oxide-0.9.1.crate"
615 -
CHECKSUM_SHA256_202="b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
616 -
SOURCE_FILENAME_202="miniz_oxide-0.9.1.tar.gz"
617 -
SOURCE_URI_203="https://static.crates.io/crates/mio/mio-1.2.3.crate"
618 -
CHECKSUM_SHA256_203="4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
619 -
SOURCE_FILENAME_203="mio-1.2.3.tar.gz"
620 -
SOURCE_URI_204="https://static.crates.io/crates/ml-dsa/ml-dsa-0.1.1.crate"
621 -
CHECKSUM_SHA256_204="add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
622 -
SOURCE_FILENAME_204="ml-dsa-0.1.1.tar.gz"
623 -
SOURCE_URI_205="https://static.crates.io/crates/module-lattice/module-lattice-0.2.3.crate"
624 -
CHECKSUM_SHA256_205="0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
625 -
SOURCE_FILENAME_205="module-lattice-0.2.3.tar.gz"
626 -
SOURCE_URI_206="https://static.crates.io/crates/moxcms/moxcms-0.8.1.crate"
627 -
CHECKSUM_SHA256_206="bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
628 -
SOURCE_FILENAME_206="moxcms-0.8.1.tar.gz"
629 -
SOURCE_URI_207="https://static.crates.io/crates/multer/multer-3.1.0.crate"
630 -
CHECKSUM_SHA256_207="83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
631 -
SOURCE_FILENAME_207="multer-3.1.0.tar.gz"
632 -
SOURCE_URI_208="https://static.crates.io/crates/multiversion/multiversion-0.9.0.crate"
633 -
CHECKSUM_SHA256_208="b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c"
634 -
SOURCE_FILENAME_208="multiversion-0.9.0.tar.gz"
635 -
SOURCE_URI_209="https://static.crates.io/crates/multiversion-macros/multiversion-macros-0.9.0.crate"
636 -
CHECKSUM_SHA256_209="0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83"
637 -
SOURCE_FILENAME_209="multiversion-macros-0.9.0.tar.gz"
638 -
SOURCE_URI_210="https://static.crates.io/crates/multiversion_no_op/multiversion_no_op-1.0.0.crate"
639 -
CHECKSUM_SHA256_210="743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
640 -
SOURCE_FILENAME_210="multiversion_no_op-1.0.0.tar.gz"
641 -
SOURCE_URI_211="https://static.crates.io/crates/new_debug_unreachable/new_debug_unreachable-1.0.6.crate"
642 -
CHECKSUM_SHA256_211="650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
643 -
SOURCE_FILENAME_211="new_debug_unreachable-1.0.6.tar.gz"
644 -
SOURCE_URI_212="https://static.crates.io/crates/nom/nom-7.1.3.crate"
645 -
CHECKSUM_SHA256_212="d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
646 -
SOURCE_FILENAME_212="nom-7.1.3.tar.gz"
647 -
SOURCE_URI_213="https://static.crates.io/crates/nom/nom-8.0.0.crate"
648 -
CHECKSUM_SHA256_213="df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
649 -
SOURCE_FILENAME_213="nom-8.0.0.tar.gz"
650 -
SOURCE_URI_214="https://static.crates.io/crates/nu-ansi-term/nu-ansi-term-0.50.3.crate"
651 -
CHECKSUM_SHA256_214="7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
652 -
SOURCE_FILENAME_214="nu-ansi-term-0.50.3.tar.gz"
653 -
SOURCE_URI_215="https://static.crates.io/crates/num-bigint/num-bigint-0.4.8.crate"
654 -
CHECKSUM_SHA256_215="c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
655 -
SOURCE_FILENAME_215="num-bigint-0.4.8.tar.gz"
656 -
SOURCE_URI_216="https://static.crates.io/crates/num-bigint-dig/num-bigint-dig-0.8.6.crate"
657 -
CHECKSUM_SHA256_216="e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
658 -
SOURCE_FILENAME_216="num-bigint-dig-0.8.6.tar.gz"
659 -
SOURCE_URI_217="https://static.crates.io/crates/num-conv/num-conv-0.2.2.crate"
660 -
CHECKSUM_SHA256_217="521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
661 -
SOURCE_FILENAME_217="num-conv-0.2.2.tar.gz"
662 -
SOURCE_URI_218="https://static.crates.io/crates/num-integer/num-integer-0.1.47.crate"
663 -
CHECKSUM_SHA256_218="7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
664 -
SOURCE_FILENAME_218="num-integer-0.1.47.tar.gz"
665 -
SOURCE_URI_219="https://static.crates.io/crates/num-iter/num-iter-0.1.46.crate"
666 -
CHECKSUM_SHA256_219="c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
667 -
SOURCE_FILENAME_219="num-iter-0.1.46.tar.gz"
668 -
SOURCE_URI_220="https://static.crates.io/crates/num-traits/num-traits-0.2.19.crate"
669 -
CHECKSUM_SHA256_220="071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
670 -
SOURCE_FILENAME_220="num-traits-0.2.19.tar.gz"
671 -
SOURCE_URI_221="https://static.crates.io/crates/object_store/object_store-0.14.2.crate"
672 -
CHECKSUM_SHA256_221="f1796bc93603f78c5760a69f2d58badc9618d22adade0a95385bb2adbae4eb94"
673 -
SOURCE_FILENAME_221="object_store-0.14.2.tar.gz"
674 -
SOURCE_URI_222="https://static.crates.io/crates/oid-registry/oid-registry-0.8.1.crate"
675 -
CHECKSUM_SHA256_222="12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
676 -
SOURCE_FILENAME_222="oid-registry-0.8.1.tar.gz"
677 -
SOURCE_URI_223="https://static.crates.io/crates/once_cell/once_cell-1.21.4.crate"
678 -
CHECKSUM_SHA256_223="9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
679 -
SOURCE_FILENAME_223="once_cell-1.21.4.tar.gz"
680 -
SOURCE_URI_224="https://static.crates.io/crates/once_cell_polyfill/once_cell_polyfill-1.70.2.crate"
681 -
CHECKSUM_SHA256_224="384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
682 -
SOURCE_FILENAME_224="once_cell_polyfill-1.70.2.tar.gz"
683 -
SOURCE_URI_225="https://static.crates.io/crates/parking/parking-2.2.1.crate"
684 -
CHECKSUM_SHA256_225="f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
685 -
SOURCE_FILENAME_225="parking-2.2.1.tar.gz"
686 -
SOURCE_URI_226="https://static.crates.io/crates/parking_lot/parking_lot-0.12.5.crate"
687 -
CHECKSUM_SHA256_226="93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
688 -
SOURCE_FILENAME_226="parking_lot-0.12.5.tar.gz"
689 -
SOURCE_URI_227="https://static.crates.io/crates/parking_lot_core/parking_lot_core-0.9.12.crate"
690 -
CHECKSUM_SHA256_227="2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
691 -
SOURCE_FILENAME_227="parking_lot_core-0.9.12.tar.gz"
692 -
SOURCE_URI_228="https://static.crates.io/crates/password-hash/password-hash-0.5.0.crate"
693 -
CHECKSUM_SHA256_228="346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
694 -
SOURCE_FILENAME_228="password-hash-0.5.0.tar.gz"
695 -
SOURCE_URI_229="https://static.crates.io/crates/pem/pem-4.0.0.crate"
696 -
CHECKSUM_SHA256_229="d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120"
697 -
SOURCE_FILENAME_229="pem-4.0.0.tar.gz"
698 -
SOURCE_URI_230="https://static.crates.io/crates/pem-rfc7468/pem-rfc7468-0.7.0.crate"
699 -
CHECKSUM_SHA256_230="88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
700 -
SOURCE_FILENAME_230="pem-rfc7468-0.7.0.tar.gz"
701 -
SOURCE_URI_231="https://static.crates.io/crates/percent-encoding/percent-encoding-2.3.2.crate"
702 -
CHECKSUM_SHA256_231="9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
703 -
SOURCE_FILENAME_231="percent-encoding-2.3.2.tar.gz"
704 -
SOURCE_URI_232="https://static.crates.io/crates/phf/phf-0.14.0.crate"
705 -
CHECKSUM_SHA256_232="010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
706 -
SOURCE_FILENAME_232="phf-0.14.0.tar.gz"
707 -
SOURCE_URI_233="https://static.crates.io/crates/phf_codegen/phf_codegen-0.14.0.crate"
708 -
CHECKSUM_SHA256_233="41b585a510fb76fdebead6897982ef2a03a21d8e6cbcca904999742a4afc6ffe"
709 -
SOURCE_FILENAME_233="phf_codegen-0.14.0.tar.gz"
710 -
SOURCE_URI_234="https://static.crates.io/crates/phf_generator/phf_generator-0.14.0.crate"
711 -
CHECKSUM_SHA256_234="aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
712 -
SOURCE_FILENAME_234="phf_generator-0.14.0.tar.gz"
713 -
SOURCE_URI_235="https://static.crates.io/crates/phf_shared/phf_shared-0.14.0.crate"
714 -
CHECKSUM_SHA256_235="c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
715 -
SOURCE_FILENAME_235="phf_shared-0.14.0.tar.gz"
716 -
SOURCE_URI_236="https://static.crates.io/crates/pin-project-lite/pin-project-lite-0.2.17.crate"
717 -
CHECKSUM_SHA256_236="a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
718 -
SOURCE_FILENAME_236="pin-project-lite-0.2.17.tar.gz"
719 -
SOURCE_URI_237="https://static.crates.io/crates/pkcs1/pkcs1-0.7.5.crate"
720 -
CHECKSUM_SHA256_237="c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
721 -
SOURCE_FILENAME_237="pkcs1-0.7.5.tar.gz"
722 -
SOURCE_URI_238="https://static.crates.io/crates/pkcs8/pkcs8-0.10.2.crate"
723 -
CHECKSUM_SHA256_238="f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
724 -
SOURCE_FILENAME_238="pkcs8-0.10.2.tar.gz"
725 -
SOURCE_URI_239="https://static.crates.io/crates/pkcs8/pkcs8-0.11.0.crate"
726 -
CHECKSUM_SHA256_239="451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
727 -
SOURCE_FILENAME_239="pkcs8-0.11.0.tar.gz"
728 -
SOURCE_URI_240="https://static.crates.io/crates/pkg-config/pkg-config-0.3.34.crate"
729 -
CHECKSUM_SHA256_240="f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
730 -
SOURCE_FILENAME_240="pkg-config-0.3.34.tar.gz"
731 -
SOURCE_URI_241="https://static.crates.io/crates/plain/plain-0.2.3.crate"
732 -
CHECKSUM_SHA256_241="b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
733 -
SOURCE_FILENAME_241="plain-0.2.3.tar.gz"
734 -
SOURCE_URI_242="https://static.crates.io/crates/plist/plist-1.10.1.crate"
735 -
CHECKSUM_SHA256_242="2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb"
736 -
SOURCE_FILENAME_242="plist-1.10.1.tar.gz"
737 -
SOURCE_URI_243="https://static.crates.io/crates/png/png-0.18.1.crate"
738 -
CHECKSUM_SHA256_243="60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
739 -
SOURCE_FILENAME_243="png-0.18.1.tar.gz"
740 -
SOURCE_URI_244="https://static.crates.io/crates/potential_utf/potential_utf-0.1.6.crate"
741 -
CHECKSUM_SHA256_244="d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
742 -
SOURCE_FILENAME_244="potential_utf-0.1.6.tar.gz"
743 -
SOURCE_URI_245="https://static.crates.io/crates/powerfmt/powerfmt-0.2.0.crate"
744 -
CHECKSUM_SHA256_245="439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
745 -
SOURCE_FILENAME_245="powerfmt-0.2.0.tar.gz"
746 -
SOURCE_URI_246="https://static.crates.io/crates/ppv-lite86/ppv-lite86-0.2.21.crate"
747 -
CHECKSUM_SHA256_246="85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
748 -
SOURCE_FILENAME_246="ppv-lite86-0.2.21.tar.gz"
749 -
SOURCE_URI_247="https://static.crates.io/crates/precomputed-hash/precomputed-hash-0.1.1.crate"
750 -
CHECKSUM_SHA256_247="925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
751 -
SOURCE_FILENAME_247="precomputed-hash-0.1.1.tar.gz"
752 -
SOURCE_URI_248="https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.107.crate"
753 -
CHECKSUM_SHA256_248="985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
754 -
SOURCE_FILENAME_248="proc-macro2-1.0.107.tar.gz"
755 -
SOURCE_URI_249="https://static.crates.io/crates/pulldown-cmark/pulldown-cmark-0.13.4.crate"
756 -
CHECKSUM_SHA256_249="e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
757 -
SOURCE_FILENAME_249="pulldown-cmark-0.13.4.tar.gz"
758 -
SOURCE_URI_250="https://static.crates.io/crates/pulldown-cmark-escape/pulldown-cmark-escape-0.11.0.crate"
759 -
CHECKSUM_SHA256_250="007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
760 -
SOURCE_FILENAME_250="pulldown-cmark-escape-0.11.0.tar.gz"
761 -
SOURCE_URI_251="https://static.crates.io/crates/pxfm/pxfm-0.1.30.crate"
762 -
CHECKSUM_SHA256_251="d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
763 -
SOURCE_FILENAME_251="pxfm-0.1.30.tar.gz"
764 -
SOURCE_URI_252="https://static.crates.io/crates/qrcode/qrcode-0.14.1.crate"
765 -
CHECKSUM_SHA256_252="d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
766 -
SOURCE_FILENAME_252="qrcode-0.14.1.tar.gz"
767 -
SOURCE_URI_253="https://static.crates.io/crates/quick-error/quick-error-2.0.1.crate"
768 -
CHECKSUM_SHA256_253="a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
769 -
SOURCE_FILENAME_253="quick-error-2.0.1.tar.gz"
770 -
SOURCE_URI_254="https://static.crates.io/crates/quick-xml/quick-xml-0.41.0.crate"
771 -
CHECKSUM_SHA256_254="e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
772 -
SOURCE_FILENAME_254="quick-xml-0.41.0.tar.gz"
773 -
SOURCE_URI_255="https://static.crates.io/crates/quick-xml/quick-xml-0.42.0.crate"
774 -
CHECKSUM_SHA256_255="41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
775 -
SOURCE_FILENAME_255="quick-xml-0.42.0.tar.gz"
776 -
SOURCE_URI_256="https://static.crates.io/crates/quinn/quinn-0.11.12.crate"
777 -
CHECKSUM_SHA256_256="4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
778 -
SOURCE_FILENAME_256="quinn-0.11.12.tar.gz"
779 -
SOURCE_URI_257="https://static.crates.io/crates/quinn-proto/quinn-proto-0.11.18.crate"
780 -
CHECKSUM_SHA256_257="a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc"
781 -
SOURCE_FILENAME_257="quinn-proto-0.11.18.tar.gz"
782 -
SOURCE_URI_258="https://static.crates.io/crates/quinn-udp/quinn-udp-0.5.15.crate"
783 -
CHECKSUM_SHA256_258="35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
784 -
SOURCE_FILENAME_258="quinn-udp-0.5.15.tar.gz"
785 -
SOURCE_URI_259="https://static.crates.io/crates/quote/quote-1.0.47.crate"
786 -
CHECKSUM_SHA256_259="1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
787 -
SOURCE_FILENAME_259="quote-1.0.47.tar.gz"
788 -
SOURCE_URI_260="https://static.crates.io/crates/quoted_printable/quoted_printable-0.5.2.crate"
789 -
CHECKSUM_SHA256_260="478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
790 -
SOURCE_FILENAME_260="quoted_printable-0.5.2.tar.gz"
791 -
SOURCE_URI_261="https://static.crates.io/crates/r-efi/r-efi-6.0.0.crate"
792 -
CHECKSUM_SHA256_261="f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
793 -
SOURCE_FILENAME_261="r-efi-6.0.0.tar.gz"
794 -
SOURCE_URI_262="https://static.crates.io/crates/rand/rand-0.10.3.crate"
795 -
CHECKSUM_SHA256_262="65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
796 -
SOURCE_FILENAME_262="rand-0.10.3.tar.gz"
797 -
SOURCE_URI_263="https://static.crates.io/crates/rand/rand-0.8.8.crate"
798 -
CHECKSUM_SHA256_263="e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
799 -
SOURCE_FILENAME_263="rand-0.8.8.tar.gz"
800 -
SOURCE_URI_264="https://static.crates.io/crates/rand_chacha/rand_chacha-0.3.1.crate"
801 -
CHECKSUM_SHA256_264="e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
802 -
SOURCE_FILENAME_264="rand_chacha-0.3.1.tar.gz"
803 -
SOURCE_URI_265="https://static.crates.io/crates/rand_core/rand_core-0.10.1.crate"
804 -
CHECKSUM_SHA256_265="63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
805 -
SOURCE_FILENAME_265="rand_core-0.10.1.tar.gz"
806 -
SOURCE_URI_266="https://static.crates.io/crates/rand_core/rand_core-0.6.4.crate"
807 -
CHECKSUM_SHA256_266="ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
808 -
SOURCE_FILENAME_266="rand_core-0.6.4.tar.gz"
809 -
SOURCE_URI_267="https://static.crates.io/crates/rand_pcg/rand_pcg-0.10.2.crate"
810 -
CHECKSUM_SHA256_267="caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
811 -
SOURCE_FILENAME_267="rand_pcg-0.10.2.tar.gz"
812 -
SOURCE_URI_268="https://static.crates.io/crates/rayon/rayon-1.12.0.crate"
813 -
CHECKSUM_SHA256_268="fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
814 -
SOURCE_FILENAME_268="rayon-1.12.0.tar.gz"
815 -
SOURCE_URI_269="https://static.crates.io/crates/rayon-core/rayon-core-1.13.0.crate"
816 -
CHECKSUM_SHA256_269="22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
817 -
SOURCE_FILENAME_269="rayon-core-1.13.0.tar.gz"
818 -
SOURCE_URI_270="https://static.crates.io/crates/rcgen/rcgen-0.14.10.crate"
819 -
CHECKSUM_SHA256_270="8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8"
820 -
SOURCE_FILENAME_270="rcgen-0.14.10.tar.gz"
821 -
SOURCE_URI_271="https://static.crates.io/crates/redox_syscall/redox_syscall-0.5.18.crate"
822 -
CHECKSUM_SHA256_271="ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
823 -
SOURCE_FILENAME_271="redox_syscall-0.5.18.tar.gz"
824 -
SOURCE_URI_272="https://static.crates.io/crates/redox_syscall/redox_syscall-0.9.4.crate"
825 -
CHECKSUM_SHA256_272="737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
826 -
SOURCE_FILENAME_272="redox_syscall-0.9.4.tar.gz"
827 -
SOURCE_URI_273="https://static.crates.io/crates/ref-cast/ref-cast-1.0.27.crate"
828 -
CHECKSUM_SHA256_273="7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
829 -
SOURCE_FILENAME_273="ref-cast-1.0.27.tar.gz"
830 -
SOURCE_URI_274="https://static.crates.io/crates/ref-cast-impl/ref-cast-impl-1.0.27.crate"
831 -
CHECKSUM_SHA256_274="92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
832 -
SOURCE_FILENAME_274="ref-cast-impl-1.0.27.tar.gz"
833 -
SOURCE_URI_275="https://static.crates.io/crates/regex/regex-1.13.1.crate"
834 -
CHECKSUM_SHA256_275="f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
835 -
SOURCE_FILENAME_275="regex-1.13.1.tar.gz"
836 -
SOURCE_URI_276="https://static.crates.io/crates/regex-automata/regex-automata-0.4.18.crate"
837 -
CHECKSUM_SHA256_276="ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
838 -
SOURCE_FILENAME_276="regex-automata-0.4.18.tar.gz"
839 -
SOURCE_URI_277="https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.11.crate"
840 -
CHECKSUM_SHA256_277="d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
841 -
SOURCE_FILENAME_277="regex-syntax-0.8.11.tar.gz"
842 -
SOURCE_URI_278="https://static.crates.io/crates/reqwest/reqwest-0.12.28.crate"
843 -
CHECKSUM_SHA256_278="eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
844 -
SOURCE_FILENAME_278="reqwest-0.12.28.tar.gz"
845 -
SOURCE_URI_279="https://static.crates.io/crates/ring/ring-0.17.14.crate"
846 -
CHECKSUM_SHA256_279="a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
847 -
SOURCE_FILENAME_279="ring-0.17.14.tar.gz"
848 -
SOURCE_URI_280="https://static.crates.io/crates/rsa/rsa-0.9.10.crate"
849 -
CHECKSUM_SHA256_280="b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
850 -
SOURCE_FILENAME_280="rsa-0.9.10.tar.gz"
851 -
SOURCE_URI_281="https://static.crates.io/crates/rustc-hash/rustc-hash-2.1.3.crate"
852 -
CHECKSUM_SHA256_281="6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
853 -
SOURCE_FILENAME_281="rustc-hash-2.1.3.tar.gz"
854 -
SOURCE_URI_282="https://static.crates.io/crates/rustc_version/rustc_version-0.4.1.crate"
855 -
CHECKSUM_SHA256_282="cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
856 -
SOURCE_FILENAME_282="rustc_version-0.4.1.tar.gz"
857 -
SOURCE_URI_283="https://static.crates.io/crates/rusticata-macros/rusticata-macros-4.1.0.crate"
858 -
CHECKSUM_SHA256_283="faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
859 -
SOURCE_FILENAME_283="rusticata-macros-4.1.0.tar.gz"
860 -
SOURCE_URI_284="https://static.crates.io/crates/rustix/rustix-1.1.5.crate"
861 -
CHECKSUM_SHA256_284="891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
862 -
SOURCE_FILENAME_284="rustix-1.1.5.tar.gz"
863 -
SOURCE_URI_285="https://static.crates.io/crates/rustls/rustls-0.23.45.crate"
864 -
CHECKSUM_SHA256_285="0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
865 -
SOURCE_FILENAME_285="rustls-0.23.45.tar.gz"
866 -
SOURCE_URI_286="https://static.crates.io/crates/rustls-pemfile/rustls-pemfile-2.2.0.crate"
867 -
CHECKSUM_SHA256_286="dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
868 -
SOURCE_FILENAME_286="rustls-pemfile-2.2.0.tar.gz"
869 -
SOURCE_URI_287="https://static.crates.io/crates/rustls-pki-types/rustls-pki-types-1.15.1.crate"
870 -
CHECKSUM_SHA256_287="2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
871 -
SOURCE_FILENAME_287="rustls-pki-types-1.15.1.tar.gz"
872 -
SOURCE_URI_288="https://static.crates.io/crates/rustls-webpki/rustls-webpki-0.103.15.crate"
873 -
CHECKSUM_SHA256_288="f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
874 -
SOURCE_FILENAME_288="rustls-webpki-0.103.15.tar.gz"
875 -
SOURCE_URI_289="https://static.crates.io/crates/rustversion/rustversion-1.0.23.crate"
876 -
CHECKSUM_SHA256_289="cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
877 -
SOURCE_FILENAME_289="rustversion-1.0.23.tar.gz"
878 -
SOURCE_URI_290="https://static.crates.io/crates/ryu/ryu-1.0.23.crate"
879 -
CHECKSUM_SHA256_290="9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
880 -
SOURCE_FILENAME_290="ryu-1.0.23.tar.gz"
881 -
SOURCE_URI_291="https://static.crates.io/crates/same-file/same-file-1.0.6.crate"
882 -
CHECKSUM_SHA256_291="93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
883 -
SOURCE_FILENAME_291="same-file-1.0.6.tar.gz"
884 -
SOURCE_URI_292="https://static.crates.io/crates/schemars/schemars-1.2.2.crate"
885 -
CHECKSUM_SHA256_292="687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
886 -
SOURCE_FILENAME_292="schemars-1.2.2.tar.gz"
887 -
SOURCE_URI_293="https://static.crates.io/crates/schemars_derive/schemars_derive-1.2.2.crate"
888 -
CHECKSUM_SHA256_293="d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba"
889 -
SOURCE_FILENAME_293="schemars_derive-1.2.2.tar.gz"
890 -
SOURCE_URI_294="https://static.crates.io/crates/scopeguard/scopeguard-1.2.0.crate"
891 -
CHECKSUM_SHA256_294="94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
892 -
SOURCE_FILENAME_294="scopeguard-1.2.0.tar.gz"
893 -
SOURCE_URI_295="https://static.crates.io/crates/semver/semver-1.0.28.crate"
894 -
CHECKSUM_SHA256_295="8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
895 -
SOURCE_FILENAME_295="semver-1.0.28.tar.gz"
896 -
SOURCE_URI_296="https://static.crates.io/crates/serde/serde-1.0.229.crate"
897 -
CHECKSUM_SHA256_296="4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
898 -
SOURCE_FILENAME_296="serde-1.0.229.tar.gz"
899 -
SOURCE_URI_297="https://static.crates.io/crates/serde_core/serde_core-1.0.229.crate"
900 -
CHECKSUM_SHA256_297="67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
901 -
SOURCE_FILENAME_297="serde_core-1.0.229.tar.gz"
902 -
SOURCE_URI_298="https://static.crates.io/crates/serde_derive/serde_derive-1.0.229.crate"
903 -
CHECKSUM_SHA256_298="e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
904 -
SOURCE_FILENAME_298="serde_derive-1.0.229.tar.gz"
905 -
SOURCE_URI_299="https://static.crates.io/crates/serde_derive_internals/serde_derive_internals-0.30.0.crate"
906 -
CHECKSUM_SHA256_299="f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
907 -
SOURCE_FILENAME_299="serde_derive_internals-0.30.0.tar.gz"
908 -
SOURCE_URI_300="https://static.crates.io/crates/serde_html_form/serde_html_form-0.2.8.crate"
909 -
CHECKSUM_SHA256_300="b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
910 -
SOURCE_FILENAME_300="serde_html_form-0.2.8.tar.gz"
911 -
SOURCE_URI_301="https://static.crates.io/crates/serde_json/serde_json-1.0.151.crate"
912 -
CHECKSUM_SHA256_301="c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
913 -
SOURCE_FILENAME_301="serde_json-1.0.151.tar.gz"
914 -
SOURCE_URI_302="https://static.crates.io/crates/serde_path_to_error/serde_path_to_error-0.1.20.crate"
915 -
CHECKSUM_SHA256_302="10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
916 -
SOURCE_FILENAME_302="serde_path_to_error-0.1.20.tar.gz"
917 -
SOURCE_URI_303="https://static.crates.io/crates/serde_spanned/serde_spanned-0.6.9.crate"
918 -
CHECKSUM_SHA256_303="bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
919 -
SOURCE_FILENAME_303="serde_spanned-0.6.9.tar.gz"
920 -
SOURCE_URI_304="https://static.crates.io/crates/serde_urlencoded/serde_urlencoded-0.7.1.crate"
921 -
CHECKSUM_SHA256_304="d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
922 -
SOURCE_FILENAME_304="serde_urlencoded-0.7.1.tar.gz"
923 -
SOURCE_URI_305="https://static.crates.io/crates/sha1/sha1-0.10.7.crate"
924 -
CHECKSUM_SHA256_305="a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
925 -
SOURCE_FILENAME_305="sha1-0.10.7.tar.gz"
926 -
SOURCE_URI_306="https://static.crates.io/crates/sha2/sha2-0.10.9.crate"
927 -
CHECKSUM_SHA256_306="a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
928 -
SOURCE_FILENAME_306="sha2-0.10.9.tar.gz"
929 -
SOURCE_URI_307="https://static.crates.io/crates/sha2/sha2-0.11.0.crate"
930 -
CHECKSUM_SHA256_307="446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
931 -
SOURCE_FILENAME_307="sha2-0.11.0.tar.gz"
932 -
SOURCE_URI_308="https://static.crates.io/crates/shake/shake-0.1.0.crate"
933 -
CHECKSUM_SHA256_308="09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
934 -
SOURCE_FILENAME_308="shake-0.1.0.tar.gz"
935 -
SOURCE_URI_309="https://static.crates.io/crates/sharded-slab/sharded-slab-0.1.7.crate"
936 -
CHECKSUM_SHA256_309="f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
937 -
SOURCE_FILENAME_309="sharded-slab-0.1.7.tar.gz"
938 -
SOURCE_URI_310="https://static.crates.io/crates/shlex/shlex-2.0.1.crate"
939 -
CHECKSUM_SHA256_310="f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
940 -
SOURCE_FILENAME_310="shlex-2.0.1.tar.gz"
941 -
SOURCE_URI_311="https://static.crates.io/crates/signal-hook-registry/signal-hook-registry-1.4.8.crate"
942 -
CHECKSUM_SHA256_311="c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
943 -
SOURCE_FILENAME_311="signal-hook-registry-1.4.8.tar.gz"
944 -
SOURCE_URI_312="https://static.crates.io/crates/signature/signature-2.2.0.crate"
945 -
CHECKSUM_SHA256_312="77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
946 -
SOURCE_FILENAME_312="signature-2.2.0.tar.gz"
947 -
SOURCE_URI_313="https://static.crates.io/crates/signature/signature-3.0.0.crate"
948 -
CHECKSUM_SHA256_313="28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
949 -
SOURCE_FILENAME_313="signature-3.0.0.tar.gz"
950 -
SOURCE_URI_314="https://static.crates.io/crates/simd-adler32/simd-adler32-0.3.10.crate"
951 -
CHECKSUM_SHA256_314="3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
952 -
SOURCE_FILENAME_314="simd-adler32-0.3.10.tar.gz"
953 -
SOURCE_URI_315="https://static.crates.io/crates/simdutf8/simdutf8-0.1.5.crate"
954 -
CHECKSUM_SHA256_315="e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
955 -
SOURCE_FILENAME_315="simdutf8-0.1.5.tar.gz"
956 -
SOURCE_URI_316="https://static.crates.io/crates/similar/similar-2.7.0.crate"
957 -
CHECKSUM_SHA256_316="bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
958 -
SOURCE_FILENAME_316="similar-2.7.0.tar.gz"
959 -
SOURCE_URI_317="https://static.crates.io/crates/siphasher/siphasher-1.0.4.crate"
960 -
CHECKSUM_SHA256_317="33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
961 -
SOURCE_FILENAME_317="siphasher-1.0.4.tar.gz"
962 -
SOURCE_URI_318="https://static.crates.io/crates/slab/slab-0.4.12.crate"
963 -
CHECKSUM_SHA256_318="0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
964 -
SOURCE_FILENAME_318="slab-0.4.12.tar.gz"
965 -
SOURCE_URI_319="https://static.crates.io/crates/smallvec/smallvec-1.16.2.crate"
966 -
CHECKSUM_SHA256_319="f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
967 -
SOURCE_FILENAME_319="smallvec-1.16.2.tar.gz"
968 -
SOURCE_URI_320="https://static.crates.io/crates/socket2/socket2-0.6.5.crate"
969 -
CHECKSUM_SHA256_320="c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
970 -
SOURCE_FILENAME_320="socket2-0.6.5.tar.gz"
971 -
SOURCE_URI_321="https://static.crates.io/crates/spin/spin-0.10.1.crate"
972 -
CHECKSUM_SHA256_321="023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
973 -
SOURCE_FILENAME_321="spin-0.10.1.tar.gz"
974 -
SOURCE_URI_322="https://static.crates.io/crates/spin/spin-0.9.9.crate"
975 -
CHECKSUM_SHA256_322="3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
976 -
SOURCE_FILENAME_322="spin-0.9.9.tar.gz"
977 -
SOURCE_URI_323="https://static.crates.io/crates/spki/spki-0.7.3.crate"
978 -
CHECKSUM_SHA256_323="d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
979 -
SOURCE_FILENAME_323="spki-0.7.3.tar.gz"
980 -
SOURCE_URI_324="https://static.crates.io/crates/spki/spki-0.8.0.crate"
981 -
CHECKSUM_SHA256_324="1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
982 -
SOURCE_FILENAME_324="spki-0.8.0.tar.gz"
983 -
SOURCE_URI_325="https://static.crates.io/crates/sponge-cursor/sponge-cursor-0.1.0.crate"
984 -
CHECKSUM_SHA256_325="3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
985 -
SOURCE_FILENAME_325="sponge-cursor-0.1.0.tar.gz"
986 -
SOURCE_URI_326="https://static.crates.io/crates/sqlx/sqlx-0.8.6.crate"
987 -
CHECKSUM_SHA256_326="1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
988 -
SOURCE_FILENAME_326="sqlx-0.8.6.tar.gz"
989 -
SOURCE_URI_327="https://static.crates.io/crates/sqlx-core/sqlx-core-0.8.6.crate"
990 -
CHECKSUM_SHA256_327="ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
991 -
SOURCE_FILENAME_327="sqlx-core-0.8.6.tar.gz"
992 -
SOURCE_URI_328="https://static.crates.io/crates/sqlx-macros/sqlx-macros-0.8.6.crate"
993 -
CHECKSUM_SHA256_328="a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
994 -
SOURCE_FILENAME_328="sqlx-macros-0.8.6.tar.gz"
995 -
SOURCE_URI_329="https://static.crates.io/crates/sqlx-macros-core/sqlx-macros-core-0.8.6.crate"
996 -
CHECKSUM_SHA256_329="19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
997 -
SOURCE_FILENAME_329="sqlx-macros-core-0.8.6.tar.gz"
998 -
SOURCE_URI_330="https://static.crates.io/crates/sqlx-mysql/sqlx-mysql-0.8.6.crate"
999 -
CHECKSUM_SHA256_330="aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
1000 -
SOURCE_FILENAME_330="sqlx-mysql-0.8.6.tar.gz"
1001 -
SOURCE_URI_331="https://static.crates.io/crates/sqlx-postgres/sqlx-postgres-0.8.6.crate"
1002 -
CHECKSUM_SHA256_331="db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
1003 -
SOURCE_FILENAME_331="sqlx-postgres-0.8.6.tar.gz"
1004 -
SOURCE_URI_332="https://static.crates.io/crates/sqlx-sqlite/sqlx-sqlite-0.8.6.crate"
1005 -
CHECKSUM_SHA256_332="c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
1006 -
SOURCE_FILENAME_332="sqlx-sqlite-0.8.6.tar.gz"
1007 -
SOURCE_URI_333="https://static.crates.io/crates/stable_deref_trait/stable_deref_trait-1.2.1.crate"
1008 -
CHECKSUM_SHA256_333="6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1009 -
SOURCE_FILENAME_333="stable_deref_trait-1.2.1.tar.gz"
1010 -
SOURCE_URI_334="https://static.crates.io/crates/streaming-iterator/streaming-iterator-0.1.9.crate"
1011 -
CHECKSUM_SHA256_334="2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
1012 -
SOURCE_FILENAME_334="streaming-iterator-0.1.9.tar.gz"
1013 -
SOURCE_URI_335="https://static.crates.io/crates/string_cache/string_cache-0.11.0.crate"
1014 -
CHECKSUM_SHA256_335="ffa8a5dbe8b3f0bbe29d4c3225daafaeead63afdc1b65fc4c01a1384166038e6"
1015 -
SOURCE_FILENAME_335="string_cache-0.11.0.tar.gz"
1016 -
SOURCE_URI_336="https://static.crates.io/crates/string_cache_codegen/string_cache_codegen-0.11.2.crate"
1017 -
CHECKSUM_SHA256_336="928dcdf75e47626b3617a976ec205d9f057584c371c1f23b782129268d0e6edc"
1018 -
SOURCE_FILENAME_336="string_cache_codegen-0.11.2.tar.gz"
1019 -
SOURCE_URI_337="https://static.crates.io/crates/stringprep/stringprep-0.1.5.crate"
1020 -
CHECKSUM_SHA256_337="7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
1021 -
SOURCE_FILENAME_337="stringprep-0.1.5.tar.gz"
1022 -
SOURCE_URI_338="https://static.crates.io/crates/strsim/strsim-0.11.1.crate"
1023 -
CHECKSUM_SHA256_338="7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1024 -
SOURCE_FILENAME_338="strsim-0.11.1.tar.gz"
1025 -
SOURCE_URI_339="https://static.crates.io/crates/subtle/subtle-2.6.1.crate"
1026 -
CHECKSUM_SHA256_339="13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1027 -
SOURCE_FILENAME_339="subtle-2.6.1.tar.gz"
1028 -
SOURCE_URI_340="https://static.crates.io/crates/syn/syn-2.0.119.crate"
1029 -
CHECKSUM_SHA256_340="872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
1030 -
SOURCE_FILENAME_340="syn-2.0.119.tar.gz"
1031 -
SOURCE_URI_341="https://static.crates.io/crates/syn/syn-3.0.6.crate"
1032 -
CHECKSUM_SHA256_341="8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
1033 -
SOURCE_FILENAME_341="syn-3.0.6.tar.gz"
1034 -
SOURCE_URI_342="https://static.crates.io/crates/sync_wrapper/sync_wrapper-1.0.2.crate"
1035 -
CHECKSUM_SHA256_342="0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1036 -
SOURCE_FILENAME_342="sync_wrapper-1.0.2.tar.gz"
1037 -
SOURCE_URI_343="https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate"
1038 -
CHECKSUM_SHA256_343="728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1039 -
SOURCE_FILENAME_343="synstructure-0.13.2.tar.gz"
1040 -
SOURCE_URI_344="https://static.crates.io/crates/synstructure/synstructure-0.14.0.crate"
1041 -
CHECKSUM_SHA256_344="901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
1042 -
SOURCE_FILENAME_344="synstructure-0.14.0.tar.gz"
1043 -
SOURCE_URI_345="https://static.crates.io/crates/syntect/syntect-5.3.0.crate"
1044 -
CHECKSUM_SHA256_345="656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
1045 -
SOURCE_FILENAME_345="syntect-5.3.0.tar.gz"
1046 -
SOURCE_URI_346="https://static.crates.io/crates/tar/tar-0.4.46.crate"
1047 -
CHECKSUM_SHA256_346="3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
1048 -
SOURCE_FILENAME_346="tar-0.4.46.tar.gz"
1049 -
SOURCE_URI_347="https://static.crates.io/crates/tempfile/tempfile-3.27.0.crate"
1050 -
CHECKSUM_SHA256_347="32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
1051 -
SOURCE_FILENAME_347="tempfile-3.27.0.tar.gz"
1052 -
SOURCE_URI_348="https://static.crates.io/crates/tendril/tendril-0.5.1.crate"
1053 -
CHECKSUM_SHA256_348="5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
1054 -
SOURCE_FILENAME_348="tendril-0.5.1.tar.gz"
1055 -
SOURCE_URI_349="https://static.crates.io/crates/thiserror/thiserror-1.0.69.crate"
1056 -
CHECKSUM_SHA256_349="b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
1057 -
SOURCE_FILENAME_349="thiserror-1.0.69.tar.gz"
1058 -
SOURCE_URI_350="https://static.crates.io/crates/thiserror/thiserror-2.0.21.crate"
1059 -
CHECKSUM_SHA256_350="09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
1060 -
SOURCE_FILENAME_350="thiserror-2.0.21.tar.gz"
1061 -
SOURCE_URI_351="https://static.crates.io/crates/thiserror-impl/thiserror-impl-1.0.69.crate"
1062 -
CHECKSUM_SHA256_351="4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
1063 -
SOURCE_FILENAME_351="thiserror-impl-1.0.69.tar.gz"
1064 -
SOURCE_URI_352="https://static.crates.io/crates/thiserror-impl/thiserror-impl-2.0.21.crate"
1065 -
CHECKSUM_SHA256_352="fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
1066 -
SOURCE_FILENAME_352="thiserror-impl-2.0.21.tar.gz"
1067 -
SOURCE_URI_353="https://static.crates.io/crates/thread_local/thread_local-1.1.10.crate"
1068 -
CHECKSUM_SHA256_353="1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
1069 -
SOURCE_FILENAME_353="thread_local-1.1.10.tar.gz"
1070 -
SOURCE_URI_354="https://static.crates.io/crates/time/time-0.3.55.crate"
1071 -
CHECKSUM_SHA256_354="cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
1072 -
SOURCE_FILENAME_354="time-0.3.55.tar.gz"
1073 -
SOURCE_URI_355="https://static.crates.io/crates/time-core/time-core-0.1.9.crate"
1074 -
CHECKSUM_SHA256_355="9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
1075 -
SOURCE_FILENAME_355="time-core-0.1.9.tar.gz"
1076 -
SOURCE_URI_356="https://static.crates.io/crates/time-macros/time-macros-0.2.32.crate"
1077 -
CHECKSUM_SHA256_356="7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
1078 -
SOURCE_FILENAME_356="time-macros-0.2.32.tar.gz"
1079 -
SOURCE_URI_357="https://static.crates.io/crates/tinystr/tinystr-0.8.4.crate"
1080 -
CHECKSUM_SHA256_357="b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
1081 -
SOURCE_FILENAME_357="tinystr-0.8.4.tar.gz"
1082 -
SOURCE_URI_358="https://static.crates.io/crates/tinyvec/tinyvec-1.13.3.crate"
1083 -
CHECKSUM_SHA256_358="fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
1084 -
SOURCE_FILENAME_358="tinyvec-1.13.3.tar.gz"
1085 -
SOURCE_URI_359="https://static.crates.io/crates/tokio/tokio-1.53.1.crate"
1086 -
CHECKSUM_SHA256_359="202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
1087 -
SOURCE_FILENAME_359="tokio-1.53.1.tar.gz"
1088 -
SOURCE_URI_360="https://static.crates.io/crates/tokio-macros/tokio-macros-2.7.2.crate"
1089 -
CHECKSUM_SHA256_360="78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
1090 -
SOURCE_FILENAME_360="tokio-macros-2.7.2.tar.gz"
1091 -
SOURCE_URI_361="https://static.crates.io/crates/tokio-rustls/tokio-rustls-0.26.5.crate"
1092 -
CHECKSUM_SHA256_361="b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
1093 -
SOURCE_FILENAME_361="tokio-rustls-0.26.5.tar.gz"
1094 -
SOURCE_URI_362="https://static.crates.io/crates/tokio-stream/tokio-stream-0.1.19.crate"
1095 -
CHECKSUM_SHA256_362="a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
1096 -
SOURCE_FILENAME_362="tokio-stream-0.1.19.tar.gz"
1097 -
SOURCE_URI_363="https://static.crates.io/crates/tokio-util/tokio-util-0.7.19.crate"
1098 -
CHECKSUM_SHA256_363="494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
1099 -
SOURCE_FILENAME_363="tokio-util-0.7.19.tar.gz"
1100 -
SOURCE_URI_364="https://static.crates.io/crates/toml/toml-0.8.23.crate"
1101 -
CHECKSUM_SHA256_364="dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
1102 -
SOURCE_FILENAME_364="toml-0.8.23.tar.gz"
1103 -
SOURCE_URI_365="https://static.crates.io/crates/toml_datetime/toml_datetime-0.6.11.crate"
1104 -
CHECKSUM_SHA256_365="22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
1105 -
SOURCE_FILENAME_365="toml_datetime-0.6.11.tar.gz"
1106 -
SOURCE_URI_366="https://static.crates.io/crates/toml_edit/toml_edit-0.22.27.crate"
1107 -
CHECKSUM_SHA256_366="41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
1108 -
SOURCE_FILENAME_366="toml_edit-0.22.27.tar.gz"
1109 -
SOURCE_URI_367="https://static.crates.io/crates/toml_write/toml_write-0.1.2.crate"
1110 -
CHECKSUM_SHA256_367="5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
1111 -
SOURCE_FILENAME_367="toml_write-0.1.2.tar.gz"
1112 -
SOURCE_URI_368="https://static.crates.io/crates/tower/tower-0.5.3.crate"
1113 -
CHECKSUM_SHA256_368="ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
1114 -
SOURCE_FILENAME_368="tower-0.5.3.tar.gz"
1115 -
SOURCE_URI_369="https://static.crates.io/crates/tower-http/tower-http-0.6.11.crate"
1116 -
CHECKSUM_SHA256_369="4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
1117 -
SOURCE_FILENAME_369="tower-http-0.6.11.tar.gz"
1118 -
SOURCE_URI_370="https://static.crates.io/crates/tower-layer/tower-layer-0.3.3.crate"
1119 -
CHECKSUM_SHA256_370="121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1120 -
SOURCE_FILENAME_370="tower-layer-0.3.3.tar.gz"
1121 -
SOURCE_URI_371="https://static.crates.io/crates/tower-service/tower-service-0.3.3.crate"
1122 -
CHECKSUM_SHA256_371="8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1123 -
SOURCE_FILENAME_371="tower-service-0.3.3.tar.gz"
1124 -
SOURCE_URI_372="https://static.crates.io/crates/tracing/tracing-0.1.44.crate"
1125 -
CHECKSUM_SHA256_372="63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
1126 -
SOURCE_FILENAME_372="tracing-0.1.44.tar.gz"
1127 -
SOURCE_URI_373="https://static.crates.io/crates/tracing-attributes/tracing-attributes-0.1.31.crate"
1128 -
CHECKSUM_SHA256_373="7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
1129 -
SOURCE_FILENAME_373="tracing-attributes-0.1.31.tar.gz"
1130 -
SOURCE_URI_374="https://static.crates.io/crates/tracing-core/tracing-core-0.1.36.crate"
1131 -
CHECKSUM_SHA256_374="db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
1132 -
SOURCE_FILENAME_374="tracing-core-0.1.36.tar.gz"
1133 -
SOURCE_URI_375="https://static.crates.io/crates/tracing-log/tracing-log-0.2.0.crate"
1134 -
CHECKSUM_SHA256_375="ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
1135 -
SOURCE_FILENAME_375="tracing-log-0.2.0.tar.gz"
1136 -
SOURCE_URI_376="https://static.crates.io/crates/tracing-subscriber/tracing-subscriber-0.3.23.crate"
1137 -
CHECKSUM_SHA256_376="cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
1138 -
SOURCE_FILENAME_376="tracing-subscriber-0.3.23.tar.gz"
1139 -
SOURCE_URI_377="https://static.crates.io/crates/tree-sitter/tree-sitter-0.27.0.crate"
1140 -
CHECKSUM_SHA256_377="2038684e0058edba0d17302619f62eabce4a8e11c6ac59506996a8d79848851d"
1141 -
SOURCE_FILENAME_377="tree-sitter-0.27.0.tar.gz"
1142 -
SOURCE_URI_378="https://static.crates.io/crates/tree-sitter-bash/tree-sitter-bash-0.25.1.crate"
1143 -
CHECKSUM_SHA256_378="9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062"
1144 -
SOURCE_FILENAME_378="tree-sitter-bash-0.25.1.tar.gz"
1145 -
SOURCE_URI_379="https://static.crates.io/crates/tree-sitter-language/tree-sitter-language-0.1.8.crate"
1146 -
CHECKSUM_SHA256_379="ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
1147 -
SOURCE_FILENAME_379="tree-sitter-language-0.1.8.tar.gz"
1148 -
SOURCE_URI_380="https://static.crates.io/crates/tree-sitter-python/tree-sitter-python-0.25.0.crate"
1149 -
CHECKSUM_SHA256_380="6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c"
1150 -
SOURCE_FILENAME_380="tree-sitter-python-0.25.0.tar.gz"
1151 -
SOURCE_URI_381="https://static.crates.io/crates/tree-sitter-rust/tree-sitter-rust-0.24.2.crate"
1152 -
CHECKSUM_SHA256_381="439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
1153 -
SOURCE_FILENAME_381="tree-sitter-rust-0.24.2.tar.gz"
1154 -
SOURCE_URI_382="https://static.crates.io/crates/tree-sitter-typescript/tree-sitter-typescript-0.23.2.crate"
1155 -
CHECKSUM_SHA256_382="6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
1156 -
SOURCE_FILENAME_382="tree-sitter-typescript-0.23.2.tar.gz"
1157 -
SOURCE_URI_383="https://static.crates.io/crates/try-lock/try-lock-0.2.5.crate"
1158 -
CHECKSUM_SHA256_383="e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
1159 -
SOURCE_FILENAME_383="try-lock-0.2.5.tar.gz"
1160 -
SOURCE_URI_384="https://static.crates.io/crates/typenum/typenum-1.20.1.crate"
1161 -
CHECKSUM_SHA256_384="b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
1162 -
SOURCE_FILENAME_384="typenum-1.20.1.tar.gz"
1163 -
SOURCE_URI_385="https://static.crates.io/crates/unicase/unicase-2.9.0.crate"
1164 -
CHECKSUM_SHA256_385="dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
1165 -
SOURCE_FILENAME_385="unicase-2.9.0.tar.gz"
1166 -
SOURCE_URI_386="https://static.crates.io/crates/unicode-bidi/unicode-bidi-0.3.18.crate"
1167 -
CHECKSUM_SHA256_386="5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
1168 -
SOURCE_FILENAME_386="unicode-bidi-0.3.18.tar.gz"
1169 -
SOURCE_URI_387="https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.26.crate"
1170 -
CHECKSUM_SHA256_387="d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
1171 -
SOURCE_FILENAME_387="unicode-ident-1.0.26.tar.gz"
1172 -
SOURCE_URI_388="https://static.crates.io/crates/unicode-normalization/unicode-normalization-0.1.25.crate"
1173 -
CHECKSUM_SHA256_388="5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
1174 -
SOURCE_FILENAME_388="unicode-normalization-0.1.25.tar.gz"
1175 -
SOURCE_URI_389="https://static.crates.io/crates/unicode-properties/unicode-properties-0.1.4.crate"
1176 -
CHECKSUM_SHA256_389="7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
1177 -
SOURCE_FILENAME_389="unicode-properties-0.1.4.tar.gz"
1178 -
SOURCE_URI_390="https://static.crates.io/crates/unicode-width/unicode-width-0.1.14.crate"
1179 -
CHECKSUM_SHA256_390="7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
1180 -
SOURCE_FILENAME_390="unicode-width-0.1.14.tar.gz"
1181 -
SOURCE_URI_391="https://static.crates.io/crates/untrusted/untrusted-0.9.0.crate"
1182 -
CHECKSUM_SHA256_391="8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
1183 -
SOURCE_FILENAME_391="untrusted-0.9.0.tar.gz"
1184 -
SOURCE_URI_392="https://static.crates.io/crates/ureq/ureq-2.12.1.crate"
1185 -
CHECKSUM_SHA256_392="02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
1186 -
SOURCE_FILENAME_392="ureq-2.12.1.tar.gz"
1187 -
SOURCE_URI_393="https://static.crates.io/crates/url/url-2.5.8.crate"
1188 -
CHECKSUM_SHA256_393="ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
1189 -
SOURCE_FILENAME_393="url-2.5.8.tar.gz"
1190 -
SOURCE_URI_394="https://static.crates.io/crates/utf8_iter/utf8_iter-1.0.4.crate"
1191 -
CHECKSUM_SHA256_394="b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
1192 -
SOURCE_FILENAME_394="utf8_iter-1.0.4.tar.gz"
1193 -
SOURCE_URI_395="https://static.crates.io/crates/utf8parse/utf8parse-0.2.2.crate"
1194 -
CHECKSUM_SHA256_395="06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1195 -
SOURCE_FILENAME_395="utf8parse-0.2.2.tar.gz"
1196 -
SOURCE_URI_396="https://static.crates.io/crates/valuable/valuable-0.1.1.crate"
1197 -
CHECKSUM_SHA256_396="ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
1198 -
SOURCE_FILENAME_396="valuable-0.1.1.tar.gz"
1199 -
SOURCE_URI_397="https://static.crates.io/crates/vcpkg/vcpkg-0.2.15.crate"
1200 -
CHECKSUM_SHA256_397="accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1201 -
SOURCE_FILENAME_397="vcpkg-0.2.15.tar.gz"
1202 -
SOURCE_URI_398="https://static.crates.io/crates/version_check/version_check-0.9.5.crate"
1203 -
CHECKSUM_SHA256_398="0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1204 -
SOURCE_FILENAME_398="version_check-0.9.5.tar.gz"
1205 -
SOURCE_URI_399="https://static.crates.io/crates/walkdir/walkdir-2.5.0.crate"
1206 -
CHECKSUM_SHA256_399="29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
1207 -
SOURCE_FILENAME_399="walkdir-2.5.0.tar.gz"
1208 -
SOURCE_URI_400="https://static.crates.io/crates/want/want-0.3.1.crate"
1209 -
CHECKSUM_SHA256_400="bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
1210 -
SOURCE_FILENAME_400="want-0.3.1.tar.gz"
1211 -
SOURCE_URI_401="https://static.crates.io/crates/wasi/wasi-0.11.1+wasi-snapshot-preview1.crate"
1212 -
CHECKSUM_SHA256_401="ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
1213 -
SOURCE_FILENAME_401="wasi-0.11.1+wasi-snapshot-preview1.tar.gz"
1214 -
SOURCE_URI_402="https://static.crates.io/crates/wasite/wasite-0.1.0.crate"
1215 -
CHECKSUM_SHA256_402="b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
1216 -
SOURCE_FILENAME_402="wasite-0.1.0.tar.gz"
1217 -
SOURCE_URI_403="https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.129.crate"
1218 -
CHECKSUM_SHA256_403="9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
1219 -
SOURCE_FILENAME_403="wasm-bindgen-0.2.129.tar.gz"
1220 -
SOURCE_URI_404="https://static.crates.io/crates/wasm-bindgen-futures/wasm-bindgen-futures-0.4.79.crate"
1221 -
CHECKSUM_SHA256_404="3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
1222 -
SOURCE_FILENAME_404="wasm-bindgen-futures-0.4.79.tar.gz"
1223 -
SOURCE_URI_405="https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.129.crate"
1224 -
CHECKSUM_SHA256_405="2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
1225 -
SOURCE_FILENAME_405="wasm-bindgen-macro-0.2.129.tar.gz"
1226 -
SOURCE_URI_406="https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.129.crate"
1227 -
CHECKSUM_SHA256_406="6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
1228 -
SOURCE_FILENAME_406="wasm-bindgen-macro-support-0.2.129.tar.gz"
1229 -
SOURCE_URI_407="https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.129.crate"
1230 -
CHECKSUM_SHA256_407="23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
1231 -
SOURCE_FILENAME_407="wasm-bindgen-shared-0.2.129.tar.gz"
1232 -
SOURCE_URI_408="https://static.crates.io/crates/web-sys/web-sys-0.3.106.crate"
1233 -
CHECKSUM_SHA256_408="88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
1234 -
SOURCE_FILENAME_408="web-sys-0.3.106.tar.gz"
1235 -
SOURCE_URI_409="https://static.crates.io/crates/web-time/web-time-1.1.0.crate"
1236 -
CHECKSUM_SHA256_409="5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
1237 -
SOURCE_FILENAME_409="web-time-1.1.0.tar.gz"
1238 -
SOURCE_URI_410="https://static.crates.io/crates/web_atoms/web_atoms-0.3.0.crate"
1239 -
CHECKSUM_SHA256_410="7572660c8890448ba236b7376f27e389c6a7e1c70195622faced601f855c0ada"
1240 -
SOURCE_FILENAME_410="web_atoms-0.3.0.tar.gz"
1241 -
SOURCE_URI_411="https://static.crates.io/crates/webpki-roots/webpki-roots-0.26.11.crate"
1242 -
CHECKSUM_SHA256_411="521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
1243 -
SOURCE_FILENAME_411="webpki-roots-0.26.11.tar.gz"
1244 -
SOURCE_URI_412="https://static.crates.io/crates/webpki-roots/webpki-roots-1.0.9.crate"
1245 -
CHECKSUM_SHA256_412="7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
1246 -
SOURCE_FILENAME_412="webpki-roots-1.0.9.tar.gz"
1247 -
SOURCE_URI_413="https://static.crates.io/crates/whoami/whoami-1.6.1.crate"
1248 -
CHECKSUM_SHA256_413="5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
1249 -
SOURCE_FILENAME_413="whoami-1.6.1.tar.gz"
1250 -
SOURCE_URI_414="https://static.crates.io/crates/winapi-util/winapi-util-0.1.11.crate"
1251 -
CHECKSUM_SHA256_414="c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
1252 -
SOURCE_FILENAME_414="winapi-util-0.1.11.tar.gz"
1253 -
SOURCE_URI_415="https://static.crates.io/crates/windows-core/windows-core-0.62.2.crate"
1254 -
CHECKSUM_SHA256_415="b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
1255 -
SOURCE_FILENAME_415="windows-core-0.62.2.tar.gz"
1256 -
SOURCE_URI_416="https://static.crates.io/crates/windows-implement/windows-implement-0.60.2.crate"
1257 -
CHECKSUM_SHA256_416="053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
1258 -
SOURCE_FILENAME_416="windows-implement-0.60.2.tar.gz"
1259 -
SOURCE_URI_417="https://static.crates.io/crates/windows-interface/windows-interface-0.59.3.crate"
1260 -
CHECKSUM_SHA256_417="3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
1261 -
SOURCE_FILENAME_417="windows-interface-0.59.3.tar.gz"
1262 -
SOURCE_URI_418="https://static.crates.io/crates/windows-link/windows-link-0.2.1.crate"
1263 -
CHECKSUM_SHA256_418="f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
1264 -
SOURCE_FILENAME_418="windows-link-0.2.1.tar.gz"
1265 -
SOURCE_URI_419="https://static.crates.io/crates/windows-result/windows-result-0.4.1.crate"
1266 -
CHECKSUM_SHA256_419="7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
1267 -
SOURCE_FILENAME_419="windows-result-0.4.1.tar.gz"
1268 -
SOURCE_URI_420="https://static.crates.io/crates/windows-strings/windows-strings-0.5.1.crate"
1269 -
CHECKSUM_SHA256_420="7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
1270 -
SOURCE_FILENAME_420="windows-strings-0.5.1.tar.gz"
1271 -
SOURCE_URI_421="https://static.crates.io/crates/windows-sys/windows-sys-0.48.0.crate"
1272 -
CHECKSUM_SHA256_421="677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1273 -
SOURCE_FILENAME_421="windows-sys-0.48.0.tar.gz"
1274 -
SOURCE_URI_422="https://static.crates.io/crates/windows-sys/windows-sys-0.52.0.crate"
1275 -
CHECKSUM_SHA256_422="282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1276 -
SOURCE_FILENAME_422="windows-sys-0.52.0.tar.gz"
1277 -
SOURCE_URI_423="https://static.crates.io/crates/windows-sys/windows-sys-0.61.2.crate"
1278 -
CHECKSUM_SHA256_423="ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
1279 -
SOURCE_FILENAME_423="windows-sys-0.61.2.tar.gz"
1280 -
SOURCE_URI_424="https://static.crates.io/crates/windows-targets/windows-targets-0.48.5.crate"
1281 -
CHECKSUM_SHA256_424="9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1282 -
SOURCE_FILENAME_424="windows-targets-0.48.5.tar.gz"
1283 -
SOURCE_URI_425="https://static.crates.io/crates/windows-targets/windows-targets-0.52.6.crate"
1284 -
CHECKSUM_SHA256_425="9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1285 -
SOURCE_FILENAME_425="windows-targets-0.52.6.tar.gz"
1286 -
SOURCE_URI_426="https://static.crates.io/crates/windows_aarch64_gnullvm/windows_aarch64_gnullvm-0.48.5.crate"
1287 -
CHECKSUM_SHA256_426="2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1288 -
SOURCE_FILENAME_426="windows_aarch64_gnullvm-0.48.5.tar.gz"
1289 -
SOURCE_URI_427="https://static.crates.io/crates/windows_aarch64_gnullvm/windows_aarch64_gnullvm-0.52.6.crate"
1290 -
CHECKSUM_SHA256_427="32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1291 -
SOURCE_FILENAME_427="windows_aarch64_gnullvm-0.52.6.tar.gz"
1292 -
SOURCE_URI_428="https://static.crates.io/crates/windows_aarch64_msvc/windows_aarch64_msvc-0.48.5.crate"
1293 -
CHECKSUM_SHA256_428="dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1294 -
SOURCE_FILENAME_428="windows_aarch64_msvc-0.48.5.tar.gz"
1295 -
SOURCE_URI_429="https://static.crates.io/crates/windows_aarch64_msvc/windows_aarch64_msvc-0.52.6.crate"
1296 -
CHECKSUM_SHA256_429="09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1297 -
SOURCE_FILENAME_429="windows_aarch64_msvc-0.52.6.tar.gz"
1298 -
SOURCE_URI_430="https://static.crates.io/crates/windows_i686_gnu/windows_i686_gnu-0.48.5.crate"
1299 -
CHECKSUM_SHA256_430="a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1300 -
SOURCE_FILENAME_430="windows_i686_gnu-0.48.5.tar.gz"
1301 -
SOURCE_URI_431="https://static.crates.io/crates/windows_i686_gnu/windows_i686_gnu-0.52.6.crate"
1302 -
CHECKSUM_SHA256_431="8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1303 -
SOURCE_FILENAME_431="windows_i686_gnu-0.52.6.tar.gz"
1304 -
SOURCE_URI_432="https://static.crates.io/crates/windows_i686_gnullvm/windows_i686_gnullvm-0.52.6.crate"
1305 -
CHECKSUM_SHA256_432="0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1306 -
SOURCE_FILENAME_432="windows_i686_gnullvm-0.52.6.tar.gz"
1307 -
SOURCE_URI_433="https://static.crates.io/crates/windows_i686_msvc/windows_i686_msvc-0.48.5.crate"
1308 -
CHECKSUM_SHA256_433="8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1309 -
SOURCE_FILENAME_433="windows_i686_msvc-0.48.5.tar.gz"
1310 -
SOURCE_URI_434="https://static.crates.io/crates/windows_i686_msvc/windows_i686_msvc-0.52.6.crate"
1311 -
CHECKSUM_SHA256_434="240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1312 -
SOURCE_FILENAME_434="windows_i686_msvc-0.52.6.tar.gz"
1313 -
SOURCE_URI_435="https://static.crates.io/crates/windows_x86_64_gnu/windows_x86_64_gnu-0.48.5.crate"
1314 -
CHECKSUM_SHA256_435="53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1315 -
SOURCE_FILENAME_435="windows_x86_64_gnu-0.48.5.tar.gz"
1316 -
SOURCE_URI_436="https://static.crates.io/crates/windows_x86_64_gnu/windows_x86_64_gnu-0.52.6.crate"
1317 -
CHECKSUM_SHA256_436="147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1318 -
SOURCE_FILENAME_436="windows_x86_64_gnu-0.52.6.tar.gz"
1319 -
SOURCE_URI_437="https://static.crates.io/crates/windows_x86_64_gnullvm/windows_x86_64_gnullvm-0.48.5.crate"
1320 -
CHECKSUM_SHA256_437="0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1321 -
SOURCE_FILENAME_437="windows_x86_64_gnullvm-0.48.5.tar.gz"
1322 -
SOURCE_URI_438="https://static.crates.io/crates/windows_x86_64_gnullvm/windows_x86_64_gnullvm-0.52.6.crate"
1323 -
CHECKSUM_SHA256_438="24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1324 -
SOURCE_FILENAME_438="windows_x86_64_gnullvm-0.52.6.tar.gz"
1325 -
SOURCE_URI_439="https://static.crates.io/crates/windows_x86_64_msvc/windows_x86_64_msvc-0.48.5.crate"
1326 -
CHECKSUM_SHA256_439="ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1327 -
SOURCE_FILENAME_439="windows_x86_64_msvc-0.48.5.tar.gz"
1328 -
SOURCE_URI_440="https://static.crates.io/crates/windows_x86_64_msvc/windows_x86_64_msvc-0.52.6.crate"
1329 -
CHECKSUM_SHA256_440="589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1330 -
SOURCE_FILENAME_440="windows_x86_64_msvc-0.52.6.tar.gz"
1331 -
SOURCE_URI_441="https://static.crates.io/crates/winnow/winnow-0.7.15.crate"
1332 -
CHECKSUM_SHA256_441="df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
1333 -
SOURCE_FILENAME_441="winnow-0.7.15.tar.gz"
1334 -
SOURCE_URI_442="https://static.crates.io/crates/writeable/writeable-0.6.4.crate"
1335 -
CHECKSUM_SHA256_442="3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
1336 -
SOURCE_FILENAME_442="writeable-0.6.4.tar.gz"
1337 -
SOURCE_URI_443="https://static.crates.io/crates/x509-parser/x509-parser-0.18.1.crate"
1338 -
CHECKSUM_SHA256_443="d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
1339 -
SOURCE_FILENAME_443="x509-parser-0.18.1.tar.gz"
1340 -
SOURCE_URI_444="https://static.crates.io/crates/xattr/xattr-1.6.1.crate"
1341 -
CHECKSUM_SHA256_444="32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
1342 -
SOURCE_FILENAME_444="xattr-1.6.1.tar.gz"
1343 -
SOURCE_URI_445="https://static.crates.io/crates/yaml-rust/yaml-rust-0.4.5.crate"
1344 -
CHECKSUM_SHA256_445="56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
1345 -
SOURCE_FILENAME_445="yaml-rust-0.4.5.tar.gz"
1346 -
SOURCE_URI_446="https://static.crates.io/crates/yasna/yasna-0.6.0.crate"
1347 -
CHECKSUM_SHA256_446="b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
1348 -
SOURCE_FILENAME_446="yasna-0.6.0.tar.gz"
1349 -
SOURCE_URI_447="https://static.crates.io/crates/yoke/yoke-0.8.3.crate"
1350 -
CHECKSUM_SHA256_447="709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
1351 -
SOURCE_FILENAME_447="yoke-0.8.3.tar.gz"
1352 -
SOURCE_URI_448="https://static.crates.io/crates/yoke-derive/yoke-derive-0.8.3.crate"
1353 -
CHECKSUM_SHA256_448="33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
1354 -
SOURCE_FILENAME_448="yoke-derive-0.8.3.tar.gz"
1355 -
SOURCE_URI_449="https://static.crates.io/crates/zerocopy/zerocopy-0.8.59.crate"
1356 -
CHECKSUM_SHA256_449="6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
1357 -
SOURCE_FILENAME_449="zerocopy-0.8.59.tar.gz"
1358 -
SOURCE_URI_450="https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.59.crate"
1359 -
CHECKSUM_SHA256_450="ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
1360 -
SOURCE_FILENAME_450="zerocopy-derive-0.8.59.tar.gz"
1361 -
SOURCE_URI_451="https://static.crates.io/crates/zerofrom/zerofrom-0.1.8.crate"
1362 -
CHECKSUM_SHA256_451="0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
1363 -
SOURCE_FILENAME_451="zerofrom-0.1.8.tar.gz"
1364 -
SOURCE_URI_452="https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.8.crate"
1365 -
CHECKSUM_SHA256_452="f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
1366 -
SOURCE_FILENAME_452="zerofrom-derive-0.1.8.tar.gz"
1367 -
SOURCE_URI_453="https://static.crates.io/crates/zeroize/zeroize-1.9.0.crate"
1368 -
CHECKSUM_SHA256_453="e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
1369 -
SOURCE_FILENAME_453="zeroize-1.9.0.tar.gz"
1370 -
SOURCE_URI_454="https://static.crates.io/crates/zerotrie/zerotrie-0.2.5.crate"
1371 -
CHECKSUM_SHA256_454="4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
1372 -
SOURCE_FILENAME_454="zerotrie-0.2.5.tar.gz"
1373 -
SOURCE_URI_455="https://static.crates.io/crates/zerovec/zerovec-0.11.8.crate"
1374 -
CHECKSUM_SHA256_455="bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
1375 -
SOURCE_FILENAME_455="zerovec-0.11.8.tar.gz"
1376 -
SOURCE_URI_456="https://static.crates.io/crates/zerovec-derive/zerovec-derive-0.11.6.crate"
1377 -
CHECKSUM_SHA256_456="34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
1378 -
SOURCE_FILENAME_456="zerovec-derive-0.11.6.tar.gz"
1379 -
SOURCE_URI_457="https://static.crates.io/crates/zip/zip-2.4.2.crate"
1380 -
CHECKSUM_SHA256_457="fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
1381 -
SOURCE_FILENAME_457="zip-2.4.2.tar.gz"
1382 -
SOURCE_URI_458="https://static.crates.io/crates/zlib-rs/zlib-rs-0.6.8.crate"
1383 -
CHECKSUM_SHA256_458="b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
1384 -
SOURCE_FILENAME_458="zlib-rs-0.6.8.tar.gz"
1385 -
SOURCE_URI_459="https://static.crates.io/crates/zmij/zmij-1.0.23.crate"
1386 -
CHECKSUM_SHA256_459="29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
1387 -
SOURCE_FILENAME_459="zmij-1.0.23.tar.gz"
1388 -
SOURCE_URI_460="https://static.crates.io/crates/zopfli/zopfli-0.8.3.crate"
1389 -
CHECKSUM_SHA256_460="f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
1390 -
SOURCE_FILENAME_460="zopfli-0.8.3.tar.gz"
1391 -
SOURCE_URI_461="https://static.crates.io/crates/zstd/zstd-0.13.3.crate"
1392 -
CHECKSUM_SHA256_461="e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
1393 -
SOURCE_FILENAME_461="zstd-0.13.3.tar.gz"
1394 -
SOURCE_URI_462="https://static.crates.io/crates/zstd-safe/zstd-safe-7.3.0.crate"
1395 -
CHECKSUM_SHA256_462="64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
1396 -
SOURCE_FILENAME_462="zstd-safe-7.3.0.tar.gz"
1397 -
SOURCE_URI_463="https://static.crates.io/crates/zstd-sys/zstd-sys-2.1.0+zstd.1.5.7.crate"
1398 -
CHECKSUM_SHA256_463="0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"
1399 -
SOURCE_FILENAME_463="zstd-sys-2.1.0+zstd.1.5.7.tar.gz"
1400 -
SOURCE_URI_464="https://static.crates.io/crates/zune-core/zune-core-0.5.3.crate"
1401 -
CHECKSUM_SHA256_464="d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
1402 -
SOURCE_FILENAME_464="zune-core-0.5.3.tar.gz"
1403 -
SOURCE_URI_465="https://static.crates.io/crates/zune-jpeg/zune-jpeg-0.5.15.crate"
1404 -
CHECKSUM_SHA256_465="27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
1405 -
SOURCE_FILENAME_465="zune-jpeg-0.5.15.tar.gz"
1406 -
1407 -
ARCHITECTURES="!all x86_64"
1408 -
1409 -
PROVIDES="
1410 -
	oh$secondaryArchSuffix = $portVersion
1411 -
	cmd:oh
1412 -
	"
1413 -
REQUIRES="
1414 -
	haiku$secondaryArchSuffix
1415 -
	lib:libzstd$secondaryArchSuffix
1416 -
	"
1417 -
1418 -
BUILD_REQUIRES="
1419 -
	haiku${secondaryArchSuffix}_devel
1420 -
	devel:libzstd$secondaryArchSuffix
1421 -
	"
1422 -
BUILD_PREREQUIRES="
1423 -
	cmd:gcc$secondaryArchSuffix
1424 -
	cmd:pkg_config$secondaryArchSuffix
1425 -
	cmd:rustc$secondaryArchSuffix
1426 -
	"
1427 -
1428 -
BUILD()
1429 -
{
1430 -
	export CARGO_HOME=$sourceDir/../cargo
1431 -
	vendor=$CARGO_HOME/haiku
1432 -
	mkdir -p "$vendor"
1433 -
	for i in $(seq 2 465); do
1434 -
		eval "srcDir=\$sourceDir$i"
1435 -
		eval "sha256sum=\$CHECKSUM_SHA256_$i"
1436 -
		set -- "$srcDir"/*
1437 -
		ln -sf "$1" "$vendor"
1438 -
		cat <<-EOF >"$vendor/${1##*/}/.cargo-checksum.json"
1439 -
		{
1440 -
		  "package": "$sha256sum",
1441 -
		  "files": {}
1442 -
		}
1443 -
		EOF
1444 -
	done
1445 -
1446 -
	cat <<-EOF >"$CARGO_HOME"/config.toml
1447 -
	[source.haiku]
1448 -
	directory = "$vendor"
1449 -
1450 -
	[source.crates-io]
1451 -
	replace-with = "haiku"
1452 -
	EOF
1453 -
1454 -
	# The bundled zstd's hidden symbols break the link on Haiku: use the system's.
1455 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
1456 -
	export OXY_BUILD=v$portVersion
1457 -
	cargo build --release --frozen -p oh
1458 -
}
1459 -
1460 -
INSTALL()
1461 -
{
1462 -
	install -v -m 755 -d "$prefix"/bin "$docDir"
1463 -
	install -v -m 755 ./target/release/oh "$prefix"/bin
1464 -
	install -v -m 644 README.md LICENSE docs/oh.md "$docDir"
1465 -
}
1466 -
1467 -
TEST()
1468 -
{
1469 -
	export CARGO_HOME=$sourceDir/../cargo
1470 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
1471 -
	cargo test --release --frozen -p hub-api
1472 -
}
removed packaging/haikuports/dev-vcs/oxy/oxy-0.9.0.recipe +0 −990
1 -
SUMMARY="A version control system of signed, commuting changes, with Git interop"
2 -
DESCRIPTION="oxy is the command line of Oxygen, a version control system built on \
3 -
patch theory: changes commute, conflicts are recorded rather than blocking, \
4 -
every change can be signed, and a trust policy says who may move what. It \
5 -
imports and exports Git history, clones from OxygenHub and other Oxygen \
6 -
hosts, and keeps repositories on disk or in S3, GCS or Azure buckets."
7 -
HOMEPAGE="https://oxygenhub.io/andymccall/oxygen"
8 -
COPYRIGHT="2026 Andy McCall"
9 -
LICENSE="MIT
10 -
	Apache v2"
11 -
REVISION="1"
12 -
SOURCE_URI="$HOMEPAGE/releases/download/v$portVersion/oxy-$portVersion-src.tar.gz"
13 -
CHECKSUM_SHA256="b284392c53677177429b7db7f47b0b3585c766a910654a24ab47a6a75555bb8a"
14 -
SOURCE_DIR="oxygen-$portVersion"
15 -
SOURCE_URI_2="https://static.crates.io/crates/adler2/adler2-2.0.1.crate"
16 -
CHECKSUM_SHA256_2="320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
17 -
SOURCE_FILENAME_2="adler2-2.0.1.tar.gz"
18 -
SOURCE_URI_3="https://static.crates.io/crates/aho-corasick/aho-corasick-1.1.5.crate"
19 -
CHECKSUM_SHA256_3="c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
20 -
SOURCE_FILENAME_3="aho-corasick-1.1.5.tar.gz"
21 -
SOURCE_URI_4="https://static.crates.io/crates/android_system_properties/android_system_properties-0.1.6.crate"
22 -
CHECKSUM_SHA256_4="ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
23 -
SOURCE_FILENAME_4="android_system_properties-0.1.6.tar.gz"
24 -
SOURCE_URI_5="https://static.crates.io/crates/anstream/anstream-1.0.0.crate"
25 -
CHECKSUM_SHA256_5="824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
26 -
SOURCE_FILENAME_5="anstream-1.0.0.tar.gz"
27 -
SOURCE_URI_6="https://static.crates.io/crates/anstyle/anstyle-1.0.14.crate"
28 -
CHECKSUM_SHA256_6="940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
29 -
SOURCE_FILENAME_6="anstyle-1.0.14.tar.gz"
30 -
SOURCE_URI_7="https://static.crates.io/crates/anstyle-parse/anstyle-parse-1.0.0.crate"
31 -
CHECKSUM_SHA256_7="52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
32 -
SOURCE_FILENAME_7="anstyle-parse-1.0.0.tar.gz"
33 -
SOURCE_URI_8="https://static.crates.io/crates/anstyle-query/anstyle-query-1.1.5.crate"
34 -
CHECKSUM_SHA256_8="40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
35 -
SOURCE_FILENAME_8="anstyle-query-1.1.5.tar.gz"
36 -
SOURCE_URI_9="https://static.crates.io/crates/anstyle-wincon/anstyle-wincon-3.0.11.crate"
37 -
CHECKSUM_SHA256_9="291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
38 -
SOURCE_FILENAME_9="anstyle-wincon-3.0.11.tar.gz"
39 -
SOURCE_URI_10="https://static.crates.io/crates/anyhow/anyhow-1.0.104.crate"
40 -
CHECKSUM_SHA256_10="330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
41 -
SOURCE_FILENAME_10="anyhow-1.0.104.tar.gz"
42 -
SOURCE_URI_11="https://static.crates.io/crates/arc-swap/arc-swap-1.9.2.crate"
43 -
CHECKSUM_SHA256_11="c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
44 -
SOURCE_FILENAME_11="arc-swap-1.9.2.tar.gz"
45 -
SOURCE_URI_12="https://static.crates.io/crates/arrayvec/arrayvec-0.7.8.crate"
46 -
CHECKSUM_SHA256_12="d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
47 -
SOURCE_FILENAME_12="arrayvec-0.7.8.tar.gz"
48 -
SOURCE_URI_13="https://static.crates.io/crates/asn1-rs/asn1-rs-0.7.2.crate"
49 -
CHECKSUM_SHA256_13="b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
50 -
SOURCE_FILENAME_13="asn1-rs-0.7.2.tar.gz"
51 -
SOURCE_URI_14="https://static.crates.io/crates/asn1-rs-derive/asn1-rs-derive-0.6.0.crate"
52 -
CHECKSUM_SHA256_14="3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
53 -
SOURCE_FILENAME_14="asn1-rs-derive-0.6.0.tar.gz"
54 -
SOURCE_URI_15="https://static.crates.io/crates/asn1-rs-impl/asn1-rs-impl-0.2.0.crate"
55 -
CHECKSUM_SHA256_15="7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
56 -
SOURCE_FILENAME_15="asn1-rs-impl-0.2.0.tar.gz"
57 -
SOURCE_URI_16="https://static.crates.io/crates/async-trait/async-trait-0.1.92.crate"
58 -
CHECKSUM_SHA256_16="82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
59 -
SOURCE_FILENAME_16="async-trait-0.1.92.tar.gz"
60 -
SOURCE_URI_17="https://static.crates.io/crates/atomic-waker/atomic-waker-1.1.2.crate"
61 -
CHECKSUM_SHA256_17="1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
62 -
SOURCE_FILENAME_17="atomic-waker-1.1.2.tar.gz"
63 -
SOURCE_URI_18="https://static.crates.io/crates/autocfg/autocfg-1.5.1.crate"
64 -
CHECKSUM_SHA256_18="f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
65 -
SOURCE_FILENAME_18="autocfg-1.5.1.tar.gz"
66 -
SOURCE_URI_19="https://static.crates.io/crates/axum/axum-0.8.9.crate"
67 -
CHECKSUM_SHA256_19="31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
68 -
SOURCE_FILENAME_19="axum-0.8.9.tar.gz"
69 -
SOURCE_URI_20="https://static.crates.io/crates/axum-core/axum-core-0.5.6.crate"
70 -
CHECKSUM_SHA256_20="08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
71 -
SOURCE_FILENAME_20="axum-core-0.5.6.tar.gz"
72 -
SOURCE_URI_21="https://static.crates.io/crates/axum-server/axum-server-0.8.0.crate"
73 -
CHECKSUM_SHA256_21="b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc"
74 -
SOURCE_FILENAME_21="axum-server-0.8.0.tar.gz"
75 -
SOURCE_URI_22="https://static.crates.io/crates/base64/base64-0.22.1.crate"
76 -
CHECKSUM_SHA256_22="72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
77 -
SOURCE_FILENAME_22="base64-0.22.1.tar.gz"
78 -
SOURCE_URI_23="https://static.crates.io/crates/base64/base64-0.23.1.crate"
79 -
CHECKSUM_SHA256_23="ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
80 -
SOURCE_FILENAME_23="base64-0.23.1.tar.gz"
81 -
SOURCE_URI_24="https://static.crates.io/crates/base64ct/base64ct-1.8.3.crate"
82 -
CHECKSUM_SHA256_24="2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
83 -
SOURCE_FILENAME_24="base64ct-1.8.3.tar.gz"
84 -
SOURCE_URI_25="https://static.crates.io/crates/bit-set/bit-set-0.8.0.crate"
85 -
CHECKSUM_SHA256_25="08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
86 -
SOURCE_FILENAME_25="bit-set-0.8.0.tar.gz"
87 -
SOURCE_URI_26="https://static.crates.io/crates/bit-vec/bit-vec-0.8.0.crate"
88 -
CHECKSUM_SHA256_26="5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
89 -
SOURCE_FILENAME_26="bit-vec-0.8.0.tar.gz"
90 -
SOURCE_URI_27="https://static.crates.io/crates/bit-vec/bit-vec-0.9.1.crate"
91 -
CHECKSUM_SHA256_27="b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
92 -
SOURCE_FILENAME_27="bit-vec-0.9.1.tar.gz"
93 -
SOURCE_URI_28="https://static.crates.io/crates/bitflags/bitflags-2.13.2.crate"
94 -
CHECKSUM_SHA256_28="3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
95 -
SOURCE_FILENAME_28="bitflags-2.13.2.tar.gz"
96 -
SOURCE_URI_29="https://static.crates.io/crates/blake3/blake3-1.8.7.crate"
97 -
CHECKSUM_SHA256_29="6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
98 -
SOURCE_FILENAME_29="blake3-1.8.7.tar.gz"
99 -
SOURCE_URI_30="https://static.crates.io/crates/block-buffer/block-buffer-0.12.1.crate"
100 -
CHECKSUM_SHA256_30="d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
101 -
SOURCE_FILENAME_30="block-buffer-0.12.1.tar.gz"
102 -
SOURCE_URI_31="https://static.crates.io/crates/bstr/bstr-1.13.1.crate"
103 -
CHECKSUM_SHA256_31="6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
104 -
SOURCE_FILENAME_31="bstr-1.13.1.tar.gz"
105 -
SOURCE_URI_32="https://static.crates.io/crates/bumpalo/bumpalo-3.20.3.crate"
106 -
CHECKSUM_SHA256_32="72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
107 -
SOURCE_FILENAME_32="bumpalo-3.20.3.tar.gz"
108 -
SOURCE_URI_33="https://static.crates.io/crates/bytes/bytes-1.12.1.crate"
109 -
CHECKSUM_SHA256_33="fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
110 -
SOURCE_FILENAME_33="bytes-1.12.1.tar.gz"
111 -
SOURCE_URI_34="https://static.crates.io/crates/cc/cc-1.5.1.crate"
112 -
CHECKSUM_SHA256_34="f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
113 -
SOURCE_FILENAME_34="cc-1.5.1.tar.gz"
114 -
SOURCE_URI_35="https://static.crates.io/crates/cfg-if/cfg-if-1.0.5.crate"
115 -
CHECKSUM_SHA256_35="4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
116 -
SOURCE_FILENAME_35="cfg-if-1.0.5.tar.gz"
117 -
SOURCE_URI_36="https://static.crates.io/crates/chacha20/chacha20-0.10.2.crate"
118 -
CHECKSUM_SHA256_36="65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
119 -
SOURCE_FILENAME_36="chacha20-0.10.2.tar.gz"
120 -
SOURCE_URI_37="https://static.crates.io/crates/chrono/chrono-0.4.45.crate"
121 -
CHECKSUM_SHA256_37="1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
122 -
SOURCE_FILENAME_37="chrono-0.4.45.tar.gz"
123 -
SOURCE_URI_38="https://static.crates.io/crates/clap/clap-4.6.7.crate"
124 -
CHECKSUM_SHA256_38="aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
125 -
SOURCE_FILENAME_38="clap-4.6.7.tar.gz"
126 -
SOURCE_URI_39="https://static.crates.io/crates/clap_builder/clap_builder-4.6.7.crate"
127 -
CHECKSUM_SHA256_39="ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
128 -
SOURCE_FILENAME_39="clap_builder-4.6.7.tar.gz"
129 -
SOURCE_URI_40="https://static.crates.io/crates/clap_complete/clap_complete-4.6.11.crate"
130 -
CHECKSUM_SHA256_40="037e2a1a92236d0aff7e845093f64661d6df4c02c9fcc61a60e9e1d736fa392f"
131 -
SOURCE_FILENAME_40="clap_complete-4.6.11.tar.gz"
132 -
SOURCE_URI_41="https://static.crates.io/crates/clap_derive/clap_derive-4.6.7.crate"
133 -
CHECKSUM_SHA256_41="f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
134 -
SOURCE_FILENAME_41="clap_derive-4.6.7.tar.gz"
135 -
SOURCE_URI_42="https://static.crates.io/crates/clap_lex/clap_lex-1.1.1.crate"
136 -
CHECKSUM_SHA256_42="1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
137 -
SOURCE_FILENAME_42="clap_lex-1.1.1.tar.gz"
138 -
SOURCE_URI_43="https://static.crates.io/crates/clap_mangen/clap_mangen-0.3.3.crate"
139 -
CHECKSUM_SHA256_43="211d617eaa4b735c96c9e0228fcbdb5120ef623f2b8cb67ffb84c3e02dbc28a4"
140 -
SOURCE_FILENAME_43="clap_mangen-0.3.3.tar.gz"
141 -
SOURCE_URI_44="https://static.crates.io/crates/cmov/cmov-0.5.4.crate"
142 -
CHECKSUM_SHA256_44="0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
143 -
SOURCE_FILENAME_44="cmov-0.5.4.tar.gz"
144 -
SOURCE_URI_45="https://static.crates.io/crates/colorchoice/colorchoice-1.0.5.crate"
145 -
CHECKSUM_SHA256_45="1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
146 -
SOURCE_FILENAME_45="colorchoice-1.0.5.tar.gz"
147 -
SOURCE_URI_46="https://static.crates.io/crates/console/console-0.15.11.crate"
148 -
CHECKSUM_SHA256_46="054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
149 -
SOURCE_FILENAME_46="console-0.15.11.tar.gz"
150 -
SOURCE_URI_47="https://static.crates.io/crates/const-oid/const-oid-0.10.2.crate"
151 -
CHECKSUM_SHA256_47="a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
152 -
SOURCE_FILENAME_47="const-oid-0.10.2.tar.gz"
153 -
SOURCE_URI_48="https://static.crates.io/crates/constant_time_eq/constant_time_eq-0.4.2.crate"
154 -
CHECKSUM_SHA256_48="3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
155 -
SOURCE_FILENAME_48="constant_time_eq-0.4.2.tar.gz"
156 -
SOURCE_URI_49="https://static.crates.io/crates/cpufeatures/cpufeatures-0.3.1.crate"
157 -
CHECKSUM_SHA256_49="5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
158 -
SOURCE_FILENAME_49="cpufeatures-0.3.1.tar.gz"
159 -
SOURCE_URI_50="https://static.crates.io/crates/crc32fast/crc32fast-1.5.2.crate"
160 -
CHECKSUM_SHA256_50="01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
161 -
SOURCE_FILENAME_50="crc32fast-1.5.2.tar.gz"
162 -
SOURCE_URI_51="https://static.crates.io/crates/crossbeam-deque/crossbeam-deque-0.8.8.crate"
163 -
CHECKSUM_SHA256_51="622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
164 -
SOURCE_FILENAME_51="crossbeam-deque-0.8.8.tar.gz"
165 -
SOURCE_URI_52="https://static.crates.io/crates/crossbeam-epoch/crossbeam-epoch-0.9.21.crate"
166 -
CHECKSUM_SHA256_52="dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
167 -
SOURCE_FILENAME_52="crossbeam-epoch-0.9.21.tar.gz"
168 -
SOURCE_URI_53="https://static.crates.io/crates/crossbeam-utils/crossbeam-utils-0.8.23.crate"
169 -
CHECKSUM_SHA256_53="a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
170 -
SOURCE_FILENAME_53="crossbeam-utils-0.8.23.tar.gz"
171 -
SOURCE_URI_54="https://static.crates.io/crates/crypto-common/crypto-common-0.1.7.crate"
172 -
CHECKSUM_SHA256_54="78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
173 -
SOURCE_FILENAME_54="crypto-common-0.1.7.tar.gz"
174 -
SOURCE_URI_55="https://static.crates.io/crates/crypto-common/crypto-common-0.2.2.crate"
175 -
CHECKSUM_SHA256_55="ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
176 -
SOURCE_FILENAME_55="crypto-common-0.2.2.tar.gz"
177 -
SOURCE_URI_56="https://static.crates.io/crates/ctutils/ctutils-0.4.2.crate"
178 -
CHECKSUM_SHA256_56="7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
179 -
SOURCE_FILENAME_56="ctutils-0.4.2.tar.gz"
180 -
SOURCE_URI_57="https://static.crates.io/crates/curve25519-dalek/curve25519-dalek-5.0.0.crate"
181 -
CHECKSUM_SHA256_57="b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
182 -
SOURCE_FILENAME_57="curve25519-dalek-5.0.0.tar.gz"
183 -
SOURCE_URI_58="https://static.crates.io/crates/curve25519-dalek-derive/curve25519-dalek-derive-0.1.1.crate"
184 -
CHECKSUM_SHA256_58="f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
185 -
SOURCE_FILENAME_58="curve25519-dalek-derive-0.1.1.tar.gz"
186 -
SOURCE_URI_59="https://static.crates.io/crates/data-encoding/data-encoding-2.11.1.crate"
187 -
CHECKSUM_SHA256_59="4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
188 -
SOURCE_FILENAME_59="data-encoding-2.11.1.tar.gz"
189 -
SOURCE_URI_60="https://static.crates.io/crates/der/der-0.8.2.crate"
190 -
CHECKSUM_SHA256_60="a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
191 -
SOURCE_FILENAME_60="der-0.8.2.tar.gz"
192 -
SOURCE_URI_61="https://static.crates.io/crates/der-parser/der-parser-10.0.0.crate"
193 -
CHECKSUM_SHA256_61="07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
194 -
SOURCE_FILENAME_61="der-parser-10.0.0.tar.gz"
195 -
SOURCE_URI_62="https://static.crates.io/crates/deranged/deranged-0.5.8.crate"
196 -
CHECKSUM_SHA256_62="7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
197 -
SOURCE_FILENAME_62="deranged-0.5.8.tar.gz"
198 -
SOURCE_URI_63="https://static.crates.io/crates/digest/digest-0.10.7.crate"
199 -
CHECKSUM_SHA256_63="9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
200 -
SOURCE_FILENAME_63="digest-0.10.7.tar.gz"
201 -
SOURCE_URI_64="https://static.crates.io/crates/digest/digest-0.11.3.crate"
202 -
CHECKSUM_SHA256_64="f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
203 -
SOURCE_FILENAME_64="digest-0.11.3.tar.gz"
204 -
SOURCE_URI_65="https://static.crates.io/crates/displaydoc/displaydoc-0.2.7.crate"
205 -
CHECKSUM_SHA256_65="c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
206 -
SOURCE_FILENAME_65="displaydoc-0.2.7.tar.gz"
207 -
SOURCE_URI_66="https://static.crates.io/crates/ed25519/ed25519-3.0.0.crate"
208 -
CHECKSUM_SHA256_66="29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
209 -
SOURCE_FILENAME_66="ed25519-3.0.0.tar.gz"
210 -
SOURCE_URI_67="https://static.crates.io/crates/ed25519-dalek/ed25519-dalek-3.0.0.crate"
211 -
CHECKSUM_SHA256_67="6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
212 -
SOURCE_FILENAME_67="ed25519-dalek-3.0.0.tar.gz"
213 -
SOURCE_URI_68="https://static.crates.io/crates/either/either-1.18.0.crate"
214 -
CHECKSUM_SHA256_68="252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
215 -
SOURCE_FILENAME_68="either-1.18.0.tar.gz"
216 -
SOURCE_URI_69="https://static.crates.io/crates/encode_unicode/encode_unicode-1.0.0.crate"
217 -
CHECKSUM_SHA256_69="34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
218 -
SOURCE_FILENAME_69="encode_unicode-1.0.0.tar.gz"
219 -
SOURCE_URI_70="https://static.crates.io/crates/equivalent/equivalent-1.0.2.crate"
220 -
CHECKSUM_SHA256_70="877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
221 -
SOURCE_FILENAME_70="equivalent-1.0.2.tar.gz"
222 -
SOURCE_URI_71="https://static.crates.io/crates/errno/errno-0.3.14.crate"
223 -
CHECKSUM_SHA256_71="39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
224 -
SOURCE_FILENAME_71="errno-0.3.14.tar.gz"
225 -
SOURCE_URI_72="https://static.crates.io/crates/fastrand/fastrand-2.5.0.crate"
226 -
CHECKSUM_SHA256_72="da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
227 -
SOURCE_FILENAME_72="fastrand-2.5.0.tar.gz"
228 -
SOURCE_URI_73="https://static.crates.io/crates/fiat-crypto/fiat-crypto-0.3.0.crate"
229 -
CHECKSUM_SHA256_73="64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
230 -
SOURCE_FILENAME_73="fiat-crypto-0.3.0.tar.gz"
231 -
SOURCE_URI_74="https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.14.crate"
232 -
CHECKSUM_SHA256_74="aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
233 -
SOURCE_FILENAME_74="find-msvc-tools-0.1.14.tar.gz"
234 -
SOURCE_URI_75="https://static.crates.io/crates/flate2/flate2-1.1.10.crate"
235 -
CHECKSUM_SHA256_75="6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
236 -
SOURCE_FILENAME_75="flate2-1.1.10.tar.gz"
237 -
SOURCE_URI_76="https://static.crates.io/crates/fnv/fnv-1.0.7.crate"
238 -
CHECKSUM_SHA256_76="3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
239 -
SOURCE_FILENAME_76="fnv-1.0.7.tar.gz"
240 -
SOURCE_URI_77="https://static.crates.io/crates/form_urlencoded/form_urlencoded-1.2.2.crate"
241 -
CHECKSUM_SHA256_77="cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
242 -
SOURCE_FILENAME_77="form_urlencoded-1.2.2.tar.gz"
243 -
SOURCE_URI_78="https://static.crates.io/crates/fs-err/fs-err-3.3.1.crate"
244 -
CHECKSUM_SHA256_78="b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
245 -
SOURCE_FILENAME_78="fs-err-3.3.1.tar.gz"
246 -
SOURCE_URI_79="https://static.crates.io/crates/futures/futures-0.3.34.crate"
247 -
CHECKSUM_SHA256_79="9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
248 -
SOURCE_FILENAME_79="futures-0.3.34.tar.gz"
249 -
SOURCE_URI_80="https://static.crates.io/crates/futures-channel/futures-channel-0.3.34.crate"
250 -
CHECKSUM_SHA256_80="b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
251 -
SOURCE_FILENAME_80="futures-channel-0.3.34.tar.gz"
252 -
SOURCE_URI_81="https://static.crates.io/crates/futures-core/futures-core-0.3.34.crate"
253 -
CHECKSUM_SHA256_81="92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
254 -
SOURCE_FILENAME_81="futures-core-0.3.34.tar.gz"
255 -
SOURCE_URI_82="https://static.crates.io/crates/futures-executor/futures-executor-0.3.34.crate"
256 -
CHECKSUM_SHA256_82="031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
257 -
SOURCE_FILENAME_82="futures-executor-0.3.34.tar.gz"
258 -
SOURCE_URI_83="https://static.crates.io/crates/futures-io/futures-io-0.3.34.crate"
259 -
CHECKSUM_SHA256_83="53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
260 -
SOURCE_FILENAME_83="futures-io-0.3.34.tar.gz"
261 -
SOURCE_URI_84="https://static.crates.io/crates/futures-macro/futures-macro-0.3.34.crate"
262 -
CHECKSUM_SHA256_84="9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
263 -
SOURCE_FILENAME_84="futures-macro-0.3.34.tar.gz"
264 -
SOURCE_URI_85="https://static.crates.io/crates/futures-sink/futures-sink-0.3.34.crate"
265 -
CHECKSUM_SHA256_85="1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
266 -
SOURCE_FILENAME_85="futures-sink-0.3.34.tar.gz"
267 -
SOURCE_URI_86="https://static.crates.io/crates/futures-task/futures-task-0.3.34.crate"
268 -
CHECKSUM_SHA256_86="cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
269 -
SOURCE_FILENAME_86="futures-task-0.3.34.tar.gz"
270 -
SOURCE_URI_87="https://static.crates.io/crates/futures-util/futures-util-0.3.34.crate"
271 -
CHECKSUM_SHA256_87="0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
272 -
SOURCE_FILENAME_87="futures-util-0.3.34.tar.gz"
273 -
SOURCE_URI_88="https://static.crates.io/crates/generic-array/generic-array-0.14.7.crate"
274 -
CHECKSUM_SHA256_88="85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
275 -
SOURCE_FILENAME_88="generic-array-0.14.7.tar.gz"
276 -
SOURCE_URI_89="https://static.crates.io/crates/getrandom/getrandom-0.2.17.crate"
277 -
CHECKSUM_SHA256_89="ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
278 -
SOURCE_FILENAME_89="getrandom-0.2.17.tar.gz"
279 -
SOURCE_URI_90="https://static.crates.io/crates/getrandom/getrandom-0.3.4.crate"
280 -
CHECKSUM_SHA256_90="899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
281 -
SOURCE_FILENAME_90="getrandom-0.3.4.tar.gz"
282 -
SOURCE_URI_91="https://static.crates.io/crates/getrandom/getrandom-0.4.3.crate"
283 -
CHECKSUM_SHA256_91="300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
284 -
SOURCE_FILENAME_91="getrandom-0.4.3.tar.gz"
285 -
SOURCE_URI_92="https://static.crates.io/crates/globset/globset-0.4.20.crate"
286 -
CHECKSUM_SHA256_92="07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
287 -
SOURCE_FILENAME_92="globset-0.4.20.tar.gz"
288 -
SOURCE_URI_93="https://static.crates.io/crates/h2/h2-0.4.19.crate"
289 -
CHECKSUM_SHA256_93="ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
290 -
SOURCE_FILENAME_93="h2-0.4.19.tar.gz"
291 -
SOURCE_URI_94="https://static.crates.io/crates/hashbrown/hashbrown-0.17.1.crate"
292 -
CHECKSUM_SHA256_94="ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
293 -
SOURCE_FILENAME_94="hashbrown-0.17.1.tar.gz"
294 -
SOURCE_URI_95="https://static.crates.io/crates/heck/heck-0.5.0.crate"
295 -
CHECKSUM_SHA256_95="2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
296 -
SOURCE_FILENAME_95="heck-0.5.0.tar.gz"
297 -
SOURCE_URI_96="https://static.crates.io/crates/hex/hex-0.4.3.crate"
298 -
CHECKSUM_SHA256_96="7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
299 -
SOURCE_FILENAME_96="hex-0.4.3.tar.gz"
300 -
SOURCE_URI_97="https://static.crates.io/crates/http/http-1.5.0.crate"
301 -
CHECKSUM_SHA256_97="918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
302 -
SOURCE_FILENAME_97="http-1.5.0.tar.gz"
303 -
SOURCE_URI_98="https://static.crates.io/crates/http-body/http-body-1.1.0.crate"
304 -
CHECKSUM_SHA256_98="ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
305 -
SOURCE_FILENAME_98="http-body-1.1.0.tar.gz"
306 -
SOURCE_URI_99="https://static.crates.io/crates/http-body-util/http-body-util-0.1.5.crate"
307 -
CHECKSUM_SHA256_99="23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
308 -
SOURCE_FILENAME_99="http-body-util-0.1.5.tar.gz"
309 -
SOURCE_URI_100="https://static.crates.io/crates/httparse/httparse-1.10.1.crate"
310 -
CHECKSUM_SHA256_100="6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
311 -
SOURCE_FILENAME_100="httparse-1.10.1.tar.gz"
312 -
SOURCE_URI_101="https://static.crates.io/crates/httpdate/httpdate-1.0.3.crate"
313 -
CHECKSUM_SHA256_101="df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
314 -
SOURCE_FILENAME_101="httpdate-1.0.3.tar.gz"
315 -
SOURCE_URI_102="https://static.crates.io/crates/humantime/humantime-2.4.0.crate"
316 -
CHECKSUM_SHA256_102="15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15"
317 -
SOURCE_FILENAME_102="humantime-2.4.0.tar.gz"
318 -
SOURCE_URI_103="https://static.crates.io/crates/hybrid-array/hybrid-array-0.4.15.crate"
319 -
CHECKSUM_SHA256_103="27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
320 -
SOURCE_FILENAME_103="hybrid-array-0.4.15.tar.gz"
321 -
SOURCE_URI_104="https://static.crates.io/crates/hyper/hyper-1.11.1.crate"
322 -
CHECKSUM_SHA256_104="27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
323 -
SOURCE_FILENAME_104="hyper-1.11.1.tar.gz"
324 -
SOURCE_URI_105="https://static.crates.io/crates/hyper-util/hyper-util-0.1.21.crate"
325 -
CHECKSUM_SHA256_105="ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
326 -
SOURCE_FILENAME_105="hyper-util-0.1.21.tar.gz"
327 -
SOURCE_URI_106="https://static.crates.io/crates/iana-time-zone-haiku/iana-time-zone-haiku-0.1.2.crate"
328 -
CHECKSUM_SHA256_106="f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
329 -
SOURCE_FILENAME_106="iana-time-zone-haiku-0.1.2.tar.gz"
330 -
SOURCE_URI_107="https://static.crates.io/crates/icu_collections/icu_collections-2.3.0.crate"
331 -
CHECKSUM_SHA256_107="fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
332 -
SOURCE_FILENAME_107="icu_collections-2.3.0.tar.gz"
333 -
SOURCE_URI_108="https://static.crates.io/crates/icu_locale_core/icu_locale_core-2.3.0.crate"
334 -
CHECKSUM_SHA256_108="d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
335 -
SOURCE_FILENAME_108="icu_locale_core-2.3.0.tar.gz"
336 -
SOURCE_URI_109="https://static.crates.io/crates/icu_normalizer/icu_normalizer-2.3.0.crate"
337 -
CHECKSUM_SHA256_109="12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
338 -
SOURCE_FILENAME_109="icu_normalizer-2.3.0.tar.gz"
339 -
SOURCE_URI_110="https://static.crates.io/crates/icu_normalizer_data/icu_normalizer_data-2.3.0.crate"
340 -
CHECKSUM_SHA256_110="1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
341 -
SOURCE_FILENAME_110="icu_normalizer_data-2.3.0.tar.gz"
342 -
SOURCE_URI_111="https://static.crates.io/crates/icu_properties/icu_properties-2.3.0.crate"
343 -
CHECKSUM_SHA256_111="7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
344 -
SOURCE_FILENAME_111="icu_properties-2.3.0.tar.gz"
345 -
SOURCE_URI_112="https://static.crates.io/crates/icu_properties_data/icu_properties_data-2.3.0.crate"
346 -
CHECKSUM_SHA256_112="e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
347 -
SOURCE_FILENAME_112="icu_properties_data-2.3.0.tar.gz"
348 -
SOURCE_URI_113="https://static.crates.io/crates/icu_provider/icu_provider-2.3.1.crate"
349 -
CHECKSUM_SHA256_113="d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
350 -
SOURCE_FILENAME_113="icu_provider-2.3.1.tar.gz"
351 -
SOURCE_URI_114="https://static.crates.io/crates/idna/idna-1.1.0.crate"
352 -
CHECKSUM_SHA256_114="3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
353 -
SOURCE_FILENAME_114="idna-1.1.0.tar.gz"
354 -
SOURCE_URI_115="https://static.crates.io/crates/idna_adapter/idna_adapter-1.2.2.crate"
355 -
CHECKSUM_SHA256_115="cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
356 -
SOURCE_FILENAME_115="idna_adapter-1.2.2.tar.gz"
357 -
SOURCE_URI_116="https://static.crates.io/crates/ignore/ignore-0.4.33.crate"
358 -
CHECKSUM_SHA256_116="00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f"
359 -
SOURCE_FILENAME_116="ignore-0.4.33.tar.gz"
360 -
SOURCE_URI_117="https://static.crates.io/crates/indexmap/indexmap-2.14.2.crate"
361 -
CHECKSUM_SHA256_117="cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
362 -
SOURCE_FILENAME_117="indexmap-2.14.2.tar.gz"
363 -
SOURCE_URI_118="https://static.crates.io/crates/indicatif/indicatif-0.17.11.crate"
364 -
CHECKSUM_SHA256_118="183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
365 -
SOURCE_FILENAME_118="indicatif-0.17.11.tar.gz"
366 -
SOURCE_URI_119="https://static.crates.io/crates/is_terminal_polyfill/is_terminal_polyfill-1.70.2.crate"
367 -
CHECKSUM_SHA256_119="a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
368 -
SOURCE_FILENAME_119="is_terminal_polyfill-1.70.2.tar.gz"
369 -
SOURCE_URI_120="https://static.crates.io/crates/itertools/itertools-0.15.0.crate"
370 -
CHECKSUM_SHA256_120="8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
371 -
SOURCE_FILENAME_120="itertools-0.15.0.tar.gz"
372 -
SOURCE_URI_121="https://static.crates.io/crates/itoa/itoa-1.0.18.crate"
373 -
CHECKSUM_SHA256_121="8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
374 -
SOURCE_FILENAME_121="itoa-1.0.18.tar.gz"
375 -
SOURCE_URI_122="https://static.crates.io/crates/jobserver/jobserver-0.1.35.crate"
376 -
CHECKSUM_SHA256_122="1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
377 -
SOURCE_FILENAME_122="jobserver-0.1.35.tar.gz"
378 -
SOURCE_URI_123="https://static.crates.io/crates/js-sys/js-sys-0.3.106.crate"
379 -
CHECKSUM_SHA256_123="7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
380 -
SOURCE_FILENAME_123="js-sys-0.3.106.tar.gz"
381 -
SOURCE_URI_124="https://static.crates.io/crates/keccak/keccak-0.2.2.crate"
382 -
CHECKSUM_SHA256_124="d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598"
383 -
SOURCE_FILENAME_124="keccak-0.2.2.tar.gz"
384 -
SOURCE_URI_125="https://static.crates.io/crates/lazy_static/lazy_static-1.5.0.crate"
385 -
CHECKSUM_SHA256_125="bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
386 -
SOURCE_FILENAME_125="lazy_static-1.5.0.tar.gz"
387 -
SOURCE_URI_126="https://static.crates.io/crates/libc/libc-0.2.189.crate"
388 -
CHECKSUM_SHA256_126="3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
389 -
SOURCE_FILENAME_126="libc-0.2.189.tar.gz"
390 -
SOURCE_URI_127="https://static.crates.io/crates/libmimalloc-sys/libmimalloc-sys-0.1.49.crate"
391 -
CHECKSUM_SHA256_127="6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
392 -
SOURCE_FILENAME_127="libmimalloc-sys-0.1.49.tar.gz"
393 -
SOURCE_URI_128="https://static.crates.io/crates/linux-raw-sys/linux-raw-sys-0.12.1.crate"
394 -
CHECKSUM_SHA256_128="32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
395 -
SOURCE_FILENAME_128="linux-raw-sys-0.12.1.tar.gz"
396 -
SOURCE_URI_129="https://static.crates.io/crates/litemap/litemap-0.8.3.crate"
397 -
CHECKSUM_SHA256_129="47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
398 -
SOURCE_FILENAME_129="litemap-0.8.3.tar.gz"
399 -
SOURCE_URI_130="https://static.crates.io/crates/lock_api/lock_api-0.4.14.crate"
400 -
CHECKSUM_SHA256_130="224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
401 -
SOURCE_FILENAME_130="lock_api-0.4.14.tar.gz"
402 -
SOURCE_URI_131="https://static.crates.io/crates/log/log-0.4.34.crate"
403 -
CHECKSUM_SHA256_131="f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
404 -
SOURCE_FILENAME_131="log-0.4.34.tar.gz"
405 -
SOURCE_URI_132="https://static.crates.io/crates/matchit/matchit-0.8.4.crate"
406 -
CHECKSUM_SHA256_132="47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
407 -
SOURCE_FILENAME_132="matchit-0.8.4.tar.gz"
408 -
SOURCE_URI_133="https://static.crates.io/crates/md-5/md-5-0.11.0.crate"
409 -
CHECKSUM_SHA256_133="69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
410 -
SOURCE_FILENAME_133="md-5-0.11.0.tar.gz"
411 -
SOURCE_URI_134="https://static.crates.io/crates/memchr/memchr-2.8.3.crate"
412 -
CHECKSUM_SHA256_134="cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
413 -
SOURCE_FILENAME_134="memchr-2.8.3.tar.gz"
414 -
SOURCE_URI_135="https://static.crates.io/crates/mimalloc/mimalloc-0.1.52.crate"
415 -
CHECKSUM_SHA256_135="2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
416 -
SOURCE_FILENAME_135="mimalloc-0.1.52.tar.gz"
417 -
SOURCE_URI_136="https://static.crates.io/crates/mime/mime-0.3.17.crate"
418 -
CHECKSUM_SHA256_136="6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
419 -
SOURCE_FILENAME_136="mime-0.3.17.tar.gz"
420 -
SOURCE_URI_137="https://static.crates.io/crates/minimal-lexical/minimal-lexical-0.2.1.crate"
421 -
CHECKSUM_SHA256_137="68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
422 -
SOURCE_FILENAME_137="minimal-lexical-0.2.1.tar.gz"
423 -
SOURCE_URI_138="https://static.crates.io/crates/miniz_oxide/miniz_oxide-0.9.1.crate"
424 -
CHECKSUM_SHA256_138="b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
425 -
SOURCE_FILENAME_138="miniz_oxide-0.9.1.tar.gz"
426 -
SOURCE_URI_139="https://static.crates.io/crates/mio/mio-1.2.3.crate"
427 -
CHECKSUM_SHA256_139="4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
428 -
SOURCE_FILENAME_139="mio-1.2.3.tar.gz"
429 -
SOURCE_URI_140="https://static.crates.io/crates/ml-dsa/ml-dsa-0.1.1.crate"
430 -
CHECKSUM_SHA256_140="add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
431 -
SOURCE_FILENAME_140="ml-dsa-0.1.1.tar.gz"
432 -
SOURCE_URI_141="https://static.crates.io/crates/module-lattice/module-lattice-0.2.3.crate"
433 -
CHECKSUM_SHA256_141="0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
434 -
SOURCE_FILENAME_141="module-lattice-0.2.3.tar.gz"
435 -
SOURCE_URI_142="https://static.crates.io/crates/nom/nom-7.1.3.crate"
436 -
CHECKSUM_SHA256_142="d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
437 -
SOURCE_FILENAME_142="nom-7.1.3.tar.gz"
438 -
SOURCE_URI_143="https://static.crates.io/crates/num-bigint/num-bigint-0.4.8.crate"
439 -
CHECKSUM_SHA256_143="c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
440 -
SOURCE_FILENAME_143="num-bigint-0.4.8.tar.gz"
441 -
SOURCE_URI_144="https://static.crates.io/crates/num-conv/num-conv-0.2.2.crate"
442 -
CHECKSUM_SHA256_144="521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
443 -
SOURCE_FILENAME_144="num-conv-0.2.2.tar.gz"
444 -
SOURCE_URI_145="https://static.crates.io/crates/num-integer/num-integer-0.1.47.crate"
445 -
CHECKSUM_SHA256_145="7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
446 -
SOURCE_FILENAME_145="num-integer-0.1.47.tar.gz"
447 -
SOURCE_URI_146="https://static.crates.io/crates/num-traits/num-traits-0.2.19.crate"
448 -
CHECKSUM_SHA256_146="071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
449 -
SOURCE_FILENAME_146="num-traits-0.2.19.tar.gz"
450 -
SOURCE_URI_147="https://static.crates.io/crates/number_prefix/number_prefix-0.4.0.crate"
451 -
CHECKSUM_SHA256_147="830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
452 -
SOURCE_FILENAME_147="number_prefix-0.4.0.tar.gz"
453 -
SOURCE_URI_148="https://static.crates.io/crates/object_store/object_store-0.14.2.crate"
454 -
CHECKSUM_SHA256_148="f1796bc93603f78c5760a69f2d58badc9618d22adade0a95385bb2adbae4eb94"
455 -
SOURCE_FILENAME_148="object_store-0.14.2.tar.gz"
456 -
SOURCE_URI_149="https://static.crates.io/crates/oid-registry/oid-registry-0.8.1.crate"
457 -
CHECKSUM_SHA256_149="12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
458 -
SOURCE_FILENAME_149="oid-registry-0.8.1.tar.gz"
459 -
SOURCE_URI_150="https://static.crates.io/crates/once_cell/once_cell-1.21.4.crate"
460 -
CHECKSUM_SHA256_150="9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
461 -
SOURCE_FILENAME_150="once_cell-1.21.4.tar.gz"
462 -
SOURCE_URI_151="https://static.crates.io/crates/once_cell_polyfill/once_cell_polyfill-1.70.2.crate"
463 -
CHECKSUM_SHA256_151="384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
464 -
SOURCE_FILENAME_151="once_cell_polyfill-1.70.2.tar.gz"
465 -
SOURCE_URI_152="https://static.crates.io/crates/parking_lot/parking_lot-0.12.5.crate"
466 -
CHECKSUM_SHA256_152="93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
467 -
SOURCE_FILENAME_152="parking_lot-0.12.5.tar.gz"
468 -
SOURCE_URI_153="https://static.crates.io/crates/parking_lot_core/parking_lot_core-0.9.12.crate"
469 -
CHECKSUM_SHA256_153="2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
470 -
SOURCE_FILENAME_153="parking_lot_core-0.9.12.tar.gz"
471 -
SOURCE_URI_154="https://static.crates.io/crates/pem/pem-4.0.0.crate"
472 -
CHECKSUM_SHA256_154="d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120"
473 -
SOURCE_FILENAME_154="pem-4.0.0.tar.gz"
474 -
SOURCE_URI_155="https://static.crates.io/crates/percent-encoding/percent-encoding-2.3.2.crate"
475 -
CHECKSUM_SHA256_155="9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
476 -
SOURCE_FILENAME_155="percent-encoding-2.3.2.tar.gz"
477 -
SOURCE_URI_156="https://static.crates.io/crates/pin-project-lite/pin-project-lite-0.2.17.crate"
478 -
CHECKSUM_SHA256_156="a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
479 -
SOURCE_FILENAME_156="pin-project-lite-0.2.17.tar.gz"
480 -
SOURCE_URI_157="https://static.crates.io/crates/pkcs8/pkcs8-0.11.0.crate"
481 -
CHECKSUM_SHA256_157="451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
482 -
SOURCE_FILENAME_157="pkcs8-0.11.0.tar.gz"
483 -
SOURCE_URI_158="https://static.crates.io/crates/pkg-config/pkg-config-0.3.34.crate"
484 -
CHECKSUM_SHA256_158="f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
485 -
SOURCE_FILENAME_158="pkg-config-0.3.34.tar.gz"
486 -
SOURCE_URI_159="https://static.crates.io/crates/portable-atomic/portable-atomic-1.15.0.crate"
487 -
CHECKSUM_SHA256_159="05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
488 -
SOURCE_FILENAME_159="portable-atomic-1.15.0.tar.gz"
489 -
SOURCE_URI_160="https://static.crates.io/crates/potential_utf/potential_utf-0.1.6.crate"
490 -
CHECKSUM_SHA256_160="d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
491 -
SOURCE_FILENAME_160="potential_utf-0.1.6.tar.gz"
492 -
SOURCE_URI_161="https://static.crates.io/crates/powerfmt/powerfmt-0.2.0.crate"
493 -
CHECKSUM_SHA256_161="439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
494 -
SOURCE_FILENAME_161="powerfmt-0.2.0.tar.gz"
495 -
SOURCE_URI_162="https://static.crates.io/crates/ppv-lite86/ppv-lite86-0.2.21.crate"
496 -
CHECKSUM_SHA256_162="85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
497 -
SOURCE_FILENAME_162="ppv-lite86-0.2.21.tar.gz"
498 -
SOURCE_URI_163="https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.107.crate"
499 -
CHECKSUM_SHA256_163="985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
500 -
SOURCE_FILENAME_163="proc-macro2-1.0.107.tar.gz"
501 -
SOURCE_URI_164="https://static.crates.io/crates/proptest/proptest-1.11.0.crate"
502 -
CHECKSUM_SHA256_164="4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
503 -
SOURCE_FILENAME_164="proptest-1.11.0.tar.gz"
504 -
SOURCE_URI_165="https://static.crates.io/crates/quick-error/quick-error-1.2.3.crate"
505 -
CHECKSUM_SHA256_165="a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
506 -
SOURCE_FILENAME_165="quick-error-1.2.3.tar.gz"
507 -
SOURCE_URI_166="https://static.crates.io/crates/quick-xml/quick-xml-0.41.0.crate"
508 -
CHECKSUM_SHA256_166="e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
509 -
SOURCE_FILENAME_166="quick-xml-0.41.0.tar.gz"
510 -
SOURCE_URI_167="https://static.crates.io/crates/quote/quote-1.0.47.crate"
511 -
CHECKSUM_SHA256_167="1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
512 -
SOURCE_FILENAME_167="quote-1.0.47.tar.gz"
513 -
SOURCE_URI_168="https://static.crates.io/crates/r-efi/r-efi-5.3.0.crate"
514 -
CHECKSUM_SHA256_168="69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
515 -
SOURCE_FILENAME_168="r-efi-5.3.0.tar.gz"
516 -
SOURCE_URI_169="https://static.crates.io/crates/r-efi/r-efi-6.0.0.crate"
517 -
CHECKSUM_SHA256_169="f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
518 -
SOURCE_FILENAME_169="r-efi-6.0.0.tar.gz"
519 -
SOURCE_URI_170="https://static.crates.io/crates/rand/rand-0.10.3.crate"
520 -
CHECKSUM_SHA256_170="65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
521 -
SOURCE_FILENAME_170="rand-0.10.3.tar.gz"
522 -
SOURCE_URI_171="https://static.crates.io/crates/rand/rand-0.8.8.crate"
523 -
CHECKSUM_SHA256_171="e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
524 -
SOURCE_FILENAME_171="rand-0.8.8.tar.gz"
525 -
SOURCE_URI_172="https://static.crates.io/crates/rand/rand-0.9.5.crate"
526 -
CHECKSUM_SHA256_172="b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
527 -
SOURCE_FILENAME_172="rand-0.9.5.tar.gz"
528 -
SOURCE_URI_173="https://static.crates.io/crates/rand_chacha/rand_chacha-0.3.1.crate"
529 -
CHECKSUM_SHA256_173="e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
530 -
SOURCE_FILENAME_173="rand_chacha-0.3.1.tar.gz"
531 -
SOURCE_URI_174="https://static.crates.io/crates/rand_chacha/rand_chacha-0.9.0.crate"
532 -
CHECKSUM_SHA256_174="d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
533 -
SOURCE_FILENAME_174="rand_chacha-0.9.0.tar.gz"
534 -
SOURCE_URI_175="https://static.crates.io/crates/rand_core/rand_core-0.10.1.crate"
535 -
CHECKSUM_SHA256_175="63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
536 -
SOURCE_FILENAME_175="rand_core-0.10.1.tar.gz"
537 -
SOURCE_URI_176="https://static.crates.io/crates/rand_core/rand_core-0.6.4.crate"
538 -
CHECKSUM_SHA256_176="ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
539 -
SOURCE_FILENAME_176="rand_core-0.6.4.tar.gz"
540 -
SOURCE_URI_177="https://static.crates.io/crates/rand_core/rand_core-0.9.5.crate"
541 -
CHECKSUM_SHA256_177="76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
542 -
SOURCE_FILENAME_177="rand_core-0.9.5.tar.gz"
543 -
SOURCE_URI_178="https://static.crates.io/crates/rand_xorshift/rand_xorshift-0.4.0.crate"
544 -
CHECKSUM_SHA256_178="513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
545 -
SOURCE_FILENAME_178="rand_xorshift-0.4.0.tar.gz"
546 -
SOURCE_URI_179="https://static.crates.io/crates/rayon/rayon-1.12.0.crate"
547 -
CHECKSUM_SHA256_179="fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
548 -
SOURCE_FILENAME_179="rayon-1.12.0.tar.gz"
549 -
SOURCE_URI_180="https://static.crates.io/crates/rayon-core/rayon-core-1.13.0.crate"
550 -
CHECKSUM_SHA256_180="22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
551 -
SOURCE_FILENAME_180="rayon-core-1.13.0.tar.gz"
552 -
SOURCE_URI_181="https://static.crates.io/crates/rcgen/rcgen-0.14.10.crate"
553 -
CHECKSUM_SHA256_181="8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8"
554 -
SOURCE_FILENAME_181="rcgen-0.14.10.tar.gz"
555 -
SOURCE_URI_182="https://static.crates.io/crates/redox_syscall/redox_syscall-0.5.18.crate"
556 -
CHECKSUM_SHA256_182="ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
557 -
SOURCE_FILENAME_182="redox_syscall-0.5.18.tar.gz"
558 -
SOURCE_URI_183="https://static.crates.io/crates/regex-automata/regex-automata-0.4.18.crate"
559 -
CHECKSUM_SHA256_183="ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
560 -
SOURCE_FILENAME_183="regex-automata-0.4.18.tar.gz"
561 -
SOURCE_URI_184="https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.11.crate"
562 -
CHECKSUM_SHA256_184="d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
563 -
SOURCE_FILENAME_184="regex-syntax-0.8.11.tar.gz"
564 -
SOURCE_URI_185="https://static.crates.io/crates/ring/ring-0.17.14.crate"
565 -
CHECKSUM_SHA256_185="a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
566 -
SOURCE_FILENAME_185="ring-0.17.14.tar.gz"
567 -
SOURCE_URI_186="https://static.crates.io/crates/roff/roff-1.1.1.crate"
568 -
CHECKSUM_SHA256_186="323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189"
569 -
SOURCE_FILENAME_186="roff-1.1.1.tar.gz"
570 -
SOURCE_URI_187="https://static.crates.io/crates/rustc_version/rustc_version-0.4.1.crate"
571 -
CHECKSUM_SHA256_187="cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
572 -
SOURCE_FILENAME_187="rustc_version-0.4.1.tar.gz"
573 -
SOURCE_URI_188="https://static.crates.io/crates/rusticata-macros/rusticata-macros-4.1.0.crate"
574 -
CHECKSUM_SHA256_188="faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
575 -
SOURCE_FILENAME_188="rusticata-macros-4.1.0.tar.gz"
576 -
SOURCE_URI_189="https://static.crates.io/crates/rustix/rustix-1.1.5.crate"
577 -
CHECKSUM_SHA256_189="891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
578 -
SOURCE_FILENAME_189="rustix-1.1.5.tar.gz"
579 -
SOURCE_URI_190="https://static.crates.io/crates/rustls/rustls-0.23.45.crate"
580 -
CHECKSUM_SHA256_190="0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
581 -
SOURCE_FILENAME_190="rustls-0.23.45.tar.gz"
582 -
SOURCE_URI_191="https://static.crates.io/crates/rustls-pemfile/rustls-pemfile-2.2.0.crate"
583 -
CHECKSUM_SHA256_191="dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
584 -
SOURCE_FILENAME_191="rustls-pemfile-2.2.0.tar.gz"
585 -
SOURCE_URI_192="https://static.crates.io/crates/rustls-pki-types/rustls-pki-types-1.15.1.crate"
586 -
CHECKSUM_SHA256_192="2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
587 -
SOURCE_FILENAME_192="rustls-pki-types-1.15.1.tar.gz"
588 -
SOURCE_URI_193="https://static.crates.io/crates/rustls-webpki/rustls-webpki-0.103.15.crate"
589 -
CHECKSUM_SHA256_193="f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
590 -
SOURCE_FILENAME_193="rustls-webpki-0.103.15.tar.gz"
591 -
SOURCE_URI_194="https://static.crates.io/crates/rustversion/rustversion-1.0.23.crate"
592 -
CHECKSUM_SHA256_194="cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
593 -
SOURCE_FILENAME_194="rustversion-1.0.23.tar.gz"
594 -
SOURCE_URI_195="https://static.crates.io/crates/rusty-fork/rusty-fork-0.3.1.crate"
595 -
CHECKSUM_SHA256_195="cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
596 -
SOURCE_FILENAME_195="rusty-fork-0.3.1.tar.gz"
597 -
SOURCE_URI_196="https://static.crates.io/crates/ryu/ryu-1.0.23.crate"
598 -
CHECKSUM_SHA256_196="9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
599 -
SOURCE_FILENAME_196="ryu-1.0.23.tar.gz"
600 -
SOURCE_URI_197="https://static.crates.io/crates/same-file/same-file-1.0.6.crate"
601 -
CHECKSUM_SHA256_197="93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
602 -
SOURCE_FILENAME_197="same-file-1.0.6.tar.gz"
603 -
SOURCE_URI_198="https://static.crates.io/crates/scopeguard/scopeguard-1.2.0.crate"
604 -
CHECKSUM_SHA256_198="94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
605 -
SOURCE_FILENAME_198="scopeguard-1.2.0.tar.gz"
606 -
SOURCE_URI_199="https://static.crates.io/crates/semver/semver-1.0.28.crate"
607 -
CHECKSUM_SHA256_199="8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
608 -
SOURCE_FILENAME_199="semver-1.0.28.tar.gz"
609 -
SOURCE_URI_200="https://static.crates.io/crates/serde/serde-1.0.229.crate"
610 -
CHECKSUM_SHA256_200="4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
611 -
SOURCE_FILENAME_200="serde-1.0.229.tar.gz"
612 -
SOURCE_URI_201="https://static.crates.io/crates/serde_core/serde_core-1.0.229.crate"
613 -
CHECKSUM_SHA256_201="67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
614 -
SOURCE_FILENAME_201="serde_core-1.0.229.tar.gz"
615 -
SOURCE_URI_202="https://static.crates.io/crates/serde_derive/serde_derive-1.0.229.crate"
616 -
CHECKSUM_SHA256_202="e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
617 -
SOURCE_FILENAME_202="serde_derive-1.0.229.tar.gz"
618 -
SOURCE_URI_203="https://static.crates.io/crates/serde_json/serde_json-1.0.151.crate"
619 -
CHECKSUM_SHA256_203="c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
620 -
SOURCE_FILENAME_203="serde_json-1.0.151.tar.gz"
621 -
SOURCE_URI_204="https://static.crates.io/crates/serde_path_to_error/serde_path_to_error-0.1.20.crate"
622 -
CHECKSUM_SHA256_204="10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
623 -
SOURCE_FILENAME_204="serde_path_to_error-0.1.20.tar.gz"
624 -
SOURCE_URI_205="https://static.crates.io/crates/serde_urlencoded/serde_urlencoded-0.7.1.crate"
625 -
CHECKSUM_SHA256_205="d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
626 -
SOURCE_FILENAME_205="serde_urlencoded-0.7.1.tar.gz"
627 -
SOURCE_URI_206="https://static.crates.io/crates/sha2/sha2-0.11.0.crate"
628 -
CHECKSUM_SHA256_206="446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
629 -
SOURCE_FILENAME_206="sha2-0.11.0.tar.gz"
630 -
SOURCE_URI_207="https://static.crates.io/crates/shake/shake-0.1.0.crate"
631 -
CHECKSUM_SHA256_207="09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
632 -
SOURCE_FILENAME_207="shake-0.1.0.tar.gz"
633 -
SOURCE_URI_208="https://static.crates.io/crates/shlex/shlex-2.0.1.crate"
634 -
CHECKSUM_SHA256_208="f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
635 -
SOURCE_FILENAME_208="shlex-2.0.1.tar.gz"
636 -
SOURCE_URI_209="https://static.crates.io/crates/signature/signature-3.0.0.crate"
637 -
CHECKSUM_SHA256_209="28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
638 -
SOURCE_FILENAME_209="signature-3.0.0.tar.gz"
639 -
SOURCE_URI_210="https://static.crates.io/crates/simd-adler32/simd-adler32-0.3.10.crate"
640 -
CHECKSUM_SHA256_210="3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
641 -
SOURCE_FILENAME_210="simd-adler32-0.3.10.tar.gz"
642 -
SOURCE_URI_211="https://static.crates.io/crates/similar/similar-2.7.0.crate"
643 -
CHECKSUM_SHA256_211="bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
644 -
SOURCE_FILENAME_211="similar-2.7.0.tar.gz"
645 -
SOURCE_URI_212="https://static.crates.io/crates/slab/slab-0.4.12.crate"
646 -
CHECKSUM_SHA256_212="0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
647 -
SOURCE_FILENAME_212="slab-0.4.12.tar.gz"
648 -
SOURCE_URI_213="https://static.crates.io/crates/smallvec/smallvec-1.16.2.crate"
649 -
CHECKSUM_SHA256_213="f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
650 -
SOURCE_FILENAME_213="smallvec-1.16.2.tar.gz"
651 -
SOURCE_URI_214="https://static.crates.io/crates/socket2/socket2-0.6.5.crate"
652 -
CHECKSUM_SHA256_214="c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
653 -
SOURCE_FILENAME_214="socket2-0.6.5.tar.gz"
654 -
SOURCE_URI_215="https://static.crates.io/crates/spin/spin-0.10.1.crate"
655 -
CHECKSUM_SHA256_215="023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
656 -
SOURCE_FILENAME_215="spin-0.10.1.tar.gz"
657 -
SOURCE_URI_216="https://static.crates.io/crates/spki/spki-0.8.0.crate"
658 -
CHECKSUM_SHA256_216="1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
659 -
SOURCE_FILENAME_216="spki-0.8.0.tar.gz"
660 -
SOURCE_URI_217="https://static.crates.io/crates/sponge-cursor/sponge-cursor-0.1.0.crate"
661 -
CHECKSUM_SHA256_217="3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
662 -
SOURCE_FILENAME_217="sponge-cursor-0.1.0.tar.gz"
663 -
SOURCE_URI_218="https://static.crates.io/crates/stable_deref_trait/stable_deref_trait-1.2.1.crate"
664 -
CHECKSUM_SHA256_218="6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
665 -
SOURCE_FILENAME_218="stable_deref_trait-1.2.1.tar.gz"
666 -
SOURCE_URI_219="https://static.crates.io/crates/strsim/strsim-0.11.1.crate"
667 -
CHECKSUM_SHA256_219="7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
668 -
SOURCE_FILENAME_219="strsim-0.11.1.tar.gz"
669 -
SOURCE_URI_220="https://static.crates.io/crates/subtle/subtle-2.6.1.crate"
670 -
CHECKSUM_SHA256_220="13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
671 -
SOURCE_FILENAME_220="subtle-2.6.1.tar.gz"
672 -
SOURCE_URI_221="https://static.crates.io/crates/syn/syn-2.0.119.crate"
673 -
CHECKSUM_SHA256_221="872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
674 -
SOURCE_FILENAME_221="syn-2.0.119.tar.gz"
675 -
SOURCE_URI_222="https://static.crates.io/crates/syn/syn-3.0.6.crate"
676 -
CHECKSUM_SHA256_222="8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
677 -
SOURCE_FILENAME_222="syn-3.0.6.tar.gz"
678 -
SOURCE_URI_223="https://static.crates.io/crates/sync_wrapper/sync_wrapper-1.0.2.crate"
679 -
CHECKSUM_SHA256_223="0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
680 -
SOURCE_FILENAME_223="sync_wrapper-1.0.2.tar.gz"
681 -
SOURCE_URI_224="https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate"
682 -
CHECKSUM_SHA256_224="728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
683 -
SOURCE_FILENAME_224="synstructure-0.13.2.tar.gz"
684 -
SOURCE_URI_225="https://static.crates.io/crates/synstructure/synstructure-0.14.0.crate"
685 -
CHECKSUM_SHA256_225="901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
686 -
SOURCE_FILENAME_225="synstructure-0.14.0.tar.gz"
687 -
SOURCE_URI_226="https://static.crates.io/crates/tempfile/tempfile-3.27.0.crate"
688 -
CHECKSUM_SHA256_226="32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
689 -
SOURCE_FILENAME_226="tempfile-3.27.0.tar.gz"
690 -
SOURCE_URI_227="https://static.crates.io/crates/thiserror/thiserror-1.0.69.crate"
691 -
CHECKSUM_SHA256_227="b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
692 -
SOURCE_FILENAME_227="thiserror-1.0.69.tar.gz"
693 -
SOURCE_URI_228="https://static.crates.io/crates/thiserror/thiserror-2.0.21.crate"
694 -
CHECKSUM_SHA256_228="09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
695 -
SOURCE_FILENAME_228="thiserror-2.0.21.tar.gz"
696 -
SOURCE_URI_229="https://static.crates.io/crates/thiserror-impl/thiserror-impl-1.0.69.crate"
697 -
CHECKSUM_SHA256_229="4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
698 -
SOURCE_FILENAME_229="thiserror-impl-1.0.69.tar.gz"
699 -
SOURCE_URI_230="https://static.crates.io/crates/thiserror-impl/thiserror-impl-2.0.21.crate"
700 -
CHECKSUM_SHA256_230="fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
701 -
SOURCE_FILENAME_230="thiserror-impl-2.0.21.tar.gz"
702 -
SOURCE_URI_231="https://static.crates.io/crates/time/time-0.3.55.crate"
703 -
CHECKSUM_SHA256_231="cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
704 -
SOURCE_FILENAME_231="time-0.3.55.tar.gz"
705 -
SOURCE_URI_232="https://static.crates.io/crates/time-core/time-core-0.1.9.crate"
706 -
CHECKSUM_SHA256_232="9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
707 -
SOURCE_FILENAME_232="time-core-0.1.9.tar.gz"
708 -
SOURCE_URI_233="https://static.crates.io/crates/time-macros/time-macros-0.2.32.crate"
709 -
CHECKSUM_SHA256_233="7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
710 -
SOURCE_FILENAME_233="time-macros-0.2.32.tar.gz"
711 -
SOURCE_URI_234="https://static.crates.io/crates/tinystr/tinystr-0.8.4.crate"
712 -
CHECKSUM_SHA256_234="b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
713 -
SOURCE_FILENAME_234="tinystr-0.8.4.tar.gz"
714 -
SOURCE_URI_235="https://static.crates.io/crates/tokio/tokio-1.53.1.crate"
715 -
CHECKSUM_SHA256_235="202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
716 -
SOURCE_FILENAME_235="tokio-1.53.1.tar.gz"
717 -
SOURCE_URI_236="https://static.crates.io/crates/tokio-macros/tokio-macros-2.7.2.crate"
718 -
CHECKSUM_SHA256_236="78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
719 -
SOURCE_FILENAME_236="tokio-macros-2.7.2.tar.gz"
720 -
SOURCE_URI_237="https://static.crates.io/crates/tokio-rustls/tokio-rustls-0.26.5.crate"
721 -
CHECKSUM_SHA256_237="b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
722 -
SOURCE_FILENAME_237="tokio-rustls-0.26.5.tar.gz"
723 -
SOURCE_URI_238="https://static.crates.io/crates/tokio-util/tokio-util-0.7.19.crate"
724 -
CHECKSUM_SHA256_238="494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
725 -
SOURCE_FILENAME_238="tokio-util-0.7.19.tar.gz"
726 -
SOURCE_URI_239="https://static.crates.io/crates/tower/tower-0.5.3.crate"
727 -
CHECKSUM_SHA256_239="ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
728 -
SOURCE_FILENAME_239="tower-0.5.3.tar.gz"
729 -
SOURCE_URI_240="https://static.crates.io/crates/tower-layer/tower-layer-0.3.3.crate"
730 -
CHECKSUM_SHA256_240="121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
731 -
SOURCE_FILENAME_240="tower-layer-0.3.3.tar.gz"
732 -
SOURCE_URI_241="https://static.crates.io/crates/tower-service/tower-service-0.3.3.crate"
733 -
CHECKSUM_SHA256_241="8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
734 -
SOURCE_FILENAME_241="tower-service-0.3.3.tar.gz"
735 -
SOURCE_URI_242="https://static.crates.io/crates/tracing/tracing-0.1.44.crate"
736 -
CHECKSUM_SHA256_242="63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
737 -
SOURCE_FILENAME_242="tracing-0.1.44.tar.gz"
738 -
SOURCE_URI_243="https://static.crates.io/crates/tracing-attributes/tracing-attributes-0.1.31.crate"
739 -
CHECKSUM_SHA256_243="7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
740 -
SOURCE_FILENAME_243="tracing-attributes-0.1.31.tar.gz"
741 -
SOURCE_URI_244="https://static.crates.io/crates/tracing-core/tracing-core-0.1.36.crate"
742 -
CHECKSUM_SHA256_244="db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
743 -
SOURCE_FILENAME_244="tracing-core-0.1.36.tar.gz"
744 -
SOURCE_URI_245="https://static.crates.io/crates/typenum/typenum-1.20.1.crate"
745 -
CHECKSUM_SHA256_245="b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
746 -
SOURCE_FILENAME_245="typenum-1.20.1.tar.gz"
747 -
SOURCE_URI_246="https://static.crates.io/crates/unarray/unarray-0.1.4.crate"
748 -
CHECKSUM_SHA256_246="eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
749 -
SOURCE_FILENAME_246="unarray-0.1.4.tar.gz"
750 -
SOURCE_URI_247="https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.26.crate"
751 -
CHECKSUM_SHA256_247="d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
752 -
SOURCE_FILENAME_247="unicode-ident-1.0.26.tar.gz"
753 -
SOURCE_URI_248="https://static.crates.io/crates/unicode-width/unicode-width-0.2.2.crate"
754 -
CHECKSUM_SHA256_248="b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
755 -
SOURCE_FILENAME_248="unicode-width-0.2.2.tar.gz"
756 -
SOURCE_URI_249="https://static.crates.io/crates/untrusted/untrusted-0.9.0.crate"
757 -
CHECKSUM_SHA256_249="8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
758 -
SOURCE_FILENAME_249="untrusted-0.9.0.tar.gz"
759 -
SOURCE_URI_250="https://static.crates.io/crates/ureq/ureq-2.12.1.crate"
760 -
CHECKSUM_SHA256_250="02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
761 -
SOURCE_FILENAME_250="ureq-2.12.1.tar.gz"
762 -
SOURCE_URI_251="https://static.crates.io/crates/url/url-2.5.8.crate"
763 -
CHECKSUM_SHA256_251="ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
764 -
SOURCE_FILENAME_251="url-2.5.8.tar.gz"
765 -
SOURCE_URI_252="https://static.crates.io/crates/utf8_iter/utf8_iter-1.0.4.crate"
766 -
CHECKSUM_SHA256_252="b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
767 -
SOURCE_FILENAME_252="utf8_iter-1.0.4.tar.gz"
768 -
SOURCE_URI_253="https://static.crates.io/crates/utf8parse/utf8parse-0.2.2.crate"
769 -
CHECKSUM_SHA256_253="06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
770 -
SOURCE_FILENAME_253="utf8parse-0.2.2.tar.gz"
771 -
SOURCE_URI_254="https://static.crates.io/crates/version_check/version_check-0.9.5.crate"
772 -
CHECKSUM_SHA256_254="0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
773 -
SOURCE_FILENAME_254="version_check-0.9.5.tar.gz"
774 -
SOURCE_URI_255="https://static.crates.io/crates/wait-timeout/wait-timeout-0.2.1.crate"
775 -
CHECKSUM_SHA256_255="09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
776 -
SOURCE_FILENAME_255="wait-timeout-0.2.1.tar.gz"
777 -
SOURCE_URI_256="https://static.crates.io/crates/walkdir/walkdir-2.5.0.crate"
778 -
CHECKSUM_SHA256_256="29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
779 -
SOURCE_FILENAME_256="walkdir-2.5.0.tar.gz"
780 -
SOURCE_URI_257="https://static.crates.io/crates/wasi/wasi-0.11.1+wasi-snapshot-preview1.crate"
781 -
CHECKSUM_SHA256_257="ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
782 -
SOURCE_FILENAME_257="wasi-0.11.1+wasi-snapshot-preview1.tar.gz"
783 -
SOURCE_URI_258="https://static.crates.io/crates/wasip2/wasip2-1.0.4+wasi-0.2.12.crate"
784 -
CHECKSUM_SHA256_258="b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
785 -
SOURCE_FILENAME_258="wasip2-1.0.4+wasi-0.2.12.tar.gz"
786 -
SOURCE_URI_259="https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.129.crate"
787 -
CHECKSUM_SHA256_259="9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
788 -
SOURCE_FILENAME_259="wasm-bindgen-0.2.129.tar.gz"
789 -
SOURCE_URI_260="https://static.crates.io/crates/wasm-bindgen-futures/wasm-bindgen-futures-0.4.79.crate"
790 -
CHECKSUM_SHA256_260="3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
791 -
SOURCE_FILENAME_260="wasm-bindgen-futures-0.4.79.tar.gz"
792 -
SOURCE_URI_261="https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.129.crate"
793 -
CHECKSUM_SHA256_261="2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
794 -
SOURCE_FILENAME_261="wasm-bindgen-macro-0.2.129.tar.gz"
795 -
SOURCE_URI_262="https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.129.crate"
796 -
CHECKSUM_SHA256_262="6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
797 -
SOURCE_FILENAME_262="wasm-bindgen-macro-support-0.2.129.tar.gz"
798 -
SOURCE_URI_263="https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.129.crate"
799 -
CHECKSUM_SHA256_263="23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
800 -
SOURCE_FILENAME_263="wasm-bindgen-shared-0.2.129.tar.gz"
801 -
SOURCE_URI_264="https://static.crates.io/crates/web-time/web-time-1.1.0.crate"
802 -
CHECKSUM_SHA256_264="5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
803 -
SOURCE_FILENAME_264="web-time-1.1.0.tar.gz"
804 -
SOURCE_URI_265="https://static.crates.io/crates/webpki-roots/webpki-roots-0.26.11.crate"
805 -
CHECKSUM_SHA256_265="521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
806 -
SOURCE_FILENAME_265="webpki-roots-0.26.11.tar.gz"
807 -
SOURCE_URI_266="https://static.crates.io/crates/webpki-roots/webpki-roots-1.0.9.crate"
808 -
CHECKSUM_SHA256_266="7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
809 -
SOURCE_FILENAME_266="webpki-roots-1.0.9.tar.gz"
810 -
SOURCE_URI_267="https://static.crates.io/crates/winapi-util/winapi-util-0.1.11.crate"
811 -
CHECKSUM_SHA256_267="c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
812 -
SOURCE_FILENAME_267="winapi-util-0.1.11.tar.gz"
813 -
SOURCE_URI_268="https://static.crates.io/crates/windows-core/windows-core-0.62.2.crate"
814 -
CHECKSUM_SHA256_268="b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
815 -
SOURCE_FILENAME_268="windows-core-0.62.2.tar.gz"
816 -
SOURCE_URI_269="https://static.crates.io/crates/windows-implement/windows-implement-0.60.2.crate"
817 -
CHECKSUM_SHA256_269="053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
818 -
SOURCE_FILENAME_269="windows-implement-0.60.2.tar.gz"
819 -
SOURCE_URI_270="https://static.crates.io/crates/windows-interface/windows-interface-0.59.3.crate"
820 -
CHECKSUM_SHA256_270="3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
821 -
SOURCE_FILENAME_270="windows-interface-0.59.3.tar.gz"
822 -
SOURCE_URI_271="https://static.crates.io/crates/windows-link/windows-link-0.2.1.crate"
823 -
CHECKSUM_SHA256_271="f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
824 -
SOURCE_FILENAME_271="windows-link-0.2.1.tar.gz"
825 -
SOURCE_URI_272="https://static.crates.io/crates/windows-result/windows-result-0.4.1.crate"
826 -
CHECKSUM_SHA256_272="7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
827 -
SOURCE_FILENAME_272="windows-result-0.4.1.tar.gz"
828 -
SOURCE_URI_273="https://static.crates.io/crates/windows-strings/windows-strings-0.5.1.crate"
829 -
CHECKSUM_SHA256_273="7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
830 -
SOURCE_FILENAME_273="windows-strings-0.5.1.tar.gz"
831 -
SOURCE_URI_274="https://static.crates.io/crates/windows-sys/windows-sys-0.52.0.crate"
832 -
CHECKSUM_SHA256_274="282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
833 -
SOURCE_FILENAME_274="windows-sys-0.52.0.tar.gz"
834 -
SOURCE_URI_275="https://static.crates.io/crates/windows-sys/windows-sys-0.59.0.crate"
835 -
CHECKSUM_SHA256_275="1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
836 -
SOURCE_FILENAME_275="windows-sys-0.59.0.tar.gz"
837 -
SOURCE_URI_276="https://static.crates.io/crates/windows-sys/windows-sys-0.61.2.crate"
838 -
CHECKSUM_SHA256_276="ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
839 -
SOURCE_FILENAME_276="windows-sys-0.61.2.tar.gz"
840 -
SOURCE_URI_277="https://static.crates.io/crates/windows-targets/windows-targets-0.52.6.crate"
841 -
CHECKSUM_SHA256_277="9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
842 -
SOURCE_FILENAME_277="windows-targets-0.52.6.tar.gz"
843 -
SOURCE_URI_278="https://static.crates.io/crates/windows_aarch64_gnullvm/windows_aarch64_gnullvm-0.52.6.crate"
844 -
CHECKSUM_SHA256_278="32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
845 -
SOURCE_FILENAME_278="windows_aarch64_gnullvm-0.52.6.tar.gz"
846 -
SOURCE_URI_279="https://static.crates.io/crates/windows_aarch64_msvc/windows_aarch64_msvc-0.52.6.crate"
847 -
CHECKSUM_SHA256_279="09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
848 -
SOURCE_FILENAME_279="windows_aarch64_msvc-0.52.6.tar.gz"
849 -
SOURCE_URI_280="https://static.crates.io/crates/windows_i686_gnu/windows_i686_gnu-0.52.6.crate"
850 -
CHECKSUM_SHA256_280="8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
851 -
SOURCE_FILENAME_280="windows_i686_gnu-0.52.6.tar.gz"
852 -
SOURCE_URI_281="https://static.crates.io/crates/windows_i686_gnullvm/windows_i686_gnullvm-0.52.6.crate"
853 -
CHECKSUM_SHA256_281="0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
854 -
SOURCE_FILENAME_281="windows_i686_gnullvm-0.52.6.tar.gz"
855 -
SOURCE_URI_282="https://static.crates.io/crates/windows_i686_msvc/windows_i686_msvc-0.52.6.crate"
856 -
CHECKSUM_SHA256_282="240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
857 -
SOURCE_FILENAME_282="windows_i686_msvc-0.52.6.tar.gz"
858 -
SOURCE_URI_283="https://static.crates.io/crates/windows_x86_64_gnu/windows_x86_64_gnu-0.52.6.crate"
859 -
CHECKSUM_SHA256_283="147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
860 -
SOURCE_FILENAME_283="windows_x86_64_gnu-0.52.6.tar.gz"
861 -
SOURCE_URI_284="https://static.crates.io/crates/windows_x86_64_gnullvm/windows_x86_64_gnullvm-0.52.6.crate"
862 -
CHECKSUM_SHA256_284="24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
863 -
SOURCE_FILENAME_284="windows_x86_64_gnullvm-0.52.6.tar.gz"
864 -
SOURCE_URI_285="https://static.crates.io/crates/windows_x86_64_msvc/windows_x86_64_msvc-0.52.6.crate"
865 -
CHECKSUM_SHA256_285="589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
866 -
SOURCE_FILENAME_285="windows_x86_64_msvc-0.52.6.tar.gz"
867 -
SOURCE_URI_286="https://static.crates.io/crates/wit-bindgen/wit-bindgen-0.57.1.crate"
868 -
CHECKSUM_SHA256_286="1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
869 -
SOURCE_FILENAME_286="wit-bindgen-0.57.1.tar.gz"
870 -
SOURCE_URI_287="https://static.crates.io/crates/writeable/writeable-0.6.4.crate"
871 -
CHECKSUM_SHA256_287="3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
872 -
SOURCE_FILENAME_287="writeable-0.6.4.tar.gz"
873 -
SOURCE_URI_288="https://static.crates.io/crates/x509-parser/x509-parser-0.18.1.crate"
874 -
CHECKSUM_SHA256_288="d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
875 -
SOURCE_FILENAME_288="x509-parser-0.18.1.tar.gz"
876 -
SOURCE_URI_289="https://static.crates.io/crates/yasna/yasna-0.6.0.crate"
877 -
CHECKSUM_SHA256_289="b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
878 -
SOURCE_FILENAME_289="yasna-0.6.0.tar.gz"
879 -
SOURCE_URI_290="https://static.crates.io/crates/yoke/yoke-0.8.3.crate"
880 -
CHECKSUM_SHA256_290="709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
881 -
SOURCE_FILENAME_290="yoke-0.8.3.tar.gz"
882 -
SOURCE_URI_291="https://static.crates.io/crates/yoke-derive/yoke-derive-0.8.3.crate"
883 -
CHECKSUM_SHA256_291="33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
884 -
SOURCE_FILENAME_291="yoke-derive-0.8.3.tar.gz"
885 -
SOURCE_URI_292="https://static.crates.io/crates/zerocopy/zerocopy-0.8.59.crate"
886 -
CHECKSUM_SHA256_292="6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
887 -
SOURCE_FILENAME_292="zerocopy-0.8.59.tar.gz"
888 -
SOURCE_URI_293="https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.59.crate"
889 -
CHECKSUM_SHA256_293="ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
890 -
SOURCE_FILENAME_293="zerocopy-derive-0.8.59.tar.gz"
891 -
SOURCE_URI_294="https://static.crates.io/crates/zerofrom/zerofrom-0.1.8.crate"
892 -
CHECKSUM_SHA256_294="0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
893 -
SOURCE_FILENAME_294="zerofrom-0.1.8.tar.gz"
894 -
SOURCE_URI_295="https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.8.crate"
895 -
CHECKSUM_SHA256_295="f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
896 -
SOURCE_FILENAME_295="zerofrom-derive-0.1.8.tar.gz"
897 -
SOURCE_URI_296="https://static.crates.io/crates/zeroize/zeroize-1.9.0.crate"
898 -
CHECKSUM_SHA256_296="e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
899 -
SOURCE_FILENAME_296="zeroize-1.9.0.tar.gz"
900 -
SOURCE_URI_297="https://static.crates.io/crates/zerotrie/zerotrie-0.2.5.crate"
901 -
CHECKSUM_SHA256_297="4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
902 -
SOURCE_FILENAME_297="zerotrie-0.2.5.tar.gz"
903 -
SOURCE_URI_298="https://static.crates.io/crates/zerovec/zerovec-0.11.8.crate"
904 -
CHECKSUM_SHA256_298="bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
905 -
SOURCE_FILENAME_298="zerovec-0.11.8.tar.gz"
906 -
SOURCE_URI_299="https://static.crates.io/crates/zerovec-derive/zerovec-derive-0.11.6.crate"
907 -
CHECKSUM_SHA256_299="34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
908 -
SOURCE_FILENAME_299="zerovec-derive-0.11.6.tar.gz"
909 -
SOURCE_URI_300="https://static.crates.io/crates/zlib-rs/zlib-rs-0.6.8.crate"
910 -
CHECKSUM_SHA256_300="b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
911 -
SOURCE_FILENAME_300="zlib-rs-0.6.8.tar.gz"
912 -
SOURCE_URI_301="https://static.crates.io/crates/zmij/zmij-1.0.23.crate"
913 -
CHECKSUM_SHA256_301="29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
914 -
SOURCE_FILENAME_301="zmij-1.0.23.tar.gz"
915 -
SOURCE_URI_302="https://static.crates.io/crates/zstd/zstd-0.13.3.crate"
916 -
CHECKSUM_SHA256_302="e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
917 -
SOURCE_FILENAME_302="zstd-0.13.3.tar.gz"
918 -
SOURCE_URI_303="https://static.crates.io/crates/zstd-safe/zstd-safe-7.3.0.crate"
919 -
CHECKSUM_SHA256_303="64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
920 -
SOURCE_FILENAME_303="zstd-safe-7.3.0.tar.gz"
921 -
SOURCE_URI_304="https://static.crates.io/crates/zstd-sys/zstd-sys-2.1.0+zstd.1.5.7.crate"
922 -
CHECKSUM_SHA256_304="0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"
923 -
SOURCE_FILENAME_304="zstd-sys-2.1.0+zstd.1.5.7.tar.gz"
924 -
925 -
ARCHITECTURES="!all x86_64"
926 -
927 -
PROVIDES="
928 -
	oxy$secondaryArchSuffix = $portVersion
929 -
	cmd:oxy
930 -
	"
931 -
REQUIRES="
932 -
	haiku$secondaryArchSuffix
933 -
	lib:libzstd$secondaryArchSuffix
934 -
	"
935 -
936 -
BUILD_REQUIRES="
937 -
	haiku${secondaryArchSuffix}_devel
938 -
	devel:libzstd$secondaryArchSuffix
939 -
	"
940 -
BUILD_PREREQUIRES="
941 -
	cmd:gcc$secondaryArchSuffix
942 -
	cmd:pkg_config$secondaryArchSuffix
943 -
	cmd:rustc$secondaryArchSuffix
944 -
	"
945 -
946 -
BUILD()
947 -
{
948 -
	export CARGO_HOME=$sourceDir/../cargo
949 -
	vendor=$CARGO_HOME/haiku
950 -
	mkdir -p "$vendor"
951 -
	for i in $(seq 2 304); do
952 -
		eval "srcDir=\$sourceDir$i"
953 -
		eval "sha256sum=\$CHECKSUM_SHA256_$i"
954 -
		set -- "$srcDir"/*
955 -
		ln -sf "$1" "$vendor"
956 -
		cat <<-EOF >"$vendor/${1##*/}/.cargo-checksum.json"
957 -
		{
958 -
		  "package": "$sha256sum",
959 -
		  "files": {}
960 -
		}
961 -
		EOF
962 -
	done
963 -
964 -
	cat <<-EOF >"$CARGO_HOME"/config.toml
965 -
	[source.haiku]
966 -
	directory = "$vendor"
967 -
968 -
	[source.crates-io]
969 -
	replace-with = "haiku"
970 -
	EOF
971 -
972 -
	# The bundled zstd's hidden symbols break the link on Haiku: use the system's.
973 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
974 -
	export OXY_BUILD=v$portVersion
975 -
	cargo build --release --frozen -p oxy
976 -
}
977 -
978 -
INSTALL()
979 -
{
980 -
	install -v -m 755 -d "$prefix"/bin "$docDir"
981 -
	install -v -m 755 ./target/release/oxy "$prefix"/bin
982 -
	install -v -m 644 README.md LICENSE* "$docDir"
983 -
}
984 -
985 -
TEST()
986 -
{
987 -
	export CARGO_HOME=$sourceDir/../cargo
988 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
989 -
	cargo test --release --frozen -p oxy-core
990 -
}
removed packaging/haikuports/icons/oh-16.png +0 −0

No line changes.

removed packaging/haikuports/icons/oh-32.png +0 −0

No line changes.

removed packaging/haikuports/icons/oh-64.png +0 −0

No line changes.

removed packaging/haikuports/icons/oh.hvif +0 −0

No line changes.

removed packaging/haikuports/icons/oh.svg +0 −17
1 -
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2 -
  <defs>
3 -
    <linearGradient id="g" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11">
4 -
      <stop offset="0%" stop-color="#00F0FF"/>
5 -
      <stop offset="100%" stop-color="#0072FF"/>
6 -
    </linearGradient>
7 -
  </defs>
8 -
  <rect width="64" height="64" rx="14" fill="#070c18"/>
9 -
  <g transform="translate(32 32)">
10 -
    <path d="M -11 -6 L 11 -6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
11 -
    <path d="M -11 6 L 11 6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
12 -
    <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
13 -
    <circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/>
14 -
    <circle cx="11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
15 -
    <circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/>
16 -
  </g>
17 -
</svg>
removed packaging/haikuports/icons/oxy-16.png +0 −0

No line changes.

removed packaging/haikuports/icons/oxy-32.png +0 −0

No line changes.

removed packaging/haikuports/icons/oxy-64.png +0 −0

No line changes.

removed packaging/haikuports/icons/oxy.hvif +0 −0

No line changes.

removed packaging/haikuports/icons/oxy.svg +0 −17
1 -
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2 -
  <defs>
3 -
    <linearGradient id="g" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11">
4 -
      <stop offset="0%" stop-color="#00F0FF"/>
5 -
      <stop offset="100%" stop-color="#0072FF"/>
6 -
    </linearGradient>
7 -
  </defs>
8 -
  <rect width="64" height="64" rx="14" fill="#C9DCF5"/><rect x="1.5" y="1.5" width="61" height="61" rx="12.5" fill="#F2F7FF"/>
9 -
  <g transform="translate(32 32)">
10 -
    <path d="M -11 -6 L 11 -6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
11 -
    <path d="M -11 6 L 11 6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
12 -
    <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
13 -
    <circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/>
14 -
    <circle cx="11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
15 -
    <circle cx="11" cy="0" r="3.5" fill="#0072FF"/>
16 -
  </g>
17 -
</svg>
removed packaging/haikuports/make-icons.py +0 −163
1 -
#!/usr/bin/env python3
2 -
"""HaikuDepot icons for oxy and oh (oxygen#157): the Oxygen mark as Haiku's
3 -
vector icon format (HVIF), and as SVG to render the 16, 32 and 64 px PNGs.
4 -
5 -
A family of two (Andy, 2026-10-29): Oxygen (oxy, the Oxygen VCS extension)
6 -
is the mark on a pale rounded tile with a blue right dot; OxygenHub (oh, the
7 -
OxygenHub extension) is the mark on the hub's dark tile with a white one, as
8 -
oxygenhub/assets/icons/oxygenhub.svg. Each keeps its edge on light and dark
9 -
backgrounds alike.
10 -
11 -
    make-icons.py OUTDIR    writes oxy.hvif, oh.hvif, oxy.svg, oh.svg
12 -
13 -
HVIF as Haiku's FlatIconImporter reads it: "ncif", then styles, paths and
14 -
shapes, each a count byte and its items; coordinates in a 64-unit canvas.
15 -
"""
16 -
import math
17 -
import struct
18 -
import sys
19 -
20 -
CYAN, BLUE, WHITE, TILE = (0x00, 0xF0, 0xFF), (0x00, 0x72, 0xFF), (0xFF, 0xFF, 0xFF), (0x07, 0x0C, 0x18)
21 -
LIGHT, EDGE = (0xF2, 0xF7, 0xFF), (0xC9, 0xDC, 0xF5)
22 -
K = 0.5522847498  # a circle's quarter as one cubic Bézier
23 -
24 -
25 -
def coord(v):
26 -
    if v == int(v) and -32 <= v <= 95:
27 -
        return bytes([int(v) + 32])
28 -
    n = int(round((v + 128.0) * 102.0))
29 -
    return bytes([(n >> 8) | 0x80, n & 0xFF])
30 -
31 -
32 -
def float24(v):
33 -
    if v == 0:
34 -
        return b"\0\0\0"
35 -
    bits = struct.unpack(">I", struct.pack(">f", v))[0]
36 -
    sign, exp, man = bits >> 31, ((bits >> 23) & 0xFF) - 127, bits & 0x7FFFFF
37 -
    if not -32 <= exp < 32:
38 -
        raise ValueError(f"{v} does not fit a float24")
39 -
    n = (sign << 23) | ((exp + 32) << 17) | (man >> 6)
40 -
    return n.to_bytes(3, "big")
41 -
42 -
43 -
def solid(rgb):
44 -
    return bytes([3, *rgb])  # STYLE_TYPE_SOLID_COLOR_NO_ALPHA
45 -
46 -
47 -
def linear(p1, p2, stops):
48 -
    """A linear gradient from p1 to p2: gradient space runs x from -64 to 64."""
49 -
    dx, dy = (p2[0] - p1[0]) / 128.0, (p2[1] - p1[1]) / 128.0
50 -
    mx, my = (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2
51 -
    out = bytes([2, 0, (1 << 1) | (1 << 2), len(stops)])  # gradient, linear, transform | no alpha
52 -
    for v in (dx, dy, -dy, dx, mx, my):
53 -
        out += float24(v)
54 -
    for off, rgb in stops:
55 -
        out += bytes([off, *rgb])
56 -
    return out
57 -
58 -
59 -
def line(a, b):
60 -
    return bytes([1 << 3, 2]) + coord(a[0]) + coord(a[1]) + coord(b[0]) + coord(b[1])  # no curves, open
61 -
62 -
63 -
def curve_path(points, closed=True):
64 -
    """points: (x, y, in_x, in_y, out_x, out_y)."""
65 -
    out = bytes([(1 << 1) if closed else 0, len(points)])
66 -
    for p in points:
67 -
        for v in p:
68 -
            out += coord(v)
69 -
    return out
70 -
71 -
72 -
def circle(cx, cy, r):
73 -
    k = r * K
74 -
    pts = [(cx, cy - r, cx - k, cy - r, cx + k, cy - r), (cx + r, cy, cx + r, cy - k, cx + r, cy + k),
75 -
           (cx, cy + r, cx + k, cy + r, cx - k, cy + r), (cx - r, cy, cx - r, cy + k, cx - r, cy - k)]
76 -
    return curve_path(pts)
77 -
78 -
79 -
def rounded(x, y, w, h, r):
80 -
    k = r * (1 - K)
81 -
    x2, y2 = x + w, y + h
82 -
    pts = [
83 -
        (x + r, y, x + k, y, x + r, y), (x2 - r, y, x2 - r, y, x2 - k, y),
84 -
        (x2, y + r, x2, y + k, x2, y + r), (x2, y2 - r, x2, y2 - r, x2, y2 - k),
85 -
        (x2 - r, y2, x2 - k, y2, x2 - r, y2), (x + r, y2, x + r, y2, x + k, y2),
86 -
        (x, y2 - r, x, y2 - k, x, y2 - r), (x, y + r, x, y + r, x, y + k),
87 -
    ]
88 -
    return curve_path(pts)
89 -
90 -
91 -
def shape(style, paths, stroke=None, cap=0):
92 -
    out = bytes([10, style, len(paths), *paths])
93 -
    if stroke is None:
94 -
        return out + bytes([0])
95 -
    # has transformers: one stroke (width + 128, cap << 4 | round join, miter limit)
96 -
    return out + bytes([1 << 4, 1, 23, int(round(stroke)) + 128, (cap << 4) | 2, 4])
97 -
98 -
99 -
def mark(scale, ox, oy):
100 -
    """The mark's geometry, scaled about its centre and moved to (ox, oy)."""
101 -
    p = lambda x, y: (ox + x * scale, oy + y * scale)
102 -
    return {
103 -
        "top": (p(-11, -6), p(11, -6)), "bottom": (p(-11, 6), p(11, 6)),
104 -
        "left": (p(-11, 0), 10 * scale), "right": (p(11, 0), 10 * scale),
105 -
        "dot": 3.5 * scale, "stroke": 3 * scale, "grad": (p(-22, -11), p(22, 11)),
106 -
    }
107 -
108 -
109 -
def hvif(dark):
110 -
    m = mark(1.0, 32, 32)
111 -
    styles = [linear(*m["grad"], [(0, CYAN), (255, BLUE)]), solid(CYAN), solid(WHITE if dark else BLUE)]
112 -
    paths = [line(*m["top"]), line(*m["bottom"]), circle(*m["left"][0], m["left"][1]),
113 -
             circle(*m["right"][0], m["right"][1]), circle(*m["left"][0], m["dot"]), circle(*m["right"][0], m["dot"])]
114 -
    if dark:
115 -
        styles.append(solid(TILE))
116 -
        paths.append(rounded(0, 0, 64, 64, 14))
117 -
        tile = [shape(3, [6])]
118 -
    else:
119 -
        # A pale tile with a thin edge: the edge's colour, then the tile inset by it.
120 -
        styles += [solid(EDGE), solid(LIGHT)]
121 -
        paths += [rounded(0, 0, 64, 64, 14), rounded(1.5, 1.5, 61, 61, 12.5)]
122 -
        tile = [shape(3, [6]), shape(4, [7])]
123 -
    shapes = tile + [
124 -
        shape(0, [0], m["stroke"], cap=2), shape(0, [1], m["stroke"], cap=2),
125 -
        shape(0, [2], m["stroke"]), shape(0, [3], m["stroke"]),
126 -
        shape(1, [4]), shape(2, [5]),
127 -
    ]
128 -
    out = b"ncif"
129 -
    for section in (styles, paths, shapes):
130 -
        out += bytes([len(section)]) + b"".join(section)
131 -
    return out
132 -
133 -
134 -
def svg(dark):
135 -
    if dark:
136 -
        bg, dot = '<rect width="64" height="64" rx="14" fill="#070c18"/>', "#FFFFFF"
137 -
    else:
138 -
        bg = '<rect width="64" height="64" rx="14" fill="#C9DCF5"/><rect x="1.5" y="1.5" width="61" height="61" rx="12.5" fill="#F2F7FF"/>'
139 -
        dot = "#0072FF"
140 -
    return f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
141 -
  <defs>
142 -
    <linearGradient id="g" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11">
143 -
      <stop offset="0%" stop-color="#00F0FF"/>
144 -
      <stop offset="100%" stop-color="#0072FF"/>
145 -
    </linearGradient>
146 -
  </defs>
147 -
  {bg}
148 -
  <g transform="translate(32 32)">
149 -
    <path d="M -11 -6 L 11 -6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
150 -
    <path d="M -11 6 L 11 6" stroke="url(#g)" stroke-width="3" stroke-linecap="round"/>
151 -
    <circle cx="-11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
152 -
    <circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/>
153 -
    <circle cx="11" cy="0" r="10" fill="none" stroke="url(#g)" stroke-width="3"/>
154 -
    <circle cx="11" cy="0" r="3.5" fill="{dot}"/>
155 -
  </g>
156 -
</svg>
157 -
'''
158 -
159 -
160 -
out = sys.argv[1]
161 -
for name, dark in (("oxy", False), ("oh", True)):
162 -
    open(f"{out}/{name}.hvif", "wb").write(hvif(dark))
163 -
    open(f"{out}/{name}.svg", "w").write(svg(dark))
removed packaging/haikuports/make-recipe.py +0 −140
1 -
#!/usr/bin/env python3
2 -
"""Write a haikuports recipe for oxy or oh (oxygen#156, #157) from a release's
3 -
source tarball, NAME-VERSION-src.tar.gz, as the release job attaches it: the
4 -
tarball itself from the release's stable download address, and every
5 -
crates.io crate in its Cargo.lock as a source of its own with its checksum,
6 -
vendored offline in BUILD() as haikuports' Rust recipes (ripgrep) do.
7 -
8 -
    make-recipe.py oxy|oh SRC_TARBALL VERSION > NAME-VERSION.recipe
9 -
"""
10 -
import hashlib
11 -
import sys
12 -
import tarfile
13 -
import tomllib
14 -
15 -
PORTS = {
16 -
    "oxy": {
17 -
        "repo": "oxygen",
18 -
        "package": "oxy",
19 -
        "summary": "A version control system of signed, commuting changes, with Git interop",
20 -
        "description": [
21 -
            "oxy is the command line of Oxygen, a version control system built on",
22 -
            "patch theory: changes commute, conflicts are recorded rather than blocking,",
23 -
            "every change can be signed, and a trust policy says who may move what. It",
24 -
            "imports and exports Git history, clones from OxygenHub and other Oxygen",
25 -
            "hosts, and keeps repositories on disk or in S3, GCS or Azure buckets.",
26 -
        ],
27 -
        "docs": "README.md LICENSE*",
28 -
        "test": "oxy-core",
29 -
    },
30 -
    "oh": {
31 -
        "repo": "oxygenhub",
32 -
        "package": "oh",
33 -
        "summary": "The command line of OxygenHub, the forge for Oxygen repositories",
34 -
        "description": [
35 -
            "oh talks to an OxygenHub (such as https://oxygenhub.io): issues, proposals",
36 -
            "and their reviews, releases, Flow runs, their logs and reports, and checks,",
37 -
            "from the terminal. It works alongside oxy, the Oxygen version control",
38 -
            "client.",
39 -
        ],
40 -
        "docs": "README.md LICENSE docs/oh.md",
41 -
        "test": "hub-api",
42 -
    },
43 -
}
44 -
45 -
name, tarball, version = sys.argv[1], sys.argv[2], sys.argv[3]
46 -
port = PORTS[name]
47 -
prefix = f"{port['repo']}-{version}"
48 -
with tarfile.open(tarball) as t:
49 -
    lock = tomllib.load(t.extractfile(f"{prefix}/Cargo.lock"))
50 -
crates = sorted(
51 -
    (p["name"], p["version"], p["checksum"])
52 -
    for p in lock["package"]
53 -
    if p.get("source", "").startswith("registry+")
54 -
)
55 -
sha = hashlib.sha256(open(tarball, "rb").read()).hexdigest()
56 -
57 -
out = []
58 -
w = out.append
59 -
w(f'SUMMARY="{port["summary"]}"')
60 -
w('DESCRIPTION="' + " \\\n".join(port["description"]) + '"')
61 -
w(f'HOMEPAGE="https://oxygenhub.io/andymccall/{port["repo"]}"')
62 -
w('COPYRIGHT="2026 Andy McCall"')
63 -
w('LICENSE="MIT\n\tApache v2"')
64 -
w('REVISION="1"')
65 -
w(f'SOURCE_URI="$HOMEPAGE/releases/download/v$portVersion/{name}-$portVersion-src.tar.gz"')
66 -
w(f'CHECKSUM_SHA256="{sha}"')
67 -
w(f'SOURCE_DIR="{port["repo"]}-$portVersion"')
68 -
for i, (crate, ver, ck) in enumerate(crates, start=2):
69 -
    w(f'SOURCE_URI_{i}="https://static.crates.io/crates/{crate}/{crate}-{ver}.crate"')
70 -
    w(f'CHECKSUM_SHA256_{i}="{ck}"')
71 -
    w(f'SOURCE_FILENAME_{i}="{crate}-{ver}.tar.gz"')
72 -
last = len(crates) + 1
73 -
w(f'''
74 -
ARCHITECTURES="!all x86_64"
75 -
76 -
PROVIDES="
77 -
	{name}$secondaryArchSuffix = $portVersion
78 -
	cmd:{name}
79 -
	"
80 -
REQUIRES="
81 -
	haiku$secondaryArchSuffix
82 -
	lib:libzstd$secondaryArchSuffix
83 -
	"
84 -
85 -
BUILD_REQUIRES="
86 -
	haiku${{secondaryArchSuffix}}_devel
87 -
	devel:libzstd$secondaryArchSuffix
88 -
	"
89 -
BUILD_PREREQUIRES="
90 -
	cmd:gcc$secondaryArchSuffix
91 -
	cmd:pkg_config$secondaryArchSuffix
92 -
	cmd:rustc$secondaryArchSuffix
93 -
	"
94 -
95 -
BUILD()
96 -
{{
97 -
	export CARGO_HOME=$sourceDir/../cargo
98 -
	vendor=$CARGO_HOME/haiku
99 -
	mkdir -p "$vendor"
100 -
	for i in $(seq 2 {last}); do
101 -
		eval "srcDir=\\$sourceDir$i"
102 -
		eval "sha256sum=\\$CHECKSUM_SHA256_$i"
103 -
		set -- "$srcDir"/*
104 -
		ln -sf "$1" "$vendor"
105 -
		cat <<-EOF >"$vendor/${{1##*/}}/.cargo-checksum.json"
106 -
		{{
107 -
		  "package": "$sha256sum",
108 -
		  "files": {{}}
109 -
		}}
110 -
		EOF
111 -
	done
112 -
113 -
	cat <<-EOF >"$CARGO_HOME"/config.toml
114 -
	[source.haiku]
115 -
	directory = "$vendor"
116 -
117 -
	[source.crates-io]
118 -
	replace-with = "haiku"
119 -
	EOF
120 -
121 -
	# The bundled zstd's hidden symbols break the link on Haiku: use the system's.
122 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
123 -
	export OXY_BUILD=v$portVersion
124 -
	cargo build --release --frozen -p {port["package"]}
125 -
}}
126 -
127 -
INSTALL()
128 -
{{
129 -
	install -v -m 755 -d "$prefix"/bin "$docDir"
130 -
	install -v -m 755 ./target/release/{name} "$prefix"/bin
131 -
	install -v -m 644 {port["docs"]} "$docDir"
132 -
}}
133 -
134 -
TEST()
135 -
{{
136 -
	export CARGO_HOME=$sourceDir/../cargo
137 -
	export ZSTD_SYS_USE_PKG_CONFIG=1
138 -
	cargo test --release --frozen -p {port["test"]}
139 -
}}''')
140 -
print("\n".join(out))
modified scripts/dist.sh +0 −11
…
136 unchanged lines
137 137
  fi
138 138
done
139 139
140 -
# The source as released (oxygen#157): the oxygen tree,
141 -
# under oxygen-VERSION/, for packagers (haikuports) to fetch from the
142 -
# release at a fixed checksum. Reproducible: sorted, fixed times and
143 -
# owners, gzip without a timestamp.
144 -
if [ -z "${ONLY:-}" ] || [[ src == $ONLY ]]; then
145 -
  echo "== src"
146 -
  tar --sort=name --mtime='1970-01-01 00:00:00Z' --owner=0 --group=0 --numeric-owner \
147 -
    --mode='u+rwX,go+rX,go-w' --exclude=./target --exclude=./lost+found --exclude=.oxy \
148 -
    --transform "s|^\.|oxygen-$VERSION|" -cf - . | gzip -9n >"$OUT/$BIN-$VERSION-src.tar.gz"
149 -
fi
150 -
151 140
rm -rf "$OUT/stage"
152 141
(cd "$OUT" && shopt -s nullglob && sha256sum -- *.tar.gz *.zip *.deb >SHA256SUMS)
153 142
# DIST_FLAT=DIR: the same files again under names without the version, for
…
12 unchanged lines
removed src/cli/artifact.rs +0 −180
1 -
//! `oxy artifact`: Cylinder's artifacts (docs/cylinder.md §4.1, step C1):
2 -
//! a directory packed into a tree of the repository's own chunks, named by
3 -
//! the BLAKE3 of its canonical encoding.
4 -
5 -
use super::json;
6 -
use super::open;
7 -
use super::ui;
8 -
use anyhow::Result;
9 -
use cylinder::{Kind, Spec};
10 -
use std::path::Path;
11 -
12 -
/// `KEY=VALUE` pairs, refused when one has no `=`.
13 -
fn pairs(list: &[String], what: &str) -> Result<std::collections::BTreeMap<String, String>> {
14 -
    list.iter()
15 -
        .map(|p| {
16 -
            p.split_once('=')
17 -
                .map(|(k, v)| (k.to_string(), v.to_string()))
18 -
                .ok_or_else(|| anyhow::anyhow!("{what} {p:?}: write it KEY=VALUE"))
19 -
        })
20 -
        .collect()
21 -
}
22 -
23 -
fn summary(k: &cylinder::Kept) -> String {
24 -
    let a = &k.artifact;
25 -
    let mut parts = vec![a.kind.as_str().to_string()];
26 -
    parts.extend(a.os.clone());
27 -
    parts.extend(a.arch.clone());
28 -
    format!(
29 -
        "{} {} {}",
30 -
        ui::hash(&k.name[..12]),
31 -
        parts.join(" "),
32 -
        ui::dim(format!("{} bytes", a.total_size))
33 -
    )
34 -
}
35 -
36 -
fn as_json(k: &cylinder::Kept, files: usize) -> String {
37 -
    let a = &k.artifact;
38 -
    let text = |v: &Option<String>| v.as_deref().map(json::s).unwrap_or_else(|| "null".into());
39 -
    let map = |m: &std::collections::BTreeMap<String, String>| {
40 -
        let inner: Vec<String> = m
41 -
            .iter()
42 -
            .map(|(k, v)| format!("{}:{}", json::s(k), json::s(v)))
43 -
            .collect();
44 -
        format!("{{{}}}", inner.join(","))
45 -
    };
46 -
    json::obj([
47 -
        ("name", json::s(&k.name)),
48 -
        ("kind", json::s(a.kind.as_str())),
49 -
        ("arch", text(&a.arch)),
50 -
        ("os", text(&a.os)),
51 -
        ("tree", json::s(&a.tree.to_hex())),
52 -
        ("files", json::n(files as u64)),
53 -
        ("total_size", json::n(a.total_size)),
54 -
        ("env", map(&a.env)),
55 -
        ("labels", map(&a.labels)),
56 -
    ])
57 -
}
58 -
59 -
#[allow(clippy::too_many_arguments)]
60 -
pub fn pack(
61 -
    dir: &Path,
62 -
    kind: &str,
63 -
    arch: Option<String>,
64 -
    os: Option<String>,
65 -
    entrypoint: Vec<String>,
66 -
    env: &[String],
67 -
    labels: &[String],
68 -
    as_json_out: bool,
69 -
) -> Result<i32> {
70 -
    let repo = open()?;
71 -
    let mut spec = Spec::new(Kind::parse(kind)?);
72 -
    spec.arch = arch;
73 -
    spec.os = os;
74 -
    spec.entrypoint = (!entrypoint.is_empty()).then_some(entrypoint);
75 -
    spec.env = pairs(env, "env")?;
76 -
    spec.labels = pairs(labels, "label")?;
77 -
    let sp = ui::spinner("Packing…");
78 -
    let k = cylinder::pack(&repo, dir, &spec)?;
79 -
    sp.clear();
80 -
    let files = cylinder::tree(&repo, &k.artifact)?.entries.len();
81 -
    if as_json_out {
82 -
        println!("{}", as_json(&k, files));
83 -
    } else {
84 -
        println!(
85 -
            "{} {} as {}",
86 -
            ui::done("Packed"),
87 -
            ui::path(dir.display()),
88 -
            summary(&k)
89 -
        );
90 -
        println!("{}", ui::kv("name", &k.name));
91 -
        println!("{}", ui::kv("files", files.to_string()));
92 -
    }
93 -
    Ok(0)
94 -
}
95 -
96 -
pub fn list(as_json_out: bool) -> Result<i32> {
97 -
    let repo = open()?;
98 -
    let names = cylinder::list(&repo)?;
99 -
    let kept: Vec<cylinder::Kept> = names
100 -
        .iter()
101 -
        .map(|n| cylinder::open(&repo, n))
102 -
        .collect::<std::result::Result<_, _>>()?;
103 -
    if as_json_out {
104 -
        let items: Vec<String> = kept
105 -
            .iter()
106 -
            .map(|k| {
107 -
                let files = cylinder::tree(&repo, &k.artifact)
108 -
                    .map(|t| t.entries.len())
109 -
                    .unwrap_or(0);
110 -
                as_json(k, files)
111 -
            })
112 -
            .collect();
113 -
        println!("[{}]", items.join(","));
114 -
        return Ok(0);
115 -
    }
116 -
    if kept.is_empty() {
117 -
        println!("No artifacts yet: oxy artifact pack DIR --kind archive");
118 -
    }
119 -
    for k in &kept {
120 -
        println!("{}", summary(k));
121 -
    }
122 -
    Ok(0)
123 -
}
124 -
125 -
pub fn show(name: &str, as_json_out: bool) -> Result<i32> {
126 -
    let repo = open()?;
127 -
    let k = cylinder::open(&repo, name)?;
128 -
    let t = cylinder::tree(&repo, &k.artifact)?;
129 -
    if as_json_out {
130 -
        println!("{}", as_json(&k, t.entries.len()));
131 -
        return Ok(0);
132 -
    }
133 -
    println!("{}", summary(&k));
134 -
    println!("{}", ui::kv("name", &k.name));
135 -
    if let Some(e) = &k.artifact.entrypoint {
136 -
        println!("{}", ui::kv("entrypoint", e.join(" ")));
137 -
    }
138 -
    for (key, v) in &k.artifact.labels {
139 -
        println!("{}", ui::kv("label", format!("{key}={v}")));
140 -
    }
141 -
    for (key, v) in &k.artifact.env {
142 -
        println!("{}", ui::kv("env", format!("{key}={v}")));
143 -
    }
144 -
    for e in &t.entries {
145 -
        let mode = match e.mode {
146 -
            cylinder::MODE_EXEC => "x",
147 -
            cylinder::MODE_LINK => "l",
148 -
            _ => " ",
149 -
        };
150 -
        println!("  {mode} {:>10}  {}", e.size, e.path);
151 -
    }
152 -
    Ok(0)
153 -
}
154 -
155 -
pub fn unpack(name: &str, dest: &Path) -> Result<i32> {
156 -
    let repo = open()?;
157 -
    let k = cylinder::open(&repo, name)?;
158 -
    let n = cylinder::unpack(&repo, &k, dest)?;
159 -
    println!(
160 -
        "{} {} files of {} into {}",
161 -
        ui::done("Unpacked"),
162 -
        n,
163 -
        ui::hash(&k.name[..12]),
164 -
        ui::path(dest.display())
165 -
    );
166 -
    Ok(0)
167 -
}
168 -
169 -
pub fn verify(name: &str) -> Result<i32> {
170 -
    let repo = open()?;
171 -
    let k = cylinder::open(&repo, name)?;
172 -
    let n = cylinder::verify(&repo, &k)?;
173 -
    println!(
174 -
        "{} {}: its name, tree and {} files check out",
175 -
        ui::done("Verified"),
176 -
        ui::hash(&k.name[..12]),
177 -
        n
178 -
    );
179 -
    Ok(0)
180 -
}
modified src/cli/format.rs +3 −25
…
41 unchanged lines
42 42
43 43
/// One line describing how a change is authenticated.
44 44
pub fn signature_line(repo: &Repo, h: &ChangeHash) -> Result<String> {
45 -
    let status = repo.authenticate_change(h)?;
46 -
    let awaiting = repo
47 -
        .store()
48 -
        .get_change(h)
49 -
        .is_ok_and(|c| awaits_seal(repo, &status, &c.author));
50 -
    Ok(match status {
51 -
        SignatureStatus::Unsigned if awaiting => {
52 -
            "awaiting seal (the next push seals it under the agent's grant)".to_string()
53 -
        }
45 +
    Ok(match repo.authenticate_change(h)? {
54 46
        SignatureStatus::Unsigned => "unsigned".to_string(),
55 47
        SignatureStatus::Valid {
56 48
            key,
…
79 unchanged lines
136 128
        }
137 129
        _ => None,
138 130
    }
139 -
}
140 -
141 -
/// Whether an unsigned change is one the next push will seal: this
142 -
/// checkout signs keyless (an agent under a grant) and the change is its
143 -
/// configured author's (oxygen#165). Read cold, "unsigned" looks like an
144 -
/// error; it is not one until the push.
145 -
pub fn awaits_seal(repo: &Repo, status: &SignatureStatus, author: &oxy_core::hash::KeyId) -> bool {
146 -
    matches!(status, SignatureStatus::Unsigned)
147 -
        && repo.config.signer_keyless
148 -
        && repo.config.author_key == Some(*author)
149 131
}
150 132
151 133
/// A compact, styled authentication marker for log lines, from a status
152 -
/// already in hand (the caller also reads who signed, see [`via`]);
153 -
/// `awaiting` from [`awaits_seal`].
154 -
pub fn signature_mark_of(status: &SignatureStatus, awaiting: bool) -> String {
134 +
/// already in hand (the caller also reads who signed, see [`via`]).
135 +
pub fn signature_mark_of(status: &SignatureStatus) -> String {
155 136
    match status {
156 -
        SignatureStatus::Unsigned if awaiting => {
157 -
            ui::dim(format!("{} awaiting seal", ui::glyph("◌", "o")))
158 -
        }
159 137
        SignatureStatus::Unsigned => ui::dim(format!("{} unsigned", ui::glyph("·", "-"))),
160 138
        SignatureStatus::Valid {
161 139
            sealed,
…
64 unchanged lines
modified src/cli/identity.rs +2 −85
…
131 unchanged lines
132 132
        ui::author(&r.identity.name),
133 133
        r.identity.email
134 134
    );
135 -
    // Which key it started from, and where that came from (oxygen#163).
136 -
    let from = if repo.config.is_repo_set("author.key") {
137 -
        "this repository's author.key"
138 -
    } else {
139 -
        "the user config's author.key"
140 -
    };
141 -
    println!(
142 -
        "{}",
143 -
        ui::kv(
144 -
            "old key",
145 -
            format!(
146 -
                "{} {}",
147 -
                ui::dim(r.old.to_hex()),
148 -
                ui::dim(format!("(from {from})"))
149 -
            )
150 -
        )
151 -
    );
135 +
    println!("{}", ui::kv("old key", ui::dim(r.old.to_hex())));
152 136
    println!("{}", ui::kv("new key", r.new.to_hex()));
153 137
    print_delegates(repo, &r.identity.delegates);
154 138
    if let Some(e) = r.identity.expires {
…
16 unchanged lines
171 155
    local: bool,
172 156
    expires: Option<u64>,
173 157
) -> Result<i32> {
174 -
    // A first identity comes before any repository (oxygen#144): only
175 -
    // --local needs one; inside one, it is kept there too.
176 -
    let mut repo = match open() {
177 -
        Ok(r) => r,
178 -
        Err(e) if local => return Err(e),
179 -
        Err(_) => {
180 -
            let (key, id) = repo::create_user_identity(
181 -
                name.as_deref(),
182 -
                email.as_deref(),
183 -
                expires.and_then(expiry_from_days),
184 -
            )?;
185 -
            println!(
186 -
                "{} identity {} <{}>",
187 -
                ui::done("Created"),
188 -
                ui::author(&id.name),
189 -
                id.email
190 -
            );
191 -
            println!("{}", ui::kv("key", key));
192 -
            println!(
193 -
                "{}",
194 -
                ui::kv("secret", ui::path(repo::default_keys_dir().display()))
195 -
            );
196 -
            println!(
197 -
                "{}",
198 -
                ui::kv(
199 -
                    "scope",
200 -
                    format!(
201 -
                        "default author in {}",
202 -
                        ui::path(repo::default_user_config().display())
203 -
                    )
204 -
                )
205 -
            );
206 -
            println!("  {} {}", ui::note(), ui::dim("changes in every repository carry this author; run oxy config --user core.sign change to sign each commit"));
207 -
            return Ok(0);
208 -
        }
209 -
    };
158 +
    let mut repo = open()?;
210 159
    let name = name.unwrap_or_else(|| repo.config.author_name.clone());
211 160
    let email = email.unwrap_or_else(|| repo.config.author_email.clone());
212 161
    let scope = if local {
…
120 unchanged lines
333 282
                ))
334 283
            );
335 284
        }
336 -
    }
337 -
    // A key with a successor is stale, and a key with two is a fork: both
338 -
    // are how an identity splits without anyone noticing (oxygen#163).
339 -
    let next = repo.successors(&key)?;
340 -
    if let Some(n) = next.first() {
341 -
        println!(
342 -
            "  {} {}",
343 -
            ui::caution(),
344 -
            ui::warn(format!(
345 -
                "this key is not your identity's newest: {} succeeds it, {} is the newest. Rotating or delegating from it would fork the identity; point author.key at the newest",
346 -
                n.short(12),
347 -
                repo.newest(n)?.short(12)
348 -
            ))
349 -
        );
350 -
    }
351 -
    let mut cur = Some(key);
352 -
    for _ in 0..256 {
353 -
        let Some(k) = cur else { break };
354 -
        let after = repo.successors(&k)?;
355 -
        if after.len() > 1 {
356 -
            println!(
357 -
                "  {} {}",
358 -
                ui::caution(),
359 -
                ui::err(format!(
360 -
                    "the identity forks at {}: {} each succeed it. Keep one branch: revoke the others' keys (oxy identity revoke KEY) and point every home's author.key at the one you keep",
361 -
                    k.short(12),
362 -
                    after.iter().map(|a| a.short(12)).collect::<Vec<_>>().join(" and ")
363 -
                ))
364 -
            );
365 -
            break;
366 -
        }
367 -
        cur = repo.identity(&k).ok().and_then(|i| i.previous);
368 285
    }
369 286
    print_delegates(&repo, &id.delegates);
370 287
    if repo.is_revoked(&key)? {
…
715 unchanged lines
removed src/cli/mcp.rs +0 −543
1 -
//! `oxy mcp` (oxygen#159): a Model Context Protocol server over stdio for
2 -
//! the repository it starts in, so an agent works through typed tools
3 -
//! instead of parsing a shell's output.
4 -
//!
5 -
//! Each tool runs this same oxy binary with the command's `--json`, in the
6 -
//! repository, with the server's environment: so a tool returns exactly
7 -
//! what the command does, and signs as whatever `OXY_HOME` configures. In
8 -
//! an agent home every change is sealed via the agent, with no environment
9 -
//! variable to forget per call. Tools that can lose work (undo, a forced
10 -
//! push) are offered only with `--allow-destructive`.
11 -
12 -
use anyhow::{bail, Result};
13 -
use oxy_core::mcp::{self, arg_str, arg_strings, ServerInfo, Tool, ToolOutput, Tools};
14 -
use serde_json::{json, Value};
15 -
use std::path::{Path, PathBuf};
16 -
use std::process::{Command, Stdio};
17 -
18 -
/// A tool's text is cut here; its structured result is then left out.
19 -
const MAX_TEXT: usize = 256 * 1024;
20 -
21 -
type Build = fn(&Value, bool) -> Result<Vec<String>, String>;
22 -
23 -
struct Spec {
24 -
    name: &'static str,
25 -
    description: &'static str,
26 -
    schema: fn() -> Value,
27 -
    build: Build,
28 -
    json: bool,
29 -
    read_only: bool,
30 -
    destructive: bool,
31 -
}
32 -
33 -
fn obj(props: Value, required: &[&str]) -> Value {
34 -
    json!({ "type": "object", "properties": props, "required": required, "additionalProperties": false })
35 -
}
36 -
37 -
fn none() -> Value {
38 -
    obj(json!({}), &[])
39 -
}
40 -
41 -
/// An option's value as `--flag=value`, so a value starting with `-` is
42 -
/// never read as another flag.
43 -
fn opt(flag: &str, value: &str) -> String {
44 -
    format!("{flag}={value}")
45 -
}
46 -
47 -
/// Positional values after `--`, for the same reason.
48 -
fn positional(out: &mut Vec<String>, values: Vec<String>) {
49 -
    if !values.is_empty() {
50 -
        out.push("--".into());
51 -
        out.extend(values);
52 -
    }
53 -
}
54 -
55 -
fn required<'a>(args: &'a Value, key: &str) -> Result<&'a str, String> {
56 -
    arg_str(args, key)
57 -
        .filter(|s| !s.trim().is_empty())
58 -
        .ok_or_else(|| format!("{key} is required"))
59 -
}
60 -
61 -
/// Paths to act on, or every path when `all` is true; never neither, so a
62 -
/// commit or amend is always a deliberate choice of what goes in.
63 -
fn paths_or_all(args: &Value) -> Result<Vec<String>, String> {
64 -
    let paths = arg_strings(args, "paths")?;
65 -
    let all = args.get("all").and_then(Value::as_bool).unwrap_or(false);
66 -
    match (paths.is_empty(), all) {
67 -
        (false, false) => Ok(paths),
68 -
        (true, true) => Ok(vec![]),
69 -
        (false, true) => Err("give paths or all: true, not both".into()),
70 -
        (true, false) => {
71 -
            Err("give the paths to include, or all: true for every changed path".into())
72 -
        }
73 -
    }
74 -
}
75 -
76 -
fn limit(args: &Value, default: u64) -> String {
77 -
    args.get("limit")
78 -
        .and_then(Value::as_u64)
79 -
        .unwrap_or(default)
80 -
        .clamp(1, 1000)
81 -
        .to_string()
82 -
}
83 -
84 -
fn specs() -> Vec<Spec> {
85 -
    vec![
86 -
        Spec {
87 -
            name: "status",
88 -
            description: "The working copy: the bookmark, changed paths, and every conflict with its kind and the changes behind it.",
89 -
            schema: none,
90 -
            build: |_, _| Ok(vec!["status".into(), "--json".into()]),
91 -
            json: true,
92 -
            read_only: true,
93 -
            destructive: false,
94 -
        },
95 -
        Spec {
96 -
            name: "diff",
97 -
            description: "Uncommitted edits in the working copy, or one change's edits when `change` is given (a ChangeId or #hash prefix).",
98 -
            schema: || obj(json!({ "change": { "type": "string" } }), &[]),
99 -
            build: |a, _| {
100 -
                let mut v = vec!["diff".into(), "--json".into()];
101 -
                positional(&mut v, arg_str(a, "change").map(|c| vec![c.to_string()]).unwrap_or_default());
102 -
                Ok(v)
103 -
            },
104 -
            json: true,
105 -
            read_only: true,
106 -
            destructive: false,
107 -
        },
108 -
        Spec {
109 -
            name: "log",
110 -
            description: "Changes, newest first: a revset (`main`, `@`, …) or the current bookmark, optionally only those touching `path`; `limit` defaults to 20.",
111 -
            schema: || obj(json!({ "revset": { "type": "string" }, "path": { "type": "string" }, "limit": { "type": "integer", "minimum": 1 } }), &[]),
112 -
            build: |a, _| {
113 -
                let mut v = vec!["log".into(), "--json".into(), opt("--limit", &limit(a, 20))];
114 -
                if let Some(r) = arg_str(a, "revset") {
115 -
                    v.push(opt("--revset", r));
116 -
                }
117 -
                if let Some(p) = arg_str(a, "path") {
118 -
                    v.push(opt("--path", p));
119 -
                }
120 -
                Ok(v)
121 -
            },
122 -
            json: true,
123 -
            read_only: true,
124 -
            destructive: false,
125 -
        },
126 -
        Spec {
127 -
            name: "show",
128 -
            description: "One change: its message, author, signatures and edits (`stat: true` for files and line counts only). `change` defaults to @, the working copy.",
129 -
            schema: || obj(json!({ "change": { "type": "string" }, "stat": { "type": "boolean" } }), &[]),
130 -
            build: |a, _| {
131 -
                let mut v = vec!["show".into(), "--json".into()];
132 -
                if a.get("stat").and_then(Value::as_bool).unwrap_or(false) {
133 -
                    v.push("--stat".into());
134 -
                }
135 -
                positional(&mut v, vec![arg_str(a, "change").unwrap_or("@").to_string()]);
136 -
                Ok(v)
137 -
            },
138 -
            json: true,
139 -
            read_only: true,
140 -
            destructive: false,
141 -
        },
142 -
        Spec {
143 -
            name: "cat",
144 -
            description: "A file's content as a bookmark, tag or change has it (`change`), or as committed on the working copy's base.",
145 -
            schema: || obj(json!({ "path": { "type": "string" }, "change": { "type": "string" } }), &["path"]),
146 -
            build: |a, _| {
147 -
                let mut v = vec!["cat".into()];
148 -
                if let Some(c) = arg_str(a, "change") {
149 -
                    v.push(opt("--change", c));
150 -
                }
151 -
                positional(&mut v, vec![required(a, "path")?.to_string()]);
152 -
                Ok(v)
153 -
            },
154 -
            json: false,
155 -
            read_only: true,
156 -
            destructive: false,
157 -
        },
158 -
        Spec {
159 -
            name: "blame",
160 -
            description: "Which change wrote each line of a file, with its line atoms.",
161 -
            schema: || obj(json!({ "path": { "type": "string" } }), &["path"]),
162 -
            build: |a, _| {
163 -
                let mut v = vec!["blame".into(), "--json".into()];
164 -
                positional(&mut v, vec![required(a, "path")?.to_string()]);
165 -
                Ok(v)
166 -
            },
167 -
            json: true,
168 -
            read_only: true,
169 -
            destructive: false,
170 -
        },
171 -
        Spec {
172 -
            name: "bookmarks",
173 -
            description: "Bookmarks with their heads and how far each is ahead of or behind its remote; `all: true` adds remotes' bookmarks.",
174 -
            schema: || obj(json!({ "all": { "type": "boolean" } }), &[]),
175 -
            build: |a, _| {
176 -
                let mut v = vec!["bookmark".into(), "list".into(), "--json".into()];
177 -
                if a.get("all").and_then(Value::as_bool).unwrap_or(false) {
178 -
                    v.push("--all".into());
179 -
                }
180 -
                Ok(v)
181 -
            },
182 -
            json: true,
183 -
            read_only: true,
184 -
            destructive: false,
185 -
        },
186 -
        Spec {
187 -
            name: "tags",
188 -
            description: "Tags with their states, messages and signers.",
189 -
            schema: none,
190 -
            build: |_, _| Ok(vec!["tag".into(), "list".into(), "--json".into()]),
191 -
            json: true,
192 -
            read_only: true,
193 -
            destructive: false,
194 -
        },
195 -
        Spec {
196 -
            name: "conflicts",
197 -
            description: "Which conflicts of the working copy's base the current files resolve, and which remain.",
198 -
            schema: none,
199 -
            build: |_, _| Ok(vec!["resolve".into(), "--json".into()]),
200 -
            json: true,
201 -
            read_only: true,
202 -
            destructive: false,
203 -
        },
204 -
        Spec {
205 -
            name: "op_log",
206 -
            description: "The operation log, newest first (what undo would take back); `limit` defaults to 20.",
207 -
            schema: || obj(json!({ "limit": { "type": "integer", "minimum": 1 } }), &[]),
208 -
            build: |a, _| Ok(vec!["op".into(), "log".into(), "--json".into(), opt("--limit", &limit(a, 20))]),
209 -
            json: true,
210 -
            read_only: true,
211 -
            destructive: false,
212 -
        },
213 -
        Spec {
214 -
            name: "identity",
215 -
            description: "Who changes are signed as here: the author, the key, delegates and the signer (an agent home signs keyless, via the agent).",
216 -
            schema: none,
217 -
            build: |_, _| Ok(vec!["identity".into(), "show".into()]),
218 -
            json: false,
219 -
            read_only: true,
220 -
            destructive: false,
221 -
        },
222 -
        Spec {
223 -
            name: "commit",
224 -
            description: "Record a change with a message: only `paths`, or every changed path with `all: true`. Signed as this server's identity.",
225 -
            schema: || obj(json!({ "message": { "type": "string" }, "paths": { "type": "array", "items": { "type": "string" } }, "all": { "type": "boolean" } }), &["message"]),
226 -
            build: |a, _| {
227 -
                let mut v = vec!["commit".into(), opt("--message", required(a, "message")?)];
228 -
                positional(&mut v, paths_or_all(a)?);
229 -
                Ok(v)
230 -
            },
231 -
            json: false,
232 -
            read_only: false,
233 -
            destructive: false,
234 -
        },
235 -
        Spec {
236 -
            name: "amend",
237 -
            description: "Fold edits into the change the working copy sits on: only `paths`, or all with `all: true`; `message` replaces its message too.",
238 -
            schema: || obj(json!({ "message": { "type": "string" }, "paths": { "type": "array", "items": { "type": "string" } }, "all": { "type": "boolean" } }), &[]),
239 -
            build: |a, _| {
240 -
                let mut v = vec!["amend".into()];
241 -
                if let Some(m) = arg_str(a, "message") {
242 -
                    v.push(opt("--message", m));
243 -
                }
244 -
                positional(&mut v, paths_or_all(a)?);
245 -
                Ok(v)
246 -
            },
247 -
            json: false,
248 -
            read_only: false,
249 -
            destructive: false,
250 -
        },
251 -
        Spec {
252 -
            name: "describe",
253 -
            description: "Change a head's message (`change` defaults to the one the working copy sits on).",
254 -
            schema: || obj(json!({ "message": { "type": "string" }, "change": { "type": "string" } }), &["message"]),
255 -
            build: |a, _| {
256 -
                let mut v = vec!["describe".into(), "--json".into(), opt("--message", required(a, "message")?)];
257 -
                positional(&mut v, arg_str(a, "change").map(|c| vec![c.to_string()]).unwrap_or_default());
258 -
                Ok(v)
259 -
            },
260 -
            json: true,
261 -
            read_only: false,
262 -
            destructive: false,
263 -
        },
264 -
        Spec {
265 -
            name: "fetch",
266 -
            description: "Fetch from a remote (default origin) without moving local bookmarks.",
267 -
            schema: || obj(json!({ "remote": { "type": "string" } }), &[]),
268 -
            build: |a, _| remote_cmd("fetch", a),
269 -
            json: true,
270 -
            read_only: false,
271 -
            destructive: false,
272 -
        },
273 -
        Spec {
274 -
            name: "pull",
275 -
            description: "Fetch from a remote (default origin) and bring the current bookmark and working copy up to date.",
276 -
            schema: || obj(json!({ "remote": { "type": "string" } }), &[]),
277 -
            build: |a, _| remote_cmd("pull", a),
278 -
            json: true,
279 -
            read_only: false,
280 -
            destructive: false,
281 -
        },
282 -
        Spec {
283 -
            name: "push",
284 -
            description: "Push the current bookmark (or `bookmark`) to a remote (default origin). Changes are sealed at push as this server's identity. `force` (drop heads the remote has) needs the server started with --allow-destructive.",
285 -
            schema: || obj(json!({ "remote": { "type": "string" }, "bookmark": { "type": "string" }, "force": { "type": "boolean" } }), &[]),
286 -
            build: |a, destructive| {
287 -
                let mut v = vec!["push".to_string(), "--json".into()];
288 -
                if let Some(b) = arg_str(a, "bookmark") {
289 -
                    v.push(opt("--bookmark", b));
290 -
                }
291 -
                if a.get("force").and_then(Value::as_bool).unwrap_or(false) {
292 -
                    if !destructive {
293 -
                        return Err("a forced push can drop the remote's heads; start oxy mcp with --allow-destructive to allow it".into());
294 -
                    }
295 -
                    v.push("--force".into());
296 -
                }
297 -
                positional(&mut v, arg_str(a, "remote").map(|r| vec![r.to_string()]).unwrap_or_default());
298 -
                Ok(v)
299 -
            },
300 -
            json: true,
301 -
            read_only: false,
302 -
            destructive: false,
303 -
        },
304 -
        Spec {
305 -
            name: "tlog_verify",
306 -
            description: "Check the hub's transparency log: that it shows everyone the same history, with this repository's entries and each witness's cosignature.",
307 -
            schema: none,
308 -
            build: |_, _| Ok(vec!["tlog".into(), "verify".into(), "--json".into()]),
309 -
            json: true,
310 -
            read_only: true,
311 -
            destructive: false,
312 -
        },
313 -
        Spec {
314 -
            name: "undo",
315 -
            description: "Take back the last operation (see op_log). Offered only when the server was started with --allow-destructive.",
316 -
            schema: none,
317 -
            build: |_, _| Ok(vec!["undo".into()]),
318 -
            json: false,
319 -
            read_only: false,
320 -
            destructive: true,
321 -
        },
322 -
    ]
323 -
}
324 -
325 -
fn remote_cmd(command: &str, a: &Value) -> Result<Vec<String>, String> {
326 -
    let mut v = vec![command.to_string(), "--json".into()];
327 -
    positional(
328 -
        &mut v,
329 -
        arg_str(a, "remote")
330 -
            .map(|r| vec![r.to_string()])
331 -
            .unwrap_or_default(),
332 -
    );
333 -
    Ok(v)
334 -
}
335 -
336 -
struct OxyTools {
337 -
    exe: PathBuf,
338 -
    dir: PathBuf,
339 -
    allow_destructive: bool,
340 -
}
341 -
342 -
impl OxyTools {
343 -
    fn offered(&self) -> impl Iterator<Item = Spec> + '_ {
344 -
        specs()
345 -
            .into_iter()
346 -
            .filter(|s| self.allow_destructive || !s.destructive)
347 -
    }
348 -
349 -
    fn run(&self, spec: &Spec, argv: Vec<String>) -> ToolOutput {
350 -
        let out = Command::new(&self.exe)
351 -
            .arg("--plain")
352 -
            .args(&argv)
353 -
            .current_dir(&self.dir)
354 -
            .stdin(Stdio::null())
355 -
            .output();
356 -
        let out = match out {
357 -
            Ok(o) => o,
358 -
            Err(e) => return ToolOutput::error(format!("could not run oxy: {e}")),
359 -
        };
360 -
        let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
361 -
        let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
362 -
        if !out.status.success() {
363 -
            let why = if stderr.trim().is_empty() {
364 -
                stdout
365 -
            } else {
366 -
                stderr
367 -
            };
368 -
            return ToolOutput::error(format!("oxy {} failed: {}", argv.join(" "), why.trim()));
369 -
        }
370 -
        let mut result = ToolOutput::ok(stdout.clone());
371 -
        if spec.json {
372 -
            result.structured = serde_json::from_str(&stdout).ok();
373 -
        } else if !stderr.trim().is_empty() && stdout.trim().is_empty() {
374 -
            // Commands that report on stderr (commit's summary) still say what they did.
375 -
            result.text = stderr;
376 -
        }
377 -
        if result.text.len() > MAX_TEXT {
378 -
            let mut cut = MAX_TEXT;
379 -
            while !result.text.is_char_boundary(cut) {
380 -
                cut -= 1;
381 -
            }
382 -
            result.text.truncate(cut);
383 -
            result.text.push_str(
384 -
                "\n… (cut at 256 KiB: narrow the request, e.g. a smaller limit or one path)",
385 -
            );
386 -
            result.structured = None;
387 -
        }
388 -
        result
389 -
    }
390 -
}
391 -
392 -
impl Tools for OxyTools {
393 -
    fn list(&self) -> Vec<Tool> {
394 -
        self.offered()
395 -
            .map(|s| Tool {
396 -
                name: s.name.into(),
397 -
                description: s.description.into(),
398 -
                input_schema: (s.schema)(),
399 -
                read_only: s.read_only,
400 -
                destructive: s.destructive,
401 -
            })
402 -
            .collect()
403 -
    }
404 -
405 -
    fn call(&self, name: &str, args: &Value) -> Option<ToolOutput> {
406 -
        let spec = self.offered().find(|s| s.name == name)?;
407 -
        Some(match (spec.build)(args, self.allow_destructive) {
408 -
            Ok(argv) => self.run(&spec, argv),
409 -
            Err(why) => ToolOutput::error(why),
410 -
        })
411 -
    }
412 -
}
413 -
414 -
/// The first line of `oxy identity show`, said in `initialize` so the client
415 -
/// knows who its changes will be signed as.
416 -
fn signer_line(exe: &Path, dir: &Path) -> String {
417 -
    let out = Command::new(exe)
418 -
        .args(["--plain", "identity", "show"])
419 -
        .current_dir(dir)
420 -
        .stdin(Stdio::null())
421 -
        .output();
422 -
    match out {
423 -
        Ok(o) if o.status.success() => {
424 -
            let text = String::from_utf8_lossy(&o.stdout);
425 -
            let who = text.lines().next().unwrap_or("").trim_start_matches(['#', '▍', ' ']).trim().to_string();
426 -
            let signing = text.lines().find(|l| l.trim_start().starts_with("signing")).map(|l| l.trim().to_string());
427 -
            match signing {
428 -
                Some(s) => format!("{who}; {s}"),
429 -
                None => who,
430 -
            }
431 -
        }
432 -
        _ => "no identity is configured: commits will be refused until one is (oxy identity create, or an agent home with OXY_HOME)".into(),
433 -
    }
434 -
}
435 -
436 -
pub fn serve(dir: Option<PathBuf>, allow_destructive: bool) -> Result<i32> {
437 -
    let dir = match dir {
438 -
        Some(d) => d,
439 -
        None => std::env::current_dir()?,
440 -
    };
441 -
    if !dir.join(oxy_core::repo::OXY_DIR).is_dir() {
442 -
        bail!("{} is not the root of an Oxygen repository (no {}): start oxy mcp in a checkout's root, or name it", dir.display(), oxy_core::repo::OXY_DIR);
443 -
    }
444 -
    let exe = std::env::current_exe()?;
445 -
    let signer = signer_line(&exe, &dir);
446 -
    let info = ServerInfo {
447 -
        name: "oxy".into(),
448 -
        version: env!("CARGO_PKG_VERSION").into(),
449 -
        instructions: format!(
450 -
            "Oxygen version control for the repository at {}. Changes are signed as: {signer}. Prefer these tools to running oxy in a shell. \
451 -
             commit and amend take explicit paths (or all: true). {}",
452 -
            dir.display(),
453 -
            if allow_destructive { "Destructive tools (undo, forced push) are enabled: use them only when asked." } else { "Destructive tools (undo, forced push) are off." }
454 -
        ),
455 -
    };
456 -
    let tools = OxyTools {
457 -
        exe,
458 -
        dir,
459 -
        allow_destructive,
460 -
    };
461 -
    let stdin = std::io::stdin();
462 -
    mcp::serve(&info, &tools, stdin.lock(), std::io::stdout().lock())?;
463 -
    Ok(0)
464 -
}
465 -
466 -
#[cfg(test)]
467 -
mod tests {
468 -
    use super::*;
469 -
470 -
    fn build(name: &str, args: Value, destructive: bool) -> Result<Vec<String>, String> {
471 -
        let spec = specs().into_iter().find(|s| s.name == name).unwrap();
472 -
        (spec.build)(&args, destructive)
473 -
    }
474 -
475 -
    #[test]
476 -
    fn values_never_read_as_flags() {
477 -
        assert_eq!(
478 -
            build(
479 -
                "commit",
480 -
                json!({"message": "-m oops", "paths": ["-rf"]}),
481 -
                false
482 -
            )
483 -
            .unwrap(),
484 -
            ["commit", "--message=-m oops", "--", "-rf"]
485 -
        );
486 -
        assert_eq!(
487 -
            build("cat", json!({"path": "--help", "change": "main"}), false).unwrap(),
488 -
            ["cat", "--change=main", "--", "--help"]
489 -
        );
490 -
        assert_eq!(
491 -
            build("log", json!({"limit": 5000}), false).unwrap(),
492 -
            ["log", "--json", "--limit=1000"],
493 -
            "limit is capped"
494 -
        );
495 -
    }
496 -
497 -
    #[test]
498 -
    fn commits_name_what_goes_in() {
499 -
        assert!(build("commit", json!({"message": "x"}), false)
500 -
            .unwrap_err()
501 -
            .contains("all: true"));
502 -
        assert!(build(
503 -
            "commit",
504 -
            json!({"message": "x", "paths": ["a"], "all": true}),
505 -
            false
506 -
        )
507 -
        .is_err());
508 -
        assert_eq!(
509 -
            build("commit", json!({"message": "x", "all": true}), false).unwrap(),
510 -
            ["commit", "--message=x"]
511 -
        );
512 -
        assert!(build("commit", json!({"message": " ", "all": true}), false)
513 -
            .unwrap_err()
514 -
            .contains("message"));
515 -
    }
516 -
517 -
    #[test]
518 -
    fn destructive_tools_need_the_flag() {
519 -
        let off = OxyTools {
520 -
            exe: "oxy".into(),
521 -
            dir: ".".into(),
522 -
            allow_destructive: false,
523 -
        };
524 -
        let on = OxyTools {
525 -
            exe: "oxy".into(),
526 -
            dir: ".".into(),
527 -
            allow_destructive: true,
528 -
        };
529 -
        assert!(!off.list().iter().any(|t| t.name == "undo"));
530 -
        assert!(
531 -
            off.call("undo", &json!({})).is_none(),
532 -
            "an unoffered tool is unknown"
533 -
        );
534 -
        assert!(on.list().iter().any(|t| t.name == "undo" && t.destructive));
535 -
        assert!(build("push", json!({"force": true}), false)
536 -
            .unwrap_err()
537 -
            .contains("--allow-destructive"));
538 -
        assert_eq!(
539 -
            build("push", json!({"force": true, "remote": "earth"}), true).unwrap(),
540 -
            ["push", "--json", "--force", "--", "earth"]
541 -
        );
542 -
    }
543 -
}
modified src/cli/mod.rs +2 −134
1 1
//! Command-line definitions and dispatch.
2 2
3 3
mod approve;
4 -
mod artifact;
5 4
mod auth;
6 5
mod format;
7 6
mod identity;
8 7
mod json;
9 -
mod mcp;
10 8
mod proposal;
11 9
mod receipt;
12 10
mod sync;
…
196 unchanged lines
209 207
        #[arg(long)]
210 208
        json: bool,
211 209
    },
212 -
    /// Serve this repository to an AI agent over the Model Context Protocol (stdio), signing as this checkout's identity
213 -
    Mcp {
214 -
        /// The repository's root (default: the current directory)
215 -
        dir: Option<PathBuf>,
216 -
        /// Also offer tools that can lose work: undo, and a forced push
217 -
        #[arg(long)]
218 -
        allow_destructive: bool,
219 -
    },
220 210
    /// Print the version of oxy, its repository format, and protocol
221 211
    Version {
222 212
        /// Machine-readable output: one JSON document on stdout
…
197 unchanged lines
420 410
    Tlog {
421 411
        #[command(subcommand)]
422 412
        cmd: TlogCmd,
423 -
    },
424 -
    /// Artifacts: a directory packed into a tree of this repository's chunks (Cylinder, docs/cylinder.md)
425 -
    Artifact {
426 -
        #[command(subcommand)]
427 -
        cmd: ArtifactCmd,
428 413
    },
429 414
    /// Execution receipts: signed records of what a runner ran on a state
430 415
    Receipt {
…
114 unchanged lines
545 530
        #[command(subcommand)]
546 531
        cmd: PolicyCmd,
547 532
    },
548 -
    /// Set a configuration key (author.*, core.sign, core.op-retention, core.gc-grace, core.checkpoint-interval, filter.max-blob, filter.paths, storage.cache, workspace.bookmark, remote.<name>.url|token|ca, git.claim, git.colocate, auth.helper with --user)
533 +
    /// Set a configuration key (author.*, core.sign, core.op-retention, core.gc-grace, core.checkpoint-interval, filter.max-blob, filter.paths, storage.cache, workspace.bookmark, remote.<name>.url|token|ca, auth.helper with --user)
549 534
    Config {
550 535
        /// The key, such as author.name or remote.origin.token
551 536
        key: String,
…
280 unchanged lines
832 817
    Remove {
833 818
        /// The subrepo to unregister
834 819
        path: String,
835 -
        /// Delete its directory too (#164); refused while it has uncommitted edits
836 -
        #[arg(long)]
837 -
        delete: bool,
838 -
        /// With --delete: delete it even with uncommitted edits
839 -
        #[arg(long, requires = "delete")]
840 -
        force: bool,
841 -
    },
842 -
}
843 -
844 -
#[derive(Subcommand)]
845 -
enum ArtifactCmd {
846 -
    /// Pack DIR into an artifact: each file a blob of this repository's store, so files and chunks it holds cost nothing
847 -
    Pack {
848 -
        /// The directory whose files make the artifact
849 -
        dir: std::path::PathBuf,
850 -
        /// container, runner-image, package, archive or report
851 -
        #[arg(long, default_value = "archive")]
852 -
        kind: String,
853 -
        /// x86_64, riscv64, aarch64…
854 -
        #[arg(long)]
855 -
        arch: Option<String>,
856 -
        /// linux, darwin, none…
857 -
        #[arg(long)]
858 -
        os: Option<String>,
859 -
        /// The default command, one word per flag
860 -
        #[arg(long = "entrypoint", value_name = "WORD")]
861 -
        entrypoint: Vec<String>,
862 -
        /// KEY=VALUE for the environment (repeatable)
863 -
        #[arg(long = "env", value_name = "KEY=VALUE")]
864 -
        env: Vec<String>,
865 -
        /// KEY=VALUE metadata (repeatable)
866 -
        #[arg(long = "label", value_name = "KEY=VALUE")]
867 -
        labels: Vec<String>,
868 -
        /// Machine-readable output: one JSON document on stdout
869 -
        #[arg(long)]
870 -
        json: bool,
871 -
    },
872 -
    /// The artifacts kept here
873 -
    List {
874 -
        /// Machine-readable output: one JSON document on stdout
875 -
        #[arg(long)]
876 -
        json: bool,
877 -
    },
878 -
    /// One artifact and its files
879 -
    Show {
880 -
        /// Its name, or a unique prefix of at least six characters
881 -
        name: String,
882 -
        /// Machine-readable output: one JSON document on stdout
883 -
        #[arg(long)]
884 -
        json: bool,
885 -
    },
886 -
    /// Write an artifact's files into DEST (missing or empty)
887 -
    Unpack {
888 -
        /// Its name, or a unique prefix of at least six characters
889 -
        name: String,
890 -
        /// Where to write its files
891 -
        dest: std::path::PathBuf,
892 820
    },
893 -
    /// Check an artifact's name, tree and every file against the store
894 -
    Verify {
895 -
        /// Its name, or a unique prefix of at least six characters
896 -
        name: String,
897 -
    },
898 821
}
899 822
900 823
#[derive(Subcommand)]
…
222 unchanged lines
1123 1046
1124 1047
#[derive(Subcommand)]
1125 1048
enum GitCmd {
1126 -
    /// Keep a .git beside .oxy, up to date after every oxy command, so editors' Git features work here (one way: oxy to Git)
1127 -
    Colocate {
1128 -
        /// Stop updating it (the .git is left as it is)
1129 -
        #[arg(long)]
1130 -
        off: bool,
1131 -
    },
1132 1049
    /// Export the current bookmark to a Git repository (created bare if absent)
1133 1050
    Export {
1134 1051
        /// Git repository to write to (created bare if absent)
…
139 unchanged lines
1274 1191
        #[arg(long)]
1275 1192
        out: Option<PathBuf>,
1276 1193
    },
1277 -
}
1278 -
1279 -
/// After a command, bring the side-by-side `.git` up to date when this
1280 -
/// checkout keeps one (`git.colocate`). It never fails the command: a
1281 -
/// problem is a warning, and the next command tries again.
1282 -
pub fn after_command() {
1283 -
    if AT_OP.get().and_then(|o| o.as_deref()).is_some() {
1284 -
        return;
1285 -
    }
1286 -
    let Ok(dir) = std::env::current_dir() else {
1287 -
        return;
1288 -
    };
1289 -
    let Ok(repo) = Repo::open(&dir) else { return };
1290 -
    if !repo.config.git_colocate || repo.is_bare() {
1291 -
        return;
1292 -
    }
1293 -
    if let Err(e) = repo.git_colocate_sync() {
1294 -
        eprintln!(
1295 -
            "{} the .git beside this checkout was not updated: {e}",
1296 -
            ui::warn("!")
1297 -
        );
1298 -
    }
1299 1194
}
1300 1195
1301 1196
/// Open the repository containing the current directory.
…
74 unchanged lines
1376 1271
        }
1377 1272
        Cmd::Cat { path, change } => workspace::cat(&path, change.as_deref()),
1378 1273
        Cmd::Root { json } => workspace::root(json),
1379 -
        Cmd::Mcp {
1380 -
            dir,
1381 -
            allow_destructive,
1382 -
        } => mcp::serve(dir, allow_destructive),
1383 1274
        Cmd::Version { json } => workspace::version(json),
1384 1275
        Cmd::Discard { paths, hunks } => workspace::discard(&paths, &hunks),
1385 1276
        Cmd::Hunks { json } => workspace::hunks(json),
…
147 unchanged lines
1533 1424
            } => sync::submodule_add(&url, &path, bookmark.as_deref()),
1534 1425
            SubmoduleCmd::Update { init, paths } => sync::submodule_update(init, &paths),
1535 1426
            SubmoduleCmd::Status { json } => sync::submodule_status(json),
1536 -
            SubmoduleCmd::Remove {
1537 -
                path,
1538 -
                delete,
1539 -
                force,
1540 -
            } => sync::submodule_remove(&path, delete, force),
1427 +
            SubmoduleCmd::Remove { path } => sync::submodule_remove(&path),
1541 1428
        },
1542 1429
        Cmd::Witness {
1543 1430
            cmd:
…
13 unchanged lines
1557 1444
                all,
1558 1445
                json,
1559 1446
            } => tlog::verify(hub.as_deref(), key.as_deref(), all, json),
1560 -
        },
1561 -
        Cmd::Artifact { cmd } => match cmd {
1562 -
            ArtifactCmd::Pack {
1563 -
                dir,
1564 -
                kind,
1565 -
                arch,
1566 -
                os,
1567 -
                entrypoint,
1568 -
                env,
1569 -
                labels,
1570 -
                json,
1571 -
            } => artifact::pack(&dir, &kind, arch, os, entrypoint, &env, &labels, json),
1572 -
            ArtifactCmd::List { json } => artifact::list(json),
1573 -
            ArtifactCmd::Show { name, json } => artifact::show(&name, json),
1574 -
            ArtifactCmd::Unpack { name, dest } => artifact::unpack(&name, &dest),
1575 -
            ArtifactCmd::Verify { name } => artifact::verify(&name),
1576 1447
        },
1577 1448
        Cmd::Receipt { cmd } => match cmd {
1578 1449
            ReceiptCmd::List { rev, json } => receipt::list(rev.as_deref(), json),
…
86 unchanged lines
1665 1536
                DelegateCmd::List => identity::delegate_list(),
1666 1537
            },
1667 1538
        },
1668 -
        Cmd::Git {
1669 -
            cmd: GitCmd::Colocate { off },
1670 -
        } => sync::git_colocate(off),
1671 1539
        Cmd::Git {
1672 1540
            cmd: GitCmd::Export { git_dir, branch },
1673 1541
        } => sync::git_export(&git_dir, branch.as_deref()),
…
121 unchanged lines
modified src/cli/sync.rs +7 −110
…
371 unchanged lines
372 372
    let repo = open()?;
373 373
    let remote = remote_name(&repo, remote);
374 374
    let sp = ui::spinner(&format!("Fetching from {remote}…"));
375 -
    let report = noted(&repo, &remote, repo.fetch(&remote))?;
375 +
    let report = repo.fetch(&remote)?;
376 376
    sp.clear();
377 377
    if as_json {
378 378
        println!(
…
160 unchanged lines
539 539
    let repo = open()?;
540 540
    let remote = remote_name(&repo, remote);
541 541
    let sp = ui::spinner(&format!("Pulling from {remote}…"));
542 -
    let r = noted(&repo, &remote, repo.pull(&remote))?;
542 +
    let r = repo.pull(&remote)?;
543 543
    if as_json {
544 544
        sp.clear();
545 545
        let conflicted = repo
…
143 unchanged lines
689 689
    let sp = ui::spinner(&format!("Pushing to {remote}…"));
690 690
    let bookmark_name = bookmark.map_or_else(|| repo.config.bookmark.clone(), String::from);
691 691
    // A long push goes in parts: the spinner says which.
692 -
    let results = noted(
693 -
        &repo,
694 -
        &remote,
695 -
        repo.push_bookmark_with(&remote, &bookmark_name, force, &mut |m| {
696 -
            sp.set(&format!("Pushing to {remote}… {m}"))
697 -
        }),
698 -
    )?;
692 +
    let results = repo.push_bookmark_with(&remote, &bookmark_name, force, &mut |m| {
693 +
        sp.set(&format!("Pushing to {remote}… {m}"))
694 +
    })?;
699 695
    sp.clear();
700 696
    if as_json {
701 697
        // The state each accepted bookmark now holds on the remote (its local
…
78 unchanged lines
780 776
    Ok(0)
781 777
}
782 778
783 -
/// `oxy git colocate [--off]`: keep a `.git` beside `.oxy` (spec §23).
784 -
pub fn git_colocate(off: bool) -> Result<i32> {
785 -
    let mut repo = open()?;
786 -
    if repo.is_bare() {
787 -
        anyhow::bail!("a bare repository has no working copy for a .git beside it");
788 -
    }
789 -
    repo.config
790 -
        .set("git.colocate", if off { "false" } else { "true" })?;
791 -
    repo.save_config()?;
792 -
    if off {
793 -
        println!(
794 -
            "{} the .git beside this checkout is no longer updated {}",
795 -
            ui::done("Stopped:"),
796 -
            ui::dim("(it is left as it is: delete .git yourself if you want it gone)")
797 -
        );
798 -
        return Ok(0);
799 -
    }
800 -
    let sp = ui::spinner("Exporting to .git…");
801 -
    repo.git_colocate_sync()?;
802 -
    sp.clear();
803 -
    println!(
804 -
        "{} .git beside .oxy holds {} {}",
805 -
        ui::done("Colocated:"),
806 -
        ui::bookmark(&repo.config.bookmark),
807 -
        ui::dim("(updated after every oxy command; Git's own commits are not read back: commit with oxy)")
808 -
    );
809 -
    Ok(0)
810 -
}
811 -
812 779
pub fn git_export(git_dir: &std::path::Path, branch: Option<&str>) -> Result<i32> {
813 780
    let repo = open()?;
814 781
    let sp = ui::spinner("Exporting to Git…");
…
421 unchanged lines
1236 1203
    Ok(0)
1237 1204
}
1238 1205
1239 -
pub fn submodule_remove(path: &str, delete: bool, force: bool) -> Result<i32> {
1206 +
pub fn submodule_remove(path: &str) -> Result<i32> {
1240 1207
    let repo = open()?;
1241 -
    let dir = repo.root().join(path.trim_end_matches('/'));
1242 -
    // Look before unregistering, so a refused delete changes nothing.
1243 -
    if delete && dir.exists() && !force {
1244 -
        if let Ok(sub) = oxy_core::repo::Repo::open(&dir) {
1245 -
            let snap = sub.snapshot("oxy submodule remove --delete")?;
1246 -
            if !snap.statuses.is_empty() {
1247 -
                anyhow::bail!(
1248 -
                    "{path} has {} with uncommitted edits; commit them in the subrepo (and push them if you want them kept), or pass --force to delete them",
1249 -
                    plural(snap.statuses.len(), "file")
1250 -
                );
1251 -
            }
1252 -
        }
1253 -
    }
1254 1208
    repo.submodule_remove(path)?;
1255 -
    if !delete {
1256 -
        println!(
1257 -
            "{} subrepo {} {}",
1258 -
            ui::done("Removed"),
1259 -
            ui::path(path),
1260 -
            ui::dim("(directory left in place; commit to record the removal)")
1261 -
        );
1262 -
        println!(
1263 -
            "  {} {}",
1264 -
            ui::note(),
1265 -
            ui::dim(format!(
1266 -
                "to delete the directory too: oxy submodule remove {path} --delete"
1267 -
            ))
1268 -
        );
1269 -
        return Ok(0);
1270 -
    }
1271 -
    if dir.exists() {
1272 -
        delete_tree(&dir)?;
1273 -
    }
1274 1209
    println!(
1275 1210
        "{} subrepo {} {}",
1276 1211
        ui::done("Removed"),
1277 1212
        ui::path(path),
1278 -
        ui::dim("and its directory (commit to record the removal)")
1213 +
        ui::dim("(directory left in place; commit to record the removal)")
1279 1214
    );
1280 1215
    Ok(0)
1281 -
}
1282 -
1283 -
/// Delete a directory tree that holds a repository: its object files are
1284 -
/// read-only, so each is made writable first (#164).
1285 -
fn delete_tree(dir: &std::path::Path) -> Result<()> {
1286 -
    fn writable(p: &std::path::Path) -> std::io::Result<()> {
1287 -
        let meta = std::fs::symlink_metadata(p)?;
1288 -
        if meta.file_type().is_symlink() {
1289 -
            return Ok(());
1290 -
        }
1291 -
        let mut perms = meta.permissions();
1292 -
        if perms.readonly() {
1293 -
            #[allow(clippy::permissions_set_readonly_false)]
1294 -
            perms.set_readonly(false);
1295 -
            std::fs::set_permissions(p, perms)?;
1296 -
        }
1297 -
        if meta.is_dir() {
1298 -
            for e in std::fs::read_dir(p)? {
1299 -
                writable(&e?.path())?;
1300 -
            }
1301 -
        }
1302 -
        Ok(())
1303 -
    }
1304 -
    writable(dir).map_err(|e| anyhow::anyhow!("{}: {e}", dir.display()))?;
1305 -
    std::fs::remove_dir_all(dir).map_err(|e| anyhow::anyhow!("{}: {e}", dir.display()))?;
1306 -
    Ok(())
1307 1216
}
1308 1217
1309 1218
pub fn deepen(remote: &str, depth: u32) -> Result<i32> {
…
18 unchanged lines
1328 1237
        }
1329 1238
    ));
1330 1239
    Ok(0)
1331 -
}
1332 -
1333 -
/// `r`, with a note when the remote's own token was refused while another
1334 -
/// sign-in for its hub would do (oxygen#155).
1335 -
fn noted<T>(repo: &oxy_core::repo::Repo, remote: &str, r: oxy_core::repo::Result<T>) -> Result<T> {
1336 -
    r.map_err(|e| {
1337 -
        let m = e.to_string();
1338 -
        match repo.stale_token_note(remote, &m) {
1339 -
            Some(note) => anyhow::anyhow!("{m}\n  note: {note}"),
1340 -
            None => e.into(),
1341 -
        }
1342 -
    })
1343 1240
}
modified src/cli/workspace.rs +5 −118
1 1
//! Commands that act on the local repository and working copy.
2 2
3 3
use super::format::{
4 -
    author_name, awaits_seal, deps_line, deps_of, plural, short_id, signature_line,
5 -
    signature_mark_of, subject_of, via, when,
4 +
    author_name, deps_line, deps_of, plural, short_id, signature_line, signature_mark_of,
5 +
    subject_of, via, when,
6 6
};
7 7
use super::json;
8 8
use super::open;
…
235 unchanged lines
244 244
        );
245 245
        print_behind(&repo);
246 246
        print_statuses(&statuses);
247 -
        print_pin_moves(&pin_moves(&repo, None));
248 247
        print_untracked_nested(&repo, &repo.untracked_nested());
249 248
        print_skipped_large(&repo);
250 249
        return Ok(0);
…
25 unchanged lines
276 275
    );
277 276
    print_behind(&repo);
278 277
    print_statuses(&snap.statuses);
279 -
    print_pin_moves(&pin_moves(&repo, None));
280 278
    print_untracked_nested(&repo, &snap.untracked_nested);
281 279
    print_skipped_large(&repo);
282 280
    if !snap.base_snapshot.is_conflicted() {
…
139 unchanged lines
422 420
        oxy_core::repo::Selection::All
423 421
    } else {
424 422
        oxy_core::repo::Selection::Paths(paths.to_vec())
425 -
    };
426 -
    // Pins this commit records as moved, said after it (oxygen#162): a pin
427 -
    // moved by work inside a subrepo is a pin bump, easy to make unasked.
428 -
    let moves = match &selection {
429 -
        oxy_core::repo::Selection::All => pin_moves(&repo, None),
430 -
        oxy_core::repo::Selection::Paths(p) => pin_moves(&repo, Some(p)),
431 -
        oxy_core::repo::Selection::Hunks(_) => Vec::new(),
432 423
    };
433 424
    let sp = ui::spinner("Committing…");
434 425
    let (h, partial) = repo.commit_selection_partial(message, &deps, &selection)?;
…
6 unchanged lines
441 432
        ui::hash(format!("#{}", h.short(12))),
442 433
        ui::subject(subject_of(message))
443 434
    ));
444 -
    for m in &moves {
445 -
        let line = match (&m.to, m.dirty) {
446 -
            (Some(to), _) => format!(
447 -
                "recorded subrepo {}'s pin moving {} {} {}: a pin bump. Meant only your own files? `oxy commit PATHS` leaves pins out",
448 -
                m.path,
449 -
                m.from,
450 -
                ui::glyph("→", "->"),
451 -
                to
452 -
            ),
453 -
            (None, true) => format!(
454 -
                "subrepo {} has uncommitted edits of its own: the pin names its last committed state; the edits stay in it",
455 -
                m.path
456 -
            ),
457 -
            (None, false) => continue,
458 -
        };
459 -
        println!("  {} {}", ui::caution(), ui::warn(line));
460 -
    }
461 435
    print_untracked_nested(&repo, &repo.untracked_nested());
462 436
    print_skipped_large(&repo);
463 437
    // Only a commit that took part of the working copy leaves anything.
…
12 unchanged lines
476 450
    }
477 451
    where_now(&repo);
478 452
    Ok(0)
479 -
}
480 -
481 -
/// A subrepo whose pin the next commit would move (`to`), or which has
482 -
/// uncommitted edits of its own (oxygen#162).
483 -
struct PinMove {
484 -
    path: String,
485 -
    from: String,
486 -
    to: Option<String>,
487 -
    dirty: bool,
488 453
}
489 454
490 -
/// The subrepos (among `paths`, when given) whose checkout is not at the
491 -
/// pin the base records, or holds uncommitted edits. Only a checkout with
492 -
/// subrepos (an `.oxymodules`) pays for the look.
493 -
fn pin_moves(repo: &oxy_core::repo::Repo, paths: Option<&[String]>) -> Vec<PinMove> {
494 -
    if !repo.root().join(".oxymodules").exists() {
495 -
        return Vec::new();
496 -
    }
497 -
    let Ok(subs) = repo.subrepos() else {
498 -
        return Vec::new();
499 -
    };
500 -
    let chosen = |p: &str| {
501 -
        paths.is_none_or(|ps| {
502 -
            ps.iter().any(|x| {
503 -
                let x = x.trim_end_matches('/');
504 -
                p == x || p.starts_with(&format!("{x}/")) || x.is_empty() || x == "."
505 -
            })
506 -
        })
507 -
    };
508 -
    subs.into_iter()
509 -
        .filter(|s| s.committed && chosen(&s.path))
510 -
        .filter_map(|s| {
511 -
            let to = s
512 -
                .actual
513 -
                .as_ref()
514 -
                .filter(|a| !a.same_state(&s.pin))
515 -
                .map(|a| a.summary());
516 -
            (to.is_some() || s.dirty).then(|| PinMove {
517 -
                from: s.pin.summary(),
518 -
                to,
519 -
                dirty: s.dirty,
520 -
                path: s.path,
521 -
            })
522 -
        })
523 -
        .collect()
524 -
}
525 -
526 -
/// `oxy status`'s word on subrepos: a moved pin is a change the next commit
527 -
/// records; edits inside a subrepo are not.
528 -
fn print_pin_moves(moves: &[PinMove]) {
529 -
    if moves.is_empty() {
530 -
        return;
531 -
    }
532 -
    println!("{}", ui::section("Subrepo pins"));
533 -
    for m in moves {
534 -
        if let Some(to) = &m.to {
535 -
            println!(
536 -
                "  {} {}  {} {} {}  {}",
537 -
                ui::warn(ui::glyph("↕", "<>")),
538 -
                ui::path(&m.path),
539 -
                ui::dim(&m.from),
540 -
                ui::dim(ui::glyph("→", "->")),
541 -
                to,
542 -
                ui::dim("pin moved: a bare `oxy commit` records it")
543 -
            );
544 -
        }
545 -
        if m.dirty {
546 -
            println!(
547 -
                "  {} {}  {}",
548 -
                ui::warn(ui::glyph("✎", "~")),
549 -
                ui::path(&m.path),
550 -
                ui::dim("uncommitted edits inside it: commit them there; the parent records only its pin")
551 -
            );
552 -
        }
553 -
    }
554 -
}
555 -
556 455
pub fn blame(path: &str, json: bool) -> Result<i32> {
557 456
    let repo = open()?;
558 457
    let lines = repo.blame(path)?;
…
368 unchanged lines
927 826
            if let Some(agent) = via(&repo, &status, &c.author) {
928 827
                head.push(ui::dim(format!("via {}", agent.name)));
929 828
            }
930 -
            head.push(signature_mark_of(
931 -
                &status,
932 -
                awaits_seal(&repo, &status, &c.author),
933 -
            ));
829 +
            head.push(signature_mark_of(&status));
934 830
        }
935 831
        println!("{}{}", node_line(row, &bullet), head.join(" "));
936 832
        let subject = if e.is_working_copy && c.message.is_empty() {
…
649 unchanged lines
1586 1482
/// Union files (`merge=union`, oxygen#145) a checkout settled itself.
1587 1483
fn print_kept_both(r: &oxy_core::repo::CheckoutReport) {
1588 1484
    for k in &r.kept_both {
1589 -
        let how = if k.identical.len() == k.settled.len() {
1590 -
            "kept one of its identical sides"
1591 -
        } else if k.identical.is_empty() {
1592 -
            "kept both sides (merge=union)"
1593 -
        } else {
1594 -
            "kept one of identical sides and both of others (merge=union)"
1595 -
        };
1596 1485
        println!(
1597 -
            "  {} settled {}: {how}; the next commit records it",
1486 +
            "  {} kept both sides in {} (merge=union); the next commit records it",
1598 1487
            ui::note(),
1599 1488
            ui::path(&k.path)
1600 1489
        );
…
354 unchanged lines
1955 1844
        repo.config.snapshot_retention_days = 0;
1956 1845
    }
1957 1846
    let sp = ui::spinner("Packing and pruning…");
1958 -
    // Cylinder's artifacts are named from outside the history: keep them.
1959 -
    let artifacts = cylinder::roots(&repo)?;
1960 -
    let r = repo.gc_keeping(now, &artifacts, &mut |phase| sp.set(&format!("{phase}…")))?;
1847 +
    let r = repo.gc_with(now, &mut |phase| sp.set(&format!("{phase}…")))?;
1961 1848
    sp.clear();
1962 1849
    if r.packed.objects > 0 {
1963 1850
        println!(
…
1315 unchanged lines
modified src/main.rs +0 −3
…
34 unchanged lines
35 35
    }
36 36
    let command = oxy_core::timing::phase("command");
37 37
    let result = cli::run();
38 -
    if result.is_ok() {
39 -
        cli::after_command();
40 -
    }
41 38
    drop(command);
42 39
    oxy_core::timing::memory_summary();
43 40
    match result {
…
8 unchanged lines
removed tests/cli_colocate.rs +0 −110
1 -
//! `oxy git colocate` (oxygenhub#355, part two): a `.git` beside `.oxy`,
2 -
//! kept up to date after every oxy command, so Git tools read an Oxygen
3 -
//! checkout; oxy never takes `.git` as content.
4 -
5 -
use std::path::Path;
6 -
use std::process::Command;
7 -
8 -
fn oxy(dir: &Path, home: &Path, args: &[&str]) -> (bool, String) {
9 -
    let out = Command::new(env!("CARGO_BIN_EXE_oxy"))
10 -
        .arg("--plain")
11 -
        .args(args)
12 -
        .current_dir(dir)
13 -
        .env("OXY_HOME", home)
14 -
        .env("NO_COLOR", "1")
15 -
        .output()
16 -
        .unwrap();
17 -
    (
18 -
        out.status.success(),
19 -
        format!(
20 -
            "{}{}",
21 -
            String::from_utf8_lossy(&out.stdout),
22 -
            String::from_utf8_lossy(&out.stderr)
23 -
        ),
24 -
    )
25 -
}
26 -
27 -
fn git(dir: &Path, args: &[&str]) -> String {
28 -
    let out = Command::new("git")
29 -
        .args(args)
30 -
        .current_dir(dir)
31 -
        .env("GIT_CONFIG_NOSYSTEM", "1")
32 -
        .env("HOME", dir)
33 -
        .output()
34 -
        .unwrap();
35 -
    assert!(
36 -
        out.status.success(),
37 -
        "git {args:?}: {}",
38 -
        String::from_utf8_lossy(&out.stderr)
39 -
    );
40 -
    String::from_utf8_lossy(&out.stdout).trim().to_string()
41 -
}
42 -
43 -
#[test]
44 -
fn a_colocated_git_follows_the_bookmark() {
45 -
    if Command::new("git").arg("--version").output().is_err() {
46 -
        eprintln!("git not installed; skipping");
47 -
        return;
48 -
    }
49 -
    let tmp = tempfile::tempdir().unwrap();
50 -
    let home = tmp.path().join("home");
51 -
    std::fs::create_dir_all(&home).unwrap();
52 -
    let r = tmp.path().join("r");
53 -
    assert!(oxy(tmp.path(), &home, &["init", r.to_str().unwrap()]).0);
54 -
    assert!(
55 -
        oxy(
56 -
            &r,
57 -
            &home,
58 -
            &[
59 -
                "identity",
60 -
                "create",
61 -
                "--name",
62 -
                "Ada",
63 -
                "--email",
64 -
                "[email protected]"
65 -
            ]
66 -
        )
67 -
        .0
68 -
    );
69 -
    std::fs::write(r.join("a.txt"), "one\n").unwrap();
70 -
    assert!(oxy(&r, &home, &["commit", "-m", "first"]).0);
71 -
72 -
    let (ok, out) = oxy(&r, &home, &["git", "colocate"]);
73 -
    assert!(ok, "{out}");
74 -
    assert!(r.join(".git/HEAD").exists());
75 -
    assert_eq!(git(&r, &["log", "--format=%s"]), "first");
76 -
    assert_eq!(git(&r, &["symbolic-ref", "HEAD"]), "refs/heads/main");
77 -
    assert_eq!(
78 -
        git(&r, &["status", "--porcelain"]),
79 -
        "",
80 -
        "Git sees no difference, and not .oxy either"
81 -
    );
82 -
    let (_, status) = oxy(&r, &home, &["status"]);
83 -
    assert!(
84 -
        !status.contains(".git"),
85 -
        "oxy never takes .git as content: {status}"
86 -
    );
87 -
88 -
    // An oxy commit shows in Git without asking.
89 -
    std::fs::write(r.join("a.txt"), "one\ntwo\n").unwrap();
90 -
    assert_eq!(
91 -
        git(&r, &["status", "--porcelain"]),
92 -
        "M a.txt",
93 -
        "uncommitted work shows as modified"
94 -
    );
95 -
    assert!(oxy(&r, &home, &["commit", "-m", "second"]).0);
96 -
    assert_eq!(git(&r, &["log", "--format=%s"]), "second\nfirst");
97 -
    assert_eq!(git(&r, &["status", "--porcelain"]), "");
98 -
    assert_eq!(git(&r, &["show", "HEAD:a.txt"]), "one\ntwo");
99 -
100 -
    // Off: no more updates, the .git stays.
101 -
    assert!(oxy(&r, &home, &["git", "colocate", "--off"]).0);
102 -
    std::fs::write(r.join("b.txt"), "b\n").unwrap();
103 -
    assert!(oxy(&r, &home, &["commit", "-m", "third"]).0);
104 -
    assert_eq!(
105 -
        git(&r, &["log", "--format=%s"]),
106 -
        "second\nfirst",
107 -
        "no longer followed"
108 -
    );
109 -
    assert!(r.join(".git/HEAD").exists());
110 -
}
modified tests/cli_json.rs +0 −118
…
261 unchanged lines
262 262
263 263
/// `oxy commit -F`, `oxy describe` naming and tags as changes (feedback
264 264
/// 4 and 6).
265 -
/// Cylinder C1 (oxygen#108): a directory packs into an artifact of this
266 -
/// repository's chunks, lists and shows as JSON, survives gc, and unpacks.
267 -
#[test]
268 -
fn artifacts_pack_show_survive_gc_and_unpack() {
269 -
    let tmp = tempfile::tempdir().unwrap();
270 -
    let home = tmp.path().join("home");
271 -
    let dir = tmp.path().join("r");
272 -
    std::fs::create_dir_all(&dir).unwrap();
273 -
    assert_eq!(oxy(&dir, &home, &["init"]).0, 0);
274 -
    write(tmp.path(), "out/bin/tool", "#!/bin/sh\necho hi\n");
275 -
    write(tmp.path(), "out/README", "a tool\n");
276 -
    let out = tmp.path().join("out");
277 -
    let pack = [
278 -
        "artifact",
279 -
        "pack",
280 -
        out.to_str().unwrap(),
281 -
        "--kind",
282 -
        "package",
283 -
        "--arch",
284 -
        "x86_64",
285 -
        "--label",
286 -
        "version=0.1.0",
287 -
        "--json",
288 -
    ];
289 -
    let a = json(&dir, &home, &pack);
290 -
    let name = a["name"].as_str().unwrap().to_string();
291 -
    assert_eq!(
292 -
        (
293 -
            a["kind"].as_str(),
294 -
            a["files"].as_u64(),
295 -
            a["labels"]["version"].as_str()
296 -
        ),
297 -
        (Some("package"), Some(2), Some("0.1.0"))
298 -
    );
299 -
    let list = json(&dir, &home, &["artifact", "list", "--json"]);
300 -
    assert_eq!(list[0]["name"], name.as_str());
301 -
    let (code, _, err) = oxy(
302 -
        &dir,
303 -
        &home,
304 -
        &[
305 -
            "artifact",
306 -
            "pack",
307 -
            out.to_str().unwrap(),
308 -
            "--kind",
309 -
            "tarball",
310 -
        ],
311 -
    );
312 -
    assert_ne!(code, 0);
313 -
    assert!(err.contains("runner-image"), "{err}");
314 -
    assert_eq!(oxy(&dir, &home, &["gc", "--now"]).0, 0);
315 -
    let (code, verified, err) = oxy(&dir, &home, &["artifact", "verify", &name[..8]]);
316 -
    assert_eq!(code, 0, "{err}");
317 -
    assert!(String::from_utf8_lossy(&verified).contains("2 files check out"));
318 -
    let back = tmp.path().join("back");
319 -
    assert_eq!(
320 -
        oxy(
321 -
            &dir,
322 -
            &home,
323 -
            &["artifact", "unpack", &name, back.to_str().unwrap()]
324 -
        )
325 -
        .0,
326 -
        0
327 -
    );
328 -
    assert_eq!(
329 -
        std::fs::read_to_string(back.join("bin/tool")).unwrap(),
330 -
        "#!/bin/sh\necho hi\n"
331 -
    );
332 -
}
333 -
334 -
/// oxygen#144: a first identity needs no repository; the next repository
335 -
/// signs as it.
336 -
#[test]
337 -
fn a_first_identity_needs_no_repository() {
338 -
    let tmp = tempfile::tempdir().unwrap();
339 -
    let home = tmp.path().join("home");
340 -
    let nowhere = tmp.path().join("nowhere");
341 -
    std::fs::create_dir_all(&nowhere).unwrap();
342 -
    let local = ["identity", "create", "--local"];
343 -
    let (code, _, err) = oxy(&nowhere, &home, &local);
344 -
    assert_ne!(code, 0, "--local needs a repository");
345 -
    assert!(err.contains("no oxy repository"), "{err}");
346 -
    let ident = [
347 -
        "identity",
348 -
        "create",
349 -
        "--name",
350 -
        "Ada",
351 -
        "--email",
352 -
        "[email protected]",
353 -
    ];
354 -
    let (code, out, err) = oxy(&nowhere, &home, &ident);
355 -
    assert_eq!(code, 0, "{err}");
356 -
    let out = String::from_utf8_lossy(&out);
357 -
    assert!(
358 -
        out.contains("Created identity Ada <[email protected]>"),
359 -
        "{out}"
360 -
    );
361 -
    let user = oxy_core::repo::read_config_file(&home.join("config.toml")).unwrap();
362 -
    let key = user["author.key"].clone();
363 -
    assert_eq!(user["author.name"], "Ada");
364 -
    assert!(home.join("keys").join(format!("{key}.secret")).exists());
365 -
    assert!(!nowhere.join(".oxy").exists(), "no repository made");
366 -
367 -
    // A repository made afterwards signs its changes with it.
368 -
    let dir = tmp.path().join("r");
369 -
    std::fs::create_dir_all(&dir).unwrap();
370 -
    assert_eq!(oxy(&dir, &home, &["init"]).0, 0);
371 -
    assert_eq!(oxy(&dir, &home, &["config", "core.sign", "change"]).0, 0);
372 -
    write(&dir, "a.txt", "a\n");
373 -
    let (code, _, err) = oxy(&dir, &home, &["commit", "-m", "First"]);
374 -
    assert_eq!(code, 0, "{err}");
375 -
    let log = json(&dir, &home, &["log", "--json", "-n", "2"]);
376 -
    let first = &log["changes"][1];
377 -
    assert_eq!(first["message"], "First");
378 -
    assert_eq!(first["signature"]["status"], "signed", "{first}");
379 -
    assert_eq!(first["signature"]["key"], key.as_str());
380 -
    assert_eq!(first["signature"]["name"], "Ada");
381 -
}
382 -
383 265
#[test]
384 266
fn commit_from_a_file_and_describe_for_builds() {
385 267
    let tmp = tempfile::tempdir().unwrap();
…
500 unchanged lines
removed tests/cli_mcp.rs +0 −231
1 -
//! `oxy mcp` (oxygen#159) end to end: an MCP client talks to the server over
2 -
//! stdio, lists its tools, reads the repository, commits by path as the
3 -
//! configured identity, and is refused what it may not do.
4 -
5 -
use serde_json::{json, Value};
6 -
use std::io::{BufRead, BufReader, Write};
7 -
use std::path::Path;
8 -
use std::process::{Command, Stdio};
9 -
10 -
fn oxy(dir: &Path, home: &Path, args: &[&str]) -> i32 {
11 -
    Command::new(env!("CARGO_BIN_EXE_oxy"))
12 -
        .arg("--plain")
13 -
        .args(args)
14 -
        .current_dir(dir)
15 -
        .env("OXY_HOME", home)
16 -
        .env("NO_COLOR", "1")
17 -
        .status()
18 -
        .unwrap()
19 -
        .code()
20 -
        .unwrap_or(-1)
21 -
}
22 -
23 -
/// Send each request in turn and collect the replies, one per request (the
24 -
/// notification gets none).
25 -
fn session(dir: &Path, home: &Path, extra: &[&str], requests: &[Value]) -> Vec<Value> {
26 -
    let mut child = Command::new(env!("CARGO_BIN_EXE_oxy"))
27 -
        .arg("mcp")
28 -
        .args(extra)
29 -
        .current_dir(dir)
30 -
        .env("OXY_HOME", home)
31 -
        .env("NO_COLOR", "1")
32 -
        .stdin(Stdio::piped())
33 -
        .stdout(Stdio::piped())
34 -
        .stderr(Stdio::null())
35 -
        .spawn()
36 -
        .unwrap();
37 -
    let mut stdin = child.stdin.take().unwrap();
38 -
    let mut stdout = BufReader::new(child.stdout.take().unwrap());
39 -
    let mut replies = vec![];
40 -
    for r in requests {
41 -
        writeln!(stdin, "{r}").unwrap();
42 -
        stdin.flush().unwrap();
43 -
        if r.get("id").is_some() {
44 -
            let mut line = String::new();
45 -
            stdout.read_line(&mut line).unwrap();
46 -
            replies.push(
47 -
                serde_json::from_str(&line).unwrap_or_else(|e| panic!("not JSON ({e}): {line}")),
48 -
            );
49 -
        }
50 -
    }
51 -
    drop(stdin);
52 -
    assert!(
53 -
        child.wait().unwrap().success(),
54 -
        "the server ends cleanly when its input does"
55 -
    );
56 -
    replies
57 -
}
58 -
59 -
fn call(id: u64, name: &str, args: Value) -> Value {
60 -
    json!({"jsonrpc": "2.0", "id": id, "method": "tools/call", "params": {"name": name, "arguments": args}})
61 -
}
62 -
63 -
#[test]
64 -
fn an_agent_reads_and_commits_through_mcp() {
65 -
    let tmp = tempfile::tempdir().unwrap();
66 -
    let home = tmp.path().join("home");
67 -
    std::fs::create_dir_all(&home).unwrap();
68 -
    let repo = tmp.path().join("repo");
69 -
    assert_eq!(oxy(tmp.path(), &home, &["init", repo.to_str().unwrap()]), 0);
70 -
    assert_eq!(
71 -
        oxy(
72 -
            &repo,
73 -
            &home,
74 -
            &[
75 -
                "identity",
76 -
                "create",
77 -
                "--name",
78 -
                "Ada",
79 -
                "--email",
80 -
                "[email protected]"
81 -
            ]
82 -
        ),
83 -
        0
84 -
    );
85 -
    assert_eq!(
86 -
        oxy(&repo, &home, &["config", "--user", "core.sign", "change"]),
87 -
        0
88 -
    );
89 -
    std::fs::write(repo.join("a.txt"), "one\n").unwrap();
90 -
    std::fs::write(repo.join("b.txt"), "two\n").unwrap();
91 -
92 -
    let r = session(
93 -
        &repo,
94 -
        &home,
95 -
        &[],
96 -
        &[
97 -
            json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test", "version": "0"}}}),
98 -
            json!({"jsonrpc": "2.0", "method": "notifications/initialized"}),
99 -
            json!({"jsonrpc": "2.0", "id": 2, "method": "tools/list"}),
100 -
            call(3, "status", json!({})),
101 -
            call(4, "commit", json!({"message": "everything"})),
102 -
            call(5, "commit", json!({"message": "Add a", "paths": ["a.txt"]})),
103 -
            call(6, "log", json!({"limit": 5})),
104 -
            call(7, "status", json!({})),
105 -
            call(8, "undo", json!({})),
106 -
            call(9, "push", json!({"force": true})),
107 -
            call(10, "cat", json!({"path": "a.txt", "change": "@"})),
108 -
        ],
109 -
    );
110 -
111 -
    let init = &r[0]["result"];
112 -
    assert_eq!(init["serverInfo"]["name"], "oxy");
113 -
    let said = init["instructions"].as_str().unwrap();
114 -
    assert!(
115 -
        said.contains("Ada"),
116 -
        "initialize says who changes are signed as: {said}"
117 -
    );
118 -
    assert!(
119 -
        said.contains("Destructive tools (undo, forced push) are off"),
120 -
        "{said}"
121 -
    );
122 -
123 -
    let names: Vec<&str> = r[1]["result"]["tools"]
124 -
        .as_array()
125 -
        .unwrap()
126 -
        .iter()
127 -
        .map(|t| t["name"].as_str().unwrap())
128 -
        .collect();
129 -
    for t in [
130 -
        "status",
131 -
        "diff",
132 -
        "log",
133 -
        "show",
134 -
        "cat",
135 -
        "blame",
136 -
        "bookmarks",
137 -
        "commit",
138 -
        "amend",
139 -
        "push",
140 -
        "pull",
141 -
        "tlog_verify",
142 -
    ] {
143 -
        assert!(names.contains(&t), "{t} is offered: {names:?}");
144 -
    }
145 -
    assert!(!names.contains(&"undo"), "undo needs --allow-destructive");
146 -
147 -
    let before = &r[2]["result"];
148 -
    assert_eq!(before["isError"], false, "{before}");
149 -
    assert!(
150 -
        before["structuredContent"].is_object(),
151 -
        "status comes back as JSON: {before}"
152 -
    );
153 -
    assert!(before["content"][0]["text"]
154 -
        .as_str()
155 -
        .unwrap()
156 -
        .contains("b.txt"));
157 -
158 -
    assert_eq!(
159 -
        r[3]["result"]["isError"], true,
160 -
        "a commit names its paths or all: {}",
161 -
        r[3]
162 -
    );
163 -
    assert!(r[3]["result"]["content"][0]["text"]
164 -
        .as_str()
165 -
        .unwrap()
166 -
        .contains("all: true"));
167 -
168 -
    assert_eq!(r[4]["result"]["isError"], false, "commit by path: {}", r[4]);
169 -
170 -
    let log = r[5]["result"]["content"][0]["text"].as_str().unwrap();
171 -
    assert!(log.contains("Add a"), "{log}");
172 -
    assert!(
173 -
        r[5]["result"]["structuredContent"].is_object(),
174 -
        "a JSON array comes wrapped: {}",
175 -
        r[5]
176 -
    );
177 -
178 -
    let after = r[6]["result"]["content"][0]["text"].as_str().unwrap();
179 -
    assert!(
180 -
        after.contains("b.txt") && !after.contains("a.txt"),
181 -
        "only a.txt went in: {after}"
182 -
    );
183 -
184 -
    assert!(
185 -
        r[7]["error"]["message"]
186 -
            .as_str()
187 -
            .unwrap()
188 -
            .contains("no tool named undo"),
189 -
        "{}",
190 -
        r[7]
191 -
    );
192 -
    assert_eq!(r[8]["result"]["isError"], true);
193 -
    assert!(r[8]["result"]["content"][0]["text"]
194 -
        .as_str()
195 -
        .unwrap()
196 -
        .contains("--allow-destructive"));
197 -
    assert_eq!(r[9]["result"]["content"][0]["text"], "one\n");
198 -
}
199 -
200 -
#[test]
201 -
fn destructive_tools_come_with_the_flag_and_a_bad_directory_is_refused() {
202 -
    let tmp = tempfile::tempdir().unwrap();
203 -
    let home = tmp.path().join("home");
204 -
    std::fs::create_dir_all(&home).unwrap();
205 -
    let repo = tmp.path().join("repo");
206 -
    assert_eq!(oxy(tmp.path(), &home, &["init", repo.to_str().unwrap()]), 0);
207 -
    let r = session(
208 -
        &repo,
209 -
        &home,
210 -
        &["--allow-destructive"],
211 -
        &[json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"})],
212 -
    );
213 -
    let undo = r[0]["result"]["tools"]
214 -
        .as_array()
215 -
        .unwrap()
216 -
        .iter()
217 -
        .find(|t| t["name"] == "undo")
218 -
        .cloned();
219 -
    assert_eq!(
220 -
        undo.expect("undo is offered")["annotations"]["destructiveHint"],
221 -
        true
222 -
    );
223 -
224 -
    let out = Command::new(env!("CARGO_BIN_EXE_oxy"))
225 -
        .args(["mcp", tmp.path().to_str().unwrap()])
226 -
        .env("OXY_HOME", &home)
227 -
        .output()
228 -
        .unwrap();
229 -
    assert!(!out.status.success());
230 -
    assert!(String::from_utf8_lossy(&out.stderr).contains("not the root of an Oxygen repository"));
231 -
}
removed tests/cli_subrepo.rs +0 −94
1 -
//! oxygen#162: a subrepo pin moved by work inside the subrepo is a change
2 -
//! the parent's next commit records; `oxy status` and `oxy commit` say so,
3 -
//! and say when a subrepo holds uncommitted edits of its own.
4 -
5 -
use std::path::Path;
6 -
use std::process::Command;
7 -
8 -
fn oxy(dir: &Path, home: &Path, args: &[&str]) -> (i32, String) {
9 -
    let out = Command::new(env!("CARGO_BIN_EXE_oxy"))
10 -
        .arg("--plain")
11 -
        .args(args)
12 -
        .current_dir(dir)
13 -
        .env("OXY_HOME", home)
14 -
        .env("NO_COLOR", "1")
15 -
        .output()
16 -
        .unwrap();
17 -
    let text = format!(
18 -
        "{}{}",
19 -
        String::from_utf8_lossy(&out.stdout),
20 -
        String::from_utf8_lossy(&out.stderr)
21 -
    );
22 -
    (out.status.code().unwrap_or(-1), text)
23 -
}
24 -
25 -
fn ok(dir: &Path, home: &Path, args: &[&str]) -> String {
26 -
    let (code, out) = oxy(dir, home, args);
27 -
    assert_eq!(code, 0, "oxy {args:?}: {out}");
28 -
    out
29 -
}
30 -
31 -
#[test]
32 -
fn moved_pins_and_edits_inside_subrepos_are_named() {
33 -
    let tmp = tempfile::tempdir().unwrap();
34 -
    let t = tmp.path();
35 -
    let home = t.join("home");
36 -
    std::fs::create_dir_all(&home).unwrap();
37 -
    ok(
38 -
        t,
39 -
        &home,
40 -
        &[
41 -
            "identity",
42 -
            "create",
43 -
            "--name",
44 -
            "Ada",
45 -
            "--email",
46 -
            "[email protected]",
47 -
        ],
48 -
    );
49 -
    // The subrepo's own repository, with one change.
50 -
    let lib = t.join("lib");
51 -
    ok(t, &home, &["init", lib.to_str().unwrap()]);
52 -
    std::fs::write(lib.join("lib.txt"), "one\n").unwrap();
53 -
    ok(&lib, &home, &["commit", "-m", "lib one"]);
54 -
    // The parent pins it.
55 -
    let app = t.join("app");
56 -
    ok(t, &home, &["init", app.to_str().unwrap()]);
57 -
    std::fs::write(app.join("app.txt"), "app\n").unwrap();
58 -
    ok(
59 -
        &app,
60 -
        &home,
61 -
        &["submodule", "add", lib.to_str().unwrap(), "deps/lib"],
62 -
    );
63 -
    ok(&app, &home, &["commit", "-m", "app with lib"]);
64 -
    let quiet = ok(&app, &home, &["status"]);
65 -
    assert!(!quiet.contains("Subrepo pins"), "{quiet}");
66 -
67 -
    // Edits inside the subrepo, not committed there: named, not a pin move.
68 -
    let inner = app.join("deps/lib");
69 -
    std::fs::write(inner.join("lib.txt"), "two\n").unwrap();
70 -
    let st = ok(&app, &home, &["status"]);
71 -
    assert!(
72 -
        st.contains("Subrepo pins") && st.contains("uncommitted edits inside it"),
73 -
        "{st}"
74 -
    );
75 -
    assert!(!st.contains("pin moved"), "{st}");
76 -
77 -
    // Committed inside: the pin has moved, and a bare commit says it records it.
78 -
    ok(&inner, &home, &["commit", "-m", "lib two"]);
79 -
    let st = ok(&app, &home, &["status"]);
80 -
    assert!(st.contains("deps/lib") && st.contains("pin moved"), "{st}");
81 -
    std::fs::write(app.join("app.txt"), "app 2\n").unwrap();
82 -
    let by_path = ok(&app, &home, &["commit", "-m", "app only", "app.txt"]);
83 -
    assert!(
84 -
        !by_path.contains("recorded subrepo"),
85 -
        "a commit by path leaves the pin out: {by_path}"
86 -
    );
87 -
    let bare = ok(&app, &home, &["commit", "-m", "everything"]);
88 -
    assert!(
89 -
        bare.contains("recorded subrepo deps/lib's pin moving") && bare.contains("a pin bump"),
90 -
        "{bare}"
91 -
    );
92 -
    let after = ok(&app, &home, &["status"]);
93 -
    assert!(!after.contains("Subrepo pins"), "recorded: {after}");
94 -
}