forked from mirrors/jj
commands: Support the option of colocating a git repo with a jj repo
This adds the new --colocate flag to `jj git clone`. ``` jj git clone --colocate https://github.com/foo/bar ``` is effectively equivalent to: ``` git clone https://github.com/foo/bar cd bar jj init --git-repo=. ```
This commit is contained in:
parent
9c8d8b73b8
commit
56e6233f9e
3 changed files with 122 additions and 4 deletions
|
@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
* `jj log` output is now topologically grouped.
|
||||
[#242](https://github.com/martinvonz/jj/issues/242)
|
||||
|
||||
* `jj git clone` now supports the `--colocate` flag to create the git repo
|
||||
in the same directory as the jj repo.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
## [0.8.0] - 2023-07-09
|
||||
|
|
|
@ -115,6 +115,9 @@ pub struct GitCloneArgs {
|
|||
/// The directory to write the Jujutsu repo to
|
||||
#[arg(value_hint = clap::ValueHint::DirPath)]
|
||||
destination: Option<String>,
|
||||
/// Whether or not to colocate the Jujutsu repo with the git repo
|
||||
#[arg(long)]
|
||||
colocate: bool,
|
||||
}
|
||||
|
||||
/// Push to a Git remote
|
||||
|
@ -410,11 +413,11 @@ fn cmd_git_clone(
|
|||
fs::create_dir(&wc_path).unwrap();
|
||||
}
|
||||
|
||||
let clone_result = do_git_clone(ui, command, &source, &wc_path);
|
||||
let canonical_wc_path: PathBuf = wc_path.canonicalize().unwrap();
|
||||
let clone_result = do_git_clone(ui, command, args.colocate, &source, &canonical_wc_path);
|
||||
if clone_result.is_err() {
|
||||
// Canonicalize because fs::remove_dir_all() doesn't seem to like e.g.
|
||||
// `/some/path/.`
|
||||
let canonical_wc_path = wc_path.canonicalize().unwrap();
|
||||
if let Err(err) = fs::remove_dir_all(canonical_wc_path.join(".jj")).and_then(|_| {
|
||||
if !wc_path_existed {
|
||||
fs::remove_dir(&canonical_wc_path)
|
||||
|
@ -452,10 +455,16 @@ fn cmd_git_clone(
|
|||
fn do_git_clone(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
colocate: bool,
|
||||
source: &str,
|
||||
wc_path: &Path,
|
||||
) -> Result<(WorkspaceCommandHelper, Option<String>), CommandError> {
|
||||
let (workspace, repo) = Workspace::init_internal_git(command.settings(), wc_path)?;
|
||||
let (workspace, repo) = if colocate {
|
||||
let git_repo = git2::Repository::init(wc_path)?;
|
||||
Workspace::init_external_git(command.settings(), wc_path, git_repo.path())?
|
||||
} else {
|
||||
Workspace::init_internal_git(command.settings(), wc_path)?
|
||||
};
|
||||
let git_repo = get_git_repo(repo.store())?;
|
||||
writeln!(ui, r#"Fetching into new repo in "{}""#, wc_path.display())?;
|
||||
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
|
||||
|
|
|
@ -29,7 +29,7 @@ fn test_git_clone() {
|
|||
Nothing changed.
|
||||
"###);
|
||||
|
||||
// Clone a non-empty repo
|
||||
// Set-up a non-empty repo
|
||||
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();
|
||||
|
@ -87,3 +87,109 @@ fn test_git_clone() {
|
|||
Error: Destination path exists and is not an empty directory
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_clone_colocate() {
|
||||
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();
|
||||
|
||||
// Clone an empty repo
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
test_env.env_root(),
|
||||
&["git", "clone", "source", "empty", "--colocate"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Fetching into new repo in "$TEST_ENV/empty"
|
||||
Nothing changed.
|
||||
"###);
|
||||
|
||||
// Set-up a non-empty repo
|
||||
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
|
||||
.insert("file", file_oid, git2::FileMode::Blob.into())
|
||||
.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();
|
||||
|
||||
// Clone with relative source path
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
test_env.env_root(),
|
||||
&["git", "clone", "source", "clone", "--colocate"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
Fetching into new repo in "$TEST_ENV/clone"
|
||||
Working copy now at: 1f0b881a057d (no description set)
|
||||
Parent commit : 9f01a0e04879 message
|
||||
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()
|
||||
);
|
||||
|
||||
// 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", "--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
|
||||
"###);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue