Skip to content

Commit 1b51541

Browse files
committed
fix: handle legacy GitLab deep links with no /-/ marker
Signed-off-by: anilb <epipav@gmail.com>
1 parent dc14b30 commit 1b51541

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

services/apps/packages_worker/src/utils/__tests__/canonicalizeRepoUrl.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ describe('canonicalizeRepoUrl', () => {
4949
'https://gitlab.com/group/subgroup/project',
5050
'gitlab',
5151
],
52+
[
53+
// Pre-2018 GitLab / shorthand copies: no `/-/` marker ahead of the deep-link.
54+
'https://gitlab.com/group/project/tree/master/src',
55+
'https://gitlab.com/group/project',
56+
'gitlab',
57+
],
58+
[
59+
'https://gitlab.com/group/subgroup/project/blob/main/README.md',
60+
'https://gitlab.com/group/subgroup/project',
61+
'gitlab',
62+
],
5263
])('canonicalizes %s', (input, expectedUrl, expectedHost) => {
5364
expect(canonicalizeRepoUrl(input)).toEqual({ url: expectedUrl, host: expectedHost })
5465
})

services/apps/packages_worker/src/utils/canonicalizeRepoUrl.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ const HOST_ENUM: Record<string, RepoHost> = {
2424
// same repo never produces two distinct repos.url keys.
2525
const CASE_INSENSITIVE_HOSTS = new Set(['github.com', 'gitlab.com'])
2626

27+
// Pre-2018 GitLab URLs (and shorthand copies of them still circulating) mark deep-links
28+
// the same way GitHub does — appended directly after the project path, with no `/-/`
29+
// separator. GitLab reserves these route words at the project-slug position precisely so
30+
// they can never collide with a real project name (docs: user/reserved_names), so treating
31+
// the first one as a deep-link boundary is safe even for arbitrarily nested subgroups.
32+
const GITLAB_LEGACY_ROUTE_SEGMENTS = new Set([
33+
'tree',
34+
'blob',
35+
'commits',
36+
'commit',
37+
'compare',
38+
'issues',
39+
'merge_requests',
40+
'wikis',
41+
])
42+
2743
/**
2844
* Canonicalize a source-repository URL to `{ url, host }` where url is
2945
* `https://<host>/<owner>/<name>` and host is the coarse classification stored
@@ -35,8 +51,10 @@ const CASE_INSENSITIVE_HOSTS = new Set(['github.com', 'gitlab.com'])
3551
* `git+`, `git://`, `www.`, and monorepo deep-links: GitHub/Bitbucket's bare
3652
* `/tree/<branch>/<path>` (only the first two path segments are kept) and
3753
* GitLab's `/-/tree/<branch>/<path>` (kept segments run up to the `/-/`,
38-
* preserving arbitrarily nested subgroups). Returns null when the input
39-
* cannot be reduced to an owner/name pair.
54+
* preserving arbitrarily nested subgroups) — plus a legacy fallback for pre-2018
55+
* GitLab links with no `/-/` marker, cut at the first reserved route keyword
56+
* (`tree`, `blob`, `issues`, ...) instead. Returns null when the input cannot be
57+
* reduced to an owner/name pair.
4058
*/
4159
export function canonicalizeRepoUrl(raw: string): CanonicalRepo | null {
4260
let s = raw.trim().replace(/#.*$/, '')
@@ -89,7 +107,13 @@ export function canonicalizeRepoUrl(raw: string): CanonicalRepo | null {
89107
let pathSegments: string[]
90108
if (hostname === 'gitlab.com') {
91109
const dashIdx = segments.indexOf('-')
92-
pathSegments = dashIdx === -1 ? segments : segments.slice(0, dashIdx)
110+
// Legacy fallback: a route keyword with no `/-/` ahead of it (index 2+, so at least
111+
// group + project survive) also marks the boundary — whichever comes first wins.
112+
const legacyIdx = segments.findIndex(
113+
(seg, i) => i >= 2 && GITLAB_LEGACY_ROUTE_SEGMENTS.has(seg),
114+
)
115+
const cutIdx = [dashIdx, legacyIdx].filter((i) => i !== -1).sort((a, b) => a - b)[0]
116+
pathSegments = cutIdx === undefined ? segments : segments.slice(0, cutIdx)
93117
} else if (isKnownHost) {
94118
pathSegments = segments.slice(0, 2)
95119
} else {

0 commit comments

Comments
 (0)