2022-11-26 23:57:50 +00:00
|
|
|
// Copyright 2022 The Jujutsu Authors
|
2022-06-30 05:21:35 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2024-08-23 02:54:12 +00:00
|
|
|
use std::path;
|
2024-08-22 18:18:15 +00:00
|
|
|
use std::path::Path;
|
|
|
|
use std::path::PathBuf;
|
2023-10-14 11:31:55 +00:00
|
|
|
|
2024-08-22 18:18:15 +00:00
|
|
|
use crate::common::get_stderr_string;
|
|
|
|
use crate::common::get_stdout_string;
|
|
|
|
use crate::common::TestEnvironment;
|
2022-06-30 05:21:35 +00:00
|
|
|
|
2023-10-16 03:16:19 +00:00
|
|
|
fn set_up_non_empty_git_repo(git_repo: &git2::Repository) {
|
local_working_copy: do not create file or write in directory named .jj or .git
I originally considered adding deny-list-based implementation, but the Windows
compatibility rules are super confusing and I don't have a machine to find out
possible aliases. This patch instead adds directory equivalence tests.
In order to test file entity equivalence, we first need to create a file or
directory of the requested name. It's harmless to create an empty .jj or .git
directory, but materializing .git file or symlink can temporarily set up RCE
situation. That's why new empty file is created to test the path validity. We
might want to add some optimization for safe names (e.g. ASCII, not contain
"git" or "jj", not contain "~", etc.)
That being said, I'm not pretty sure if .git/.jj in sub directory must be
checked. It's not safe to cd into the directory and run "jj", but the same
thing can be said to other tools such as "cargo". Perhaps, our minimum
requirement is to protect our metadata (= the root .jj and .git) directories.
Despite the crate name (and internal use of std::fs::File),
same_file::is_same_file() can test equivalence of directories. This is
documented and tested, so I've removed my custom implementation, which was
slightly simpler but lacks Windows support.
2024-10-22 10:20:54 +00:00
|
|
|
set_up_git_repo_with_file(git_repo, "file");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_up_git_repo_with_file(git_repo: &git2::Repository, filename: &str) {
|
2022-06-30 05:21:35 +00:00
|
|
|
let signature =
|
|
|
|
git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
|
|
|
|
let mut tree_builder = git_repo.treebuilder(None).unwrap();
|
|
|
|
let file_oid = git_repo.blob(b"content").unwrap();
|
|
|
|
tree_builder
|
local_working_copy: do not create file or write in directory named .jj or .git
I originally considered adding deny-list-based implementation, but the Windows
compatibility rules are super confusing and I don't have a machine to find out
possible aliases. This patch instead adds directory equivalence tests.
In order to test file entity equivalence, we first need to create a file or
directory of the requested name. It's harmless to create an empty .jj or .git
directory, but materializing .git file or symlink can temporarily set up RCE
situation. That's why new empty file is created to test the path validity. We
might want to add some optimization for safe names (e.g. ASCII, not contain
"git" or "jj", not contain "~", etc.)
That being said, I'm not pretty sure if .git/.jj in sub directory must be
checked. It's not safe to cd into the directory and run "jj", but the same
thing can be said to other tools such as "cargo". Perhaps, our minimum
requirement is to protect our metadata (= the root .jj and .git) directories.
Despite the crate name (and internal use of std::fs::File),
same_file::is_same_file() can test equivalence of directories. This is
documented and tested, so I've removed my custom implementation, which was
slightly simpler but lacks Windows support.
2024-10-22 10:20:54 +00:00
|
|
|
.insert(filename, file_oid, git2::FileMode::Blob.into())
|
2022-06-30 05:21:35 +00:00
|
|
|
.unwrap();
|
|
|
|
let tree_oid = tree_builder.write().unwrap();
|
|
|
|
let tree = git_repo.find_tree(tree_oid).unwrap();
|
|
|
|
git_repo
|
|
|
|
.commit(
|
|
|
|
Some("refs/heads/main"),
|
|
|
|
&signature,
|
|
|
|
&signature,
|
|
|
|
"message",
|
|
|
|
&tree,
|
|
|
|
&[],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
git_repo.set_head("refs/heads/main").unwrap();
|
2023-10-16 03:16:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_git_clone() {
|
|
|
|
let test_env = TestEnvironment::default();
|
2024-10-30 04:36:31 +00:00
|
|
|
test_env.add_config("git.auto-local-bookmark = true");
|
2023-10-16 03:16:19 +00:00
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
|
|
|
|
// Clone an empty repo
|
|
|
|
let (stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "empty"]);
|
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/empty"
|
|
|
|
Nothing changed.
|
|
|
|
"###);
|
|
|
|
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
2022-12-07 03:42:31 +00:00
|
|
|
|
|
|
|
// Clone with relative source path
|
2023-10-10 11:59:18 +00:00
|
|
|
let (stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2022-11-03 20:14:23 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: main@origin [new] tracked
|
2024-03-04 10:52:48 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
2023-07-11 01:46:38 +00:00
|
|
|
Working copy now at: uuqppmxq 1f0b881a (empty) (no description set)
|
2023-08-08 03:11:59 +00:00
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
2022-06-30 05:21:35 +00:00
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("clone").join("file").exists());
|
|
|
|
|
2022-12-07 03:42:31 +00:00
|
|
|
// Subsequent fetch should just work even if the source path was relative
|
2023-10-10 11:59:18 +00:00
|
|
|
let (stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(&test_env.env_root().join("clone"), &["git", "fetch"]);
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2022-12-07 03:42:31 +00:00
|
|
|
Nothing changed.
|
|
|
|
"###);
|
|
|
|
|
2023-08-09 02:36:02 +00:00
|
|
|
// Failed clone should clean up the destination directory
|
|
|
|
std::fs::create_dir(test_env.env_root().join("bad")).unwrap();
|
|
|
|
let assert = test_env
|
|
|
|
.jj_cmd(test_env.env_root(), &["git", "clone", "bad", "failed"])
|
|
|
|
.assert()
|
|
|
|
.code(1);
|
2024-02-02 07:55:24 +00:00
|
|
|
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
|
|
|
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
2023-08-09 02:36:02 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-10-10 11:07:06 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/failed"
|
2023-12-15 03:57:10 +00:00
|
|
|
Error: could not find repository at '$TEST_ENV/bad'; class=Repository (6)
|
2023-08-09 02:36:02 +00:00
|
|
|
"###);
|
|
|
|
assert!(!test_env.env_root().join("failed").exists());
|
|
|
|
|
|
|
|
// Failed clone shouldn't remove the existing destination directory
|
|
|
|
std::fs::create_dir(test_env.env_root().join("failed")).unwrap();
|
|
|
|
let assert = test_env
|
|
|
|
.jj_cmd(test_env.env_root(), &["git", "clone", "bad", "failed"])
|
|
|
|
.assert()
|
|
|
|
.code(1);
|
2024-02-02 07:55:24 +00:00
|
|
|
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
|
|
|
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
2023-08-09 02:36:02 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-10-10 11:07:06 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/failed"
|
2023-12-15 03:57:10 +00:00
|
|
|
Error: could not find repository at '$TEST_ENV/bad'; class=Repository (6)
|
2023-08-09 02:36:02 +00:00
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("failed").exists());
|
|
|
|
assert!(!test_env.env_root().join("failed").join(".jj").exists());
|
|
|
|
|
|
|
|
// Failed clone (if attempted) shouldn't remove the existing workspace
|
|
|
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "bad", "clone"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("clone").join(".jj").exists());
|
|
|
|
|
2022-06-30 05:21:35 +00:00
|
|
|
// Try cloning into an existing workspace
|
|
|
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
2022-11-03 20:14:23 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2022-06-30 04:55:20 +00:00
|
|
|
Error: Destination path exists and is not an empty directory
|
2022-06-30 05:21:35 +00:00
|
|
|
"###);
|
|
|
|
|
|
|
|
// Try cloning into an existing file
|
|
|
|
std::fs::write(test_env.env_root().join("file"), "contents").unwrap();
|
2022-06-30 04:55:20 +00:00
|
|
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "source", "file"]);
|
2022-11-03 20:14:23 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2022-06-30 04:55:20 +00:00
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
2022-06-30 05:21:35 +00:00
|
|
|
|
|
|
|
// Try cloning into non-empty, non-workspace directory
|
|
|
|
std::fs::remove_dir_all(test_env.env_root().join("clone").join(".jj")).unwrap();
|
2022-06-30 04:55:20 +00:00
|
|
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
2022-11-03 20:14:23 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2022-06-30 04:55:20 +00:00
|
|
|
Error: Destination path exists and is not an empty directory
|
2022-06-30 05:21:35 +00:00
|
|
|
"###);
|
2024-07-07 13:19:47 +00:00
|
|
|
|
|
|
|
// Clone into a nested path
|
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "nested/path/to/repo"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/nested/path/to/repo"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: main@origin [new] tracked
|
2024-07-07 13:19:47 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
Working copy now at: uuzqqzqu df8acbac (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
2022-06-30 05:21:35 +00:00
|
|
|
}
|
2023-07-24 09:38:16 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_git_clone_colocate() {
|
|
|
|
let test_env = TestEnvironment::default();
|
2024-10-30 04:36:31 +00:00
|
|
|
test_env.add_config("git.auto-local-bookmark = true");
|
2023-07-24 09:38:16 +00:00
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
|
|
|
|
// Clone an empty repo
|
2023-10-10 11:59:18 +00:00
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(
|
2023-07-24 09:38:16 +00:00
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "empty", "--colocate"],
|
|
|
|
);
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-07-24 09:38:16 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/empty"
|
|
|
|
Nothing changed.
|
|
|
|
"###);
|
|
|
|
|
2023-10-14 11:31:55 +00:00
|
|
|
// git_target path should be relative to the store
|
|
|
|
let store_path = test_env
|
|
|
|
.env_root()
|
|
|
|
.join(PathBuf::from_iter(["empty", ".jj", "repo", "store"]));
|
|
|
|
let git_target_file_contents = std::fs::read_to_string(store_path.join("git_target")).unwrap();
|
|
|
|
insta::assert_snapshot!(
|
|
|
|
git_target_file_contents.replace(path::MAIN_SEPARATOR, "/"),
|
|
|
|
@"../../../.git");
|
|
|
|
|
2023-10-16 03:16:19 +00:00
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
2023-07-24 09:38:16 +00:00
|
|
|
|
|
|
|
// Clone with relative source path
|
2023-10-10 11:59:18 +00:00
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(
|
2023-07-24 09:38:16 +00:00
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "clone", "--colocate"],
|
|
|
|
);
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-07-24 09:38:16 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: main@origin [new] tracked
|
2024-03-04 10:52:48 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
2023-07-11 01:46:38 +00:00
|
|
|
Working copy now at: uuqppmxq 1f0b881a (empty) (no description set)
|
2023-08-08 03:11:59 +00:00
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
2023-07-24 09:38:16 +00:00
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("clone").join("file").exists());
|
|
|
|
assert!(test_env.env_root().join("clone").join(".git").exists());
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
"{:?}",
|
|
|
|
git_repo.head().expect("Repo head should be set").name()
|
|
|
|
);
|
|
|
|
|
|
|
|
let jj_git_repo = git2::Repository::open(test_env.env_root().join("clone"))
|
|
|
|
.expect("Could not open clone repo");
|
|
|
|
assert_eq!(
|
|
|
|
jj_git_repo
|
|
|
|
.head()
|
|
|
|
.expect("Clone Repo HEAD should be set.")
|
|
|
|
.symbolic_target(),
|
|
|
|
git_repo
|
|
|
|
.head()
|
|
|
|
.expect("Repo HEAD should be set.")
|
|
|
|
.symbolic_target()
|
|
|
|
);
|
2023-08-12 08:26:46 +00:00
|
|
|
// ".jj" directory should be ignored at Git side.
|
2023-08-15 03:18:52 +00:00
|
|
|
#[allow(clippy::format_collect)]
|
2023-08-12 08:26:46 +00:00
|
|
|
let git_statuses: String = jj_git_repo
|
|
|
|
.statuses(None)
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|entry| format!("{:?} {}\n", entry.status(), entry.path().unwrap()))
|
|
|
|
.collect();
|
|
|
|
insta::assert_snapshot!(git_statuses, @r###"
|
2023-12-15 03:57:10 +00:00
|
|
|
Status(IGNORED) .jj/.gitignore
|
|
|
|
Status(IGNORED) .jj/repo/
|
|
|
|
Status(IGNORED) .jj/working_copy/
|
2023-08-12 08:26:46 +00:00
|
|
|
"###);
|
2023-07-24 09:38:16 +00:00
|
|
|
|
2024-08-21 19:59:15 +00:00
|
|
|
// The old default bookmark "master" shouldn't exist.
|
2023-10-16 01:17:51 +00:00
|
|
|
insta::assert_snapshot!(
|
2024-08-21 19:59:15 +00:00
|
|
|
get_bookmark_output(&test_env, &test_env.env_root().join("clone")), @r###"
|
2023-08-12 06:51:37 +00:00
|
|
|
main: mzyxwzks 9f01a0e0 message
|
2023-10-18 06:12:17 +00:00
|
|
|
@git: mzyxwzks 9f01a0e0 message
|
|
|
|
@origin: mzyxwzks 9f01a0e0 message
|
2023-08-12 06:51:37 +00:00
|
|
|
"###);
|
|
|
|
|
2023-07-24 09:38:16 +00:00
|
|
|
// Subsequent fetch should just work even if the source path was relative
|
2023-10-10 11:59:18 +00:00
|
|
|
let (stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(&test_env.env_root().join("clone"), &["git", "fetch"]);
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-07-24 09:38:16 +00:00
|
|
|
Nothing changed.
|
|
|
|
"###);
|
|
|
|
|
2023-08-09 02:36:02 +00:00
|
|
|
// Failed clone should clean up the destination directory
|
|
|
|
std::fs::create_dir(test_env.env_root().join("bad")).unwrap();
|
|
|
|
let assert = test_env
|
|
|
|
.jj_cmd(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--colocate", "bad", "failed"],
|
|
|
|
)
|
|
|
|
.assert()
|
|
|
|
.code(1);
|
2024-02-02 07:55:24 +00:00
|
|
|
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
|
|
|
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
2023-08-09 02:36:02 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-10-10 11:07:06 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/failed"
|
2023-12-15 03:57:10 +00:00
|
|
|
Error: could not find repository at '$TEST_ENV/bad'; class=Repository (6)
|
2023-08-09 02:36:02 +00:00
|
|
|
"###);
|
2023-08-09 03:27:48 +00:00
|
|
|
assert!(!test_env.env_root().join("failed").exists());
|
2023-08-09 02:36:02 +00:00
|
|
|
|
|
|
|
// Failed clone shouldn't remove the existing destination directory
|
|
|
|
std::fs::create_dir(test_env.env_root().join("failed")).unwrap();
|
|
|
|
let assert = test_env
|
|
|
|
.jj_cmd(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--colocate", "bad", "failed"],
|
|
|
|
)
|
|
|
|
.assert()
|
|
|
|
.code(1);
|
2024-02-02 07:55:24 +00:00
|
|
|
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
|
|
|
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
2023-10-10 11:07:06 +00:00
|
|
|
insta::assert_snapshot!(stdout, @"");
|
2023-08-09 02:36:02 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2023-10-10 11:07:06 +00:00
|
|
|
Fetching into new repo in "$TEST_ENV/failed"
|
2023-12-15 03:57:10 +00:00
|
|
|
Error: could not find repository at '$TEST_ENV/bad'; class=Repository (6)
|
2023-08-09 02:36:02 +00:00
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("failed").exists());
|
2023-08-09 03:27:48 +00:00
|
|
|
assert!(!test_env.env_root().join("failed").join(".git").exists());
|
2023-08-09 02:36:02 +00:00
|
|
|
assert!(!test_env.env_root().join("failed").join(".jj").exists());
|
|
|
|
|
|
|
|
// Failed clone (if attempted) shouldn't remove the existing workspace
|
|
|
|
let stderr = test_env.jj_cmd_failure(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--colocate", "bad", "clone"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
|
|
|
assert!(test_env.env_root().join("clone").join(".git").exists());
|
|
|
|
assert!(test_env.env_root().join("clone").join(".jj").exists());
|
|
|
|
|
2023-07-24 09:38:16 +00:00
|
|
|
// Try cloning into an existing workspace
|
|
|
|
let stderr = test_env.jj_cmd_failure(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "clone", "--colocate"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
|
|
|
|
|
|
|
// Try cloning into an existing file
|
|
|
|
std::fs::write(test_env.env_root().join("file"), "contents").unwrap();
|
|
|
|
let stderr = test_env.jj_cmd_failure(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "file", "--colocate"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
|
|
|
|
|
|
|
// Try cloning into non-empty, non-workspace directory
|
|
|
|
std::fs::remove_dir_all(test_env.env_root().join("clone").join(".jj")).unwrap();
|
|
|
|
let stderr = test_env.jj_cmd_failure(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "clone", "--colocate"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Error: Destination path exists and is not an empty directory
|
|
|
|
"###);
|
2024-07-07 13:19:47 +00:00
|
|
|
|
|
|
|
// Clone into a nested path
|
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(
|
|
|
|
test_env.env_root(),
|
|
|
|
&[
|
|
|
|
"git",
|
|
|
|
"clone",
|
|
|
|
"source",
|
|
|
|
"nested/path/to/repo",
|
|
|
|
"--colocate",
|
|
|
|
],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/nested/path/to/repo"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: main@origin [new] tracked
|
2024-07-07 13:19:47 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
Working copy now at: vzqnnsmr 9407107f (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
2023-07-24 09:38:16 +00:00
|
|
|
}
|
2023-10-16 01:17:51 +00:00
|
|
|
|
|
|
|
#[test]
|
2024-08-21 19:59:15 +00:00
|
|
|
fn test_git_clone_remote_default_bookmark() {
|
2023-10-16 01:17:51 +00:00
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
2024-08-21 19:59:15 +00:00
|
|
|
// Create non-default bookmark in remote
|
2023-10-16 01:17:51 +00:00
|
|
|
let oid = git_repo
|
|
|
|
.find_reference("refs/heads/main")
|
|
|
|
.unwrap()
|
|
|
|
.target()
|
|
|
|
.unwrap();
|
|
|
|
git_repo
|
|
|
|
.reference("refs/heads/feature1", oid, false, "")
|
|
|
|
.unwrap();
|
|
|
|
|
2024-10-30 04:36:31 +00:00
|
|
|
// All fetched bookmarks will be imported if auto-local-bookmark is on
|
|
|
|
test_env.add_config("git.auto-local-bookmark = true");
|
2023-10-16 01:17:51 +00:00
|
|
|
let (_stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone1"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone1"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: feature1@origin [new] tracked
|
|
|
|
bookmark: main@origin [new] tracked
|
2024-03-04 10:52:48 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
2023-10-16 01:17:51 +00:00
|
|
|
Working copy now at: sqpuoqvx cad212e1 (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 feature1 main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
|
|
|
insta::assert_snapshot!(
|
2024-08-21 19:59:15 +00:00
|
|
|
get_bookmark_output(&test_env, &test_env.env_root().join("clone1")), @r###"
|
2023-10-16 01:17:51 +00:00
|
|
|
feature1: mzyxwzks 9f01a0e0 message
|
|
|
|
@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
main: mzyxwzks 9f01a0e0 message
|
|
|
|
@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
"###);
|
|
|
|
|
2024-08-21 19:59:15 +00:00
|
|
|
// "trunk()" alias should be set to default bookmark "main"
|
2024-03-04 10:52:48 +00:00
|
|
|
let stdout = test_env.jj_cmd_success(
|
|
|
|
&test_env.env_root().join("clone1"),
|
|
|
|
&["config", "list", "--repo", "revset-aliases.'trunk()'"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stdout, @r###"
|
|
|
|
revset-aliases.'trunk()' = "main@origin"
|
|
|
|
"###);
|
|
|
|
|
2024-10-30 04:36:31 +00:00
|
|
|
// Only the default bookmark will be imported if auto-local-bookmark is off
|
|
|
|
test_env.add_config("git.auto-local-bookmark = false");
|
2023-10-16 01:17:51 +00:00
|
|
|
let (_stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone2"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone2"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: feature1@origin [new] untracked
|
|
|
|
bookmark: main@origin [new] untracked
|
2024-03-04 10:52:48 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
Working copy now at: rzvqmyuk cc8a5041 (empty) (no description set)
|
2023-10-16 01:17:51 +00:00
|
|
|
Parent commit : mzyxwzks 9f01a0e0 feature1@origin main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
|
|
|
insta::assert_snapshot!(
|
2024-08-21 19:59:15 +00:00
|
|
|
get_bookmark_output(&test_env, &test_env.env_root().join("clone2")), @r###"
|
2023-10-16 01:17:51 +00:00
|
|
|
feature1@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
main: mzyxwzks 9f01a0e0 message
|
|
|
|
@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
"###);
|
2024-03-04 10:52:48 +00:00
|
|
|
|
2024-08-21 19:59:15 +00:00
|
|
|
// Change the default bookmark in remote
|
2024-03-04 10:52:48 +00:00
|
|
|
git_repo.set_head("refs/heads/feature1").unwrap();
|
|
|
|
let (_stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone3"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone3"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: feature1@origin [new] untracked
|
|
|
|
bookmark: main@origin [new] untracked
|
2024-03-04 10:52:48 +00:00
|
|
|
Setting the revset alias "trunk()" to "feature1@origin"
|
|
|
|
Working copy now at: nppvrztz b8a8a17b (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 feature1 main@origin | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"###);
|
|
|
|
insta::assert_snapshot!(
|
2024-08-21 19:59:15 +00:00
|
|
|
get_bookmark_output(&test_env, &test_env.env_root().join("clone2")), @r###"
|
2024-03-04 10:52:48 +00:00
|
|
|
feature1@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
main: mzyxwzks 9f01a0e0 message
|
|
|
|
@origin: mzyxwzks 9f01a0e0 message
|
|
|
|
"###);
|
|
|
|
|
2024-08-21 19:59:15 +00:00
|
|
|
// "trunk()" alias should be set to new default bookmark "feature1"
|
2024-03-04 10:52:48 +00:00
|
|
|
let stdout = test_env.jj_cmd_success(
|
|
|
|
&test_env.env_root().join("clone3"),
|
|
|
|
&["config", "list", "--repo", "revset-aliases.'trunk()'"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stdout, @r###"
|
|
|
|
revset-aliases.'trunk()' = "feature1@origin"
|
|
|
|
"###);
|
2023-10-16 01:17:51 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 14:06:49 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_ignore_working_copy() {
|
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
|
|
|
|
// Should not update working-copy files
|
|
|
|
let (_stdout, stderr) = test_env.jj_cmd_ok(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--ignore-working-copy", "source", "clone"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
2024-09-11 16:11:50 +00:00
|
|
|
bookmark: main@origin [new] untracked
|
2024-07-22 14:06:49 +00:00
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
"###);
|
|
|
|
let clone_path = test_env.env_root().join("clone");
|
|
|
|
|
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(&clone_path, &["status", "--ignore-working-copy"]);
|
|
|
|
insta::assert_snapshot!(stdout, @r###"
|
|
|
|
The working copy is clean
|
|
|
|
Working copy : sqpuoqvx cad212e1 (empty) (no description set)
|
|
|
|
Parent commit: mzyxwzks 9f01a0e0 main | message
|
|
|
|
"###);
|
|
|
|
insta::assert_snapshot!(stderr, @"");
|
|
|
|
|
|
|
|
// TODO: Correct, but might be better to check out the root commit?
|
|
|
|
let stderr = test_env.jj_cmd_failure(&clone_path, &["status"]);
|
2024-10-07 02:00:37 +00:00
|
|
|
insta::assert_snapshot!(stderr, @r##"
|
|
|
|
Error: The working copy is stale (not updated since operation eac759b9ab75).
|
2024-07-22 14:06:49 +00:00
|
|
|
Hint: Run `jj workspace update-stale` to update it.
|
2024-09-05 05:38:56 +00:00
|
|
|
See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy for more information.
|
2024-10-07 02:00:37 +00:00
|
|
|
"##);
|
2024-07-22 14:06:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_git_clone_at_operation() {
|
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
|
2024-07-23 04:50:03 +00:00
|
|
|
let stderr = test_env.jj_cmd_cli_error(
|
2024-07-22 14:06:49 +00:00
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--at-op=@-", "source", "clone"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r###"
|
2024-07-23 04:50:03 +00:00
|
|
|
Error: --at-op is not respected
|
2024-07-22 14:06:49 +00:00
|
|
|
"###);
|
|
|
|
}
|
|
|
|
|
2024-09-12 19:20:46 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_with_remote_name() {
|
|
|
|
let test_env = TestEnvironment::default();
|
2024-10-30 04:36:31 +00:00
|
|
|
test_env.add_config("git.auto-local-bookmark = true");
|
2024-09-12 19:20:46 +00:00
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
|
|
|
|
// Clone with relative source path and a non-default remote name
|
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "source", "clone", "--remote", "upstream"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
|
|
|
bookmark: main@upstream [new] tracked
|
|
|
|
Setting the revset alias "trunk()" to "main@upstream"
|
|
|
|
Working copy now at: sqpuoqvx cad212e1 (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"#);
|
|
|
|
}
|
|
|
|
|
2024-10-10 08:34:07 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_trunk_deleted() {
|
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
let clone_path = test_env.env_root().join("clone");
|
|
|
|
|
|
|
|
let (stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
|
|
|
insta::assert_snapshot!(stdout, @"");
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
|
|
|
bookmark: main@origin [new] untracked
|
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
Working copy now at: sqpuoqvx cad212e1 (empty) (no description set)
|
|
|
|
Parent commit : mzyxwzks 9f01a0e0 main | message
|
|
|
|
Added 1 files, modified 0 files, removed 0 files
|
|
|
|
"#);
|
|
|
|
|
|
|
|
test_env.jj_cmd_ok(&clone_path, &["bookmark", "forget", "main"]);
|
|
|
|
let (stdout, stderr) = test_env.jj_cmd_ok(&clone_path, &["log"]);
|
|
|
|
insta::assert_snapshot!(stdout, @r#"
|
|
|
|
@ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 cad212e1
|
|
|
|
│ (empty) (no description set)
|
|
|
|
○ mzyxwzks some.one@example.com 1970-01-01 11:00:00 9f01a0e0
|
|
|
|
│ message
|
|
|
|
◆ zzzzzzzz root() 00000000
|
|
|
|
"#);
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Warning: Failed to resolve `revset-aliases.trunk()`: Revision "main@origin" doesn't exist
|
|
|
|
Hint: Use `jj config edit --repo` to adjust the `trunk()` alias.
|
|
|
|
"#);
|
|
|
|
}
|
|
|
|
|
2024-09-12 19:47:49 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_with_depth() {
|
|
|
|
let test_env = TestEnvironment::default();
|
2024-10-30 04:36:31 +00:00
|
|
|
test_env.add_config("git.auto-local-bookmark = true");
|
2024-09-12 19:47:49 +00:00
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
|
|
|
|
// local transport does not support shallow clones so we just test that the
|
|
|
|
// depth arg is passed on here
|
|
|
|
let stderr = test_env.jj_cmd_failure(
|
|
|
|
test_env.env_root(),
|
|
|
|
&["git", "clone", "--depth", "1", "source", "clone"],
|
|
|
|
);
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
|
|
|
Error: shallow fetch is not supported by the local transport; class=Net (12)
|
|
|
|
"#);
|
|
|
|
}
|
|
|
|
|
2024-11-08 07:12:17 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_invalid_immutable_heads() {
|
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
set_up_non_empty_git_repo(&git_repo);
|
|
|
|
|
|
|
|
test_env.add_config("revset-aliases.'immutable_heads()' = 'unknown'");
|
|
|
|
// Suppress lengthy warnings in commit summary template
|
|
|
|
test_env.add_config("revsets.short-prefixes = ''");
|
|
|
|
|
|
|
|
// The error shouldn't be counted as an immutable working-copy commit. It
|
|
|
|
// should be reported.
|
|
|
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
|
|
|
bookmark: main@origin [new] untracked
|
|
|
|
Config error: Invalid `revset-aliases.immutable_heads()`
|
|
|
|
Caused by: Revision "unknown" doesn't exist
|
|
|
|
For help, see https://martinvonz.github.io/jj/latest/config/.
|
|
|
|
"#);
|
|
|
|
}
|
|
|
|
|
local_working_copy: do not create file or write in directory named .jj or .git
I originally considered adding deny-list-based implementation, but the Windows
compatibility rules are super confusing and I don't have a machine to find out
possible aliases. This patch instead adds directory equivalence tests.
In order to test file entity equivalence, we first need to create a file or
directory of the requested name. It's harmless to create an empty .jj or .git
directory, but materializing .git file or symlink can temporarily set up RCE
situation. That's why new empty file is created to test the path validity. We
might want to add some optimization for safe names (e.g. ASCII, not contain
"git" or "jj", not contain "~", etc.)
That being said, I'm not pretty sure if .git/.jj in sub directory must be
checked. It's not safe to cd into the directory and run "jj", but the same
thing can be said to other tools such as "cargo". Perhaps, our minimum
requirement is to protect our metadata (= the root .jj and .git) directories.
Despite the crate name (and internal use of std::fs::File),
same_file::is_same_file() can test equivalence of directories. This is
documented and tested, so I've removed my custom implementation, which was
slightly simpler but lacks Windows support.
2024-10-22 10:20:54 +00:00
|
|
|
#[test]
|
|
|
|
fn test_git_clone_malformed() {
|
|
|
|
let test_env = TestEnvironment::default();
|
|
|
|
let git_repo_path = test_env.env_root().join("source");
|
|
|
|
let git_repo = git2::Repository::init(git_repo_path).unwrap();
|
|
|
|
let clone_path = test_env.env_root().join("clone");
|
|
|
|
// libgit2 doesn't allow to create a malformed repo containing ".git", etc.,
|
|
|
|
// but we can insert ".jj" entry.
|
|
|
|
set_up_git_repo_with_file(&git_repo, ".jj");
|
|
|
|
|
|
|
|
// TODO: Perhaps, this should be a user error, not an internal error.
|
|
|
|
let stderr =
|
|
|
|
test_env.jj_cmd_internal_error(test_env.env_root(), &["git", "clone", "source", "clone"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Fetching into new repo in "$TEST_ENV/clone"
|
|
|
|
bookmark: main@origin [new] untracked
|
|
|
|
Setting the revset alias "trunk()" to "main@origin"
|
|
|
|
Internal error: Failed to check out commit 039a1eae03465fd3be0fbad87c9ca97303742677
|
|
|
|
Caused by: Reserved path component .jj in $TEST_ENV/clone/.jj
|
|
|
|
"#);
|
|
|
|
|
|
|
|
// The cloned workspace isn't usable.
|
|
|
|
let stderr = test_env.jj_cmd_failure(&clone_path, &["status"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r##"
|
|
|
|
Error: The working copy is stale (not updated since operation 4a8ddda0ff63).
|
|
|
|
Hint: Run `jj workspace update-stale` to update it.
|
|
|
|
See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy for more information.
|
|
|
|
"##);
|
|
|
|
|
|
|
|
// The error can be somehow recovered.
|
|
|
|
// TODO: add an update-stale flag to reset the working-copy?
|
|
|
|
let stderr = test_env.jj_cmd_internal_error(&clone_path, &["workspace", "update-stale"]);
|
|
|
|
insta::assert_snapshot!(stderr, @r#"
|
|
|
|
Internal error: Failed to check out commit 039a1eae03465fd3be0fbad87c9ca97303742677
|
|
|
|
Caused by: Reserved path component .jj in $TEST_ENV/clone/.jj
|
|
|
|
"#);
|
|
|
|
let (_stdout, stderr) =
|
|
|
|
test_env.jj_cmd_ok(&clone_path, &["new", "root()", "--ignore-working-copy"]);
|
|
|
|
insta::assert_snapshot!(stderr, @"");
|
|
|
|
let stdout = test_env.jj_cmd_success(&clone_path, &["status"]);
|
|
|
|
insta::assert_snapshot!(stdout, @r#"
|
|
|
|
The working copy is clean
|
|
|
|
Working copy : zsuskuln f652c321 (empty) (no description set)
|
|
|
|
Parent commit: zzzzzzzz 00000000 (empty) (no description set)
|
|
|
|
"#);
|
|
|
|
}
|
|
|
|
|
2024-08-21 19:59:15 +00:00
|
|
|
fn get_bookmark_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
|
|
|
|
test_env.jj_cmd_success(repo_path, &["bookmark", "list", "--all-remotes"])
|
2023-10-16 01:17:51 +00:00
|
|
|
}
|