forked from mirrors/jj
cli: detect .git symlink as a colocated workspace
Maybe we could load GitBackend without resolving .git symlink, but that would introduce more subtle bugs. Instead, we calculate the expected Git workdir path from the canonical ".git" path. Fixes #2011
This commit is contained in:
parent
900300cf5f
commit
b2101d15c8
3 changed files with 175 additions and 13 deletions
|
@ -56,6 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
* SSH authentication can now use ed25519 and ed25519-sk keys. They still need
|
* SSH authentication can now use ed25519 and ed25519-sk keys. They still need
|
||||||
to be password-less.
|
to be password-less.
|
||||||
|
|
||||||
|
* Git repository managed by the repo tool can now be detected as a "colocated"
|
||||||
|
repository.
|
||||||
|
[#2011](https://github.com/martinvonz/jj/issues/2011)
|
||||||
|
|
||||||
## [0.8.0] - 2023-07-09
|
## [0.8.0] - 2023-07-09
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
|
@ -696,14 +696,7 @@ impl WorkspaceCommandHelper {
|
||||||
)?;
|
)?;
|
||||||
let loaded_at_head = command.global_args.at_operation == "@";
|
let loaded_at_head = command.global_args.at_operation == "@";
|
||||||
let may_update_working_copy = loaded_at_head && !command.global_args.ignore_working_copy;
|
let may_update_working_copy = loaded_at_head && !command.global_args.ignore_working_copy;
|
||||||
let mut working_copy_shared_with_git = false;
|
let working_copy_shared_with_git = is_colocated_git_workspace(&workspace, &repo);
|
||||||
let maybe_git_backend = repo.store().backend_impl().downcast_ref::<GitBackend>();
|
|
||||||
if let Some(git_workdir) = maybe_git_backend
|
|
||||||
.and_then(|git_backend| git_backend.git_repo().workdir().map(ToOwned::to_owned))
|
|
||||||
.and_then(|workdir| workdir.canonicalize().ok())
|
|
||||||
{
|
|
||||||
working_copy_shared_with_git = git_workdir == workspace.workspace_root().as_path();
|
|
||||||
}
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
cwd: command.cwd.clone(),
|
cwd: command.cwd.clone(),
|
||||||
string_args: command.string_args.clone(),
|
string_args: command.string_args.clone(),
|
||||||
|
@ -1599,6 +1592,23 @@ jj init --git-repo=.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_colocated_git_workspace(workspace: &Workspace, repo: &ReadonlyRepo) -> bool {
|
||||||
|
let Some(git_backend) = repo.store().backend_impl().downcast_ref::<GitBackend>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let git_repo = git_backend.git_repo();
|
||||||
|
let Some(git_workdir) = git_repo.workdir().and_then(|path| path.canonicalize().ok()) else {
|
||||||
|
return false; // Bare repository
|
||||||
|
};
|
||||||
|
// Colocated workspace should have ".git" directory, file, or symlink. Since the
|
||||||
|
// backend is loaded from the canonical path, its working directory should also
|
||||||
|
// be resolved from the canonical ".git" path.
|
||||||
|
let Ok(dot_git_path) = workspace.workspace_root().join(".git").canonicalize() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
Some(git_workdir.as_ref()) == dot_git_path.parent()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_repo_transaction(
|
pub fn start_repo_transaction(
|
||||||
repo: &Arc<ReadonlyRepo>,
|
repo: &Arc<ReadonlyRepo>,
|
||||||
settings: &UserSettings,
|
settings: &UserSettings,
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::Path;
|
||||||
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
|
@ -20,10 +20,12 @@ use crate::common::TestEnvironment;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
||||||
fn init_git_repo(git_repo_path: &PathBuf, bare: bool) {
|
fn init_git_repo(git_repo_path: &Path, bare: bool) {
|
||||||
let git_repo =
|
init_git_repo_with_opts(git_repo_path, git2::RepositoryInitOptions::new().bare(bare));
|
||||||
git2::Repository::init_opts(git_repo_path, git2::RepositoryInitOptions::new().bare(bare))
|
}
|
||||||
.unwrap();
|
|
||||||
|
fn init_git_repo_with_opts(git_repo_path: &Path, opts: &git2::RepositoryInitOptions) {
|
||||||
|
let git_repo = git2::Repository::init_opts(git_repo_path, opts).unwrap();
|
||||||
let git_blob_oid = git_repo.blob(b"some content").unwrap();
|
let git_blob_oid = git_repo.blob(b"some content").unwrap();
|
||||||
let mut git_tree_builder = git_repo.treebuilder(None).unwrap();
|
let mut git_tree_builder = git_repo.treebuilder(None).unwrap();
|
||||||
git_tree_builder
|
git_tree_builder
|
||||||
|
@ -185,6 +187,152 @@ fn test_init_git_colocated() {
|
||||||
│ My commit message
|
│ My commit message
|
||||||
~
|
~
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD moves
|
||||||
|
test_env.jj_cmd_success(&workspace_root, &["new"]);
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ sqpuoqvx test.user@example.com 2001-02-03 04:05:07.000 +07:00 HEAD@git f61b77cd
|
||||||
|
│ (no description set)
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_git_colocated_gitlink() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
// <workspace_root>/.git -> <git_repo_path>
|
||||||
|
let git_repo_path = test_env.env_root().join("git-repo");
|
||||||
|
let workspace_root = test_env.env_root().join("repo");
|
||||||
|
init_git_repo_with_opts(
|
||||||
|
&git_repo_path,
|
||||||
|
git2::RepositoryInitOptions::new().workdir_path(&workspace_root),
|
||||||
|
);
|
||||||
|
assert!(workspace_root.join(".git").is_file());
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["init", "--git-repo", "."]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
Initialized repo in "."
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD got checked out
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ mwrttmos git.user@example.com 1970-01-01 01:02:03.000 +01:00 my-branch HEAD@git 8d698d4a
|
||||||
|
│ My commit message
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD moves
|
||||||
|
test_env.jj_cmd_success(&workspace_root, &["new"]);
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ sqpuoqvx test.user@example.com 2001-02-03 04:05:07.000 +07:00 HEAD@git f61b77cd
|
||||||
|
│ (no description set)
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_init_git_colocated_symlink_directory() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
// <workspace_root>/.git -> <git_repo_path>
|
||||||
|
let git_repo_path = test_env.env_root().join("git-repo");
|
||||||
|
let workspace_root = test_env.env_root().join("repo");
|
||||||
|
init_git_repo(&git_repo_path, false);
|
||||||
|
std::fs::create_dir(&workspace_root).unwrap();
|
||||||
|
std::os::unix::fs::symlink(git_repo_path.join(".git"), workspace_root.join(".git")).unwrap();
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["init", "--git-repo", "."]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
Initialized repo in "."
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD got checked out
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ mwrttmos git.user@example.com 1970-01-01 01:02:03.000 +01:00 my-branch HEAD@git 8d698d4a
|
||||||
|
│ My commit message
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD moves
|
||||||
|
test_env.jj_cmd_success(&workspace_root, &["new"]);
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ sqpuoqvx test.user@example.com 2001-02-03 04:05:07.000 +07:00 HEAD@git f61b77cd
|
||||||
|
│ (no description set)
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_init_git_colocated_symlink_gitlink() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
// <workspace_root>/.git -> <git_workdir_path>/.git -> <git_repo_path>
|
||||||
|
let git_repo_path = test_env.env_root().join("git-repo");
|
||||||
|
let git_workdir_path = test_env.env_root().join("git-workdir");
|
||||||
|
let workspace_root = test_env.env_root().join("repo");
|
||||||
|
init_git_repo_with_opts(
|
||||||
|
&git_repo_path,
|
||||||
|
git2::RepositoryInitOptions::new().workdir_path(&git_workdir_path),
|
||||||
|
);
|
||||||
|
assert!(git_workdir_path.join(".git").is_file());
|
||||||
|
std::fs::create_dir(&workspace_root).unwrap();
|
||||||
|
std::os::unix::fs::symlink(git_workdir_path.join(".git"), workspace_root.join(".git")).unwrap();
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["init", "--git-repo", "."]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
Initialized repo in "."
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD got checked out
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ mwrttmos git.user@example.com 1970-01-01 01:02:03.000 +01:00 my-branch HEAD@git 8d698d4a
|
||||||
|
│ My commit message
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that the Git repo's HEAD moves
|
||||||
|
test_env.jj_cmd_success(&workspace_root, &["new"]);
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ sqpuoqvx test.user@example.com 2001-02-03 04:05:07.000 +07:00 HEAD@git f61b77cd
|
||||||
|
│ (no description set)
|
||||||
|
~
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_git_external_but_git_dir_exists() {
|
||||||
|
let test_env = TestEnvironment::default();
|
||||||
|
let git_repo_path = test_env.env_root().join("git-repo");
|
||||||
|
let workspace_root = test_env.env_root().join("repo");
|
||||||
|
git2::Repository::init(&git_repo_path).unwrap();
|
||||||
|
init_git_repo(&workspace_root, false);
|
||||||
|
let stdout = test_env.jj_cmd_success(
|
||||||
|
&workspace_root,
|
||||||
|
&["init", "--git-repo", git_repo_path.to_str().unwrap()],
|
||||||
|
);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
Initialized repo in "."
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// The local ".git" repository is unrelated, so no commits should be imported
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ zzzzzzzz 1970-01-01 00:00:00.000 +00:00 00000000
|
||||||
|
(empty) (no description set)
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Check that Git HEAD is not set because this isn't a colocated repo
|
||||||
|
test_env.jj_cmd_success(&workspace_root, &["new"]);
|
||||||
|
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
|
||||||
|
insta::assert_snapshot!(stdout, @r###"
|
||||||
|
◉ qpvuntsm test.user@example.com 2001-02-03 04:05:07.000 +07:00 230dd059
|
||||||
|
│ (empty) (no description set)
|
||||||
|
~
|
||||||
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue