Skip to content

fix: sanitize slashes in remote ref when building the checkout dir name - #1486

Merged
mrexox merged 2 commits into
evilmartians:masterfrom
hamodywe:fix/remote-dir-name-slash-in-ref
Sep 23, 2026
Merged

mrexox merged 2 commits into
evilmartians:masterfrom
hamodywe:fix/remote-dir-name-slash-in-ref

Conversation

@hamodywe

@hamodywe hamodywe commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

RemoteDirectoryName() appended a remote's ref straight onto the directory name it builds (e.g. "lefthook-test-feat/test-branch" for ref "feat/test-branch"). RemoteFolder() then joins that name onto the remotes directory with filepath.Join, so the slash turns into an actual path separator and lefthook clones into a nested directory instead of the single flat one it expects.

This breaks two things: the initial checkout is written one level deeper than lefthook looks for it (scripts and merged configs from the remote can't be found), and the stale-remote cleanup in loadFullConfig only lists the top-level entries of the remotes directory, so it sees just the ref's first path segment, never matches it against the expected full path, and deletes it as stale on every single run -- forcing a full reclone every time regardless of refetch_frequency.

Replace "/" with "-" in the ref before appending it, keeping the result a single path component. Escape literal "-" to "--" first so the mapping can't collide two distinct refs onto the same directory (e.g. "feat/a-b" and "feat/a/b" would otherwise both sanitize to "feat-a-b").

Fixes #1478

Context

Remote branches whose ref contains a slash (e.g. "feat/x") broke checkout entirely and forced a full reclone on every single run.

Changes

internal/git/remote.go: RemoteDirectoryName() now escapes literal "-" to "--" before mapping "/" to "-", making the sanitized name collision-free. internal/git/remote_test.go: switched to the table-driven map[string]struct + testify/assert style used elsewhere in this package, and added a regression test proving previously-colliding refs now sanitize to distinct names.

Confidence Score: 4/5

The PR is not yet safe to merge because distinct valid refs can still resolve to the same remote checkout directory.

Escaping hyphens as doubled hyphens before converting slashes to hyphens remains ambiguous: refs such as -/ and /- both produce the same suffix, preserving the checkout and cache aliasing reported in the earlier thread.

Files Needing Attention: internal/git/remote.go

Reviews (2): Last reviewed commit: "fix: make remote ref sanitization collis..." | Re-trigger Greptile

RemoteDirectoryName() appended a remote's ref straight onto the
directory name it builds (e.g. "lefthook-test-feat/test-branch" for
ref "feat/test-branch"). RemoteFolder() then joins that name onto the
remotes directory with filepath.Join, so the slash turns into an
actual path separator and lefthook clones into a nested directory
instead of the single flat one it expects.

This breaks two things: the initial checkout is written one level
deeper than lefthook looks for it (scripts and merged configs from the
remote can't be found), and the stale-remote cleanup in
loadFullConfig only lists the top-level entries of the remotes
directory, so it sees just the ref's first path segment, never
matches it against the expected full path, and deletes it as stale on
every single run — forcing a full reclone every time regardless of
refetch_frequency.

Replace "/" with "-" in the ref before appending it, keeping the
result a single path component.

Fixes evilmartians#1478
@hamodywe
hamodywe requested a review from mrexox as a code owner August 8, 2026 23:09
Comment thread internal/git/remote.go Outdated
Comment thread internal/git/remote_test.go Outdated
Replacing every "/" with "-" let distinct refs alias onto the same
checkout directory (e.g. "feat/a-b" and "feat/a/b" both sanitized to
"feat-a-b"), letting synchronizing one ref silently overwrite the
checkout and cached state used by the other. Escape literal "-" to
"--" before mapping "/" to "-": every single "-" in the result now
unambiguously came from a "/", and every "--" from a literal "-".

Also switches the test to the table-driven map[string]struct +
testify/assert style used elsewhere in this package, and adds a
regression test asserting a set of refs that previously collided now
sanitize to distinct names.
@mrexox
mrexox merged commit 07ce565 into evilmartians:master Sep 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Problems with remote refs containing slashes

2 participants