ok/jj
1
0
Fork 0
forked from mirrors/jj

cli: unblock "jj git init --colocate" in existing Git repo directory

I'm not sure what's the conclusion in #2747, but I don't think there is a
disagreement on allowing --colocate to import existing Git repo.
This commit is contained in:
Yuya Nishihara 2024-02-25 20:39:42 +09:00
parent 356037379a
commit 8d0414549b
9 changed files with 90 additions and 60 deletions

View file

@ -66,6 +66,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* When creating a new workspace, the sparse patterns are now copied over from * When creating a new workspace, the sparse patterns are now copied over from
the current workspace. the current workspace.
* `jj git init --colocate` can now import an existing Git repository. This is
equivalent to `jj git init --git-repo=.`.
* `jj git fetch` now automatically prints new remote branches and tags by default. * `jj git fetch` now automatically prints new remote branches and tags by default.
* `--verbose/-v` is now `--debug` (no short option since it's not intended to be used often) * `--verbose/-v` is now `--debug` (no short option since it's not intended to be used often)

View file

@ -1829,7 +1829,7 @@ fn map_workspace_load_error(err: WorkspaceLoadError, workspace_path: Option<&str
message, message,
"It looks like this is a git repo. You can create a jj repo backed by it by \ "It looks like this is a git repo. You can create a jj repo backed by it by \
running this: running this:
jj git init --git-repo=.", jj git init --colocate",
) )
} else { } else {
user_error(message) user_error(message)

View file

@ -125,12 +125,9 @@ pub struct GitInitArgs {
/// `git` repo, allowing the use of both `jj` and `git` commands /// `git` repo, allowing the use of both `jj` and `git` commands
/// in the same directory. /// in the same directory.
/// ///
/// This is done by placing the backing git repo into a `.git` directory /// This is done by placing the backing git repo into a `.git` directory in
/// in the root of the `jj` repo along with the `.jj` directory. /// the root of the `jj` repo along with the `.jj` directory. If the `.git`
/// /// directory already exists, all the existing commits will be imported.
/// This option is only valid when creating new repos. To
/// reuse an existing `.git` directory in an existing git
/// repo, see the `--git-repo` param below.
/// ///
/// This option is mutually exclusive with `--git-repo`. /// This option is mutually exclusive with `--git-repo`.
#[arg(long, conflicts_with = "git_repo")] #[arg(long, conflicts_with = "git_repo")]
@ -379,52 +376,67 @@ pub fn git_init(
colocate: bool, colocate: bool,
git_repo: Option<&str>, git_repo: Option<&str>,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
if colocate { #[derive(Clone, Debug)]
let (workspace, repo) = Workspace::init_colocated_git(command.settings(), workspace_root)?; enum GitInitMode {
let workspace_command = command.for_loaded_repo(ui, workspace, repo)?; Colocate,
maybe_add_gitignore(&workspace_command)?; External(PathBuf),
return Ok(()); Internal,
} }
if let Some(git_store_str) = git_repo { let colocated_git_repo_path = workspace_root.join(".git");
let git_store_path = command.cwd().join(git_store_str); let init_mode = if colocate {
let (workspace, repo) = if colocated_git_repo_path.exists() {
Workspace::init_external_git(command.settings(), workspace_root, &git_store_path)?; GitInitMode::External(colocated_git_repo_path)
// Import refs first so all the reachable commits are indexed in } else {
// chronological order. GitInitMode::Colocate
let colocated = is_colocated_git_workspace(&workspace, &repo);
let repo = init_git_refs(ui, command, repo, colocated)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
workspace_command.maybe_snapshot(ui)?;
if !workspace_command.working_copy_shared_with_git() {
let mut tx = workspace_command.start_transaction();
jj_lib::git::import_head(tx.mut_repo())?;
if let Some(git_head_id) = tx.mut_repo().view().git_head().as_normal().cloned() {
let git_head_commit = tx.mut_repo().store().get_commit(&git_head_id)?;
tx.check_out(&git_head_commit)?;
}
if tx.mut_repo().has_changes() {
tx.finish(ui, "import git head")?;
}
} }
print_trackable_remote_branches(ui, workspace_command.repo().view())?; } else if let Some(path_str) = git_repo {
GitInitMode::External(command.cwd().join(path_str))
} else { } else {
if workspace_root.join(".git").exists() { if colocated_git_repo_path.exists() {
let cwd = command.cwd().canonicalize().unwrap();
let relative_wc_path = file_util::relative_path(&cwd, workspace_root);
return Err(user_error_with_hint( return Err(user_error_with_hint(
"Did not create a jj repo because there is an existing Git repo in this directory.", "Did not create a jj repo because there is an existing Git repo in this directory.",
format!( "To create a repo backed by the existing Git repo, run `jj git init --colocate` \
r#"To create a repo backed by the existing Git repo, run `jj git init --git-repo={}` instead."#, instead.",
relative_wc_path.display()
),
)); ));
} }
GitInitMode::Internal
};
Workspace::init_internal_git(command.settings(), workspace_root)?; match &init_mode {
GitInitMode::Colocate => {
let (workspace, repo) =
Workspace::init_colocated_git(command.settings(), workspace_root)?;
let workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
}
GitInitMode::External(git_repo_path) => {
let (workspace, repo) =
Workspace::init_external_git(command.settings(), workspace_root, git_repo_path)?;
// Import refs first so all the reachable commits are indexed in
// chronological order.
let colocated = is_colocated_git_workspace(&workspace, &repo);
let repo = init_git_refs(ui, command, repo, colocated)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
workspace_command.maybe_snapshot(ui)?;
if !workspace_command.working_copy_shared_with_git() {
let mut tx = workspace_command.start_transaction();
jj_lib::git::import_head(tx.mut_repo())?;
if let Some(git_head_id) = tx.mut_repo().view().git_head().as_normal().cloned() {
let git_head_commit = tx.mut_repo().store().get_commit(&git_head_id)?;
tx.check_out(&git_head_commit)?;
}
if tx.mut_repo().has_changes() {
tx.finish(ui, "import git head")?;
}
}
print_trackable_remote_branches(ui, workspace_command.repo().view())?;
}
GitInitMode::Internal => {
Workspace::init_internal_git(command.settings(), workspace_root)?;
}
} }
Ok(()) Ok(())
} }

View file

@ -542,12 +542,29 @@ fn test_git_init_colocated_via_flag_git_dir_exists() {
let workspace_root = test_env.env_root().join("repo"); let workspace_root = test_env.env_root().join("repo");
init_git_repo(&workspace_root, false); init_git_repo(&workspace_root, false);
let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "init", "--colocate"]); let (stdout, stderr) =
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "--colocate", "repo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###" insta::assert_snapshot!(stderr, @r###"
Error: Failed to access the repository Done importing changes from the underlying Git repo.
Caused by: Initialized repo in "repo"
1: Failed to initialize git repository "###);
2: Refusing to initialize the existing '$TEST_ENV/repo/.git' directory
// 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_ok(&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)
~
"###); "###);
} }

View file

@ -210,7 +210,7 @@ fn test_no_workspace_directory() {
insta::assert_snapshot!(stderr, @r###" insta::assert_snapshot!(stderr, @r###"
Error: There is no jj repo in "." Error: There is no jj repo in "."
Hint: It looks like this is a git repo. You can create a jj repo backed by it by running this: Hint: It looks like this is a git repo. You can create a jj repo backed by it by running this:
jj git init --git-repo=. jj git init --colocate
"###); "###);
} }

View file

@ -497,7 +497,7 @@ fn test_init_git_internal_must_be_colocated() {
let stderr = test_env.jj_cmd_failure(&workspace_root, &["init", "--git"]); let stderr = test_env.jj_cmd_failure(&workspace_root, &["init", "--git"]);
insta::assert_snapshot!(stderr, @r###" insta::assert_snapshot!(stderr, @r###"
Error: Did not create a jj repo because there is an existing Git repo in this directory. Error: Did not create a jj repo because there is an existing Git repo in this directory.
Hint: To create a repo backed by the existing Git repo, run `jj git init --git-repo=.` instead. Hint: To create a repo backed by the existing Git repo, run `jj git init --colocate` instead.
"###); "###);
} }

View file

@ -100,8 +100,7 @@ parent.
<tbody> <tbody>
<tr> <tr>
<td>Create a new repo</td> <td>Create a new repo</td>
<td><code>jj init --git</code> (without <code>--git</code>, you get a <td><code>jj git init [--colocate]</code></td>
native Jujutsu repo, which is slow and whose format will change)</td>
<td><code>git init</code></td> <td><code>git init</code></td>
</tr> </tr>
<tr> <tr>

View file

@ -96,8 +96,8 @@ into a directory by the same name.
## Co-located Jujutsu/Git repos ## Co-located Jujutsu/Git repos
A "co-located" Jujutsu repo is a hybrid Jujutsu/Git repo. These can be created A "co-located" Jujutsu repo is a hybrid Jujutsu/Git repo. These can be created
if you initialize the Jujutsu repo in an existing Git repo by running `jj init if you initialize the Jujutsu repo in an existing Git repo by running `jj git
--git-repo=.` or with `jj git clone --colocate`. The Git repo and the Jujutsu init --colocate` or with `jj git clone --colocate`. The Git repo and the Jujutsu
repo then share the same working copy. Jujutsu will import and export from and repo then share the same working copy. Jujutsu will import and export from and
to the Git repo on every `jj` command automatically. to the Git repo on every `jj` command automatically.

View file

@ -66,12 +66,11 @@ changes.
## Working in a Git co-located repository ## Working in a Git co-located repository
After doing `jj init --git-repo=.`, Git will be in After doing `jj git init --colocate`, Git will be in a [detached HEAD
a [detached HEAD state][detached], which is unusual, as Git mainly works with state][detached], which is unusual, as Git mainly works with branches. In a
branches. In a co-located repository, every `jj` command will automatically co-located repository, every `jj` command will automatically synchronize
synchronize Jujutsu's view of the repo with Git's view. For example, Jujutsu's view of the repo with Git's view. For example, `jj commit` updates the
`jj commit` updates the HEAD of the Git repository, enabling an incremental HEAD of the Git repository, enabling an incremental migration.
migration.
```shell ```shell
$ nvim docs/tutorial.md $ nvim docs/tutorial.md