Use merge-base in find_commit.dart (#138033)
Closes #97595 The prior approach of manually diffing the entire log chain is less efficient, and only found the original branch point ignoring subsequent merges. The limitation forced PR workflows into rebasing and force pushing new history to get the branch point far enough for CI to pass. Use `git merge-base` to find the latest common commit with the main branch. Add an `allowFailure` argument to the `git` utility to use a more specific failure in the case of no shared history when this command will fail with a generic error. Use `^branch` with the `git log` commands to exclude shared history and more easily count the unique commits on each branch. Drop the `Commit` abstraction. Parse directly to timestamp or line counts.
This commit is contained in:
parent
ed92828968
commit
fa3a37c76d
@ -18,23 +18,9 @@ void log(String 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();
|
||||
}
|
||||
}
|
||||
const String _commitTimestampFormat = '--format=%cI';
|
||||
DateTime _parseTimestamp(String line) => DateTime.parse(line.trim());
|
||||
int _countLines(String output) => output.trim().split('/n').where((String line) => line.isNotEmpty).length;
|
||||
|
||||
String findCommit({
|
||||
required String primaryRepoDirectory,
|
||||
@ -43,41 +29,39 @@ String findCommit({
|
||||
required String secondaryRepoDirectory,
|
||||
required String secondaryBranch,
|
||||
}) {
|
||||
final Commit anchor;
|
||||
final DateTime 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, '--']));
|
||||
log('on $primaryTrunk, using last commit time');
|
||||
anchor = _parseTimestamp(git(primaryRepoDirectory, <String>['log', _commitTimestampFormat, '--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) {
|
||||
final String mergeBase = git(primaryRepoDirectory, <String>['merge-base', primaryBranch, primaryTrunk], allowFailure: true).trim();
|
||||
if (mergeBase.isEmpty) {
|
||||
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;
|
||||
anchor = _parseTimestamp(git(primaryRepoDirectory, <String>['log', _commitTimestampFormat, '--max-count=1', mergeBase, '--']));
|
||||
if (debugLogging) {
|
||||
final int missingTrunkCommits = _countLines(git(primaryRepoDirectory, <String>['rev-list', primaryTrunk, '^$primaryBranch', '--']));
|
||||
final int extraCommits = _countLines(git(primaryRepoDirectory, <String>['rev-list', primaryBranch, '^$primaryTrunk', '--']));
|
||||
if (missingTrunkCommits == 0 && extraCommits == 0) {
|
||||
log('$primaryBranch is even with $primaryTrunk at $mergeBase');
|
||||
} else {
|
||||
log('$primaryBranch branched from $primaryTrunk $missingTrunkCommits commits ago, trunk has advanced by $extraCommits commits since then.');
|
||||
}
|
||||
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()}',
|
||||
'--until=${anchor.toIso8601String()}',
|
||||
'--max-count=1',
|
||||
secondaryBranch,
|
||||
'--',
|
||||
]);
|
||||
}
|
||||
|
||||
String git(String workingDirectory, List<String> arguments) {
|
||||
String git(String workingDirectory, List<String> arguments, {bool allowFailure = false}) {
|
||||
final ProcessResult result = Process.runSync('git', arguments, workingDirectory: workingDirectory);
|
||||
if (result.exitCode != 0 || '${result.stderr}'.isNotEmpty) {
|
||||
if (!allowFailure && result.exitCode != 0 || '${result.stderr}'.isNotEmpty) {
|
||||
throw ProcessException('git', arguments, '${result.stdout}${result.stderr}', result.exitCode);
|
||||
}
|
||||
return '${result.stdout}';
|
||||
@ -89,7 +73,7 @@ void main(List<String> arguments) {
|
||||
'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"
|
||||
"branch, then the date of the branch's last merge is used instead.\n"
|
||||
'If <path-to-primary-repo> is omitted, the current directory is used for the\n'
|
||||
'primary repo.'
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user