cli: git: store absolute remote path in config file

The "git" CLI chdir()s to the work tree root, so paths in config file are
usually resolved relative to the workspace root. OTOH, jj doesn't modify the
process environment, so libgit2 resolves remote paths relative to cwd, not to
the workspace root. To mitigate the problem, this patch makes "jj git remote"
sub commands to store resolved path in .git/config. It would be nice if we can
reconfigure in-memory git2 remote object to use absolute paths (or set up
in-memory named remote without writing a config file), but there's no usable
API afaik.

This behavior is different from "git remote add"/"set-url". I don't know the
rationale, but these commands don't resolve relative paths, whereas "git clone"
writes resolved path to .git/config. I think it's more consistent to make all
"jj git" sub commands resolve relative paths.
This commit is contained in:
Yuya Nishihara 2025-01-11 15:32:01 +09:00
parent 20b3d02ff2
commit 89e0a7021a
6 changed files with 52 additions and 6 deletions

View file

@ -48,6 +48,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* In `jj config list` template, `value` is now typed as `ConfigValue`, not as
`String` serialized in TOML syntax.
* `jj git remote add`/`set-url` now converts relative Git remote path to
absolute path.
* Upgraded `scm-record` from v0.4.0 to v0.5.0. See release notes at
<https://github.com/arxanas/scm-record/releases/tag/v0.5.0>.

View file

@ -46,6 +46,8 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)]
pub struct GitCloneArgs {
/// URL or path of the Git repo to clone
///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::DirPath)]
source: String,
/// Specifies the target directory for the Jujutsu repository clone.

View file

@ -17,6 +17,7 @@ use jj_lib::repo::Repo;
use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::absolute_git_url;
use crate::git_util::get_git_repo;
use crate::ui::Ui;
@ -25,7 +26,10 @@ use crate::ui::Ui;
pub struct GitRemoteAddArgs {
/// The remote's name
remote: String,
/// The remote's URL
/// The remote's URL or path
///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::DirPath)]
url: String,
}
@ -37,6 +41,7 @@ pub fn cmd_git_remote_add(
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
git::add_remote(&git_repo, &args.remote, &args.url)?;
let url = absolute_git_url(command.cwd(), &args.url)?;
git::add_remote(&git_repo, &args.remote, &url)?;
Ok(())
}

View file

@ -19,6 +19,7 @@ use jj_lib::repo::Repo;
use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::complete;
use crate::git_util::absolute_git_url;
use crate::git_util::get_git_repo;
use crate::ui::Ui;
@ -28,7 +29,10 @@ pub struct GitRemoteSetUrlArgs {
/// The remote's name
#[arg(add = ArgValueCandidates::new(complete::git_remotes))]
remote: String,
/// The desired url for `remote`
/// The desired URL or path for `remote`
///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::DirPath)]
url: String,
}
@ -40,6 +44,7 @@ pub fn cmd_git_remote_set_url(
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
git::set_remote_url(&git_repo, &args.remote, &args.url)?;
let url = absolute_git_url(command.cwd(), &args.url)?;
git::set_remote_url(&git_repo, &args.remote, &url)?;
Ok(())
}

View file

@ -1051,6 +1051,8 @@ The Git repo will be a bare git repo stored inside the `.jj/` directory.
###### **Arguments:**
* `<SOURCE>` — URL or path of the Git repo to clone
Local path will be resolved to absolute form.
* `<DESTINATION>` — Specifies the target directory for the Jujutsu repository clone. If not provided, defaults to a directory named after the last component of the source URL. The full directory path will be created if it doesn't exist
###### **Options:**
@ -1203,7 +1205,9 @@ Add a Git remote
###### **Arguments:**
* `<REMOTE>` — The remote's name
* `<URL>` — The remote's URL
* `<URL>` — The remote's URL or path
Local path will be resolved to absolute form.
@ -1249,7 +1253,9 @@ Set the URL of a Git remote
###### **Arguments:**
* `<REMOTE>` — The remote's name
* `<URL>` — The desired url for `remote`
* `<URL>` — The desired URL or path for `remote`
Local path will be resolved to absolute form.

View file

@ -13,6 +13,7 @@
// limitations under the License.
use std::fs;
use std::path::PathBuf;
use crate::common::TestEnvironment;
@ -144,6 +145,30 @@ fn test_git_remote_set_url() {
"###);
}
#[test]
fn test_git_remote_relative_path() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Relative path using OS-native separator
let path = PathBuf::from_iter(["..", "native", "sep"]);
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "foo", path.to_str().unwrap()],
);
let stdout = test_env.jj_cmd_success(&repo_path, &["git", "remote", "list"]);
insta::assert_snapshot!(stdout, @"foo $TEST_ENV/native/sep");
// Relative path using UNIX separator
test_env.jj_cmd_ok(
test_env.env_root(),
&["-Rrepo", "git", "remote", "set-url", "foo", "unix/sep"],
);
let stdout = test_env.jj_cmd_success(&repo_path, &["git", "remote", "list"]);
insta::assert_snapshot!(stdout, @"foo $TEST_ENV/unix/sep");
}
#[test]
fn test_git_remote_rename() {
let test_env = TestEnvironment::default();