Skip to content

Publish and add a workspace's Rust crates with Cargo, from a sparse registry of its own on g1t.sh, from your machine and from workflows.

Every workspace has a Cargo registry of its own: a sparse index, with the web API that cargo publish, cargo yank and cargo search use. It works with Cargo 1.74 or later, private crates included.

sparse+https://g1t.sh/-/cargo/<workspace>/index/

Crates from crates.io still come from crates.io; only the crates you name with the workspace’s registry come from g1t.

Name the registry in the project’s .cargo/config.toml, or in ~/.cargo/config.toml for every project. The name you give it is the one you pass to --registry; these pages use the workspace’s slug:

[registries.acme]
index = "sparse+https://g1t.sh/-/cargo/acme/index/"
credential-provider = "cargo:token"

Cargo sends a token to a registry that asks for one only through a credential provider named for it. cargo:token keeps the token in ~/.cargo/credentials.toml, which stays out of the project. To keep it in your system’s keychain instead, name cargo:wincred (Windows), cargo:macos-keychain (macOS) or cargo:libsecret (Linux).

Then give Cargo an access token:

cargo login --registry acme

Cargo asks for the token and hands it to the provider. A token with full access works; one with scopes needs packages:read to add private crates and packages:write to publish and yank them.

The token can also come from the environment instead of cargo login, as CARGO_REGISTRIES_ACME_TOKEN for a registry named acme (the name in capitals, with - written _). The cargo:token provider reads it from there, which is how workflows give it.

Say in Cargo.toml where the crate is published and which repository it comes from:

[package]
name = "http-client"
version = "0.3.1"
edition = "2021"
description = "Our HTTP client"
license = "MIT"
readme = "README.md"
repository = "https://g1t.sh/acme/http-client"
publish = ["acme"]
cargo publish --registry acme

The first publish makes the crate. When its repository is a g1t.sh repository of the same workspace, or a repository is named like the crate (a crate http_client is also matched to a repository http-client), it is linked to that repository and has its visibility and roles: publishing needs Write on it. Otherwise it is the workspace’s, private, and needs the workspace’s Write base permission. See who can see and publish a package.

publish = ["acme"] keeps the crate from being published to crates.io by mistake, and lets cargo publish leave out --registry when it is the only registry named.

A crate may depend on crates.io crates and on other crates of the workspace’s registry. The crate’s page on g1t.sh shows the README and description of its highest stable version.

cargo add http-client --registry acme

That writes the dependency with its registry into Cargo.toml:

[dependencies]
http-client = { version = "0.3.1", registry = "acme" }

Cargo.lock records each crate’s registry and the SHA-256 of its .crate file, which g1t computes when the version is published.

cargo search --registry acme http lists the workspace’s crates you can see whose names match.

A crate’s name follows the crates.io rules: ASCII letters, digits, - and _, starting with a letter, at most 64 characters. In a workspace, names that differ only in case or in - against _ are one name: once http-client is published, HTTP_Client is refused.

A version is published once. Publishing a version that is already there is refused, as is one that differs from it only in build metadata (1.0.0+a and 1.0.0+b), so bump version first.

cargo yank --registry acme http-client@0.3.1
cargo yank --registry acme http-client@0.3.1 --undo

A yanked version stays in the registry: projects whose Cargo.lock names it still build, but Cargo no longer picks it for new lockfiles or cargo update. Yanking needs what publishing does. The crate’s page marks yanked versions, and someone with Admin on the linked repository (an owner, for the workspace’s own crates) can delete a version there for good.

Crate owners are not kept: who may publish a crate is decided by its repository’s roles, or the workspace’s, so cargo owner answers with an error that says so.

A crate linked to a repository has the repository’s visibility; one of the workspace’s own is private until an owner makes it public on its page.

The workspace’s crates Without a token With a token
All public Cargo reads the index and downloads them. The same; the token is sent to publish and yank.
Some private The registry answers 401: Cargo needs a token for every crate in it, public ones too. Cargo sends the token with every request, and sees the crates the token’s owner may see.

Cargo first asks for the registry’s config.json without a token. When the registry answers 401, Cargo asks again with its token, and the answer says "auth-required": true, so Cargo sends the token from then on. A private crate you cannot see looks exactly like one that does not exist.

A workflow’s G1T_TOKEN is the workspace’s own token for the run, and can add and publish the workspace’s crates. A workflow runs no cargo login: give Cargo the registry, its credential provider and the token in the environment, each named for the registry:

jobs:
publish:
runs-on: ubuntu-latest
env:
CARGO_REGISTRIES_ACME_INDEX: sparse+https://g1t.sh/-/cargo/acme/index/
CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER: cargo:token
CARGO_REGISTRIES_ACME_TOKEN: ${{ secrets.G1T_TOKEN }}
steps:
- uses: actions/checkout@v4
- run: cargo test
- run: cargo publish --registry acme
Variable Is Needed
CARGO_REGISTRIES_ACME_INDEX The registry’s index, as index in .cargo/config.toml. When no .cargo/config.toml names the registry.
CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER cargo:token, as credential-provider in .cargo/config.toml. When no .cargo/config.toml names the provider. Without one, Cargo refuses a registry with private crates, even with the token in the environment.
CARGO_REGISTRIES_ACME_TOKEN The token, for cargo:token to hand to the registry. Always: cargo publish sends it, and Cargo sends it to read a registry with private crates.

With the project’s .cargo/config.toml from above checked in, CARGO_REGISTRIES_ACME_TOKEN is all a workflow sets. The same variables let cargo build and cargo test download the workspace’s private crates.

A publish is one request with the .crate file inside it, and may hold at most 100 MB. Without the g1t plan, a workspace’s private packages may hold 500 MB and its public ones 10 GB, as for container images. A .crate file is stored once, by its content.

Error Means
401 No token, or a wrong or expired one. Run cargo login --registry acme with a g1t access token, or set CARGO_REGISTRIES_ACME_TOKEN.
authenticated registries require a credential-provider to be configured The workspace has private crates, and Cargo has no provider for its token. Add credential-provider = "cargo:token" to the registry in .cargo/config.toml, or set CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER=cargo:token.
403 Signed in, but your role or your token’s scopes do not allow it, or the workspace is out of free package storage. The message says which.
404 No such crate or version, or a private one you cannot see.
400 The publish was refused: a name that is not valid or is taken, a version already published, or metadata Cargo did not send in full. The message says which.
413 The publish is over 100 MB. Leave large files out with exclude or include in Cargo.toml, and check with cargo package --list.