diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 731987d61..46a486462 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -61,6 +61,7 @@ use jj_lib::signing::SignInitError; use jj_lib::str_util::{StringPattern, StringPatternParseError}; use jj_lib::transaction::Transaction; use jj_lib::tree::TreeMergeError; +use jj_lib::view::View; use jj_lib::working_copy::{ CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, SnapshotOptions, WorkingCopy, WorkingCopyFactory, WorkingCopyStateError, @@ -2069,6 +2070,40 @@ Discard the conflicting changes with `jj restore --from {}`.", Ok(()) } +pub fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> { + let remote_branch_names = view + .branches() + .filter(|(_, branch_target)| branch_target.local_target.is_present()) + .flat_map(|(name, branch_target)| { + branch_target + .remote_refs + .into_iter() + .filter(|&(_, remote_ref)| !remote_ref.is_tracking()) + .map(move |(remote, _)| format!("{name}@{remote}")) + }) + .collect_vec(); + if remote_branch_names.is_empty() { + return Ok(()); + } + + writeln!( + ui.hint(), + "The following remote branches aren't associated with the existing local branches:" + )?; + let mut formatter = ui.stderr_formatter(); + for full_name in &remote_branch_names { + write!(formatter, " ")?; + writeln!(formatter.labeled("branch"), "{full_name}")?; + } + drop(formatter); + writeln!( + ui.hint(), + "Hint: Run `jj branch track {names}` to keep local branches updated on future pulls.", + names = remote_branch_names.join(" "), + )?; + Ok(()) +} + pub fn parse_string_pattern(src: &str) -> Result { if let Some((kind, pat)) = src.split_once(':') { StringPattern::from_str_kind(pat, kind) diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index a8d364832..235414f09 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -21,6 +21,7 @@ use std::{fmt, fs, io}; use clap::{ArgGroup, Subcommand}; use itertools::Itertools; use jj_lib::backend::TreeValue; +use jj_lib::file_util; use jj_lib::git::{ self, parse_gitmodules, GitBranchPushTargets, GitFetchError, GitFetchStats, GitPushError, }; @@ -39,9 +40,10 @@ use jj_lib::workspace::Workspace; use maplit::hashset; use crate::cli_util::{ - parse_string_pattern, resolve_multiple_nonempty_revsets, short_change_hash, short_commit_hash, - user_error, user_error_with_hint, user_error_with_hint_opt, user_error_with_message, - CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper, + parse_string_pattern, print_trackable_remote_branches, resolve_multiple_nonempty_revsets, + short_change_hash, short_commit_hash, user_error, user_error_with_hint, + user_error_with_hint_opt, user_error_with_message, CommandError, CommandHelper, RevisionArg, + WorkspaceCommandHelper, }; use crate::git_util::{ get_git_repo, print_failed_git_export, print_git_import_stats, with_remote_git_callbacks, @@ -311,6 +313,54 @@ fn cmd_git_remote_list( Ok(()) } +pub fn git_init( + ui: &mut Ui, + command: &CommandHelper, + workspace_root: &Path, + git_repo: Option<&str>, +) -> Result<(), CommandError> { + let cwd = command.cwd().canonicalize().unwrap(); + let relative_wc_path = file_util::relative_path(&cwd, workspace_root); + + if let Some(git_store_str) = git_repo { + let git_store_path = cwd.join(git_store_str); + let (workspace, repo) = + Workspace::init_external_git(command.settings(), workspace_root, &git_store_path)?; + let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?; + maybe_add_gitignore(&workspace_command)?; + // Import refs first so all the reachable commits are indexed in + // chronological order. + workspace_command.import_git_refs(ui)?; + 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 workspace_root.join(".git").exists() { + 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."#, + relative_wc_path.display() + ), + )); + } + + Workspace::init_internal_git(command.settings(), workspace_root)?; + } + + Ok(()) +} + #[tracing::instrument(skip(ui, command))] fn cmd_git_fetch( ui: &mut Ui, diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs index 764145605..9fd7a36e4 100644 --- a/cli/src/commands/init.rs +++ b/cli/src/commands/init.rs @@ -12,15 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::io; use std::io::Write; -use std::path::Path; use clap::ArgGroup; -use itertools::Itertools as _; use jj_lib::file_util; -use jj_lib::repo::Repo; -use jj_lib::view::View; use jj_lib::workspace::Workspace; use tracing::instrument; @@ -59,7 +54,7 @@ pub(crate) fn cmd_init( .map_err(|e| user_error_with_message("Failed to create workspace", e))?; if args.git || args.git_repo.is_some() { - git_init(ui, command, &wc_path, args.git_repo.as_deref())?; + git::git_init(ui, command, &wc_path, args.git_repo.as_deref())?; } else { if !command.settings().allow_native_backend() { return Err(user_error_with_hint( @@ -79,86 +74,3 @@ Set `ui.allow-init-native` to allow initializing a repo with the native backend. )?; Ok(()) } - -fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> { - let remote_branch_names = view - .branches() - .filter(|(_, branch_target)| branch_target.local_target.is_present()) - .flat_map(|(name, branch_target)| { - branch_target - .remote_refs - .into_iter() - .filter(|&(_, remote_ref)| !remote_ref.is_tracking()) - .map(move |(remote, _)| format!("{name}@{remote}")) - }) - .collect_vec(); - if remote_branch_names.is_empty() { - return Ok(()); - } - - writeln!( - ui.hint(), - "The following remote branches aren't associated with the existing local branches:" - )?; - let mut formatter = ui.stderr_formatter(); - for full_name in &remote_branch_names { - write!(formatter, " ")?; - writeln!(formatter.labeled("branch"), "{full_name}")?; - } - drop(formatter); - writeln!( - ui.hint(), - "Hint: Run `jj branch track {names}` to keep local branches updated on future pulls.", - names = remote_branch_names.join(" "), - )?; - Ok(()) -} - -// TODO(essiene): Move to cli/src/commands/git.rs for `jj git init` -fn git_init( - ui: &mut Ui, - command: &CommandHelper, - workspace_root: &Path, - git_repo: Option<&str>, -) -> Result<(), CommandError> { - let cwd = command.cwd().canonicalize().unwrap(); - let relative_wc_path = file_util::relative_path(&cwd, workspace_root); - - if let Some(git_store_str) = git_repo { - let git_store_path = cwd.join(git_store_str); - let (workspace, repo) = - Workspace::init_external_git(command.settings(), workspace_root, &git_store_path)?; - let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?; - git::maybe_add_gitignore(&workspace_command)?; - // Import refs first so all the reachable commits are indexed in - // chronological order. - workspace_command.import_git_refs(ui)?; - 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 workspace_root.join(".git").exists() { - 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."#, - relative_wc_path.display() - ), - )); - } - - Workspace::init_internal_git(command.settings(), workspace_root)?; - } - - Ok(()) -}