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

Colocated workspaces, minimal #4644

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
16 changes: 13 additions & 3 deletions cli/examples/custom-backend/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ fn create_store_factories() -> StoreFactories {
// must match `Backend::name()`.
store_factories.add_backend(
"jit",
Box::new(|settings, store_path| Ok(Box::new(JitBackend::load(settings, store_path)?))),
Box::new(|settings, store_path, workspace_root| {
Ok(Box::new(JitBackend::load(
settings,
store_path,
workspace_root,
)?))
}),
);
store_factories
}
Expand Down Expand Up @@ -105,8 +111,12 @@ impl JitBackend {
Ok(JitBackend { inner })
}

fn load(settings: &UserSettings, store_path: &Path) -> Result<Self, BackendLoadError> {
let inner = GitBackend::load(settings, store_path)?;
fn load(
settings: &UserSettings,
store_path: &Path,
workspace_root: Option<&Path>,
) -> Result<Self, BackendLoadError> {
let inner = GitBackend::load(settings, store_path, workspace_root)?;
Ok(JitBackend { inner })
}
}
Expand Down
8 changes: 6 additions & 2 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ 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_factory(&self) -> Result<&dyn WorkingCopyFactory, CommandError> {
let loader = self.workspace_loader()?;

Expand Down Expand Up @@ -831,7 +835,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 @@ -874,7 +878,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
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::LoadNewlyCreated(err) => internal_error(err),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion cli/src/commands/git/init.rs
Original file line number Diff line number Diff line change
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, &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)?;
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/workspace/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub fn cmd_workspace_add(
repo_path,
repo,
working_copy_factory,
command.get_store_factories(),
workspace_id,
)?;
writeln!(
Expand Down
72 changes: 61 additions & 11 deletions cli/src/git_util.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this warning could be very noisy, so we should probably move it behind the future config flag.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same thought. Want me to add a flag in this PR? git.suppress-colocation-warning or something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it can be verbose, I would remove the warning at all. Maybe the warning can be inserted to initialization/debug commands?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want me to add a flag in this PR? git.suppress-colocation-warning or something?

No, I think landing it as is or with Yuya's suggestion is fine, but it should definitely move behind the auto-colocate workspace flag in the follow-up

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We currently hard error if you try to jj git init in an existing Git repo. To accomplish that you have to manually move files around. git init in an existing JJ repo is what this is most likely to warn about.
  2. I think we should have a config flag, and I think the flag should also control whether jj reads/writes HEAD and the index after mutating jj/git operations. If you want jj to stop touching the HEAD and the index for a while, because you urgently need to push to production using git and some script using jj keeps messing with everything (e.g. your prompt uses jj but doesn't ignore wc), then I think you should be able to do that by temporarily disabling the flag.
  3. I would call it git.sync-with-colocated or similar. I think that flag should be separate from git.auto-colocate because of the use case I just described.
  4. It's also very simple to implement, all the HEAD mutation stuff is already contingent on colocation being discovered, so just don't discover.
    • I will move MaybeColocatedGitRepo to git_backend.rs because that's indeed where it will need to end up.
  5. However I would probably want to disable these warnings specifically for .git directories / symlinks for now, just in case this PR makes it to 0.24 and we're still debating the name. Worktree warnings can stay because nobody is doing that yet, and I have written so many tests for it anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we would want a flag to disable import/export in colocated workspace, but that's a separate issue, so let's not discuss here.

Regarding the warning, suppose we have some concerns, I would remove it at least from this PR. I personally don't think we'll need these warnings, and there are no bug report about that iirc.

Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,72 @@ pub fn get_git_repo(store: &Store) -> Result<git2::Repository, CommandError> {
}
}

pub fn is_colocated_git_workspace(workspace: &Workspace, repo: &ReadonlyRepo) -> bool {
pub fn is_colocated_git_workspace(
ui: Option<&Ui>,
workspace: &Workspace,
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() {

if git_backend.is_colocated() {
return true;
}
// 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()

if let Some(ui) = ui {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to add a separate function to print warning instead of passing Option<&Ui>?

let workspace_dot_git = workspace.workspace_root().join(".git");
let Ok(meta) = workspace_dot_git.symlink_metadata() else {
return git_backend.is_colocated();
};
// Check symlink first as is_dir follows symlinks
if meta.is_symlink() {
let target = std::fs::read_link(&workspace_dot_git)
.unwrap_or_else(|_| Path::new("<could not read link>").to_path_buf());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: PathBuf::from or .into()?

writeln!(
ui.warning_default(),
"Workspace has a .git symlink (pointing to {}), that isn't pointing to JJ's git \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: JJ -> jj (lowercase) ?? (I'm not sure which one is the standard)

repo",
target.display()
)
.ok();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need to suppress io::Error

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you do instead? If we can't write a warning to stderr, we aren't going to be able to print a panic message either. So I figured there was no point panicking. All other instances of writeln!(ui.xxx()) in the codebase either use ? or if not in a ->Result function .ok(), so I followed that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant was just make this function return Result<(), CommandError>. All callers can propagate the error.

} else if meta.is_dir() {
writeln!(
ui.warning_default(),
"Workspace has a .git directory that is not managed by JJ"
)
.ok();
} else if let Ok(_worktree_repo) = gix::open(workspace.workspace_root()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: .is_ok()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I had it like this because there are more details you can add to the warning if you don't have to make the tests work on windows with its myriad path formats, and I had to remove them.

writeln!(
ui.warning_default(),
"Workspace is also a Git worktree that is not managed by JJ",
)
.ok();
} else {
let gitfile_contents = std::fs::read_to_string(&workspace_dot_git)
.unwrap_or_else(|_| "<could not read .git file>".to_string());
let gitdir = gitfile_contents
.strip_prefix("gitdir: ")
.unwrap_or(&gitfile_contents)
.trim();
writeln!(ui.warning_default(), "Workspace is a broken Git worktree").ok();
writeln!(ui.warning_no_heading(), "The .git file points at: {gitdir}").ok();
// work_dir may return Some even if colocated = false, because we may be in some
// secondary workspace, and we may have just opened the colocated primary
// workspace with a .git directory.
let git_repo = git_backend.git_repo();
let repair_dir = git_repo.work_dir().unwrap_or(git_repo.common_dir());
writeln!(
ui.hint_default(),
"If this is meant to be a colocated JJ workspace, you may like to try `git -C {} \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: s/JJ/jj/g, orient yourself on the previous uses in the project.

worktree repair`",
repair_dir.display()
)
.ok();
}
}

false
}

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