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

POC for GitBackend opening worktrees #4801

Closed
wants to merge 10 commits into from
Closed
3 changes: 2 additions & 1 deletion cli/examples/custom-backend/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ impl JitBackend {
}

fn load(settings: &UserSettings, store_path: &Path) -> Result<Self, BackendLoadError> {
let inner = GitBackend::load(settings, store_path)?;
let workspace_root = None;
let inner = GitBackend::load(settings, store_path, workspace_root)?;
Ok(JitBackend { inner })
}
}
Expand Down
27 changes: 23 additions & 4 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,14 @@ impl CommandHelper {
WorkspaceCommandHelper::new(ui, workspace, repo, env, self.is_at_head_operation())
}

pub fn get_store_factories(&self) -> &StoreFactories {
&self.data.store_factories
}

pub fn get_working_copy_factories(&self) -> &WorkingCopyFactories {
&self.data.working_copy_factories
}

pub fn get_working_copy_factory(&self) -> Result<&dyn WorkingCopyFactory, CommandError> {
let loader = self.workspace_loader()?;

Expand Down Expand Up @@ -855,7 +863,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), &repo);
let helper = Self {
workspace,
user_repo: ReadonlyUserRepo::new(repo),
Expand Down Expand Up @@ -898,7 +906,7 @@ 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 {
Expand Down Expand Up @@ -1074,6 +1082,18 @@ 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 git_backend_repo(&self) -> Option<gix::Repository> {
self.git_backend().map(|backend| backend.git_repo())
}

pub fn format_file_path(&self, file: &RepoPath) -> String {
self.path_converter().format_file_path(file)
}
Expand Down Expand Up @@ -1748,8 +1768,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
1 change: 1 addition & 0 deletions cli/src/command_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ impl From<WorkspaceInitError> for CommandError {
}
WorkspaceInitError::SignInit(err @ SignInitError::UnknownBackend(_)) => user_error(err),
WorkspaceInitError::SignInit(err) => internal_error(err),
WorkspaceInitError::Internal(err) => internal_error(err),
}
}
}
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
8 changes: 5 additions & 3 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,7 +167,9 @@ 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, &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)?;
Expand Down Expand Up @@ -237,7 +239,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(())
}
2 changes: 2 additions & 0 deletions cli/src/commands/workspace/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ pub fn cmd_workspace_add(
repo_path,
repo,
working_copy_factory,
command.get_store_factories(),
workspace_id,
)?;

writeln!(
ui.status(),
"Created workspace in \"{}\"",
Expand Down
34 changes: 20 additions & 14 deletions cli/src/git_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ use jj_lib::op_store::RemoteRef;
use jj_lib::repo::ReadonlyRepo;
use jj_lib::repo::Repo;
use jj_lib::store::Store;
use jj_lib::workspace::Workspace;
use unicode_width::UnicodeWidthStr;

use crate::command_error::user_error;
Expand All @@ -44,29 +43,36 @@ use crate::formatter::Formatter;
use crate::progress::Progress;
use crate::ui::Ui;

pub fn get_git_repo(store: &Store) -> Result<git2::Repository, CommandError> {
/// This opens a [git2::Repository] for the underlying [GitBackend], assuming
/// this store is git-backed.
///
/// If the JJ repo is colocated with the git repo, then this will often not be
/// what you want, because its HEAD will always be for the default workspace.
/// Most JJ commands that access the HEAD need the one for the current
/// workspace.
///
/// However, sometimes you don't care. For example, when you run `jj git fetch`.
pub fn get_git_backend_repo(store: &Store) -> Result<git2::Repository, CommandError> {
match store.backend_impl().downcast_ref::<GitBackend>() {
None => Err(user_error("The repo is not backed by a git repo")),
Some(git_backend) => Ok(git_backend.open_git_repo()?),
}
}

pub fn is_colocated_git_workspace(workspace: &Workspace, repo: &ReadonlyRepo) -> bool {
pub fn is_colocated_git_workspace(ui: Option<&Ui>, repo: &ReadonlyRepo) -> bool {
let Some(git_backend) = repo.store().backend_impl().downcast_ref::<GitBackend>() else {
return false;
};
let Some(git_workdir) = git_backend.git_workdir() else {
return false; // Bare repository
};
if git_workdir == workspace.workspace_root() {
return true;

let colocation_type = git_backend.colocation_type();

if let Some((ui, warning)) = ui.zip(colocation_type.warning()) {
writeln!(ui.warning_default(), "{warning}").ok();
if let Some(hint) = warning.hint() {
writeln!(ui.hint_default(), "{hint}").ok();
}
}
// Colocated workspace should have ".git" directory, file, or symlink. Compare
// its parent as the git_workdir might be resolved from the real ".git" path.
let Ok(dot_git_path) = workspace.workspace_root().join(".git").canonicalize() else {
return false;
};
git_workdir.canonicalize().ok().as_deref() == dot_git_path.parent()
colocation_type.is_colocated()
}

fn terminal_get_username(ui: &Ui, url: &str) -> Option<String> {
Expand Down
Loading
Loading