Skip to content

Commit

Permalink
Add package:trebuchet to repo. (#289)
Browse files Browse the repository at this point in the history
Moving over from https://github.com/mosuem/merger/.

We might want to move the issue transfer tool from `repo_manage` to this
tool as well, it would be cleaner.

---

- [x] I’ve reviewed the contributor guide and applied the relevant
portions to this PR.

<details>
  <summary>Contribution guidelines:</summary><br>

- See our [contributor
guide](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md)
for general expectations for PRs.
- Larger or significant changes should be discussed in an issue before
creating a PR.
- Contributions to our repos should follow the [Dart style
guide](https://dart.dev/guides/language/effective-dart) and use `dart
format`.
- Most changes should add an entry to the changelog and may need to [rev
the pubspec package
version](https://github.com/dart-lang/sdk/blob/main/docs/External-Package-Maintenance.md#making-a-change).
- Changes to packages require [corresponding
tests](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md#Testing).

Note that many Dart repos have a weekly cadence for reviewing PRs -
please allow for some latency before initial review feedback.
</details>

---------

Co-authored-by: Devon Carew <devoncarew@google.com>
  • Loading branch information
mosuem and devoncarew authored Aug 26, 2024
1 parent dd8d12a commit 5da8a7a
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This repository is home to general Dart Ecosystem tools and packages.
| [firehose](pkgs/firehose/) | A tool to automate publishing of Pub packages from GitHub actions. | [![pub package](https://img.shields.io/pub/v/firehose.svg)](https://pub.dev/packages/firehose) |
| [repo_manage](pkgs/repo_manage/) | Miscellaneous issue, repo, and PR query tools. | |
| [sdk_triage_bot](pkgs/sdk_triage_bot/) | A triage automation tool for dart-lang/sdk issues. | |
| [trebuchet](pkgs/trebuchet/) | A tool for moving existing packages into monorepos. | |

## Publishing automation

Expand Down
16 changes: 16 additions & 0 deletions pkgs/trebuchet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## What's this?

This is a tool to move existing packages into monorepos.

## Running this tool

```bash
dart run bin/trebuchet.dart \
--input-name coverage \
--branch-name master \
--input-path ~/projects/coverage/ \
--target-path ~/projects/tools/ \
--git-filter-repo ~/tools/git-filter-repo
```

This basically executes the instructions at https://github.com/dart-lang/ecosystem/wiki/Merging-existing-repos-into-a-monorepo
5 changes: 5 additions & 0 deletions pkgs/trebuchet/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include: package:dart_flutter_team_lints/analysis_options.yaml

linter:
rules:
- prefer_final_locals
219 changes: 219 additions & 0 deletions pkgs/trebuchet/bin/trebuchet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:args/args.dart';
import 'package:path/path.dart' as p;

Future<void> main(List<String> arguments) async {
final argParser = ArgParser()
..addOption(
'input-name',
help: 'Name of the package which should be transferred to a mono-repo',
)
..addOption(
'input-path',
help: 'Path to the package which should be transferred to a mono-repo',
)
..addOption(
'target-path',
help: 'Path to the mono-repo',
)
..addOption(
'branch-name',
help: 'The name of the main branch on the input repo',
defaultsTo: 'main',
)
..addOption(
'git-filter-repo',
help: 'Path to the git-filter-repo tool',
)
..addFlag(
'dry-run',
help: 'Do not actually execute any of the steps',
defaultsTo: false,
)
..addFlag(
'help',
abbr: 'h',
help: 'Prints usage info',
negatable: false,
);

String? input;
String? inputPath;
String? targetPath;
String? branchName;
String? gitFilterRepo;
bool dryRun;
try {
final parsed = argParser.parse(arguments);
if (parsed.flag('help')) {
print(argParser.usage);
exit(0);
}

input = parsed.option('input-name')!;
inputPath = parsed.option('input-path')!;
targetPath = parsed.option('target-path')!;
branchName = parsed.option('branch-name')!;
gitFilterRepo = parsed.option('git-filter-repo')!;
dryRun = parsed.flag('dry-run');
} catch (e) {
print(e);
print('');
print(argParser.usage);
exit(1);
}

final trebuchet = Trebuchet(
input: input,
inputPath: inputPath,
targetPath: targetPath,
branchName: branchName,
gitFilterRepo: gitFilterRepo,
dryRun: dryRun,
);

await trebuchet.hurl();
}

class Trebuchet {
final String input;
final String inputPath;
final String targetPath;
final String branchName;
final String gitFilterRepo;
final bool dryRun;

Trebuchet({
required this.input,
required this.inputPath,
required this.targetPath,
required this.branchName,
required this.gitFilterRepo,
required this.dryRun,
});

Future<void> hurl() async {
print('Check existence of python3 on path');
await runProcess(
'python3',
['--version'],
inTarget: false,
);

print('Start moving package');

print('Rename to `pkgs/`');
await filterRepo(['--path-rename', ':pkgs/$input/']);

print('Prefix tags');
await filterRepo(['--tag-rename', ':$input-']);

print('Replace issue references in commit messages');
await inTempDir((tempDirectory) async {
final regexFile = File(p.join(tempDirectory.path, 'expressions.txt'));
await regexFile.create();
await regexFile.writeAsString('regex:#(\\d)==>dart-lang/$input#\\1');
await filterRepo(['--replace-message', regexFile.path]);
});

print('Create branch at target');
await runProcess('git', ['checkout', '-b', 'merge-$input-package']);

print('Add a remote for the local clone of the moving package');
await runProcess(
'git',
['remote', 'add', '${input}_package', inputPath],
);
await runProcess('git', ['fetch', '${input}_package']);

print('Merge branch into monorepo');
await runProcess(
'git',
[
'merge',
'--allow-unrelated-histories',
'${input}_package/$branchName',
'-m',
'Merge package:$input into shared tool repository'
],
);

final shouldPush = getInput('Push to remote? (y/N)');

if (shouldPush) {
print('Push to remote');
await runProcess(
'git',
['push', '--set-upstream', 'origin', 'merge-$input-package'],
);
}

print('DONE!');
print('''
Steps left to do:
- Move and fix workflow files
${shouldPush ? '' : '- Run `git push --set-upstream origin merge-$input-package` in the monorepo directory'}
- Disable squash-only in GitHub settings, and merge with a fast forward merge to the main branch, enable squash-only in GitHub settings.
- Push tags to github using `git tag --list '$input*' | xargs git push origin`
- Follow up with a PR adding links to the top-level readme table.
- Add a commit to https://github.com/dart-lang/$input/ with it's readme pointing to the monorepo.
- Update the auto-publishing settings on pub.dev/packages/$input.
- Archive https://github.com/dart-lang/$input/.
''');
}

bool getInput(String question) {
print(question);
final line = stdin.readLineSync()?.toLowerCase();
return line == 'y' || line == 'yes';
}

Future<void> runProcess(
String executable,
List<String> arguments, {
bool inTarget = true,
}) async {
final workingDirectory = inTarget ? targetPath : inputPath;
print('----------');
print('Running `$executable $arguments` in $workingDirectory');
if (!dryRun) {
final processResult = await Process.run(
executable,
arguments,
workingDirectory: workingDirectory,
);
print('stdout:');
print(processResult.stdout);
if ((processResult.stderr as String).isNotEmpty) {
print('stderr:');
print(processResult.stderr);
}
if (processResult.exitCode != 0) {
throw ProcessException(executable, arguments);
}
} else {
print('Not running, as --dry-run is set.');
}
print('==========');
}

Future<void> filterRepo(List<String> args) async {
await runProcess(
'python3',
[p.relative(gitFilterRepo, from: inputPath), ...args],
inTarget: false,
);
}
}

Future<void> inTempDir(Future<void> Function(Directory temp) f) async {
final tempDirectory = await Directory.systemTemp.createTemp();
await f(tempDirectory);
await tempDirectory.delete(recursive: true);
}
16 changes: 16 additions & 0 deletions pkgs/trebuchet/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: trebuchet
description: A tool for hurling packages into monorepos.

publish_to: none

environment:
sdk: ^3.3.0

dependencies:
args: ^2.5.0
path: ^1.9.0

dev_dependencies:
dart_flutter_team_lints: ^3.2.0
lints: ^4.0.0
test: ^1.24.0

0 comments on commit 5da8a7a

Please sign in to comment.