Match branches with worktrees always, even with slashes

This commit is contained in:
2022-06-17 01:44:47 +02:00
parent d18c49982e
commit c56765ce26
3 changed files with 83 additions and 27 deletions

View File

@@ -502,25 +502,9 @@ fn main() {
None => None, None => None,
}; };
let mut name: &str = &action_args.name;
let subdirectory;
let split = name.split_once('/');
match split {
None => subdirectory = None,
Some(split) => {
if split.0.is_empty() || split.1.is_empty() {
print_error("Worktree name cannot start or end with a slash");
process::exit(1);
} else {
(subdirectory, name) = (Some(Path::new(split.0)), split.1);
}
}
}
match worktree::add_worktree( match worktree::add_worktree(
&cwd, &cwd,
name, &action_args.name,
subdirectory,
track, track,
action_args.no_track, action_args.no_track,
) { ) {

View File

@@ -222,7 +222,7 @@ fn sync_repo(root_path: &Path, repo: &repo::Repo, init_worktree: bool) -> Result
if newly_created && repo.worktree_setup && init_worktree { if newly_created && repo.worktree_setup && init_worktree {
match repo_handle.default_branch() { match repo_handle.default_branch() {
Ok(branch) => { Ok(branch) => {
worktree::add_worktree(&repo_path, &branch.name()?, None, None, false)?; worktree::add_worktree(&repo_path, &branch.name()?, None, false)?;
} }
Err(_error) => print_repo_error( Err(_error) => print_repo_error(
&repo.name, &repo.name,

View File

@@ -21,10 +21,17 @@ pub const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
pub fn add_worktree( pub fn add_worktree(
directory: &Path, directory: &Path,
name: &str, name: &str,
subdirectory: Option<&Path>,
track: Option<(&str, &str)>, track: Option<(&str, &str)>,
no_track: bool, no_track: bool,
) -> Result<(), String> { ) -> Result<(), String> {
// A branch name must never start or end with a slash. Everything else is ok.
if name.starts_with('/') || name.ends_with('/') {
return Err(format!(
"Invalid worktree name: {}. It cannot start or end with a slash",
name
));
}
let repo = repo::RepoHandle::open(directory, true).map_err(|error| match error.kind { let repo = repo::RepoHandle::open(directory, true).map_err(|error| match error.kind {
repo::RepoErrorKind::NotFound => { repo::RepoErrorKind::NotFound => {
String::from("Current directory does not contain a worktree setup") String::from("Current directory does not contain a worktree setup")
@@ -38,11 +45,6 @@ pub fn add_worktree(
return Err(format!("Worktree {} already exists", &name)); return Err(format!("Worktree {} already exists", &name));
} }
let path = match subdirectory {
Some(dir) => directory.join(dir).join(name),
None => directory.join(Path::new(name)),
};
let mut remote_branch_exists = false; let mut remote_branch_exists = false;
let mut target_branch = match repo.find_local_branch(name) { let mut target_branch = match repo.find_local_branch(name) {
@@ -193,10 +195,80 @@ pub fn add_worktree(
} }
} }
if let Some(subdirectory) = subdirectory { // We have to create subdirectories first, otherwise adding the worktree
std::fs::create_dir_all(subdirectory).map_err(|error| error.to_string())?; // will fail
if name.contains('/') {
let path = Path::new(&name);
if let Some(base) = path.parent() {
// This is a workaround of a bug in libgit2 (?)
//
// When *not* doing this, we will receive an error from the `Repository::worktree()`
// like this:
//
// > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test
//
// This is a discrepancy between the behaviour of libgit2 and the
// git CLI when creating worktrees with slashes:
//
// The git CLI will create the worktree's configuration directory
// inside {git_dir}/worktrees/{last_path_component}. Look at this:
//
// ```
// $ git worktree add 1/2/3 -b 1/2/3
// $ ls .git/worktrees
// 3
// ```
//
// Interesting: When adding a worktree with a different name but the
// same final path component, git starts adding a counter suffix to
// the worktree directories:
//
// ```
// $ git worktree add 1/3/3 -b 1/3/3
// $ git worktree add 1/4/3 -b 1/4/3
// $ ls .git/worktrees
// 3
// 31
// 32
// ```
//
// I *guess* that the mapping back from the worktree directory under .git to the actual
// worktree directory is done via the `gitdir` file inside `.git/worktrees/{worktree}.
// This means that the actual directory would not matter. You can verify this by
// just renaming it:
//
// ```
// $ mv .git/worktrees/3 .git/worktrees/foobar
// $ git worktree list
// /tmp/ fcc8a2a7 [master]
// /tmp/1/2/3 fcc8a2a7 [1/2/3]
// /tmp/1/3/3 fcc8a2a7 [1/3/3]
// /tmp/1/4/3 fcc8a2a7 [1/4/3]
// ```
//
// => Still works
//
// Anyway, libgit2 does not do this: It tries to create the worktree
// directory inside .git with the exact name of the worktree, including
// any slashes. It should be this code:
//
// https://github.com/libgit2/libgit2/blob/f98dd5438f8d7bfd557b612fdf1605b1c3fb8eaf/src/libgit2/worktree.c#L346
//
// As a workaround, we can create the base directory manually for now.
//
// Tracking upstream issue: https://github.com/libgit2/libgit2/issues/6327
std::fs::create_dir_all(
directory
.join(GIT_MAIN_WORKTREE_DIRECTORY)
.join("worktrees")
.join(base),
)
.map_err(|error| error.to_string())?;
std::fs::create_dir_all(base).map_err(|error| error.to_string())?;
}
} }
repo.new_worktree(name, &path, &target_branch)?;
repo.new_worktree(name, &directory.join(&name), &target_branch)?;
Ok(()) Ok(())
} }