blob: ab3472484bf91dbba075c02bdcb8fd6dfddc9395 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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' show ProcessException;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:nnbd_migration/src/fantasyland/fantasy_repo.dart';
import 'package:nnbd_migration/src/fantasyland/fantasy_workspace_impl.dart';
import 'package:nnbd_migration/src/utilities/subprocess_launcher.dart';
const _httpGithub = 'https://github.com';
class FantasyRepoDependencies {
final ResourceProvider resourceProvider;
final SubprocessLauncher launcher;
FantasyRepoDependencies(
{ResourceProvider resourceProvider,
String name,
SubprocessLauncher launcher})
: resourceProvider =
resourceProvider ?? PhysicalResourceProvider.INSTANCE,
launcher = launcher ??
SubprocessLauncher(
'FantasyRepo.${name == null ? "buildFrom" : "buildFrom-$name"}');
factory FantasyRepoDependencies.fromWorkspaceDependencies(
FantasyWorkspaceDependencies workspaceDependencies) {
return FantasyRepoDependencies(
resourceProvider: workspaceDependencies.resourceProvider,
launcher: workspaceDependencies.launcher);
}
}
/// Represent a single git clone that may be referred to by one or more
/// [FantasySubPackage]s.
class FantasyRepoGitImpl extends FantasyRepo {
final String name;
final FantasyRepoSettings repoSettings;
final String _repoRootPath;
Folder get repoRoot => _external.resourceProvider.getFolder(_repoRootPath);
final FantasyRepoDependencies _external;
FantasyRepoGitImpl(this.repoSettings, this._repoRootPath,
{FantasyRepoDependencies fantasyRepoDependencies})
: name = repoSettings.name,
_external = fantasyRepoDependencies ??
FantasyRepoDependencies(name: repoSettings.name);
bool _isInitialized = false;
/// Call exactly once per [FantasyRepoGitImpl].
///
/// May throw [FantasyRepoException] in the event of problems and does
/// not clean up filesystem state.
Future<void> init() async {
assert(_isInitialized == false);
if (repoRoot.exists) {
await _update(_external.launcher);
// TODO(jcollins-g): handle "update" of pinned revision edge case
} else {
await _clone(_external.launcher);
}
_isInitialized = true;
return;
}
/// Configure a git repository locally and initialize it.
///
/// Throws [FantasyRepoCloneException] in the event we can not finish
/// initializing.
Future<void> _clone(SubprocessLauncher launcher) async {
assert(_isInitialized == false);
repoRoot.parent.create();
await launcher.runStreamed('git', ['init', repoRoot.path]);
await launcher.runStreamed(
'git',
[
'remote',
'add',
'origin',
'-t',
repoSettings.branch,
repoSettings.clone
],
workingDirectory: repoRoot.path);
String cloneHttp =
repoSettings.clone.replaceFirst('$githubHost:', '$_httpGithub/');
await launcher.runStreamed('git',
['remote', 'add', 'originHTTP', '-t', repoSettings.branch, cloneHttp],
workingDirectory: repoRoot.path);
// Do not get the working directory wrong on this command or it could
// alter a user's repository config based on the CWD, which is bad. Other
// commands in [FantasyRepo] will not fail silently with permanent,
// confusing results, but this one can.
await launcher.runStreamed('git', ['config', 'core.sparsecheckout', 'true'],
workingDirectory: repoRoot.path);
File sparseCheckout = _external.resourceProvider.getFile(_external
.resourceProvider.pathContext
.join(repoRoot.path, '.git', 'info', 'sparse-checkout'));
sparseCheckout.writeAsStringSync([
'**\n',
'!**/.packages\n',
'!**/pubspec.lock\n',
'!**/.dart_tool/package_config.json\n'
].join());
await launcher.runStreamed(
'git', ['pull', '--depth=1', '--rebase', 'originHTTP'],
workingDirectory: repoRoot.path, retries: 5);
}
Future<void> _update(SubprocessLauncher launcher) async {
assert(_isInitialized == false);
try {
await launcher.runStreamed(
'git', ['pull', '--rebase', 'originHTTP', repoSettings.revision],
workingDirectory: repoRoot.path, retries: 5);
} catch (e) {
if (e is ProcessException) {
throw FantasyRepoUpdateException(
'Unable to update clone for: $repoSettings');
}
rethrow;
}
}
}