git: add .jj/.gitignore when creating colocated repo

This replaces our existing mechanism of adding `/.jj/` to
`.git/info/exclude` by adding `*` to `.jj/.gitignore`, as suggested by
@ppwwyyxx. That simplifies the code quite a bit, and it avoids the
problem with `.git/info/exclude` not existing (it apparently doesn't
exist when the user uses
https://git-scm.com/docs/git-init#_template_directory).

Closes #2385.
This commit is contained in:
Martin von Zweigbergk 2023-10-16 22:19:04 -07:00 committed by Martin von Zweigbergk
parent 2afc28d706
commit 872265a220
3 changed files with 18 additions and 41 deletions

View file

@ -1,5 +1,5 @@
use std::collections::HashSet;
use std::io::{Read, Seek as _, SeekFrom, Write};
use std::io::{Read, Write};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
@ -210,43 +210,19 @@ fn map_git_error(err: git2::Error) -> CommandError {
}
}
pub fn add_to_git_exclude(ui: &Ui, git_repo: &git2::Repository) -> Result<(), CommandError> {
let exclude_file_path = git_repo.path().join("info").join("exclude");
if exclude_file_path.exists() {
match fs::OpenOptions::new()
.read(true)
.write(true)
.open(&exclude_file_path)
{
Ok(mut exclude_file) => {
let mut buf = vec![];
exclude_file.read_to_end(&mut buf)?;
let pattern = b"\n/.jj/\n";
if !buf.windows(pattern.len()).any(|window| window == pattern) {
exclude_file.seek(SeekFrom::End(0))?;
if !buf.ends_with(b"\n") {
exclude_file.write_all(b"\n")?;
}
exclude_file.write_all(b"/.jj/\n")?;
}
}
Err(err) => {
writeln!(
ui.error(),
"Failed to add `.jj/` to {}: {}",
exclude_file_path.to_string_lossy(),
err
)?;
}
}
pub fn maybe_add_gitignore(workspace_command: &WorkspaceCommandHelper) -> Result<(), CommandError> {
if workspace_command.working_copy_shared_with_git() {
std::fs::write(
workspace_command
.workspace_root()
.join(".jj")
.join(".gitignore"),
"/*\n",
)
.map_err(|e| user_error(format!("Failed to write .jj/.gitignore file: {e}")))
} else {
writeln!(
ui.error(),
"Failed to add `.jj/` to {} because it doesn't exist",
exclude_file_path.to_string_lossy()
)?;
Ok(())
}
Ok(())
}
fn cmd_git_remote_add(
@ -525,7 +501,6 @@ fn do_git_clone(
) -> Result<(WorkspaceCommandHelper, GitFetchStats), CommandError> {
let (workspace, repo) = if colocate {
let git_repo = git2::Repository::init(wc_path)?;
add_to_git_exclude(ui, &git_repo)?;
Workspace::init_external_git(command.settings(), wc_path, git_repo.path())?
} else {
Workspace::init_internal_git(command.settings(), wc_path)?
@ -537,6 +512,7 @@ fn do_git_clone(
wc_path.display()
)?;
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
git_repo.remote(remote_name, source).unwrap();
let mut fetch_tx = workspace_command.start_transaction("fetch from git remote into empty repo");

View file

@ -1213,10 +1213,9 @@ fn cmd_init(ui: &mut Ui, command: &CommandHelper, args: &InitArgs) -> Result<(),
.unwrap()
.open_git_repo()?;
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() {
git::add_to_git_exclude(ui, &git_repo)?;
} else {
if !workspace_command.working_copy_shared_with_git() {
let mut tx = workspace_command.start_transaction("import git refs");
let stats = jj_lib::git::import_some_refs(
tx.mut_repo(),

View file

@ -224,7 +224,9 @@ fn test_git_clone_colocate() {
.map(|entry| format!("{:?} {}\n", entry.status(), entry.path().unwrap()))
.collect();
insta::assert_snapshot!(git_statuses, @r###"
IGNORED .jj/
IGNORED .jj/.gitignore
IGNORED .jj/repo/
IGNORED .jj/working_copy/
"###);
// The old default branch "master" shouldn't exist.