diff --git a/src/commands.rs b/src/commands.rs index 1e95a8137..4e601f006 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4193,6 +4193,23 @@ fn cmd_git_fetch( Ok(()) } +fn absolute_git_source(cwd: &Path, source: &str) -> String { + // Git appears to turn URL-like source to absolute path if local git directory + // exits, and fails because '$PWD/https' is unsupported protocol. Since it would + // be tedious to copy the exact git (or libgit2) behavior, we simply assume a + // source containing ':' is a URL, SSH remote, or absolute path with Windows + // drive letter. + if !source.contains(':') && Path::new(source).exists() { + // It's less likely that cwd isn't utf-8, so just fall back to original source. + cwd.join(source) + .into_os_string() + .into_string() + .unwrap_or_else(|_| source.to_owned()) + } else { + source.to_owned() + } +} + fn clone_destination_for_source(source: &str) -> Option<&str> { let destination = source.strip_suffix(".git").unwrap_or(source); let destination = destination.strip_suffix('/').unwrap_or(destination); @@ -4217,11 +4234,11 @@ fn cmd_git_clone( if command.global_args().repository.is_some() { return Err(user_error("'--repository' cannot be used with 'git clone'")); } - let source = &args.source; + let source = absolute_git_source(ui.cwd(), &args.source); let wc_path_str = args .destination .as_deref() - .or_else(|| clone_destination_for_source(source)) + .or_else(|| clone_destination_for_source(&source)) .ok_or_else(|| user_error("No destination specified and wasn't able to guess it"))?; let wc_path = ui.cwd().join(wc_path_str); let wc_path_existed = wc_path.exists(); @@ -4235,7 +4252,7 @@ fn cmd_git_clone( fs::create_dir(&wc_path).unwrap(); } - let clone_result = do_git_clone(ui, command, source, &wc_path); + let clone_result = do_git_clone(ui, command, &source, &wc_path); if clone_result.is_err() { // Canonicalize because fs::remove_dir_all() doesn't seem to like e.g. // `/some/path/.` diff --git a/tests/test_git_clone.rs b/tests/test_git_clone.rs index 8d14d847a..bc0379d48 100644 --- a/tests/test_git_clone.rs +++ b/tests/test_git_clone.rs @@ -50,6 +50,8 @@ fn test_git_clone() { ) .unwrap(); git_repo.set_head("refs/heads/main").unwrap(); + + // Clone with relative source path let stdout = test_env.jj_cmd_success(test_env.env_root(), &["git", "clone", "source", "clone"]); insta::assert_snapshot!(stdout, @r###" Fetching into new repo in "$TEST_ENV/clone" @@ -58,6 +60,12 @@ fn test_git_clone() { "###); assert!(test_env.env_root().join("clone").join("file").exists()); + // Subsequent fetch should just work even if the source path was relative + let stdout = test_env.jj_cmd_success(&test_env.env_root().join("clone"), &["git", "fetch"]); + insta::assert_snapshot!(stdout, @r###" + Nothing changed. + "###); + // Try cloning into an existing workspace let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "source", "clone"]); insta::assert_snapshot!(stderr, @r###"