fix(hooks): drop MERGE_HEAD guard from post-merge so real merges rebuild (#4463)

git leaves .git/MERGE_HEAD on disk while post-merge runs and deletes it
only after the hook returns, so the guard exited on every merge that
makes a merge commit. Only fast-forwards rebuilt. The post-commit and
post-checkout guards are unchanged.

(cherry picked from commit c37d35bd50)
This commit is contained in:
Deepjyoti-Sarmah authored and safishamsi committed 2026-10-16 17:10:31 +01:00
1 parent 6c81b838b5
commit b3af356532
2 files changed
+44 -5

No files matched your search

+4 -5
View File
@@ -583,16 +583,15 @@ if [ ! -d "graphify-out" ]; then
exit 0
fi
# This hook only fires after a merge completed cleanly, so MERGE_HEAD is
# already gone by the time it runs -- these checks are belt and braces for
# any git version/workflow quirk that lands here mid rebase or cherry-pick
# anyway, matching the commit and checkout hooks' own defensive posture.
# git leaves MERGE_HEAD on disk while post-merge runs and deletes it only
# after the hook returns, so it must not be checked here: that skipped every
# merge that makes a merge commit (#4463). The checks below are belt and
# braces for any quirk that lands here mid rebase or cherry-pick.
# git exports GIT_DIR to hooks; the rev-parse fallback only runs when invoked
# by hand (each git exec costs 1s+ on AV-scanned Windows machines).
GIT_DIR=${GIT_DIR:-$(git rev-parse --git-dir 2>/dev/null)}
[ -d "$GIT_DIR/rebase-merge" ] && exit 0
[ -d "$GIT_DIR/rebase-apply" ] && exit 0
[ -f "$GIT_DIR/MERGE_HEAD" ] && exit 0
[ -f "$GIT_DIR/CHERRY_PICK_HEAD" ] && exit 0
# Honor the same opt out the commit and checkout hooks already respect.
+40
View File
@@ -1655,6 +1655,46 @@ def test_commit_hook_skips_graph_only_commit_when_graphify_out_is_renamed(tmp_pa
)
@_SH_ONLY
def test_post_merge_hook_runs_after_a_real_merge(tmp_path):
# git writes MERGE_HEAD before running post-merge and deletes it only
# after, so a MERGE_HEAD guard here exited on every non-fast-forward
# merge and the rebuild never launched (#4463).
repo = tmp_path / "repo"
repo.mkdir()
env = {
**os.environ,
"HOME": str(tmp_path), # keeps the rebuild log out of the real ~/.cache
"GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t.co",
"GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t.co",
}
quiet = {**env, "GRAPHIFY_SKIP_HOOK": "1"} # no rebuilds during setup
def git(*args, env=env):
return subprocess.run(["git", *args], cwd=repo, env=env,
capture_output=True, text=True, check=True)
git("init", "-q", "-b", "main")
(repo / "graphify-out").mkdir()
(repo / "graphify-out" / "graph.json").write_text("{}", encoding="utf-8")
(repo / "a.py").write_text("def a(): pass\n", encoding="utf-8")
git("add", ".")
git("commit", "-qm", "init", env=quiet)
install(repo)
git("checkout", "-qb", "feat", env=quiet)
(repo / "b.py").write_text("def b(): pass\n", encoding="utf-8")
git("add", ".")
git("commit", "-qm", "b", env=quiet)
git("checkout", "-q", "main", env=quiet)
out = git("merge", "--no-ff", "-m", "merge feat", "feat")
assert _LAUNCH_LINE in out.stdout + out.stderr, (
"post-merge exited before the launch\n"
f"stdout={out.stdout!r}\nstderr={out.stderr!r}"
)
@_SH_ONLY
def test_commit_hook_skips_graph_only_commit_under_default_dir(tmp_path):
"""Control: the default-name case the filter always handled keeps working."""