halkyon/tool/release_push.dart
Tizian 「ティツィアーン」 f06fc23023
All checks were successful
CI / check (push) Successful in 41m7s
CI / deploy (push) Successful in 1m2s
build(release): several apps released in one run (train:minor learn), their version commits pushed without the checks
The release tool and tool/with_signing_keys.sh take several apps, each with its version
after a colon or none, and release them one after another, each as alone, stopping at
the first that fails; the key script fetches every named app's key.

The pre-push hook skips tool/check.dart when a push brings release commits alone: the
release tool's chore(release) subject, an app's pubspec changed in its version line and
its texts of what's new, nothing else (tool/release_push.dart). Signatures are checked as
before, and CI still runs the checks. A release no longer costs a run of the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-03 12:16:10 +01:00

77 lines
2.3 KiB
Dart

// Says whether a push to main brings release commits alone (exit 0) or
// anything else (exit 1), for tool/pre-push, which then skips the checks: a
// release commit changes an app's version and its texts of what's new alone
// (tool/src/release/release_push.dart says why that is enough). Reads what
// git hands a pre-push hook: the remote as the argument, a line per ref pushed
// on stdin (local ref, local commit, remote ref, remote commit), and looks at
// the commits pushed to main that no ref of that remote has yet, as
// tool/check_signatures.sh does.
import 'dart:convert';
import 'dart:io';
import 'src/release/log.dart';
import 'src/release/release_push.dart';
import 'src/release/version.dart';
Future<void> main(List<String> arguments) async {
final remote = arguments.firstOrNull ?? 'origin';
final commits = <(Commit, List<String>)>[];
var pushesMain = false;
await for (final line
in stdin.transform(utf8.decoder).transform(const LineSplitter())) {
final fields = line.trim().split(RegExp(r'\s+'));
if (fields.length != 4 || fields[2] != 'refs/heads/main') {
continue;
}
final local = fields[1];
if (RegExp(r'^0+$').hasMatch(local)) {
continue;
}
pushesMain = true;
final log = await _git([
'log',
'--format=$logFormat',
'--name-only',
local,
'--not',
'--remotes=$remote',
]);
for (final commit in parseLog(log)) {
final diff = await _git([
'show',
'--format=',
'--unified=0',
commit.hash,
'--',
'*pubspec.yaml',
]);
commits.add((
commit,
[
for (final line in const LineSplitter().convert(diff))
if ((line.startsWith('+') || line.startsWith('-')) &&
!line.startsWith('+++') &&
!line.startsWith('---'))
line,
],
));
}
}
if (pushesMain && onlyReleaseCommits(commits)) {
stdout.writeln(
'▸ Release commits alone (${commits.map((c) => c.$1.subject).join('; ')}): '
'no checks needed.',
);
exit(0);
}
exit(1);
}
Future<String> _git(List<String> arguments) async {
final result = await Process.run('git', arguments);
if (result.exitCode != 0) {
stderr.writeln('git ${arguments.join(' ')}: ${result.stderr}');
exit(1);
}
return '${result.stdout}';
}