blob: 27e6ab6b6d84d9c187319001f469c1df4cfcb447 [file] [log] [blame]
// 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.
// This script looks at the current commit and branch of the git repository in
// which it was run, and finds the contemporary commit in the master branch of
// another git repository, whose path is provided on the command line. The
// contemporary commit is the one that was public at the time of the last commit
// on the master branch before the current commit's branch was created.
import 'dart:io';
const bool debugLogging = false;
void log(String message) {
if (debugLogging) {
print(message);
}
}
class Commit {
Commit(this.hash, this.timestamp);
final String hash;
final DateTime timestamp;
static String formatArgument = '--format=%H %cI';
static Commit parse(String line) {
final int space = line.indexOf(' ');
return Commit(line.substring(0, space), DateTime.parse(line.substring(space+1, line.length).trimRight()));
}
static List<Commit> parseList(String lines) {
return lines.split('\n').where((String line) => line.isNotEmpty).map(parse).toList().reversed.toList();
}
}
String findCommit({
required String primaryRepoDirectory,
required String primaryBranch,
required String primaryTrunk,
required String secondaryRepoDirectory,
required String secondaryBranch,
}) {
final Commit anchor;
if (primaryBranch == primaryTrunk) {
log('on $primaryTrunk, using last commit as anchor');
anchor = Commit.parse(git(primaryRepoDirectory, <String>['log', Commit.formatArgument, '--max-count=1', primaryBranch, '--']));
} else {
final List<Commit> branchCommits = Commit.parseList(git(primaryRepoDirectory, <String>['log', Commit.formatArgument, primaryBranch, '--']));
final List<Commit> trunkCommits = Commit.parseList(git(primaryRepoDirectory, <String>['log', Commit.formatArgument, primaryTrunk, '--']));
if (branchCommits.isEmpty || trunkCommits.isEmpty || branchCommits.first.hash != trunkCommits.first.hash) {
throw StateError('Branch $primaryBranch does not seem to have a common history with trunk $primaryTrunk.');
}
if (branchCommits.last.hash == trunkCommits.last.hash) {
log('$primaryBranch is even with $primaryTrunk, using last commit as anchor');
anchor = trunkCommits.last;
} else {
int index = 0;
while (branchCommits.length > index && trunkCommits.length > index && branchCommits[index].hash == trunkCommits[index].hash) {
index += 1;
}
log('$primaryBranch branched from $primaryTrunk ${branchCommits.length - index} commits ago, trunk has advanced by ${trunkCommits.length - index} commits since then.');
anchor = trunkCommits[index - 1];
}
}
return git(secondaryRepoDirectory, <String>[
'log',
'--format=%H',
'--until=${anchor.timestamp.toIso8601String()}',
'--max-count=1',
secondaryBranch,
'--',
]);
}
String git(String workingDirectory, List<String> arguments) {
final ProcessResult result = Process.runSync('git', arguments, workingDirectory: workingDirectory);
if (result.exitCode != 0 || '${result.stderr}'.isNotEmpty) {
throw ProcessException('git', arguments, '${result.stdout}${result.stderr}', result.exitCode);
}
return '${result.stdout}';
}
void main(List<String> arguments) {
if (arguments.isEmpty || arguments.length > 2 || arguments.contains('--help') || arguments.contains('-h')) {
print(
'Usage: dart find_commit.dart [<path-to-primary-repo>] <path-to-secondary-repo>\n'
'This script will find the commit in the secondary repo that was contemporary\n'
'when the commit in the primary repo was created. If that commit is on a\n'
"branch, then the date of the branch's creation is used instead.\n"
'If <path-to-primary-repo> is omitted, the current directory is used for the\n'
'primary repo.'
);
} else {
final String primaryRepo = arguments.length == 1 ? '.' : arguments.first;
final String secondaryRepo = arguments.last;
print(findCommit(
primaryRepoDirectory: primaryRepo,
primaryBranch: git(primaryRepo, <String>['rev-parse', '--abbrev-ref', 'HEAD']).trim(),
primaryTrunk: 'master',
secondaryRepoDirectory: secondaryRepo,
secondaryBranch: 'master',
).trim());
}
}