diff --git a/CHANGELOG.md b/CHANGELOG.md index e18ab7ad5c..12c8b8947f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `jj workspace root` was aliased to `jj root`, for ease of discoverability +* `jj git` now has an `init` command that initializes a git backed repo. + `jj init --git` and `jj init --git-repo` are now deprecated and + will be removed in the near future. + + ### Fixed bugs * Fixed snapshots of symlinks in `gitignore`-d directory. diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index 235414f092..2be7893f67 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -58,6 +58,7 @@ use crate::ui::Ui; pub enum GitCommand { #[command(subcommand)] Remote(GitRemoteCommand), + Init(GitInitArgs), Fetch(GitFetchArgs), Clone(GitCloneArgs), Push(GitPushArgs), @@ -107,6 +108,49 @@ pub struct GitRemoteRenameArgs { #[derive(clap::Args, Clone, Debug)] pub struct GitRemoteListArgs {} +/// Create a new Git backed repo. +#[derive(clap::Args, Clone, Debug)] +pub struct GitInitArgs { + /// The destination directory where the `jj` repo will be created. + /// If the directory does not exist, it will be created. + /// If no directory is diven, the current directory is used. + /// + /// By default the `git` repo is under `$destination/.jj/repo/store/git` + #[arg(default_value = ".", value_hint = clap::ValueHint::DirPath)] + destination: String, + + /// Specifies that the `jj` repo should also be a valid + /// `git` repo, allowing the use of both `jj` and `git` commands + /// in the same directory. + /// + /// This is done by placing the backing git repo into a `.git` folder + /// in the root of the Jujutsu repo along with the `.jj` folder. + /// + /// 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`. + #[arg(long, conflicts_with = "git_repo")] + colocated: bool, + + /// Specifies a path to an **existing** git repository to be + /// used as the backing git repo for the newly created `jj` repo. + /// + /// If the specified `--git-repo` path happens to be the same as + /// the `jj` repo path (both .jj and .git directories are in the + /// same working directory), then both `jj` and `git` commands + /// will work on the repo, with the exception that changes from `jj` + /// will not be auto-exported to the git repo. + /// + /// Auto-exporting from `jj` to `git` is only enabled for new repos. + /// See `--colocated` above. + /// + /// This option is mutually exclusive with `--colocated`. + #[arg(long, conflicts_with = "colocated", value_hint = clap::ValueHint::DirPath)] + git_repo: Option, +} + /// Fetch from a Git remote #[derive(clap::Args, Clone, Debug)] pub struct GitFetchArgs { @@ -317,11 +361,17 @@ pub fn git_init( ui: &mut Ui, command: &CommandHelper, workspace_root: &Path, + colocated: bool, git_repo: Option<&str>, ) -> Result<(), CommandError> { let cwd = command.cwd().canonicalize().unwrap(); let relative_wc_path = file_util::relative_path(&cwd, workspace_root); + if colocated { + Workspace::init_colocated_git(command.settings(), workspace_root)?; + return Ok(()); + } + if let Some(git_store_str) = git_repo { let git_store_path = cwd.join(git_store_str); let (workspace, repo) = @@ -349,7 +399,7 @@ pub fn git_init( return Err(user_error_with_hint( "Did not create a jj repo because there is an existing Git repo in this directory.", format!( - r#"To create a repo backed by the existing Git repo, run `jj init --git-repo={}` instead."#, + r#"To create a repo backed by the existing Git repo, run `jj git init --git-repo={}` instead."#, relative_wc_path.display() ), )); @@ -361,6 +411,26 @@ pub fn git_init( Ok(()) } +fn cmd_git_init( + ui: &mut Ui, + command: &CommandHelper, + args: &GitInitArgs, +) -> Result<(), CommandError> { + let cwd = command.cwd().canonicalize().unwrap(); + let wc_path = cwd.join(&args.destination); + let wc_path = file_util::create_or_reuse_dir(&wc_path) + .and_then(|_| wc_path.canonicalize()) + .map_err(|e| user_error_with_message("Failed to create workspace", e))?; + + git_init( + ui, + command, + &wc_path, + args.colocated, + args.git_repo.as_deref(), + ) +} + #[tracing::instrument(skip(ui, command))] fn cmd_git_fetch( ui: &mut Ui, @@ -1097,6 +1167,7 @@ pub fn cmd_git( subcommand: &GitCommand, ) -> Result<(), CommandError> { match subcommand { + GitCommand::Init(args) => cmd_git_init(ui, command, args), GitCommand::Fetch(args) => cmd_git_fetch(ui, command, args), GitCommand::Clone(args) => cmd_git_clone(ui, command, args), GitCommand::Remote(GitRemoteCommand::Add(args)) => cmd_git_remote_add(ui, command, args), diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs index 9fd7a36e44..84cb6b02ee 100644 --- a/cli/src/commands/init.rs +++ b/cli/src/commands/init.rs @@ -33,9 +33,11 @@ pub(crate) struct InitArgs { /// The destination directory #[arg(default_value = ".", value_hint = clap::ValueHint::DirPath)] destination: String, + /// DEPRECATED: Use `jj git init` /// Use the Git backend, creating a jj repo backed by a Git repo #[arg(long)] git: bool, + /// DEPRECATED: Use `jj git init` /// Path to a git repo the jj repo will be backed by #[arg(long, value_hint = clap::ValueHint::DirPath)] git_repo: Option, @@ -53,13 +55,21 @@ pub(crate) fn cmd_init( .and_then(|_| wc_path.canonicalize()) .map_err(|e| user_error_with_message("Failed to create workspace", e))?; + // Preserve existing behaviour where `jj init` is not able to create + // a collocated repo. + let collocated = false; if args.git || args.git_repo.is_some() { - git::git_init(ui, command, &wc_path, args.git_repo.as_deref())?; + git::git_init(ui, command, &wc_path, collocated, args.git_repo.as_deref())?; + writeln!( + ui.warning(), + "warning: `--git` and `--git-repo` are deprecated. +Use `jj git init` to create git repos" + )?; } else { if !command.settings().allow_native_backend() { return Err(user_error_with_hint( "The native backend is disallowed by default.", - "Did you mean to pass `--git`? + "Did you mean to call `jj git init`? Set `ui.allow-init-native` to allow initializing a repo with the native backend.", )); } diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index 34e15a0fbc..61e7207e83 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -42,6 +42,7 @@ This document contains the help content for the `jj` command-line program. * [`jj git remote remove`↴](#jj-git-remote-remove) * [`jj git remote rename`↴](#jj-git-remote-rename) * [`jj git remote list`↴](#jj-git-remote-list) +* [`jj git init`↴](#jj-git-init) * [`jj git fetch`↴](#jj-git-fetch) * [`jj git clone`↴](#jj-git-clone) * [`jj git push`↴](#jj-git-push) @@ -764,6 +765,7 @@ For a comparison with Git, including a table of commands, see https://github.com ###### **Subcommands:** * `remote` — Manage Git remotes +* `init` — Create a new Git backed repo * `fetch` — Fetch from a Git remote * `clone` — Create a new repo backed by a clone of a Git repo * `push` — Push to a Git remote @@ -835,6 +837,28 @@ List Git remotes +## `jj git init` + +Create a new Git backed repo + +**Usage:** `jj git init [OPTIONS] [DESTINATION]` + +###### **Arguments:** + +* `` — The destination directory where the `jj` repo will be created. If the directory does not exist, it will be created. If no directory is diven, the current directory is used + + Default value: `.` + +###### **Options:** + +* `--colocated` — Specifies that the `jj` repo should also be a valid `git` repo, allowing the use of both `jj` and `git` commands in the same directory + + Possible values: `true`, `false` + +* `--git-repo ` — Specifies a path to an **existing** git repository to be used as the backing git repo for the newly created `jj` repo + + + ## `jj git fetch` Fetch from a Git remote @@ -937,11 +961,11 @@ If the given directory does not exist, it will be created. If no directory is gi ###### **Options:** -* `--git` — Use the Git backend, creating a jj repo backed by a Git repo +* `--git` — DEPRECATED: Use `jj git init` Use the Git backend, creating a jj repo backed by a Git repo Possible values: `true`, `false` -* `--git-repo ` — Path to a git repo the jj repo will be backed by +* `--git-repo ` — DEPRECATED: Use `jj git init` Path to a git repo the jj repo will be backed by diff --git a/cli/tests/test_init_command.rs b/cli/tests/test_init_command.rs index 4e994705bd..34a2aaa2d7 100644 --- a/cli/tests/test_init_command.rs +++ b/cli/tests/test_init_command.rs @@ -74,6 +74,8 @@ fn test_init_git_internal() { let (stdout, stderr) = test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "repo" "###); @@ -113,6 +115,8 @@ fn test_init_git_external(bare: bool) { Working copy now at: sqpuoqvx f6950fc1 (empty) (no description set) Parent commit : mwrttmos 8d698d4a my-branch | My commit message Added 1 files, modified 0 files, removed 0 files + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "repo" "###); } @@ -183,6 +187,8 @@ fn test_init_git_colocated() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); @@ -231,6 +237,8 @@ fn test_init_git_colocated_gitlink() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git"); @@ -267,6 +275,8 @@ fn test_init_git_colocated_symlink_directory() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git"); @@ -306,6 +316,8 @@ fn test_init_git_colocated_symlink_directory_without_bare_config() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git"); @@ -347,6 +359,8 @@ fn test_init_git_colocated_symlink_gitlink() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git"); @@ -407,6 +421,8 @@ fn test_init_git_colocated_imported_refs() { let (_stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["init", "--git-repo=."]); insta::assert_snapshot!(stderr, @r###" Done importing changes from the underlying Git repo. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(get_branch_output(&test_env, &local_path), @r###" @@ -428,6 +444,8 @@ fn test_init_git_colocated_imported_refs() { The following remote branches aren't associated with the existing local branches: local-remote@origin Hint: Run `jj branch track local-remote@origin` to keep local branches updated on future pulls. + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); insta::assert_snapshot!(get_branch_output(&test_env, &local_path), @r###" @@ -451,6 +469,8 @@ fn test_init_git_external_but_git_dir_exists() { ); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" + warning: `--git` and `--git-repo` are deprecated. + Use `jj git init` to create git repos Initialized repo in "." "###); @@ -479,7 +499,7 @@ fn test_init_git_internal_must_be_colocated() { let stderr = test_env.jj_cmd_failure(&workspace_root, &["init", "--git"]); insta::assert_snapshot!(stderr, @r###" 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 init --git-repo=.` instead. + Hint: To create a repo backed by the existing Git repo, run `jj git init --git-repo=.` instead. "###); } @@ -497,7 +517,7 @@ fn test_init_local_disallowed() { let stdout = test_env.jj_cmd_failure(test_env.env_root(), &["init", "repo"]); insta::assert_snapshot!(stdout, @r###" Error: The native backend is disallowed by default. - Hint: Did you mean to pass `--git`? + Hint: Did you mean to call `jj git init`? Set `ui.allow-init-native` to allow initializing a repo with the native backend. "###); }