Fix GitLab adapter treating './' as a literal path instead of repository root#121
Merged
Conversation
… repository root GitLab's tree/file endpoints receive path as a literal query parameter or URL segment value, which is never subject to URI dot-segment normalization the way GitHub's contents API path segments are. Callers that pass './' or '.' to mean "repository root" (as is conventional in git and POSIX shells) get an empty tree / 404 instead of root contents, since GitLab looks for a file/directory literally named '.'. Normalize './' and '.' to an empty root path, and strip a leading './' from nested paths, in both listRepositoryContents() and getRepositoryContent().
Contributor
Greptile SummaryThis PR fixes GitLab repository paths that use dot segments to represent the repository root. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "Rewrite normalization as path-segment fi..." | Re-trigger Greptile |
'./././' was only stripped once, leaving a literal './' that GitLab would still look up as-is. Loop the strip and collapse a leftover '.' to root too.
…tripping The leading-'./' loop only handled prefixes; it left embedded dot segments (src/./main.php), double slashes (src//main.php), and trailing slashes untouched, all of which GitLab still resolves literally. Filter empty and '.' segments out of the whole path instead of special-casing the start of the string.
Meldiron
approved these changes
Jul 22, 2026
HarshMN2345
added a commit
to appwrite/appwrite
that referenced
this pull request
Jul 22, 2026
utopia-php/vcs 4.4.4 (utopia-php/vcs#121) fixes the root cause in the GitLab adapter itself: './', '.', embedded dot segments, and double slashes are now normalized before hitting GitLab's API. The endpoint- level normalization added here as a stopgap is no longer needed and is reverted.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GitLab's
repository/treeandrepository/files/:pathendpoints receivepathas a literal query parameter / URL segment value, which — unlike GitHub's contents API, where the path is a URI path segment subject to standard dot-segment normalization (RFC 3986 §6.2.2.3) — is never normalized. A caller passing'./'or'.'to mean "repository root" (the conventional meaning in git/POSIX shells, and what GitHub's adapter tolerates transparently) gets an empty tree or a 404 from GitLab instead, because GitLab looks for a file/directory literally named..Confirmed empirically: traced a real console request through to the raw value received (
./, 2 bytes, no encoding artifacts) before concluding this belongs in the adapter rather than being worked around by every caller.Changes
GitLab::normalizeRepositoryPath(): treats'./'/'.'as repository root (empty path) and strips a leading'./'from nested paths.listRepositoryContents()andgetRepositoryContent().testListRepositoryContentsRootSentinels(asserts'','.','./'all return the same root listing) andtestGetRepositoryContentRootSentinelPrefix(asserts'README.md'and'./README.md'fetch the same file).Test plan
composer lint(Pint) clean.composer check(PHPStan level 8) clean.tests.yml).