From 9e1d6b1feb2635cc03e07443f564f0880b540927 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Wed, 28 May 2025 19:26:30 +0200 Subject: [PATCH] radicle: Detect current repository using `jj` When using Jujutsu and a non-colocated Git repository, the detection using `git2` directly fails (just as `git` in a shell would fail) because there is no `.git` directory found by traversing up the filesystem hierarchy. Add an invocation of `jj git root` as a fallback. --- crates/radicle-cli/examples/jj-init-bare.md | 19 +++++++++ crates/radicle-cli/tests/commands.rs | 31 ++++++++++++++ crates/radicle/src/rad.rs | 47 +++++++++++++++++++-- 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 crates/radicle-cli/examples/jj-init-bare.md diff --git a/crates/radicle-cli/examples/jj-init-bare.md b/crates/radicle-cli/examples/jj-init-bare.md new file mode 100644 index 00000000..6668f965 --- /dev/null +++ b/crates/radicle-cli/examples/jj-init-bare.md @@ -0,0 +1,19 @@ +We initialize Jujutusu for our repository for use with a bare Git repo. + +```(stderr) +$ jj git init --git-repo heartwood heartwood.jj +Done importing changes from the underlying Git repo. +Working copy (@) now at: lvxkkpmk 9ec513df (empty) (no description set) +Parent commit (@-) : xpnzuzwn f2de534b master | Second commit +Added 1 files, modified 0 files, removed 0 files +Initialized repo in "heartwood.jj" +``` + +``` +$ cd heartwood.jj +``` + +``` +$ rad . +rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji +``` \ No newline at end of file diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index ebbad389..f2f2c673 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -858,6 +858,37 @@ fn rad_patch() { Environment::alice(["rad-init", "rad-patch"]); } +#[test] +fn rad_jj_bare() { + // We test whether `jj` is installed, and have this test succeed if it is not. + // Programmatic skipping of tests is not supported as of 2024-08. + if !program_reports_version("jj") { + return; + } + + let mut environment = Environment::new(); + let mut profile = environment.node("alice"); + let rid = profile.project("heartwood", "Radicle Heartwood Protocol & Stack"); + + test( + "examples/rad-init-existing-bare.md", + environment.work(&profile), + Some(&profile.home), + [( + "URL", + git::url::File::new(profile.storage.path()) + .rid(rid) + .to_string() + .as_str(), + )], + ) + .unwrap(); + + environment + .tests(["jj-config", "jj-init-bare"], &profile) + .unwrap(); +} + #[test] fn rad_jj_colocated_patch() { // We test whether `jj` is installed, and have this test succeed if it is not. diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs index 969d3c00..d9754e9a 100644 --- a/crates/radicle/src/rad.rs +++ b/crates/radicle/src/rad.rs @@ -372,6 +372,18 @@ pub fn remove_remote(repo: &git2::Repository) -> Result<(), RemoteError> { Ok(()) } +#[derive(Error, Debug)] +pub enum CwdError { + #[error(transparent)] + Remote(#[from] RemoteError), + + #[error("Detection failed (git: '{git}', jj: '{jj}')")] + Detection { + git: git2::Error, + jj: JujutsuGitRootError, + }, +} + /// Get the RID of the repository in current working directory /// /// It will atempt to search parent directories if `path` did not find @@ -382,10 +394,11 @@ pub fn remove_remote(repo: &git2::Repository) -> Result<(), RemoteError> { /// This function should only perform read operations since we do not /// want to modify the wrong repository in the case that it found a /// Git repository that is not a Radicle repository. -pub fn cwd() -> Result<(git2::Repository, RepoId), RemoteError> { - let repo = repo()?; - let (_, id) = remote(&repo)?; +pub fn cwd() -> Result<(git2::Repository, RepoId), CwdError> { + let repo = + repo().or_else(|git| repo_jj_git_root().map_err(|jj| CwdError::Detection { git, jj }))?; + let (_, id) = remote(&repo)?; Ok((repo, id)) } @@ -411,6 +424,34 @@ pub fn repo() -> Result { Ok(repo) } +#[derive(Error, Debug)] +pub enum JujutsuGitRootError { + #[error("git: {0}")] + Git(#[from] git2::Error), + + #[error("i/o: {0}")] + Io(#[from] io::Error), + + #[error("exited with status {status}")] + CommandFailure { status: std::process::ExitStatus }, +} + +/// Get the Git repo underlying the current Jujutsu repository. +pub fn repo_jj_git_root() -> Result { + let output = std::process::Command::new("jj") + .args(["git", "root"]) + .output()?; + + if !output.status.success() { + return Err(JujutsuGitRootError::CommandFailure { + status: output.status, + }); + } + + let path = std::path::PathBuf::from(String::from_utf8_lossy(&output.stdout).to_string().trim()); + Ok(git2::Repository::open(path)?) +} + /// Setup patch upstream branch such that `git push` updates the patch. pub fn setup_patch_upstream<'a>( patch: &ObjectId,