blob: 66caa978e98600fc13daf45ced5f6423df1d19ae [file] [log] [blame]
// Copyright 2023 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';
import 'package:github/github.dart';
import 'package:logging/logging.dart';
import 'cli_command.dart';
import 'utilities.dart';
/// Class to wrap the command line calls to git.
class GitCli {
Logger logger = Logger('RepositoryManager');
static const String git = 'git';
static const String repositoryHttpPrefix = 'https://github.com/';
static const String repositorySshPrefix = 'git@github.com:';
late String repositoryPrefix;
late CliCommand _cliCommand;
GitCli(GitAccessMethod gitCloneMethod, CliCommand cliCommand) {
switch (gitCloneMethod) {
case GitAccessMethod.SSH:
repositoryPrefix = repositorySshPrefix;
break;
case GitAccessMethod.HTTP:
repositoryPrefix = repositoryHttpPrefix;
break;
}
_cliCommand = cliCommand;
}
/// Check to see if the current directory is a git repository.
Future<bool> isGitRepository(String directory) async {
final ProcessResult processResult = await _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'rev-parse',
],
throwOnError: false,
workingDirectory: directory,
);
return processResult.exitCode == 0;
}
/// Checkout repository if it does not currently exist on disk.
/// We will need to protect against multiple checkouts just in case multiple
/// calls occur at the same time.
Future<ProcessResult> cloneRepository({
required RepositorySlug slug,
required String workingDirectory,
required String targetDirectory,
List<String>? options,
bool throwOnError = true,
}) async {
final List<String> clone = <String>[
'clone',
'$repositoryPrefix${slug.fullName}',
targetDirectory,
];
if (options != null) {
clone.addAll(options);
}
final ProcessResult processResult = await _cliCommand.runCliCommand(
executable: git,
arguments: clone,
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
return processResult;
}
Future<ProcessResult> setupUserConfig({
required RepositorySlug slug,
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'config',
'--global',
'user.name',
'"auto-submit[bot]"',
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
Future<ProcessResult> setupUserEmailConfig({
required RepositorySlug slug,
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'config',
'--global',
'user.email',
// TODO not sure if the bot will end up needing a valid email.
// This might also be flutter-auto-submit-bot@google.com
'"flutter-engprod-team@google.com"',
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// This is necessary with forked repos but may not be necessary with the bot
/// as the bot has direct access to the repository.
Future<ProcessResult> setUpstream({
required RepositorySlug slug,
required String workingDirectory,
required String branchName,
required String token,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'remote',
'set-url',
'origin',
'https://autosubmit[bot]:$token@github.com/${slug.fullName}.git',
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// Fetch all new refs for the repository.
Future<ProcessResult> fetchAll({
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'fetch',
'--all',
],
throwOnError: throwOnError,
);
}
Future<ProcessResult> pullRebase({
required String? workingDirectory,
bool throwOnError = true,
}) async {
return _updateRepository(
workingDirectory: workingDirectory,
pullMethod: '--rebase',
throwOnError: throwOnError,
);
}
Future<ProcessResult> pullMerge({
required String? workingDirectory,
bool throwOnError = true,
}) async {
return _updateRepository(
workingDirectory: workingDirectory,
pullMethod: '--merge',
throwOnError: throwOnError,
);
}
/// Run the git pull rebase command to keep the repository up to date.
Future<ProcessResult> _updateRepository({
required String? workingDirectory,
required String pullMethod,
bool throwOnError = true,
}) async {
final ProcessResult processResult = await _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'pull',
pullMethod,
],
workingDirectory: workingDirectory,
);
return processResult;
}
/// Checkout and create a branch for the current edit.
///
/// TODO The strategy may be unneccessary here as the bot will not have to
/// create its own fork of the repo.
Future<ProcessResult> createBranch({
required String newBranchName,
required String workingDirectory,
bool useCheckout = false,
bool throwOnError = true,
}) async {
// Then create the new branch.
List<String> args;
if (useCheckout) {
args = <String>[
'checkout',
'-b',
newBranchName,
];
} else {
args = <String>[
'branch',
newBranchName,
];
}
return _cliCommand.runCliCommand(
executable: git,
arguments: args,
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// Revert a pull request commit.
Future<ProcessResult> revertChange({
required String commitSha,
required String workingDirectory,
bool throwOnError = true,
}) async {
// Issue a revert of the pull request.
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'revert',
'--no-edit',
'-m',
'1',
commitSha,
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// Push changes made to the local branch to github.
Future<ProcessResult> pushBranch({
required String branchName,
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'push',
'--verbose',
'origin',
branchName,
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// Delete a local branch from the repo.
Future<ProcessResult> deleteLocalBranch({
required String branchName,
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'branch',
'-D',
branchName,
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
/// Delete a remote branch from the repo.
///
/// When merging a pull request the pr branch is not automatically deleted.
Future<ProcessResult> deleteRemoteBranch({
required String branchName,
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'push',
'origin',
'--delete',
branchName,
],
throwOnError: throwOnError,
);
}
/// Get the remote origin of the current repository.
Future<ProcessResult> showOriginUrl({
required String workingDirectory,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'config',
'--get',
'remote.origin.url',
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
Future<ProcessResult> switchBranch({
required String workingDirectory,
required String branchName,
bool throwOnError = true,
}) async {
return _cliCommand.runCliCommand(
executable: git,
arguments: <String>[
'switch',
branchName,
],
workingDirectory: workingDirectory,
throwOnError: throwOnError,
);
}
}