Skip to content

Commit

Permalink
feat: add support for bitbucket repository url (#608)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>
  • Loading branch information
fenrirx22 and spydon committed Dec 4, 2023
1 parent 371c22f commit 6f3ea62
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ Supported hosts:

- GitHub
- GitLab (https://gitlab.com)
- Bitbucket (https://bitbucket.org)

```yaml
repository: https://github.com/invertase/melos
```

When using a self-hosted GitHub or GitLab instance, you can specify the
When using a self-hosted GitHub, GitLab or Bitbucket instance, you can specify the
repository location like this:

```yaml
Expand Down
47 changes: 47 additions & 0 deletions packages/melos/lib/src/common/git_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,61 @@ GitLabRepository(
int get hashCode => origin.hashCode ^ owner.hashCode ^ name.hashCode;
}

class BitbucketRepository extends HostedGitRepository {
BitbucketRepository({
String origin = defaultOrigin,
required this.owner,
required this.name,
}) : origin = removeTrailingSlash(origin);

factory BitbucketRepository.fromUrl(Uri uri) {
if (uri.scheme == 'https' && uri.host == 'bitbucket.org') {
final match = RegExp(r'^/(.+)?/(.+)/?$').firstMatch(uri.path);
if (match != null) {
return BitbucketRepository(
owner: match.group(1)!,
name: match.group(2)!,
);
}
}

throw FormatException(
'The URL $uri is not a valid Bitbucket repository URL.',
);
}

static const defaultOrigin = 'https://bitbucket.org';

/// The origin of the Bitbucket server, defaults to `https://bitbucket.org`.
final String origin;

/// The owning workspace name.
final String owner;

@override
final String name;

@override
Uri commitUrl(String id) => url.resolve('commits/$id');

// TODO(fenrirx22): Implementing an issueUrl for Bitbucket requires a Jira URL
@override
Uri issueUrl(String id) => Uri();

@override
Uri get url => Uri.parse('$origin/$owner/$name/');
}

final _hostsToUrlParser = {
'GitHub': GitHubRepository.fromUrl,
'GitLab': GitLabRepository.fromUrl,
'Bitbucket': BitbucketRepository.fromUrl,
};

final _hostsToSpecParser = {
'GitHub': GitHubRepository.new,
'GitLab': GitLabRepository.new,
'Bitbucket': BitbucketRepository.new,
};

/// Tries to parse [url] into a [HostedGitRepository].
Expand Down
100 changes: 100 additions & 0 deletions packages/melos/test/git_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,106 @@ void main() {
});
});

group('BitBucketRepository', () {
group('fromUrl', () {
test('parse Bitbucket repository URL correctly', () {
final url = Uri.parse('https://bitbucket.org/a/b');
final repo = BitbucketRepository.fromUrl(url);

expect(repo.origin, 'https://bitbucket.org');
expect(repo.owner, 'a');
expect(repo.name, 'b');
expect(repo.url, Uri.parse('https://bitbucket.org/a/b/'));
});

test('throws if URL is not a valid GitLab repository URL', () {
void expectBadUrl(String url) {
final uri = Uri.parse(url);
expect(
() => BitbucketRepository.fromUrl(uri),
throwsFormatException,
reason: url,
);
}

const [
'',
'http://bitbucket.org/a/b',
'https://gitlab.com/a/b',
'https://github.com/a/b',
'https://bitbucket.org/a',
'https://bitbucket.org/',
'https://bitbucket.org',
].forEach(expectBadUrl);
});
});

group('fromSpec', () {
test('parse Bitbucket repository spec correctly', () {
final repo = BitbucketRepository(
origin: 'https://bitbucket.invertase.dev',
owner: 'a',
name: 'b',
);

expect(repo.origin, 'https://bitbucket.invertase.dev');
expect(repo.owner, 'a');
expect(repo.name, 'b');
expect(repo.url, Uri.parse('https://bitbucket.invertase.dev/a/b/'));
});

test('parse Bitbucket repository spec with nested groups correctly', () {
final repo = BitbucketRepository(
origin: 'https://bitbucket.invertase.dev',
owner: 'a/b',
name: 'c',
);

expect(repo.origin, 'https://bitbucket.invertase.dev');
expect(repo.owner, 'a/b');
expect(repo.name, 'c');
expect(repo.url, Uri.parse('https://bitbucket.invertase.dev/a/b/c/'));
});

test(
'parse Bitbucket repository spec with sub-path and nested groups '
'correctly', () {
final repo = BitbucketRepository(
origin: 'https://invertase.dev/bitbucket',
owner: 'a/b',
name: 'c',
);

expect(repo.origin, 'https://invertase.dev/bitbucket');
expect(repo.owner, 'a/b');
expect(repo.name, 'c');
expect(repo.url, Uri.parse('https://invertase.dev/bitbucket/a/b/c/'));
});
});

test('commitUrl returns correct URL', () {
final repo = BitbucketRepository(owner: 'a', name: 'b');
const commitId = 'b2841394a48cd7d84a4966a788842690e543b2ef';

expect(
repo.commitUrl(commitId),
Uri.parse(
'https://bitbucket.org/a/b/commits/b2841394a48cd7d84a4966a788842690e543b2ef',
),
);
});

test('issueUrl returns empty URL', () {
final repo = BitbucketRepository(owner: 'a', name: 'b');
const issueId = '123';

expect(
repo.issueUrl(issueId),
Uri(),
);
});
});

group('parseHostedGitRepositoryUrl', () {
test('parses GitHub repository URL', () {
final repo =
Expand Down

0 comments on commit 6f3ea62

Please sign in to comment.