blob: 49d55a442efc16d3261200f7ca9d13872735bbaf [file] [log] [blame]
// Copyright 2013 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' as io show Directory, File;
import 'package:path/path.dart' as path;
import 'package:process_runner/process_runner.dart';
/// Utility methods for working with a git repo.
class GitRepo {
/// The git repository rooted at `root`.
GitRepo(this.root, {
this.verbose = false,
});
/// Whether to produce verbose log output.
final bool verbose;
/// The root of the git repo.
final io.Directory root;
List<io.File>? _changedFiles;
/// Returns a list of all non-deleted files which differ from the nearest
/// merge-base with `main`. If it can't find a fork point, uses the default
/// merge-base.
///
/// This is only computed once and cached. Subsequent invocations of the
/// getter will return the same result.
Future<List<io.File>> get changedFiles async =>
_changedFiles ??= await _getChangedFiles();
List<io.File>? _changedFilesAtHead;
/// Returns a list of non-deleted files which differ between the HEAD
/// commit and its parent.
Future<List<io.File>> get changedFilesAtHead async =>
_changedFilesAtHead ??= await _getChangedFilesAtHead();
Future<List<io.File>> _getChangedFiles() async {
final ProcessRunner processRunner = ProcessRunner(
defaultWorkingDirectory: root,
);
await _fetch(processRunner);
ProcessRunnerResult mergeBaseResult = await processRunner.runProcess(
<String>['git', 'merge-base', '--fork-point', 'FETCH_HEAD', 'HEAD'],
failOk: true,
);
if (mergeBaseResult.exitCode != 0) {
mergeBaseResult = await processRunner.runProcess(<String>[
'git',
'merge-base',
'FETCH_HEAD',
'HEAD',
]);
}
final String mergeBase = mergeBaseResult.stdout.trim();
final ProcessRunnerResult masterResult = await processRunner
.runProcess(<String>[
'git',
'diff',
'--name-only',
'--diff-filter=ACMRT',
mergeBase,
]);
return _gitOutputToList(masterResult);
}
Future<List<io.File>> _getChangedFilesAtHead() async {
final ProcessRunner processRunner = ProcessRunner(
defaultWorkingDirectory: root,
);
await _fetch(processRunner);
final ProcessRunnerResult diffTreeResult = await processRunner.runProcess(
<String>[
'git',
'diff-tree',
'--no-commit-id',
'--name-only',
'--diff-filter=ACMRT', // Added, copied, modified, renamed, or type-changed.
'-r',
'HEAD',
],
);
return _gitOutputToList(diffTreeResult);
}
Future<void> _fetch(ProcessRunner processRunner) async {
final ProcessRunnerResult fetchResult = await processRunner.runProcess(
<String>['git', 'fetch', 'upstream', 'main'],
failOk: true,
);
if (fetchResult.exitCode != 0) {
await processRunner.runProcess(<String>[
'git',
'fetch',
'origin',
'main',
]);
}
}
List<io.File> _gitOutputToList(ProcessRunnerResult result) {
final String diffOutput = result.stdout.trim();
if (verbose) {
print('git diff output:\n$diffOutput');
}
final Set<String> resultMap = <String>{};
resultMap.addAll(diffOutput.split('\n').where(
(String str) => str.isNotEmpty,
));
return resultMap.map<io.File>(
(String filePath) => io.File(path.join(root.path, filePath)),
).toList();
}
}