diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 81b32cd2b58..6ba8ecdac60 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -18,15 +18,6 @@ import '../version.dart'; import 'channel.dart'; class UpgradeCommand extends FlutterCommand { - UpgradeCommand() { - argParser.addFlag( - 'force', - abbr: 'f', - help: 'force upgrade the flutter branch, potentially discarding local changes.', - negatable: false, - ); - } - @override final String name = 'upgrade'; @@ -38,182 +29,64 @@ class UpgradeCommand extends FlutterCommand { @override Future runCommand() async { - final UpgradeCommandRunner upgradeCommandRunner = UpgradeCommandRunner(); - await upgradeCommandRunner.runCommand(argResults['force'], GitTagVersion.determine(), FlutterVersion.instance); - return null; - } -} - - -@visibleForTesting -class UpgradeCommandRunner { - Future runCommand(bool force, GitTagVersion gitTagVersion, FlutterVersion flutterVersion) async { - await verifyUpstreamConfigured(); - if (!force && gitTagVersion == const GitTagVersion.unknown()) { - // If the commit is a recognized branch and not master, - // explain that we are avoiding potential damage. - if (flutterVersion.channel != 'master' && FlutterVersion.officialChannels.contains(flutterVersion.channel)) { - throwToolExit( - 'Unknown flutter tag. Abandoning upgrade to avoid destroying local ' - 'changes. It is recommended to use git directly if not working off of ' - 'an official channel.' - ); - // Otherwise explain that local changes can be lost. - } else { - throwToolExit( - 'Unknown flutter tag. Abandoning upgrade to avoid destroying local ' - 'changes. If it is okay to remove local changes, then re-run this ' - 'command with --force.' - ); - } - } - final String stashName = await maybeStash(gitTagVersion); - await upgradeChannel(flutterVersion); - await attemptRebase(); - await precacheArtifacts(); - await updatePackages(flutterVersion); - await runDoctor(); - await applyStash(stashName); - return null; - } - - /// Check if there is an upstream repository configured. - /// - /// Exits tool if there is no upstream. - Future verifyUpstreamConfigured() async { try { await runCheckedAsync([ 'git', 'rev-parse', '@{u}', ], workingDirectory: Cache.flutterRoot); } catch (e) { - throwToolExit( - 'Unable to upgrade Flutter: no upstream repository configured. ' - 'Run \'git remote add upstream ' - 'https://github.com/flutter/flutter\' in ${Cache.flutterRoot}', - ); + throwToolExit('Unable to upgrade Flutter: no upstream repository configured.'); } - } - /// Attempt to stash any local changes. - /// - /// Returns the stash name if any changes were stashed. Exits tool if - /// `git stash` returns a non-zero exit code. - Future maybeStash(GitTagVersion gitTagVersion) async { - final String stashName = 'flutter-upgrade-from-v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}'; - try { - final RunResult runResult = await runCheckedAsync([ - 'git', 'stash', 'push', '-m', stashName - ]); - // output message will contain stash name if any changes were stashed.. - if (runResult.stdout.contains(stashName)) { - return stashName; - } - } catch (e) { - throwToolExit('Failed to stash local changes: $e'); - } - return null; - } + final FlutterVersion flutterVersion = FlutterVersion.instance; - /// Attempts to upgrade the channel. - /// - /// If the user is on a deprecated channel, attempts to migrate them off of - /// it. - Future upgradeChannel(FlutterVersion flutterVersion) async { printStatus('Upgrading Flutter from ${Cache.flutterRoot}...'); - await ChannelCommand.upgradeChannel(); - } - /// Attempts to rebase the upstream onto the local branch. - /// - /// If there haven't been any hot fixes or local changes, this is equivalent - /// to a fast-forward. - Future attemptRebase() async { - final int code = await runCommandAndStreamOutput( - ['git', 'pull', '--rebase'], + await ChannelCommand.upgradeChannel(); + + int code = await runCommandAndStreamOutput( + ['git', 'pull', '--ff-only'], workingDirectory: Cache.flutterRoot, mapFunction: (String line) => matchesGitLine(line) ? null : line, ); - if (code != 0) { - printError('git rebase failed'); - final int undoCode = await runCommandAndStreamOutput( - ['git', 'rebase', '--abort'], - workingDirectory: Cache.flutterRoot, - mapFunction: (String line) => matchesGitLine(line) ? null : line, - ); - if (undoCode != 0) { - printError( - 'Failed to apply rebase: The flutter installation at' - ' ${Cache.flutterRoot} may be corrupted. A reinstallation of Flutter ' - 'is recommended' - ); - } - throwToolExit(null, exitCode: code); - } - } - /// Update the engine repository and precache all artifacts. - /// - /// Check for and download any engine and pkg/ updates. We run the 'flutter' - /// shell script re-entrantly here so that it will download the updated - /// Dart and so forth if necessary. - Future precacheArtifacts() async { + if (code != 0) + throwToolExit(null, exitCode: code); + + // Check for and download any engine and pkg/ updates. + // We run the 'flutter' shell script re-entrantly here + // so that it will download the updated Dart and so forth + // if necessary. printStatus(''); printStatus('Upgrading engine...'); - final int code = await runCommandAndStreamOutput( + code = await runCommandAndStreamOutput( [ fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache', ], workingDirectory: Cache.flutterRoot, allowReentrantFlutter: true, ); - if (code != 0) { - throwToolExit(null, exitCode: code); - } - } - /// Update the user's packages. - Future updatePackages(FlutterVersion flutterVersion) async { printStatus(''); printStatus(flutterVersion.toString()); + final String projectRoot = findProjectRoot(); if (projectRoot != null) { printStatus(''); await pubGet(context: PubContext.pubUpgrade, directory: projectRoot, upgrade: true, checkLastModified: false); } - } - /// Run flutter doctor in case requirements have changed. - Future runDoctor() async { + // Run a doctor check in case system requirements have changed. printStatus(''); printStatus('Running flutter doctor...'); - await runCommandAndStreamOutput( + code = await runCommandAndStreamOutput( [ fs.path.join('bin', 'flutter'), '--no-version-check', 'doctor', ], workingDirectory: Cache.flutterRoot, allowReentrantFlutter: true, ); - } - /// Pop stash changes if [stashName] is non-null and contained in stash. - Future applyStash(String stashName) async { - if (stashName == null) { - return; - } - try { - final RunResult result = await runCheckedAsync([ - 'git', 'stash', 'list' - ]); - if (!result.stdout.contains(stashName)) { - // print the same warning as if this threw. - throw Exception(); - } - await runCheckedAsync([ - 'git', 'stash', 'pop', - ]); - } catch (e) { - printError('Failed to re-apply local changes. State may have been lost.'); - } + return null; } // dev/benchmarks/complex_layout/lib/main.dart | 24 +- @@ -224,6 +97,7 @@ class UpgradeCommandRunner { // create mode 100644 examples/flutter_gallery/lib/gallery/demo.dart static final RegExp _gitChangedRegex = RegExp(r' (rename|delete mode|create mode) .+'); + @visibleForTesting static bool matchesGitLine(String line) { return _gitDiffRegex.hasMatch(line) || _gitChangedRegex.hasMatch(line) diff --git a/packages/flutter_tools/test/commands/upgrade_test.dart b/packages/flutter_tools/test/commands/upgrade_test.dart index d2a17c98014..f4a0f0da805 100644 --- a/packages/flutter_tools/test/commands/upgrade_test.dart +++ b/packages/flutter_tools/test/commands/upgrade_test.dart @@ -2,109 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/upgrade.dart'; -import 'package:flutter_tools/src/runner/flutter_command.dart'; -import 'package:flutter_tools/src/version.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; import '../src/common.dart'; import '../src/context.dart'; void main() { - group('UpgradeCommandRunner', () { - FakeUpgradeCommandRunner fakeCommandRunner; - UpgradeCommandRunner realCommandRunner; - MockProcessManager processManager; - final MockFlutterVersion flutterVersion = MockFlutterVersion(); - const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd'); - when(flutterVersion.channel).thenReturn('dev'); - - setUp(() { - fakeCommandRunner = FakeUpgradeCommandRunner(); - realCommandRunner = UpgradeCommandRunner(); - processManager = MockProcessManager(); - }); - - test('throws on unknown tag, official branch, noforce', () async { - final Future result = fakeCommandRunner.runCommand( - false, - const GitTagVersion.unknown(), - flutterVersion, - ); - expect(result, throwsA(isInstanceOf())); - }); - - test('does not throw on unknown tag, official branch, force', () async { - final Future result = fakeCommandRunner.runCommand( - true, - const GitTagVersion.unknown(), - flutterVersion, - ); - expect(await result, null); - }); - - test('Doesn\'t throw on known tag, dev branch, no force', () async { - final Future result = fakeCommandRunner.runCommand( - false, - gitTagVersion, - flutterVersion, - ); - expect(await result, null); - }); - - test('Only pops stash if it was pushed', () async { - fakeCommandRunner.stashName = 'test'; - final Future result = fakeCommandRunner.runCommand( - false, - gitTagVersion, - flutterVersion, - ); - expect(await result, null); - expect(fakeCommandRunner.appliedStashName, 'test'); - }); - - testUsingContext('verifyUpstreamConfigured', () async { - when(processManager.run( - ['git', 'rev-parse', '@{u}'], - environment:anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory')) - ).thenAnswer((Invocation invocation) async { - return FakeProcessResult() - ..exitCode = 0; - }); - await realCommandRunner.verifyUpstreamConfigured(); - }, overrides: { - ProcessManager: () => processManager, - }); - - testUsingContext('maybeStash', () async { - final String stashName = 'flutter-upgrade-from-v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}'; - when(processManager.run( - ['git', 'stash', 'push', '-m', stashName], - environment:anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory')) - ).thenAnswer((Invocation invocation) async { - return FakeProcessResult() - ..exitCode = 0; - }); - await realCommandRunner.maybeStash(gitTagVersion); - }, overrides: { - ProcessManager: () => processManager, - }); - }); - - group('matchesGitLine', () { + group('upgrade', () { setUpAll(() { Cache.disableLocking(); }); - bool _match(String line) => UpgradeCommandRunner.matchesGitLine(line); + bool _match(String line) => UpgradeCommand.matchesGitLine(line); test('regex match', () { expect(_match(' .../flutter_gallery/lib/demo/buttons_demo.dart | 10 +--'), true); @@ -151,52 +63,3 @@ void main() { }); }); } - -class FakeUpgradeCommandRunner extends UpgradeCommandRunner { - String stashName; - String appliedStashName; - - @override - Future verifyUpstreamConfigured() async {} - - @override - Future maybeStash(GitTagVersion gitTagVersion) async { - return stashName; - } - - @override - Future upgradeChannel(FlutterVersion flutterVersion) async {} - - @override - Future attemptRebase() async {} - - @override - Future precacheArtifacts() async {} - - @override - Future updatePackages(FlutterVersion flutterVersion) async {} - - @override - Future runDoctor() async {} - - @override - Future applyStash(String stashName) async { - appliedStashName = stashName; - } -} - -class MockFlutterVersion extends Mock implements FlutterVersion {} -class MockProcessManager extends Mock implements ProcessManager {} -class FakeProcessResult implements ProcessResult { - @override - int exitCode; - - @override - int pid = 0; - - @override - String stderr = ''; - - @override - String stdout = ''; -}