Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workspace colocate 2 #4678

Draft
wants to merge 13 commits into
base: workspace-colocate-minimal
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 45 additions & 8 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ impl WorkspaceCommandHelper {
let op_summary_template_text = settings.config().get_string("templates.op_summary")?;
let may_update_working_copy =
loaded_at_head && !env.command.global_args().ignore_working_copy;
let working_copy_shared_with_git = is_colocated_git_workspace(&workspace, &repo);
let working_copy_shared_with_git = is_colocated_git_workspace(Some(ui), &workspace, &repo);
let helper = Self {
workspace,
user_repo: ReadonlyUserRepo::new(repo),
Expand Down Expand Up @@ -898,12 +898,12 @@ impl WorkspaceCommandHelper {
}

/// Snapshot the working copy if allowed, and import Git refs if the working
/// copy is collocated with Git.
/// copy is colocated with Git.
#[instrument(skip_all)]
pub fn maybe_snapshot(&mut self, ui: &Ui) -> Result<(), CommandError> {
if self.may_update_working_copy {
if self.working_copy_shared_with_git {
self.import_git_head(ui)?;
if let Some(git_repo) = self.open_colocated_git_repo_gix()? {
self.import_git_head(ui, &git_repo)?;
}
// Because the Git refs (except HEAD) aren't imported yet, the ref
// pointing to the new working-copy commit might not be exported.
Expand All @@ -925,11 +925,11 @@ impl WorkspaceCommandHelper {
/// working-copy state will be reset to point to the new Git HEAD. The
/// working-copy contents won't be updated.
#[instrument(skip_all)]
fn import_git_head(&mut self, ui: &Ui) -> Result<(), CommandError> {
fn import_git_head(&mut self, ui: &Ui, git_repo: &gix::Repository) -> Result<(), CommandError> {
assert!(self.may_update_working_copy);
let command = self.env.command.clone();
let mut tx = self.start_transaction();
git::import_head(tx.repo_mut())?;
git::import_head(tx.repo_mut(), git_repo)?;
if !tx.repo().has_changes() {
return Ok(());
}
Expand Down Expand Up @@ -1074,6 +1074,44 @@ impl WorkspaceCommandHelper {
self.working_copy_shared_with_git
}

pub fn open_colocated_git_repo_git2(&self) -> Result<Option<git2::Repository>, git2::Error> {
if self.working_copy_shared_with_git() {
git2::Repository::open(self.workspace_root()).map(Some)
} else {
Ok(None)
}
}

pub fn open_colocated_git_repo_gix(&self) -> Result<Option<gix::Repository>, CommandError> {
if self.working_copy_shared_with_git() {
let repo = gix::open(self.workspace_root()).map_err(|x| {
user_error(format!(
"Could not open git repository at {}: {x}",
self.workspace_root().display()
))
})?;
Ok(Some(repo))
} else {
Ok(None)
}
}

pub fn git_backend_repo(&self) -> Option<gix::Repository> {
self.git_backend().map(|backend| backend.git_repo())
}

pub fn git_either_colocated_or_backend(&self) -> Result<gix::Repository, CommandError> {
if let Some(colocated) = self.open_colocated_git_repo_gix()? {
Ok(colocated)
} else {
self.git_backend_repo().ok_or_else(|| {
internal_error(
"JJ repo is not backed by git, but JJ attempted to use the git backend",
)
})
}
}

pub fn format_file_path(&self, file: &RepoPath) -> String {
self.path_converter().format_file_path(file)
}
Expand Down Expand Up @@ -1748,8 +1786,7 @@ See https://martinvonz.github.io/jj/latest/working-copy/#stale-working-copy \
.map(|commit_id| tx.repo().store().get_commit(commit_id))
.transpose()?;

if self.working_copy_shared_with_git {
let git_repo = self.git_backend().unwrap().open_git_repo()?;
if let Some(git_repo) = self.open_colocated_git_repo_git2()? {
if let Some(wc_commit) = &maybe_new_wc_commit {
git::reset_head(tx.repo_mut(), &git_repo, wc_commit)?;
}
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::commands::git::map_git_error;
use crate::commands::git::maybe_add_gitignore;
use crate::config::write_config_value_to_file;
use crate::config::ConfigNamePathBuf;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::git_util::print_git_import_stats;
use crate::git_util::with_remote_git_callbacks;
use crate::ui::Ui;
Expand Down Expand Up @@ -204,7 +204,7 @@ fn do_git_clone(
} else {
Workspace::init_internal_git(command.settings(), wc_path)?
};
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
writeln!(
ui.status(),
r#"Fetching into new repo in "{}""#,
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError;
use crate::commands::git::get_single_remote;
use crate::commands::git::map_git_error;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::git_util::print_git_import_stats;
use crate::git_util::with_remote_git_callbacks;
use crate::ui::Ui;
Expand Down Expand Up @@ -60,7 +60,7 @@ pub fn cmd_git_fetch(
args: &GitFetchArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let git_repo = get_git_repo(workspace_command.repo().store())?;
let git_repo = get_git_backend_repo(workspace_command.repo().store())?;
let remotes = if args.all_remotes {
get_all_remotes(&git_repo)?
} else if args.remotes.is_empty() {
Expand Down
3 changes: 2 additions & 1 deletion cli/src/commands/git/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ pub fn cmd_git_import(
_args: &GitImportArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let git_repo = workspace_command.git_either_colocated_or_backend()?;
let mut tx = workspace_command.start_transaction();
// In non-colocated repo, HEAD@git will never be moved internally by jj.
// That's why cmd_git_export() doesn't export the HEAD ref.
git::import_head(tx.repo_mut())?;
git::import_head(tx.repo_mut(), &git_repo)?;
let stats = git::import_refs(tx.repo_mut(), &command.settings().git_settings())?;
print_git_import_stats(ui, tx.repo(), &stats, true)?;
tx.finish(ui, "import git refs")?;
Expand Down
13 changes: 9 additions & 4 deletions cli/src/commands/git/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::command_error::CommandError;
use crate::commands::git::maybe_add_gitignore;
use crate::config::write_config_value_to_file;
use crate::config::ConfigNamePathBuf;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::git_util::is_colocated_git_workspace;
use crate::git_util::print_failed_git_export;
use crate::git_util::print_git_import_stats;
Expand Down Expand Up @@ -167,15 +167,20 @@ pub fn do_init(
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);
// No UI, because we're about to call this function again in the workspace
// command helper, and warnings can be shown then.
let colocated = is_colocated_git_workspace(None, &workspace, &repo);
let repo = init_git_refs(ui, command, repo, colocated)?;
let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
workspace_command.maybe_snapshot(ui)?;
maybe_set_repository_level_trunk_alias(ui, &workspace_command)?;
if !workspace_command.working_copy_shared_with_git() {
let git_repo = workspace_command
.git_backend_repo()
.expect("Just initialized with the git backend");
let mut tx = workspace_command.start_transaction();
jj_lib::git::import_head(tx.repo_mut())?;
jj_lib::git::import_head(tx.repo_mut(), &git_repo)?;
if let Some(git_head_id) = tx.repo().view().git_head().as_normal().cloned() {
let git_head_commit = tx.repo().store().get_commit(&git_head_id)?;
tx.check_out(&git_head_commit)?;
Expand Down Expand Up @@ -237,7 +242,7 @@ pub fn maybe_set_repository_level_trunk_alias(
ui: &Ui,
workspace_command: &WorkspaceCommandHelper,
) -> Result<(), CommandError> {
let git_repo = get_git_repo(workspace_command.repo().store())?;
let git_repo = get_git_backend_repo(workspace_command.repo().store())?;
if let Ok(reference) = git_repo.find_reference("refs/remotes/origin/HEAD") {
if let Some(reference_name) = reference.symbolic_target() {
if let Some(RefName::RemoteBranch {
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use crate::command_error::CommandError;
use crate::commands::git::get_single_remote;
use crate::commands::git::map_git_error;
use crate::formatter::Formatter;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::git_util::with_remote_git_callbacks;
use crate::git_util::GitSidebandProgressMessageWriter;
use crate::ui::Ui;
Expand Down Expand Up @@ -143,7 +143,7 @@ pub fn cmd_git_push(
args: &GitPushArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let git_repo = get_git_repo(workspace_command.repo().store())?;
let git_repo = get_git_backend_repo(workspace_command.repo().store())?;

let remote = if let Some(name) = &args.remote {
name.clone()
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/remote/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use jj_lib::repo::Repo;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::ui::Ui;

/// Add a Git remote
Expand All @@ -36,7 +36,7 @@ pub fn cmd_git_remote_add(
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
git::add_remote(&git_repo, &args.remote, &args.url)?;
Ok(())
}
4 changes: 2 additions & 2 deletions cli/src/commands/git/remote/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use jj_lib::repo::Repo;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::ui::Ui;

/// List Git remotes
Expand All @@ -32,7 +32,7 @@ pub fn cmd_git_remote_list(
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
for remote_name in git_repo.remotes()?.iter().flatten() {
let remote = git_repo.find_remote(remote_name)?;
writeln!(
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/remote/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use jj_lib::repo::Repo;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::ui::Ui;

/// Remove a Git remote and forget its bookmarks
Expand All @@ -34,7 +34,7 @@ pub fn cmd_git_remote_remove(
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
let mut tx = workspace_command.start_transaction();
git::remove_remote(tx.repo_mut(), &git_repo, &args.remote)?;
if tx.repo().has_changes() {
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/remote/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use jj_lib::repo::Repo;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::ui::Ui;

/// Rename a Git remote
Expand All @@ -36,7 +36,7 @@ pub fn cmd_git_remote_rename(
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
let mut tx = workspace_command.start_transaction();
git::rename_remote(tx.repo_mut(), &git_repo, &args.old, &args.new)?;
if tx.repo().has_changes() {
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/git/remote/set_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use jj_lib::repo::Repo;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::git_util::get_git_backend_repo;
use crate::ui::Ui;

/// Set the URL of a Git remote
Expand All @@ -36,7 +36,7 @@ pub fn cmd_git_remote_set_url(
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let git_repo = get_git_backend_repo(repo.store())?;
git::set_remote_url(&git_repo, &args.remote, &args.url)?;
Ok(())
}
48 changes: 48 additions & 0 deletions cli/src/commands/workspace/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
// limitations under the License.

use std::fs;
use std::io::Write;

use itertools::Itertools;
use jj_lib::commit::CommitIteratorExt;
use jj_lib::file_util;
use jj_lib::file_util::IoResultExt;
use jj_lib::git::git_worktree_add;
use jj_lib::op_store::WorkspaceId;
use jj_lib::repo::Repo;
use jj_lib::rewrite::merge_commit_trees;
Expand All @@ -28,6 +30,7 @@ use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::internal_error_with_message;
use crate::command_error::user_error;
use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError;
use crate::ui::Ui;

Expand Down Expand Up @@ -73,6 +76,22 @@ pub struct WorkspaceAddArgs {
/// How to handle sparse patterns when creating a new workspace.
#[arg(long, value_enum, default_value_t = SparseInheritance::Copy)]
sparse_patterns: SparseInheritance,
/// Specifies that the new `jj` workspace should also be a valid `git`
/// worktree, allowing the use of both `jj` and `git` commands in the
/// same directory.
///
/// This has a similar effect to `jj git init --colocate`, but for new
/// workspaces. It works regardless of whether a repository was
/// initialised with `--colocate`.
///
/// This is implemented by adding a worktree to the backing git repo.
/// If you move a colocated workspace around on disk, you will need to
/// run `git worktree repair` in the workspace to fix up the paths.
/// You should also be able to run `git worktree list` to see which
/// workspaces are no longer where git thinks they are (they show up as
/// `[prunable]`)
#[arg(long, hide = true)]
colocate: bool,
}

#[instrument(skip_all)]
Expand All @@ -98,6 +117,32 @@ pub fn cmd_workspace_add(
.unwrap()
.to_string()
};

if let Some(git_backend) = old_workspace_command.git_backend() {
if args.colocate {
git_worktree_add(
git_backend.git_repo().common_dir(),
&destination_path,
&name,
)
.inspect_err(|_| {
// we failed to create a worktree, and the user is probably going to want to
// try again. Attempt to destroy the workspace for a clean slate.
// This is ok because we have already successfully run `create_dir`
// for destination_path, so there is nothing there.
fs::remove_dir_all(&destination_path).ok();
})
.map_err(|error| {
let msg = format!("Failed to add git worktree: {error}");
if let Some(hint) = error.hint() {
user_error_with_hint(msg, hint)
} else {
user_error(msg)
}
})?;
}
}

let workspace_id = WorkspaceId::new(name.clone());
let repo = old_workspace_command.repo();
if repo.view().get_wc_commit_id(&workspace_id).is_some() {
Expand Down Expand Up @@ -133,6 +178,8 @@ pub fn cmd_workspace_add(

let mut new_workspace_command = command.for_workable_repo(ui, new_workspace, repo)?;

crate::commands::git::maybe_add_gitignore(&new_workspace_command)?;

let sparsity = match args.sparse_patterns {
SparseInheritance::Full => None,
SparseInheritance::Empty => Some(vec![]),
Expand Down Expand Up @@ -194,5 +241,6 @@ pub fn cmd_workspace_add(
ui,
format!("create initial working-copy commit in workspace {name}"),
)?;

Ok(())
}
Loading
Loading