Skip to content

Commit 4c039c5

Browse files
committed
fix(hooks): invoke Git hooks in worktrees
1 parent 1a86528 commit 4c039c5

File tree

7 files changed

+119
-18
lines changed

7 files changed

+119
-18
lines changed

git-branchless-init/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ use path_slash::PathExt;
2323
use tracing::{instrument, warn};
2424

2525
use git_branchless_opts::{write_man_pages, InitArgs, InstallManPagesArgs};
26-
use lib::core::config::{get_default_branch_name, get_default_hooks_dir, get_hooks_dir};
26+
use lib::core::config::{
27+
get_default_branch_name, get_default_hooks_dir, get_main_worktree_hooks_dir,
28+
};
2729
use lib::core::dag::Dag;
2830
use lib::core::effects::Effects;
2931
use lib::core::eventlog::{EventLogDb, EventReplayer};
@@ -249,12 +251,12 @@ fn install_hooks(effects: &Effects, git_run_info: &GitRunInfo, repo: &Repo) -> e
249251
.map(|(hook_type, _hook_script)| hook_type)
250252
.join(", ")
251253
)?;
252-
let hooks_dir = get_hooks_dir(git_run_info, repo, None)?;
254+
let hooks_dir = get_main_worktree_hooks_dir(git_run_info, repo, None)?;
253255
for (hook_type, hook_script) in ALL_HOOKS {
254256
install_hook(repo, &hooks_dir, hook_type, hook_script)?;
255257
}
256258

257-
let default_hooks_dir = get_default_hooks_dir(repo);
259+
let default_hooks_dir = get_default_hooks_dir(repo)?;
258260
if hooks_dir != default_hooks_dir {
259261
writeln!(
260262
effects.get_output_stream(),
@@ -281,7 +283,7 @@ fn uninstall_hooks(effects: &Effects, git_run_info: &GitRunInfo, repo: &Repo) ->
281283
.map(|(hook_type, _hook_script)| hook_type)
282284
.join(", ")
283285
)?;
284-
let hooks_dir = get_hooks_dir(git_run_info, repo, None)?;
286+
let hooks_dir = get_main_worktree_hooks_dir(git_run_info, repo, None)?;
285287
for (hook_type, _hook_script) in ALL_HOOKS {
286288
install_hook(
287289
repo,

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,24 @@ use super::eventlog::EventTransactionId;
1818
/// Get the expected hooks dir inside `.git`, assuming that the user has not
1919
/// overridden it.
2020
#[instrument]
21-
pub fn get_default_hooks_dir(repo: &Repo) -> PathBuf {
22-
repo.get_path().join("hooks")
21+
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"))
2325
}
2426

25-
/// Get the path where Git hooks are stored on disk.
27+
/// Get the path where the main worktree's Git hooks are stored on disk.
28+
///
29+
/// Git hooks live at `$GIT_DIR/hooks` by default, which means that they will be
30+
/// different per wortkree. Most people, when creating a new worktree, will not
31+
/// also reinstall hooks or reinitialize git-branchless in that worktree, so we
32+
/// instead look up hooks for the main worktree, which is most likely to have them
33+
/// installed.
34+
///
35+
/// This could in theory cause problems for users who have different
36+
/// per-worktree hooks.
2637
#[instrument]
27-
pub fn get_hooks_dir(
38+
pub fn get_main_worktree_hooks_dir(
2839
git_run_info: &GitRunInfo,
2940
repo: &Repo,
3041
event_tx_id: Option<EventTransactionId>,
@@ -45,7 +56,7 @@ pub fn get_hooks_dir(
4556
.context("Decoding git config output for hooks path")?;
4657
PathBuf::from(path.strip_suffix('\n').unwrap_or(&path))
4758
} else {
48-
get_default_hooks_dir(repo)
59+
get_default_hooks_dir(repo)?
4960
};
5061
Ok(hooks_path)
5162
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ pub struct EventLogDb<'conn> {
471471

472472
impl std::fmt::Debug for EventLogDb<'_> {
473473
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474-
write!(f, "<EventLogDb>")
474+
write!(f, "<EventLogDb path={:?}>", self.conn.path())
475475
}
476476
}
477477

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use std::thread::{self, JoinHandle};
1111
use bstr::BString;
1212
use eyre::{eyre, Context};
1313
use itertools::Itertools;
14-
use tracing::instrument;
14+
use tracing::{instrument, warn};
1515

16-
use crate::core::config::get_hooks_dir;
16+
use crate::core::config::get_main_worktree_hooks_dir;
1717
use crate::core::effects::{Effects, OperationType};
1818
use crate::core::eventlog::{EventTransactionId, BRANCHLESS_TRANSACTION_ID_ENV_VAR};
1919
use crate::git::repo::Repo;
@@ -394,8 +394,13 @@ impl GitRunInfo {
394394
args: &[&str],
395395
stdin: Option<BString>,
396396
) -> eyre::Result<()> {
397-
let hook_dir = get_hooks_dir(self, repo, Some(event_tx_id))?;
397+
let hook_dir = get_main_worktree_hooks_dir(self, repo, Some(event_tx_id))?;
398398
if !hook_dir.exists() {
399+
warn!(
400+
?hook_dir,
401+
?hook_name,
402+
"Git hooks dir did not exist, so could not invoke hook"
403+
);
399404
return Ok(());
400405
}
401406

git-branchless/src/commands/bug_report.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bugreport::collector::{CollectionError, Collector};
88
use bugreport::format::Markdown;
99
use bugreport::report::ReportEntry;
1010
use itertools::Itertools;
11-
use lib::core::config::get_hooks_dir;
11+
use lib::core::config::get_main_worktree_hooks_dir;
1212
use lib::core::repo_ext::{RepoExt, RepoReferencesSnapshot};
1313
use lib::util::EyreExitOr;
1414

@@ -242,7 +242,7 @@ struct HookCollector {
242242

243243
fn collect_hooks(git_run_info: &GitRunInfo) -> eyre::Result<ReportEntry> {
244244
let repo = Repo::from_current_dir()?;
245-
let hooks_dir = get_hooks_dir(git_run_info, &repo, None)?;
245+
let hooks_dir = get_main_worktree_hooks_dir(git_run_info, &repo, None)?;
246246
let hook_contents = {
247247
let mut result = Vec::new();
248248
for (hook_type, _content) in ALL_HOOKS {

git-branchless/tests/test_init.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ fn test_main_branch_not_found_error_message() -> eyre::Result<()> {
314314
315315
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
316316
317-
0: branchless::core::eventlog::from_event_log_db with effects=<Output fancy=false> repo=<Git repository at: "<repo-path>/.git/"> event_log_db=<EventLogDb>
317+
0: branchless::core::eventlog::from_event_log_db with effects=<Output fancy=false> repo=<Git repository at: "<repo-path>/.git/"> event_log_db=<EventLogDb path=Some("<repo-path>/.git/branchless/db.sqlite3")>
318318
at some/file/path.rs:123
319319
1: git_branchless_smartlog::smartlog with effects=<Output fancy=false> git_run_info=<GitRunInfo path_to_git="<git-executable>" working_directory="<repo-path>" env=not shown> options=SmartlogOptions { event_id: None, revset: None, resolve_revset_options: ResolveRevsetOptions { show_hidden_commits: false }, reverse: false }
320320
at some/file/path.rs:123

git-branchless/tests/test_move.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use lib::testing::{
2-
extract_hint_command, make_git, make_git_with_remote_repo, remove_rebase_lines, GitInitOptions,
3-
GitRunOptions, GitWrapperWithRemoteRepo,
2+
extract_hint_command, make_git, make_git_with_remote_repo, make_git_worktree,
3+
remove_rebase_lines, GitInitOptions, GitRunOptions, GitWorktreeWrapper,
4+
GitWrapperWithRemoteRepo,
45
};
56

67
#[test]
@@ -6237,3 +6238,85 @@ fn test_move_fixup_added_files() -> eyre::Result<()> {
62376238

62386239
Ok(())
62396240
}
6241+
6242+
#[test]
6243+
fn test_worktree_rebase_in_memory() -> eyre::Result<()> {
6244+
let git = make_git()?;
6245+
6246+
if !git.supports_reference_transactions()? {
6247+
return Ok(());
6248+
}
6249+
git.init_repo()?;
6250+
6251+
git.commit_file("test1", 1)?;
6252+
git.detach_head()?;
6253+
git.commit_file("test2", 2)?;
6254+
git.commit_file("test3", 3)?;
6255+
6256+
let GitWorktreeWrapper {
6257+
temp_dir: _temp_dir,
6258+
worktree,
6259+
} = make_git_worktree(&git, "new-worktree")?;
6260+
git.run(&["checkout", "master"])?;
6261+
{
6262+
let stdout = worktree.smartlog()?;
6263+
insta::assert_snapshot!(stdout, @r###"
6264+
:
6265+
O 62fc20d (master) create test1.txt
6266+
|
6267+
o 96d1c37 create test2.txt
6268+
|
6269+
@ 70deb1e create test3.txt
6270+
"###);
6271+
}
6272+
6273+
{
6274+
let (stdout, stderr) = worktree.branchless("move", &["-s", "@", "-d", "master"])?;
6275+
insta::assert_snapshot!(stderr, @r###"
6276+
branchless: creating working copy snapshot
6277+
Previous HEAD position was 70deb1e create test3.txt
6278+
branchless: processing 1 update: ref HEAD
6279+
HEAD is now at 4838e49 create test3.txt
6280+
branchless: processing checkout
6281+
"###);
6282+
insta::assert_snapshot!(stdout, @r###"
6283+
Attempting rebase in-memory...
6284+
[1/1] Committed as: 4838e49 create test3.txt
6285+
branchless: processing 1 rewritten commit
6286+
branchless: running command: <git-executable> checkout 4838e49b08954becdd17c0900c1179c2c654c627
6287+
:
6288+
O 62fc20d (master) create test1.txt
6289+
|\
6290+
| o 96d1c37 create test2.txt
6291+
|
6292+
@ 4838e49 create test3.txt
6293+
In-memory rebase succeeded.
6294+
"###);
6295+
}
6296+
6297+
{
6298+
let stdout = worktree.smartlog()?;
6299+
insta::assert_snapshot!(stdout, @r###"
6300+
:
6301+
O 62fc20d (master) create test1.txt
6302+
|\
6303+
| o 96d1c37 create test2.txt
6304+
|
6305+
@ 4838e49 create test3.txt
6306+
"###);
6307+
}
6308+
6309+
{
6310+
let stdout = git.smartlog()?;
6311+
insta::assert_snapshot!(stdout, @r###"
6312+
:
6313+
@ 62fc20d (> master) create test1.txt
6314+
|\
6315+
| o 96d1c37 create test2.txt
6316+
|
6317+
o 4838e49 create test3.txt
6318+
"###);
6319+
}
6320+
6321+
Ok(())
6322+
}

0 commit comments

Comments
 (0)