Skip to content

Commit

Permalink
Refactor init command in preparation to move git related portions.
Browse files Browse the repository at this point in the history
* Add Workspace::create_workspace_root() which is needed by all init
  functionality regardless of the backend.
* Move canonicalization of the external git repo path into the Workspace::init_git_external().
  This keeps necessary code together.
* Create a git_init() function in cli/src/commands/init.rs where all git related work is done.
  This function will be moved to cli/src/commands/git.rs in a subsequent PR.
* Add a new variant of WorkspaceInitError for reporting path not found errors. The user error
  string is written to pass existing tests.
  • Loading branch information
essiene committed Jan 11, 2024
1 parent 1c82ccf commit bc2eb71
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 60 deletions.
3 changes: 3 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ impl From<WorkspaceInitError> for CommandError {
WorkspaceInitError::Path(err) => {
CommandError::InternalError(format!("Failed to access the repository: {err}"))
}
WorkspaceInitError::PathNotFound(path) => {
user_error(format!("{} doesn't exist", path.display()))
}
WorkspaceInitError::Backend(err) => {
user_error(format!("Failed to access the repository: {err}"))
}
Expand Down
123 changes: 64 additions & 59 deletions cli/src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io;
use std::io::Write;
use std::{fs, io};
use std::path::Path;

use clap::ArgGroup;
use itertools::Itertools as _;
Expand Down Expand Up @@ -53,64 +54,11 @@ pub(crate) fn cmd_init(
command: &CommandHelper,
args: &InitArgs,
) -> Result<(), CommandError> {
let wc_path = command.cwd().join(&args.destination);
match fs::create_dir(&wc_path) {
Ok(()) => {}
Err(_) if wc_path.is_dir() => {}
Err(e) => return Err(user_error(format!("Failed to create workspace: {e}"))),
}
let wc_path = wc_path
.canonicalize()
.map_err(|e| user_error(format!("Failed to create workspace: {e}")))?; // raced?
let cwd = command.cwd().canonicalize().unwrap();
let relative_wc_path = file_util::relative_path(&cwd, &wc_path);

if let Some(git_store_str) = &args.git_repo {
let mut git_store_path = command.cwd().join(git_store_str);
git_store_path = git_store_path
.canonicalize()
.map_err(|_| user_error(format!("{} doesn't exist", git_store_path.display())))?;
if !git_store_path.ends_with(".git") {
git_store_path.push(".git");
// Undo if .git doesn't exist - likely a bare repo.
if !git_store_path.exists() {
git_store_path.pop();
}
}
let (workspace, repo) =
Workspace::init_external_git(command.settings(), &wc_path, &git_store_path)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
git::maybe_add_gitignore(&workspace_command)?;
workspace_command.snapshot(ui)?;
if !workspace_command.working_copy_shared_with_git() {
let mut tx = workspace_command.start_transaction();
let stats = jj_lib::git::import_some_refs(
tx.mut_repo(),
&command.settings().git_settings(),
|ref_name| !jj_lib::git::is_reserved_git_remote_ref(ref_name),
)?;
print_git_import_stats(ui, &stats)?;
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 refs")?;
}
}
print_trackable_remote_branches(ui, workspace_command.repo().view())?;
} else if args.git {
if wc_path.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()
),
));
}
let wc_path = Workspace::create_workspace_root(&command.cwd().join(&args.destination))
.map_err(|e| user_error(format!("Failed to create workspace: {e}")))?;

Workspace::init_internal_git(command.settings(), &wc_path)?;
if args.git || args.git_repo.is_some() {
git_init(ui, command, &wc_path, args.git, &args.git_repo)?;
} else {
if !command.settings().allow_native_backend() {
return Err(user_error_with_hint(
Expand All @@ -120,8 +68,10 @@ Set `ui.allow-init-native` to allow initializing a repo with the native backend.
));
}
Workspace::init_local(command.settings(), &wc_path)?;
};
}

let cwd = command.cwd().canonicalize().unwrap();
let relative_wc_path = file_util::relative_path(&cwd, &wc_path);
writeln!(
ui.stderr(),
"Initialized repo in \"{}\"",
Expand Down Expand Up @@ -163,3 +113,58 @@ fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> {
)?;
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,
use_git_backend: bool,
git_repo: &Option<String>,
) -> Result<(), CommandError> {
let cwd = command.cwd().canonicalize().unwrap();
let relative_wc_path = file_util::relative_path(&cwd, workspace_root);

if git_repo.is_none() {
if use_git_backend {
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)?;
}
} else if let Some(git_store_str) = &git_repo {
let git_store_path = command.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)?;
workspace_command.snapshot(ui)?;
if !workspace_command.working_copy_shared_with_git() {
let mut tx = workspace_command.start_transaction();
let stats = jj_lib::git::import_some_refs(
tx.mut_repo(),
&command.settings().git_settings(),
|ref_name| !jj_lib::git::is_reserved_git_remote_ref(ref_name),
)?;
print_git_import_stats(ui, &stats)?;
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 refs")?;
}
}
print_trackable_remote_branches(ui, workspace_command.repo().view())?;
}

Ok(())
}
26 changes: 25 additions & 1 deletion lib/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub enum WorkspaceInitError {
DestinationExists(PathBuf),
#[error("Repo path could not be interpreted as Unicode text")]
NonUnicodePath,
#[error("Path {0} does not exist")]
PathNotFound(PathBuf),
#[error(transparent)]
CheckOutCommit(#[from] CheckOutCommitError),
#[error(transparent)]
Expand Down Expand Up @@ -140,6 +142,17 @@ impl Workspace {
})
}

pub fn create_workspace_root(workspace_root: &Path) -> std::io::Result<PathBuf> {
match fs::create_dir(workspace_root) {
Ok(()) => {}
Err(_) if workspace_root.is_dir() => {}
Err(e) => return Err(e),
}

// if it fails, race condition?
workspace_root.canonicalize()
}

pub fn init_local(
user_settings: &UserSettings,
workspace_root: &Path,
Expand Down Expand Up @@ -194,6 +207,17 @@ impl Workspace {
workspace_root: &Path,
git_repo_path: &Path,
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
let mut git_repo_path = git_repo_path
.canonicalize()
.map_err(|_| WorkspaceInitError::PathNotFound(git_repo_path.to_path_buf()))?;
if !git_repo_path.ends_with(".git") {
git_repo_path.push(".git");

if !git_repo_path.exists() {
git_repo_path.pop();
}
}

let backend_initializer =
|settings: &UserSettings, store_path: &Path| -> Result<Box<dyn Backend>, _> {
// If the git repo is inside the workspace, use a relative path to it so the
Expand All @@ -203,7 +227,7 @@ impl Workspace {
// Workspace::new(), but it's not yet here.
let store_relative_git_repo_path = match (
workspace_root.canonicalize(),
canonicalize_git_repo_path(git_repo_path),
canonicalize_git_repo_path(&git_repo_path),
) {
(Ok(workspace_root), Ok(git_repo_path))
if git_repo_path.starts_with(&workspace_root) =>
Expand Down

0 comments on commit bc2eb71

Please sign in to comment.