Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
Avoid hard-coding "radicle.xyz" in Rust code
Merged lorenz opened 8 months ago

This patch does three things:

  • Justifies the changes in CONTRIBUTING.md
  • Removes a few occurrences of the domain name, and makes the more complex case of fixtures easier in the future.
  • Allows defining the default URL for Radicle Explorer at compile-time.
  • Adds a Git hook that will fail if people commit to files which contain “radicle.xyz” or “radicle.zulipchat.com”. Note that it does not make that impossible, it just adds another step to make people stop and think.
8 files changed +114 -69 2127782b 9b7529ba
modified CONTRIBUTING.md
@@ -180,6 +180,25 @@ for the reader:
    for rid in self.storage.inventory()? {
        ...

+
### Referring to radicle.xyz in Code
+

+
While <https://radicle.xyz> is the main website of the project, and also the domain
+
associated with COBs implemented in this repo, we strive to write code that is as
+
indepent as reasonably possible from this particular domain name. For example, it
+
should not be used for default configuration values, or if it is, there should be a
+
way to override.
+

+
This makes it easier to re-package Radicle for distribution under a different domain
+
or fork it altogether. It also tends to produce better, more flexible, code.
+

+
In tests, instead use names that are compliant with RFC 2606, e.g.
+
"radicle.example.com".
+

+
Note that as of 2025-08, there are still a few mentions of "radicle.xyz" in the
+
codebase (mostly tests or user hints, fallback for configuration), and some of them
+
are not easy to remove. However, this is in no way a justification to add more
+
references.
+

### Proposing changes

When proposing changes via a patch:
modified crates/radicle-cli/src/terminal/patch.rs
@@ -489,7 +489,7 @@ mod test {
    ) -> git::Oid {
        let sig = git::raw::Signature::new(
            "anonymous",
-
            "anonymous@radicle.xyz",
+
            "anonymous@radicle.example.com",
            &git::raw::Time::new(0, 0),
        )
        .unwrap();
modified crates/radicle/src/explorer.rs
@@ -80,7 +80,7 @@ impl std::fmt::Display for ExplorerUrl {
    }
}

-
/// A public explorer, eg. `https://app.radicle.xyz`.
+
/// A public explorer.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@@ -89,7 +89,8 @@ pub struct Explorer(String);
impl Default for Explorer {
    fn default() -> Self {
        Self(String::from(
-
            "https://app.radicle.xyz/nodes/$host/$rid$path",
+
            std::option_env!("RADICLE_EXPLORER")
+
                .unwrap_or("https://app.radicle.xyz/nodes/$host/$rid$path"),
        ))
    }
}
modified crates/radicle/src/profile.rs
@@ -784,7 +784,7 @@ mod test {
    #[test]
    fn test_config() {
        let cfg = json::from_value::<Config>(json::json!({
-
          "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
+
          "publicExplorer": "https://app.radicle.example.com/nodes/$host/$rid$path",
          "preferredSeeds": [],
          "web": {
            "pinned": {
@@ -800,8 +800,8 @@ mod test {
            "listen": [],
            "peers": { "type": "dynamic", "target": 8 },
            "connect": [
-
              "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@rosa.radicle.xyz:8776",
-
              "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@iris.radicle.xyz:8776"
+
              "z6MkmJzKhSjQz1USfh8NBtaAFyz5gJace9eBV9yFcfMY5BN5@a.radicle.example.com:8776",
+
              "z6MkrUZHwJD3pqerEBugSZRxDFdVqKnMUbyPHcFe5gkfFvTe@b.radicle.example.com:8776"
            ],
            "externalAddresses": [ "seed.radicle.example.com:8776" ],
            "db": { "journalMode": "wal" },
modified crates/radicle/src/storage/git.rs
@@ -1093,7 +1093,8 @@ mod tests {
        let (rid, _, working, _) =
            fixtures::project(tmp.path().join("project"), &storage, &signer).unwrap();
        let stored = storage.repository(rid).unwrap();
-
        let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
+
        let sig =
+
            git2::Signature::now(&alice.to_string(), "anonymous@radicle.example.com").unwrap();
        let head = working.head().unwrap().peel_to_commit().unwrap();

        git::commit(
modified crates/radicle/src/test/arbitrary.rs
@@ -298,7 +298,7 @@ impl Arbitrary for Address {
                cyphernet::addr::HostName::Ip(net::IpAddr::V6(net::Ipv6Addr::from(octets)))
            }
            AddressType::Dns => cyphernet::addr::HostName::Dns(
-
                g.choose(&["iris.radicle.xyz", "rosa.radicle.xyz"])
+
                g.choose(&["iris.radicle.example.com", "rosa.radicle.example.com"])
                    .unwrap()
                    .to_string(),
            ),
modified crates/radicle/src/test/fixtures.rs
@@ -15,6 +15,12 @@ use crate::storage::refs::SignedRefs;
/// The birth of the radicle project, January 1st, 2018.
pub const RADICLE_EPOCH: i64 = 1514817556;

+
const USER_NAME: &str = "anonymous";
+

+
// TODO: Next time we do something that changes all hashes,
+
// also change this to "anonymous@radicle.example.com".
+
const USER_EMAIL: &str = "anonymous@radicle.xyz";
+

/// Create a new user info object.
pub fn user() -> git::UserInfo {
    git::UserInfo {
@@ -94,15 +100,14 @@ pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
        git2::RepositoryInitOptions::new().external_template(false),
    )
    .unwrap();
-
    let user_name = "anonymous";
-
    let user_email = "anonymous@radicle.xyz";
+

    {
        let mut config = repo.config().unwrap();
-
        config.set_str("user.name", user_name).unwrap();
-
        config.set_str("user.email", user_email).unwrap();
+
        config.set_str("user.name", USER_NAME).unwrap();
+
        config.set_str("user.email", USER_EMAIL).unwrap();
    }
    let sig =
-
        git2::Signature::new(user_name, user_email, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
+
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
    let head = git::initial_commit(&repo, &sig).unwrap();
    let tree = git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
    let oid = {
@@ -130,12 +135,8 @@ pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
/// Create an empty commit on the current branch.
pub fn commit(msg: &str, parents: &[git2::Oid], repo: &git2::Repository) -> git::Oid {
    let head = repo.head().unwrap();
-
    let sig = git2::Signature::new(
-
        "anonymous",
-
        "anonymous@radicle.xyz",
-
        &git2::Time::new(RADICLE_EPOCH, 0),
-
    )
-
    .unwrap();
+
    let sig =
+
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
    let tree = head.peel_to_commit().unwrap().tree().unwrap();
    let parents = parents
        .iter()
@@ -153,12 +154,8 @@ pub fn tag(name: &str, message: &str, commit: git2::Oid, repo: &git2::Repository
    let target = repo
        .find_object(commit, Some(git2::ObjectType::Commit))
        .unwrap();
-
    let tagger = git2::Signature::new(
-
        "anonymous",
-
        "anonymous@radicle.xyz",
-
        &git2::Time::new(RADICLE_EPOCH, 0),
-
    )
-
    .unwrap();
+
    let tagger =
+
        git2::Signature::new(USER_NAME, USER_EMAIL, &git2::Time::new(RADICLE_EPOCH, 0)).unwrap();
    repo.tag(name, &target, &tagger, message, false)
        .unwrap()
        .into()
modified flake.nix
@@ -168,54 +168,81 @@
          prefix = "msrv-";
        })
        // {
-
          pre-commit-check = inputs.git-hooks.lib.${system}.run {
-
            src = ./.;
-
            settings.rust.check.cargoDeps = pkgs.rustPlatform.importCargoLock {lockFile = ./Cargo.lock;};
-
            hooks = {
-
              alejandra.enable = true;
-
              rustfmt = {
+
          pre-commit-check = let
+
            grep = rec {
+
              words = ["radicle.xyz" "radicle.zulipchat.com"];
+
              after = map id words;
+
              prefix = "grep-";
+
              id = word: prefix + word;
+
              hooks = builtins.listToAttrs (map (word: {
+
                  # "," is problematic, as this is used to split
+
                  # lists of hook names, when skipping, see:
+
                  # https://pre-commit.com/#temporarily-disabling-hooks
+
                  name = assert !lib.hasInfix "," word; id word;
+
                  value = hook word;
+
                })
+
                words);
+
              hook = word: {
                enable = true;
-
                fail_fast = true;
-
                packageOverrides.rustfmt = rustup.toolchain;
-
              };
-
              cargo-check = {
-
                enable = true;
-
                name = "cargo check";
-
                after = ["rustfmt"];
-
                fail_fast = true;
-
              };
-
              cargo-doc = let
-
                # We wrap `cargo` in order to set an environment variable that
-
                # gives us a non-zero exit on warning.
-
                command =
-
                  pkgs.writeShellScript
-
                  "cargo"
-
                  "RUSTDOCFLAGS='--deny warnings' ${lib.getExe' rustup.toolchain "cargo"} $@";
-
              in {
-
                enable = true;
-
                name = "cargo doc";
-
                after = ["rustfmt"];
-
                fail_fast = true;
-
                entry = "${command} doc --workspace --all-features --no-deps";
+
                entry = builtins.toString (pkgs.writeShellScript
+
                  "grep-${word}"
+
                  "! ${lib.getExe pkgs.ripgrep} --context=3 --fixed-strings '${word}' $@");
+
                name = "Avoid '${word}' in Rust code";
                files = "\\.rs$";
-
                pass_filenames = false;
+
                pass_filenames = true;
              };
-
              clippy = {
-
                enable = true;
-
                name = "cargo clippy";
-
                stages = ["pre-push"]; # Only pre-push, because it takes a while.
-
                settings = {
-
                  allFeatures = true;
-
                  denyWarnings = true;
-
                };
-
                packageOverrides = {
-
                  cargo = rustup.toolchain;
-
                  clippy = rustup.toolchain;
-
                };
-
              };
-
              shellcheck.enable = true;
            };
-
          };
+
          in
+
            inputs.git-hooks.lib.${system}.run {
+
              src = ./.;
+
              settings.rust.check.cargoDeps = pkgs.rustPlatform.importCargoLock {lockFile = ./Cargo.lock;};
+
              hooks =
+
                {
+
                  alejandra.enable = true;
+
                  rustfmt = {
+
                    enable = true;
+
                    fail_fast = true;
+
                    packageOverrides.rustfmt = rustup.toolchain;
+
                  };
+
                  cargo-check = {
+
                    enable = true;
+
                    name = "cargo check";
+
                    after = ["rustfmt"] ++ grep.after;
+
                    fail_fast = true;
+
                  };
+
                  cargo-doc = let
+
                    # We wrap `cargo` in order to set an environment variable that
+
                    # gives us a non-zero exit on warning.
+
                    command =
+
                      pkgs.writeShellScript
+
                      "cargo"
+
                      "RUSTDOCFLAGS='--deny warnings' ${lib.getExe' rustup.toolchain "cargo"} $@";
+
                  in {
+
                    enable = true;
+
                    name = "cargo doc";
+
                    after = ["rustfmt"] ++ grep.after;
+
                    fail_fast = true;
+
                    entry = "${command} doc --workspace --all-features --no-deps";
+
                    files = "\\.rs$";
+
                    pass_filenames = false;
+
                  };
+
                  clippy = {
+
                    enable = true;
+
                    name = "cargo clippy";
+
                    stages = ["pre-push"]; # Only pre-push, because it takes a while.
+
                    settings = {
+
                      allFeatures = true;
+
                      denyWarnings = true;
+
                    };
+
                    packageOverrides = {
+
                      cargo = rustup.toolchain;
+
                      clippy = rustup.toolchain;
+
                    };
+
                  };
+
                  shellcheck.enable = true;
+
                }
+
                // grep.hooks;
+
            };

          # Build the crate as part of `nix flake check` for convenience
          inherit (self.packages.${system}) radicle;