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.
This commit is contained in:
Lorenz Leutgeb 2025-05-28 19:26:30 +02:00 committed by Fintan Halpenny
parent abc963f2f2
commit 9e1d6b1feb
3 changed files with 94 additions and 3 deletions

View File

@ -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
```

View File

@ -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.

View File

@ -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<git2::Repository, git2::Error> {
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<git2::Repository, JujutsuGitRootError> {
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,