Skip to content

Commit 4d6d5fb

Browse files
committed
fix(init): make relative hook path work in worktrees
Since init previously used the root worktree for all of its actions, it failed to resolve `core.hooksPath` relative to the worktree it was being run in. Change `init` to work with the current worktree, and update code to explicitly refer to the shared git directory when necessary. This also entirely removes the need for `open_worktree_parent_repo`.
1 parent 4cc3812 commit 4d6d5fb

File tree

4 files changed

+123
-42
lines changed

4 files changed

+123
-42
lines changed

git-branchless-init/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ fn create_isolated_config(
544544

545545
let config = Config::open(&config_path)?;
546546
let config_path_relative = config_path
547-
.strip_prefix(repo.get_path())
547+
.strip_prefix(repo.get_shared_path())
548548
.wrap_err("Getting relative config path")?;
549549
// Be careful when setting paths on Windows. Since the path would have a
550550
// backslash, naively using it produces
@@ -606,8 +606,7 @@ fn command_init(
606606
main_branch_name: Option<&str>,
607607
) -> EyreExitOr<()> {
608608
let mut in_ = BufReader::new(stdin());
609-
let repo = Repo::from_current_dir()?;
610-
let mut repo = repo.open_worktree_parent_repo()?.unwrap_or(repo);
609+
let mut repo = Repo::from_current_dir()?;
611610

612611
let default_config = Config::open_default()?;
613612
let readonly_config = repo.get_readonly_config()?;

git-branchless-lib/src/core/config.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ use super::eventlog::EventTransactionId;
1919
/// overridden it.
2020
#[instrument]
2121
pub fn get_default_hooks_dir(repo: &Repo) -> eyre::Result<PathBuf> {
22-
let parent_repo = repo.open_worktree_parent_repo()?;
23-
let repo = parent_repo.as_ref().unwrap_or(repo);
24-
Ok(repo.get_path().join("hooks"))
22+
Ok(repo.get_shared_path().join("hooks"))
2523
}
2624

2725
/// Get the path where the main worktree's Git hooks are stored on disk.

git-branchless-lib/src/git/repo.rs

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ pub enum Error {
4949
#[error("could not open repository: {0}")]
5050
OpenRepo(#[source] git2::Error),
5151

52-
#[error("could not find repository to open for worktree {path:?}")]
53-
OpenParentWorktreeRepository { path: PathBuf },
54-
5552
#[error("could not open repository: {0}")]
5653
UnsupportedExtensionWorktreeConfig(#[source] git2::Error),
5754

@@ -505,11 +502,22 @@ impl Repo {
505502
Ok(Repo { inner: repo })
506503
}
507504

508-
/// Get the path to the `.git` directory for the repository.
505+
/// Get the path to the local `.git` directory for the repository.
506+
///
507+
/// If this repository is a non-root worktree, this will return the path to
508+
/// the worktree's local directory, i.e. `.git/worktrees/<name>`
509509
pub fn get_path(&self) -> &Path {
510510
self.inner.path()
511511
}
512512

513+
/// Get the path to the shared common `.git` directory for the repository.
514+
///
515+
/// This always gets the shared common `.git` directory instead of the
516+
/// per-worktree directory (`.git/worktrees/...`).
517+
pub fn get_shared_path(&self) -> &Path {
518+
self.inner.commondir()
519+
}
520+
513521
/// Get the path to the `packed-refs` file for the repository.
514522
pub fn get_packed_refs_path(&self) -> PathBuf {
515523
self.inner.path().join("packed-refs")
@@ -569,32 +577,6 @@ impl Repo {
569577
Ok(Index { inner: index })
570578
}
571579

572-
/// If this repository is a worktree for another "parent" repository, return a [`Repo`] object
573-
/// corresponding to that repository.
574-
#[instrument]
575-
pub fn open_worktree_parent_repo(&self) -> Result<Option<Self>> {
576-
if !self.inner.is_worktree() {
577-
return Ok(None);
578-
}
579-
580-
// `git2` doesn't seem to support a way to directly look up the parent repository for the
581-
// worktree.
582-
let worktree_info_dir = self.get_path();
583-
let parent_repo_path = match worktree_info_dir
584-
.parent() // remove `.git`
585-
.and_then(|path| path.parent()) // remove worktree name
586-
.and_then(|path| path.parent()) // remove `worktrees`
587-
{
588-
Some(path) => path,
589-
None => {
590-
return Err(Error::OpenParentWorktreeRepository {
591-
path: worktree_info_dir.to_owned()});
592-
},
593-
};
594-
let parent_repo = Self::from_dir(parent_repo_path)?;
595-
Ok(Some(parent_repo))
596-
}
597-
598580
/// Get the configuration object for the repository.
599581
///
600582
/// **Warning**: This object should only be used for read operations. Write
@@ -608,12 +590,7 @@ impl Repo {
608590

609591
/// Get the directory where all repo-specific git-branchless state is stored.
610592
pub fn get_branchless_dir(&self) -> Result<PathBuf> {
611-
let maybe_worktree_parent_repo = self.open_worktree_parent_repo()?;
612-
let repo = match maybe_worktree_parent_repo.as_ref() {
613-
Some(repo) => repo,
614-
None => self,
615-
};
616-
let dir = repo.get_path().join("branchless");
593+
let dir = self.get_shared_path().join("branchless");
617594
std::fs::create_dir_all(&dir).map_err(|err| Error::CreateBranchlessDir {
618595
source: err,
619596
path: dir.clone(),

git-branchless/tests/test_init.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,113 @@ fn test_init_worktree() -> eyre::Result<()> {
730730
Ok(())
731731
}
732732

733+
#[test]
734+
fn test_init_worktree_from_subdir() -> eyre::Result<()> {
735+
let git = make_git()?;
736+
git.init_repo_with_options(&GitInitOptions {
737+
run_branchless_init: false,
738+
make_initial_commit: true,
739+
})?;
740+
git.commit_file("test1", 1)?;
741+
git.commit_file("test2", 2)?;
742+
743+
let GitWorktreeWrapper {
744+
temp_dir: _temp_dir,
745+
worktree,
746+
} = make_git_worktree(&git, "new-worktree")?;
747+
748+
let subdir = worktree.repo_path.join("subdir");
749+
std::fs::create_dir(&subdir)?;
750+
751+
worktree.branchless_with_options(
752+
"init",
753+
&[],
754+
&GitRunOptions {
755+
working_dir: Some(subdir),
756+
..Default::default()
757+
},
758+
)?;
759+
{
760+
let (stdout, stderr) = worktree.run(&["commit", "--allow-empty", "-m", "test"])?;
761+
insta::assert_snapshot!(stdout, @"[detached HEAD a3e6886] test
762+
");
763+
insta::assert_snapshot!(stderr, @r###"
764+
branchless: processing 1 update: ref HEAD
765+
branchless: processed commit: a3e6886 test
766+
"###);
767+
}
768+
769+
Ok(())
770+
}
771+
772+
#[test]
773+
fn test_init_worktree_from_subdir_with_hooks_path() -> eyre::Result<()> {
774+
let git = make_git()?;
775+
git.init_repo_with_options(&GitInitOptions {
776+
run_branchless_init: false,
777+
make_initial_commit: true,
778+
})?;
779+
git.commit_file("test1", 1)?;
780+
git.commit_file("test2", 2)?;
781+
782+
let GitWorktreeWrapper {
783+
temp_dir: _temp_dir,
784+
worktree,
785+
} = make_git_worktree(&git, "new-worktree")?;
786+
787+
let hooks_path = git.get_repo()?.get_path().join("my-hooks");
788+
std::fs::create_dir(hooks_path)?;
789+
git.run(&["config", "core.hooksPath", "my-hooks"])?;
790+
791+
let subdir = worktree.repo_path.join("subdir");
792+
std::fs::create_dir(&subdir)?;
793+
794+
{
795+
let (stdout, stderr) = worktree.branchless_with_options(
796+
"init",
797+
&[],
798+
&GitRunOptions {
799+
working_dir: Some(subdir),
800+
..Default::default()
801+
},
802+
)?;
803+
804+
// Copied from [Git::preprocess_output] - replace the path of the root
805+
// repo.
806+
let stdout = stdout.replace(
807+
std::fs::canonicalize(&git.repo_path)?
808+
.to_str()
809+
.ok_or_else(|| eyre::eyre!("Could not convert repo path to string"))?,
810+
"<root-repo-path>",
811+
);
812+
813+
insta::assert_snapshot!(stderr, @"");
814+
insta::assert_snapshot!(stdout, @r###"
815+
Created config file at <root-repo-path>/.git/branchless/config
816+
Auto-detected your main branch as: master
817+
If this is incorrect, run: git branchless init --main-branch <branch>
818+
Installing hooks: post-applypatch, post-checkout, post-commit, post-merge, post-rewrite, pre-auto-gc, reference-transaction
819+
Warning: the configuration value core.hooksPath was set to: <repo-path>/my-hooks,
820+
which is not the expected default value of: <root-repo-path>/.git/hooks
821+
The Git hooks above may have been installed to an unexpected global location.
822+
Successfully installed git-branchless.
823+
To uninstall, run: git branchless init --uninstall
824+
"###);
825+
}
826+
827+
{
828+
let (stdout, stderr) = worktree.run(&["commit", "--allow-empty", "-m", "test"])?;
829+
insta::assert_snapshot!(stdout, @"[detached HEAD a3e6886] test
830+
");
831+
insta::assert_snapshot!(stderr, @r###"
832+
branchless: processing 1 update: ref HEAD
833+
branchless: processed commit: a3e6886 test
834+
"###);
835+
}
836+
837+
Ok(())
838+
}
839+
733840
#[test]
734841
fn test_install_man_pages() -> eyre::Result<()> {
735842
let git = make_git()?;

0 commit comments

Comments
 (0)