// Copyright 2014 The Flutter Authors. 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'; void _validate(List<String> args) { bool errors = false; if (!File('bin/internal/engine.version').existsSync()) { errors = true; print('This program must be run from the root of your flutter repository.'); } if (!File('../engine/src/flutter/DEPS').existsSync()) { errors = true; print('This program assumes the engine directory is a sibling to the flutter repository directory.'); } if (args.length != 1) { errors = true; print('This program takes the engine revision as a single argument.'); } if (errors) { exit(-1); } } const String engineRepo = '../engine/src/flutter'; Future<void> main(List<String> args) async { _validate(args); await _fetchUpstream(); await _fetchUpstream(engineRepo); String? flutterRevision; await for (final FlutterEngineRevision revision in _logEngineVersions()) { if (!await containsRevision(args[0], revision.engineRevision)) { if (flutterRevision == null) { print('Revision not found.'); exit(-1); } print('earliest revision: $flutterRevision'); print('Tags that contain this engine revision:'); print(await _tagsForRevision(flutterRevision)); exit(0); } flutterRevision = revision.flutterRevision; } } Future<void> _fetchUpstream([String workingDirectory = '.']) async { print('Fetching remotes for "$workingDirectory" - you may be prompted for SSH credentials by git.'); final ProcessResult fetchResult = await Process.run( 'git', <String>[ 'fetch', '--all', ], workingDirectory: workingDirectory, ); if (fetchResult.exitCode != 0) { throw Exception('Failed to fetch upstream in repository $workingDirectory'); } } Future<String> _tagsForRevision(String flutterRevision) async { final ProcessResult tagResult = await Process.run( 'git', <String>[ 'tag', '--contains', flutterRevision, ], ); return tagResult.stdout as String; } Future<bool> containsRevision(String ancestorRevision, String revision) async { final ProcessResult result = await Process.run( 'git', <String>[ 'merge-base', '--is-ancestor', ancestorRevision, revision, ], workingDirectory: engineRepo, ); return result.exitCode == 0; } Stream<FlutterEngineRevision> _logEngineVersions() async* { final ProcessResult result = await Process.run( 'git', <String>[ 'log', '--oneline', '-p', '--', 'bin/internal/engine.version', ], ); if (result.exitCode != 0) { print(result.stderr); throw Exception('Failed to log bin/internal/engine.version'); } final List<String> lines = (result.stdout as String).split('\n'); int index = 0; while (index < lines.length - 1) { final String flutterRevision = lines[index].split(' ').first; index += 1; while (!lines[index].startsWith('+') || lines[index].startsWith('+++')) { index += 1; } if (index >= lines.length) { break; } final String engineRevision = lines[index].substring(1); yield FlutterEngineRevision(flutterRevision, engineRevision); index += lines[index + 1].startsWith(r'\ ') ? 2 : 1; } } class FlutterEngineRevision { const FlutterEngineRevision(this.flutterRevision, this.engineRevision); final String flutterRevision; final String engineRevision; @override String toString() => '$flutterRevision: $engineRevision'; }