blob: 0956e3ca9ec3f34dc1ef49ebd46d356e13a70e22 [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;
import 'package:git/git.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
/// Finding diffs based on `baseGitDir` and `baseSha`.
class GitVersionFinder {
/// Constructor
GitVersionFinder(this.baseGitDir, {String? baseSha, String? baseBranch})
: assert(baseSha == null || baseBranch == null,
'At most one of baseSha and baseBranch can be provided'),
_baseSha = baseSha,
_baseBranch = baseBranch ?? 'FETCH_HEAD';
/// The top level directory of the git repo.
///
/// That is where the .git/ folder exists.
final GitDir baseGitDir;
/// The base sha used to get diff.
String? _baseSha;
/// The base branche used to find a merge point if baseSha is not provided.
final String _baseBranch;
static bool _isPubspec(String file) {
return file.trim().endsWith('pubspec.yaml');
}
/// Get a list of all the pubspec.yaml file that is changed.
Future<List<String>> getChangedPubSpecs() async {
return (await getChangedFiles()).where(_isPubspec).toList();
}
/// Get a list of all the changed files.
Future<List<String>> getChangedFiles(
{bool includeUncommitted = false}) async {
final String baseSha = await getBaseSha();
final io.ProcessResult changedFilesCommand = await baseGitDir
.runCommand(<String>[
'diff',
'--name-only',
baseSha,
if (!includeUncommitted) 'HEAD'
]);
final String changedFilesStdout = changedFilesCommand.stdout.toString();
if (changedFilesStdout.isEmpty) {
return <String>[];
}
final List<String> changedFiles = changedFilesStdout.split('\n')
..removeWhere((String element) => element.isEmpty);
return changedFiles.toList();
}
/// Get a list of all the changed files.
Future<List<String>> getDiffContents({
String? targetPath,
bool includeUncommitted = false,
}) async {
final String baseSha = await getBaseSha();
final io.ProcessResult diffCommand = await baseGitDir.runCommand(<String>[
'diff',
baseSha,
if (!includeUncommitted) 'HEAD',
if (targetPath != null) ...<String>['--', targetPath],
]);
final String diffStdout = diffCommand.stdout.toString();
if (diffStdout.isEmpty) {
return <String>[];
}
final List<String> changedFiles = diffStdout.split('\n')
..removeWhere((String element) => element.isEmpty);
return changedFiles.toList();
}
/// Get the package version specified in the pubspec file in `pubspecPath` and
/// at the revision of `gitRef` (defaulting to the base if not provided).
Future<Version?> getPackageVersion(String pubspecPath,
{String? gitRef}) async {
final String ref = gitRef ?? (await getBaseSha());
io.ProcessResult gitShow;
try {
gitShow =
await baseGitDir.runCommand(<String>['show', '$ref:$pubspecPath']);
} on io.ProcessException {
return null;
}
final String fileContent = gitShow.stdout as String;
if (fileContent.trim().isEmpty) {
return null;
}
final YamlMap fileYaml = loadYaml(fileContent) as YamlMap;
final String? versionString = fileYaml['version'] as String?;
return versionString == null ? null : Version.parse(versionString);
}
/// Returns the base used to diff against.
Future<String> getBaseSha() async {
String? baseSha = _baseSha;
if (baseSha != null && baseSha.isNotEmpty) {
return baseSha;
}
io.ProcessResult baseShaFromMergeBase = await baseGitDir.runCommand(
<String>['merge-base', '--fork-point', _baseBranch, 'HEAD'],
throwOnError: false);
final String stdout = (baseShaFromMergeBase.stdout as String? ?? '').trim();
final String stderr = (baseShaFromMergeBase.stderr as String? ?? '').trim();
if (stderr.isNotEmpty || stdout.isEmpty) {
baseShaFromMergeBase = await baseGitDir
.runCommand(<String>['merge-base', _baseBranch, 'HEAD']);
}
baseSha = (baseShaFromMergeBase.stdout as String).trim();
_baseSha = baseSha;
return baseSha;
}
}