[flutter_tool] Crash less when git fails during 'version' (#45628)
diff --git a/packages/flutter_tools/lib/src/commands/version.dart b/packages/flutter_tools/lib/src/commands/version.dart
index 7e77b96..b01975a 100644
--- a/packages/flutter_tools/lib/src/commands/version.dart
+++ b/packages/flutter_tools/lib/src/commands/version.dart
@@ -18,6 +18,7 @@
class VersionCommand extends FlutterCommand {
VersionCommand() : super() {
+ usesPubOption(hide: true);
argParser.addFlag('force',
abbr: 'f',
help: 'Force switch to older Flutter versions that do not include a version command',
@@ -117,7 +118,7 @@
printStatus(flutterVersion.toString());
final String projectRoot = findProjectRoot();
- if (projectRoot != null) {
+ if (projectRoot != null && shouldRunPub) {
printStatus('');
await pub.get(
context: PubContext.pubUpgrade,
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index f5b36de..f5bbf7c 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -596,8 +596,15 @@
final FlutterVersion version = FlutterVersion.instance;
versionChannel = version.channel;
frameworkVersion = version.frameworkVersion;
- messages.add(ValidationMessage(userMessages.flutterVersion(frameworkVersion, Cache.flutterRoot)));
- messages.add(ValidationMessage(userMessages.flutterRevision(version.frameworkRevisionShort, version.frameworkAge, version.frameworkDate)));
+ messages.add(ValidationMessage(userMessages.flutterVersion(
+ frameworkVersion,
+ Cache.flutterRoot,
+ )));
+ messages.add(ValidationMessage(userMessages.flutterRevision(
+ version.frameworkRevisionShort,
+ version.frameworkAge,
+ version.frameworkDate,
+ )));
messages.add(ValidationMessage(userMessages.engineRevision(version.engineRevisionShort)));
messages.add(ValidationMessage(userMessages.dartRevision(version.dartSdkVersion)));
} on VersionCheckError catch (e) {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 250de74..2771ef8 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -173,9 +173,10 @@
return bundle.defaultMainPath;
}
- void usesPubOption() {
+ void usesPubOption({bool hide = false}) {
argParser.addFlag('pub',
defaultsTo: true,
+ hide: hide,
help: 'Whether to run "flutter pub get" before executing this command.');
_usesPubOption = true;
}
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index 5c7702a..981127e 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -130,9 +130,19 @@
};
/// A date String describing the last framework commit.
- String get frameworkCommitDate => _latestGitCommitDate();
+ ///
+ /// If a git command fails, this will return a placeholder date.
+ String get frameworkCommitDate => _latestGitCommitDate(lenient: true);
- static String _latestGitCommitDate([ String branch ]) {
+ // The date of the latest commit on the given branch. If no branch is
+ // specified, then it is the current local branch.
+ //
+ // If lenient is true, and the git command fails, a placeholder date is
+ // returned. Otherwise, the VersionCheckError exception is propagated.
+ static String _latestGitCommitDate({
+ String branch,
+ bool lenient = false,
+ }) {
final List<String> args = gitLog(<String>[
if (branch != null) branch,
'-n',
@@ -140,7 +150,21 @@
'--pretty=format:%ad',
'--date=iso',
]);
- return _runSync(args, lenient: false);
+ try {
+ // Don't plumb 'lenient' through directly so that we can print an error
+ // if something goes wrong.
+ return _runSync(args, lenient: false);
+ } on VersionCheckError catch (e) {
+ if (lenient) {
+ final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0);
+ printError('Failed to find the latest git commit date: $e\n'
+ 'Returning $dummyDate instead.');
+ // Return something that DateTime.parse() can parse.
+ return dummyDate.toString();
+ } else {
+ rethrow;
+ }
+ }
}
/// The name of the temporary git remote used to check for the latest
@@ -153,8 +177,8 @@
/// The date of the latest framework commit in the remote repository.
///
- /// Throws [ToolExit] if a git command fails, for example, when the remote git
- /// repository is not reachable due to a network issue.
+ /// Throws [VersionCheckError] if a git command fails, for example, when the
+ /// remote git repository is not reachable due to a network issue.
static Future<String> fetchRemoteFrameworkCommitDate(String branch) async {
await _removeVersionCheckRemoteIfExists();
try {
@@ -166,7 +190,10 @@
'https://github.com/flutter/flutter.git',
]);
await _run(<String>['git', 'fetch', _versionCheckRemote, branch]);
- return _latestGitCommitDate('$_versionCheckRemote/$branch');
+ return _latestGitCommitDate(
+ branch: '$_versionCheckRemote/$branch',
+ lenient: false,
+ );
} finally {
await _removeVersionCheckRemoteIfExists();
}
@@ -219,7 +246,13 @@
String tentativeAncestorRevision,
}) {
final ProcessResult result = processManager.runSync(
- <String>['git', 'merge-base', '--is-ancestor', tentativeAncestorRevision, tentativeDescendantRevision],
+ <String>[
+ 'git',
+ 'merge-base',
+ '--is-ancestor',
+ tentativeAncestorRevision,
+ tentativeDescendantRevision
+ ],
workingDirectory: Cache.flutterRoot,
);
return result.exitCode == 0;
@@ -291,7 +324,16 @@
return;
}
- final DateTime localFrameworkCommitDate = DateTime.parse(frameworkCommitDate);
+ DateTime localFrameworkCommitDate;
+ try {
+ localFrameworkCommitDate = DateTime.parse(_latestGitCommitDate(
+ lenient: false
+ ));
+ } on VersionCheckError {
+ // Don't perform the update check if the verison check failed.
+ return;
+ }
+
final Duration frameworkAge = _clock.now().difference(localFrameworkCommitDate);
final bool installationSeemsOutdated = frameworkAge > versionAgeConsideredUpToDate(channel);
@@ -299,12 +341,11 @@
// to the server if we haven't checked recently so won't happen on every
// command.
final DateTime latestFlutterCommitDate = await _getLatestAvailableFlutterDate();
- final VersionCheckResult remoteVersionStatus =
- latestFlutterCommitDate == null
- ? VersionCheckResult.unknown
- : latestFlutterCommitDate.isAfter(localFrameworkCommitDate)
- ? VersionCheckResult.newVersionAvailable
- : VersionCheckResult.versionIsCurrent;
+ final VersionCheckResult remoteVersionStatus = latestFlutterCommitDate == null
+ ? VersionCheckResult.unknown
+ : latestFlutterCommitDate.isAfter(localFrameworkCommitDate)
+ ? VersionCheckResult.newVersionAvailable
+ : VersionCheckResult.versionIsCurrent;
// Do not load the stamp before the above server check as it may modify the stamp file.
final VersionCheckStamp stamp = await VersionCheckStamp.load();
@@ -314,15 +355,15 @@
// We show a warning if either we know there is a new remote version, or we couldn't tell but the local
// version is outdated.
final bool canShowWarning =
- remoteVersionStatus == VersionCheckResult.newVersionAvailable ||
- (remoteVersionStatus == VersionCheckResult.unknown &&
- installationSeemsOutdated);
+ remoteVersionStatus == VersionCheckResult.newVersionAvailable ||
+ (remoteVersionStatus == VersionCheckResult.unknown &&
+ installationSeemsOutdated);
if (beenAWhileSinceWarningWasPrinted && canShowWarning) {
final String updateMessage =
- remoteVersionStatus == VersionCheckResult.newVersionAvailable
- ? newVersionAvailableMessage()
- : versionOutOfDateMessage(frameworkAge);
+ remoteVersionStatus == VersionCheckResult.newVersionAvailable
+ ? newVersionAvailableMessage()
+ : versionOutOfDateMessage(frameworkAge);
printStatus(updateMessage, emphasis: true);
await Future.wait<void>(<Future<void>>[
stamp.store(
@@ -379,7 +420,9 @@
final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load();
if (versionCheckStamp.lastTimeVersionWasChecked != null) {
- final Duration timeSinceLastCheck = _clock.now().difference(versionCheckStamp.lastTimeVersionWasChecked);
+ final Duration timeSinceLastCheck = _clock.now().difference(
+ versionCheckStamp.lastTimeVersionWasChecked,
+ );
// Don't ping the server too often. Return cached value if it's fresh.
if (timeSinceLastCheck < checkAgeConsideredUpToDate) {
@@ -389,7 +432,9 @@
// Cache is empty or it's been a while since the last server ping. Ping the server.
try {
- final DateTime remoteFrameworkCommitDate = DateTime.parse(await FlutterVersion.fetchRemoteFrameworkCommitDate(channel));
+ final DateTime remoteFrameworkCommitDate = DateTime.parse(
+ await FlutterVersion.fetchRemoteFrameworkCommitDate(channel),
+ );
await versionCheckStamp.store(
newTimeVersionWasChecked: _clock.now(),
newKnownRemoteVersion: remoteFrameworkCommitDate,
@@ -533,7 +578,10 @@
/// If [lenient] is true and the command fails, returns an empty string.
/// Otherwise, throws a [ToolExit] exception.
String _runSync(List<String> command, { bool lenient = true }) {
- final ProcessResult results = processManager.runSync(command, workingDirectory: Cache.flutterRoot);
+ final ProcessResult results = processManager.runSync(
+ command,
+ workingDirectory: Cache.flutterRoot,
+ );
if (results.exitCode == 0) {
return (results.stdout as String).trim();
@@ -542,6 +590,7 @@
if (!lenient) {
throw VersionCheckError(
'Command exited with code ${results.exitCode}: ${command.join(' ')}\n'
+ 'Standard out: ${results.stdout}\n'
'Standard error: ${results.stderr}'
);
}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
index 45a7025..d9bf1e0 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
@@ -10,7 +10,6 @@
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/version.dart';
-import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
@@ -27,60 +26,95 @@
testUsingContext('version ls', () async {
final VersionCommand command = VersionCommand();
- await createTestCommandRunner(command).run(<String>['version']);
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ ]);
expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n' ''));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
- Pub: () => const Pub(),
});
testUsingContext('version switch', () async {
const String version = '10.0.0';
final VersionCommand command = VersionCommand();
- final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
- await Future.wait<void>(<Future<void>>[runCommand]);
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ version,
+ ]);
expect(testLogger.statusText, contains('Switching Flutter to version $version'));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
- Pub: () => const Pub(),
+ });
+
+ testUsingContext('version switch, latest commit query fails', () async {
+ const String version = '10.0.0';
+ final VersionCommand command = VersionCommand();
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ version,
+ ]);
+ expect(testLogger.errorText, contains('git failed'));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(latestCommitFails: true),
+ });
+
+ testUsingContext('latest commit is parsable when query fails', () {
+ final FlutterVersion flutterVersion = FlutterVersion();
+ expect(
+ () => DateTime.parse(flutterVersion.frameworkCommitDate),
+ returnsNormally,
+ );
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => MockProcessManager(latestCommitFails: true),
});
testUsingContext('switch to not supported version without force', () async {
const String version = '1.1.5';
final VersionCommand command = VersionCommand();
- final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
- await Future.wait<void>(<Future<void>>[runCommand]);
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ version,
+ ]);
expect(testLogger.errorText, contains('Version command is not supported in'));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
- Pub: () => const Pub(),
});
testUsingContext('switch to not supported version with force', () async {
const String version = '1.1.5';
final VersionCommand command = VersionCommand();
- final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', '--force', version]);
- await Future.wait<void>(<Future<void>>[runCommand]);
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ '--force',
+ version,
+ ]);
expect(testLogger.statusText, contains('Switching Flutter to version $version with force'));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
- Pub: () => const Pub(),
});
testUsingContext('tool exit on confusing version', () async {
const String version = 'master';
final VersionCommand command = VersionCommand();
- final Future<void> runCommand = createTestCommandRunner(command).run(<String>['version', version]);
- expect(() async => await Future.wait<void>(<Future<void>>[runCommand]),
- throwsA(isInstanceOf<ToolExit>()));
+ expect(() async =>
+ await createTestCommandRunner(command).run(<String>[
+ 'version',
+ '--no-pub',
+ version,
+ ]),
+ throwsA(isInstanceOf<ToolExit>()),
+ );
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
- Pub: () => const Pub(),
});
testUsingContext('exit tool if can\'t get the tags', () async {
final VersionCommand command = VersionCommand();
-
try {
await command.getTags();
fail('ToolExit expected');
@@ -94,11 +128,15 @@
}
class MockProcessManager extends Mock implements ProcessManager {
- MockProcessManager({ this.failGitTag = false });
+ MockProcessManager({
+ this.failGitTag = false,
+ this.latestCommitFails = false,
+ });
String version = '';
- bool failGitTag;
+ final bool failGitTag;
+ final bool latestCommitFails;
@override
Future<ProcessResult> run(
@@ -142,6 +180,14 @@
return ProcessResult(0, 0, '$version-0-g00000000', '');
}
}
+ final List<String> commitDateCommand = <String>[
+ '-n', '1',
+ '--pretty=format:%ad',
+ '--date=iso',
+ ];
+ if (latestCommitFails && commandStr == FlutterVersion.gitLog(commitDateCommand).join(' ')) {
+ return ProcessResult(0, -9, '', 'git failed');
+ }
return ProcessResult(0, 0, '', '');
}