Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/mobile/src/lib/app-actions/app-action-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,24 @@ describe('parseActionRepository', () => {
});
});

it('keeps a dotted project name', () => {
expect(parseActionRepository('o/.github')).toEqual({ kind: 'github', fullName: 'o/.github' });
expect(parseActionRepository('https://gitlab.com/g/.config.schema')).toEqual({
kind: 'gitlab',
fullName: 'g/.config.schema',
});
});

it('returns null for a path segment the providers never name a project with', () => {
expect(parseActionRepository('../r')).toBeNull();
expect(parseActionRepository('./r')).toBeNull();
expect(parseActionRepository('o/..')).toBeNull();
expect(parseActionRepository('https://github.com/o/..')).toBeNull();
expect(parseActionRepository('https://gitlab.com/g/../r')).toBeNull();
expect(parseActionRepository('git@gitlab.com:g/./r.git')).toBeNull();
expect(parseActionRepository('https://bitbucket.org/w/../r')).toBeNull();
});

it('returns null for an unknown host, a blank value and an unusable path', () => {
expect(parseActionRepository('https://example.com/o/r')).toBeNull();
expect(parseActionRepository(' ')).toBeNull();
Expand Down
14 changes: 10 additions & 4 deletions apps/mobile/src/lib/app-actions/app-action-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,12 @@ export type ParsedActionRepository =
| { kind: 'github' | 'gitlab'; fullName: string }
| { kind: 'unsupported'; fullName: string };

/** A project-path segment the providers accept. */
const REPOSITORY_SEGMENT_PATTERN = /^[A-Za-z0-9_.-]+$/;
/**
* A project-path segment the providers accept. The providers never name a
* project `.` or `..`, so those two are not segments even though they use only
* accepted characters.
*/
const REPOSITORY_SEGMENT_PATTERN = /^(?!\.\.?$)[A-Za-z0-9_.-]+$/;

/**
* Walk a path, dropping empty segments and a trailing `.git`, and reject any
Expand Down Expand Up @@ -259,7 +263,8 @@ function urlRepositoryPath(raw: string): string | null {
* cannot use. A host that is not a known provider is null (unknown); a bare
* path with no host defaults to GitHub, whose repository form is exactly
* `owner/repo` — a GitLab project with subgroups is addressed by its URL
* instead.
* instead. A value whose path holds a segment no provider names a project with
* is null whichever provider the host belongs to.
*/
export function parseActionRepository(raw: string): ParsedActionRepository | null {
const trimmed = raw.trim();
Expand All @@ -268,7 +273,8 @@ export function parseActionRepository(raw: string): ParsedActionRepository | nul
}
const platform = detectRepositoryPlatform(trimmed);
if (platform === 'bitbucket') {
return { kind: 'unsupported', fullName: urlRepositoryPath(trimmed) ?? trimmed };
const fullName = urlRepositoryPath(trimmed);
return fullName === null ? null : { kind: 'unsupported', fullName };
}
if (platform === 'github' || platform === 'gitlab') {
const fullName = urlRepositoryPath(trimmed);
Expand Down
Loading