Unverified Commit 4552af15 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tools] enable `flutter upgrade` to support force pushed branches (#55594)

parent f37a91a7
...@@ -100,7 +100,12 @@ class UpgradeCommandRunner { ...@@ -100,7 +100,12 @@ class UpgradeCommandRunner {
@required FlutterVersion flutterVersion, @required FlutterVersion flutterVersion,
@required bool testFlow, @required bool testFlow,
}) async { }) async {
await verifyUpstreamConfigured(); final String upstreamRevision = await fetchRemoteRevision();
if (flutterVersion.frameworkRevision == upstreamRevision) {
globals.printStatus('Flutter is already up to date on channel ${flutterVersion.channel}');
globals.printStatus('$flutterVersion');
return;
}
if (!force && gitTagVersion == const GitTagVersion.unknown()) { if (!force && gitTagVersion == const GitTagVersion.unknown()) {
// If the commit is a recognized branch and not master, // If the commit is a recognized branch and not master,
// explain that we are avoiding potential damage. // explain that we are avoiding potential damage.
...@@ -132,12 +137,8 @@ class UpgradeCommandRunner { ...@@ -132,12 +137,8 @@ class UpgradeCommandRunner {
} }
recordState(flutterVersion); recordState(flutterVersion);
await upgradeChannel(flutterVersion); await upgradeChannel(flutterVersion);
final bool alreadyUpToDate = await attemptFastForward(flutterVersion); await attemptReset(flutterVersion, upstreamRevision);
if (alreadyUpToDate) { if (!testFlow) {
// If the upgrade was a no op, then do not continue with the second half.
globals.printStatus('Flutter is already up to date on channel ${flutterVersion.channel}');
globals.printStatus('$flutterVersion');
} else if (!testFlow) {
await flutterUpgradeContinue(); await flutterUpgradeContinue();
} }
} }
...@@ -199,16 +200,25 @@ class UpgradeCommandRunner { ...@@ -199,16 +200,25 @@ class UpgradeCommandRunner {
return false; return false;
} }
/// Check if there is an upstream repository configured. /// Returns the remote HEAD revision.
/// ///
/// Exits tool if there is no upstream. /// Exits tool if there is no upstream.
Future<void> verifyUpstreamConfigured() async { Future<String> fetchRemoteRevision() async {
String revision;
try { try {
// Fetch upstream branch's commits and tags
await processUtils.run( await processUtils.run(
<String>[ 'git', 'rev-parse', '@{u}'], <String>['git', 'fetch', '--tags'],
throwOnError: true, throwOnError: true,
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
); );
// '@{u}' means upstream HEAD
final RunResult result = await processUtils.run(
<String>[ 'git', 'rev-parse', '--verify', '@{u}'],
throwOnError: true,
workingDirectory: workingDirectory,
);
revision = result.stdout.trim();
} on Exception { } on Exception {
throwToolExit( throwToolExit(
'Unable to upgrade Flutter: no origin repository configured. ' 'Unable to upgrade Flutter: no origin repository configured. '
...@@ -216,6 +226,7 @@ class UpgradeCommandRunner { ...@@ -216,6 +226,7 @@ class UpgradeCommandRunner {
"https://github.com/flutter/flutter' in $workingDirectory", "https://github.com/flutter/flutter' in $workingDirectory",
); );
} }
return revision;
} }
/// Attempts to upgrade the channel. /// Attempts to upgrade the channel.
...@@ -227,33 +238,20 @@ class UpgradeCommandRunner { ...@@ -227,33 +238,20 @@ class UpgradeCommandRunner {
await ChannelCommand.upgradeChannel(); await ChannelCommand.upgradeChannel();
} }
/// Attempts to rebase the upstream onto the local branch. /// Attempts a hard reset to the given revision.
///
/// If there haven't been any hot fixes or local changes, this is equivalent
/// to a fast-forward.
/// ///
/// If the fast forward lands us on the same channel and revision, then /// This is a reset instead of fast forward because if we are on a release
/// returns true, otherwise returns false. /// branch with cherry picks, there may not be a direct fast-forward route
Future<bool> attemptFastForward(FlutterVersion oldFlutterVersion) async { /// to the next release.
final int code = await processUtils.stream( Future<void> attemptReset(FlutterVersion oldFlutterVersion, String newRevision) async {
<String>['git', 'pull', '--ff-only'], final RunResult result = await processUtils.run(
<String>['git', 'reset', '--hard', newRevision],
throwOnError: true,
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
mapFunction: (String line) => matchesGitLine(line) ? null : line,
); );
if (code != 0) { if (result.exitCode != 0) {
throwToolExit(null, exitCode: code); throwToolExit(null, exitCode: result.exitCode);
} }
// Check if the upgrade did anything.
bool alreadyUpToDate = false;
try {
final FlutterVersion newFlutterVersion = FlutterVersion(const SystemClock(), workingDirectory);
alreadyUpToDate = newFlutterVersion.channel == oldFlutterVersion.channel &&
newFlutterVersion.frameworkRevision == oldFlutterVersion.frameworkRevision;
} on Exception catch (e) {
globals.printTrace('Failed to determine FlutterVersion after upgrade fast-forward: $e');
}
return alreadyUpToDate;
} }
/// Update the engine repository and precache all artifacts. /// Update the engine repository and precache all artifacts.
...@@ -300,18 +298,4 @@ class UpgradeCommandRunner { ...@@ -300,18 +298,4 @@ class UpgradeCommandRunner {
allowReentrantFlutter: true, allowReentrantFlutter: true,
); );
} }
// dev/benchmarks/complex_layout/lib/main.dart | 24 +-
static final RegExp _gitDiffRegex = RegExp(r' (\S+)\s+\|\s+\d+ [+-]+');
// rename {packages/flutter/doc => dev/docs}/styles.html (92%)
// delete mode 100644 doc/index.html
// create mode 100644 dev/integration_tests/flutter_gallery/lib/gallery/demo.dart
static final RegExp _gitChangedRegex = RegExp(r' (rename|delete mode|create mode) .+');
static bool matchesGitLine(String line) {
return _gitDiffRegex.hasMatch(line)
|| _gitChangedRegex.hasMatch(line)
|| line == 'Fast-forward';
}
} }
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.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/cache.dart';
import 'package:flutter_tools/src/commands/upgrade.dart'; import 'package:flutter_tools/src/commands/upgrade.dart';
import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/convert.dart';
...@@ -134,7 +133,10 @@ void main() { ...@@ -134,7 +133,10 @@ void main() {
}); });
testUsingContext("Doesn't continue on known tag, dev branch, no force, already up-to-date", () async { testUsingContext("Doesn't continue on known tag, dev branch, no force, already up-to-date", () async {
const String revision = 'abc123';
when(flutterVersion.frameworkRevision).thenReturn(revision);
fakeCommandRunner.alreadyUpToDate = true; fakeCommandRunner.alreadyUpToDate = true;
fakeCommandRunner.remoteRevision = revision;
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand( final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
force: false, force: false,
continueFlow: false, continueFlow: false,
...@@ -159,16 +161,26 @@ void main() { ...@@ -159,16 +161,26 @@ void main() {
Platform: () => fakePlatform, Platform: () => fakePlatform,
}); });
testUsingContext('verifyUpstreamConfigured', () async { testUsingContext('fetchRemoteRevision', () async {
when(globals.processManager.run( const String revision = 'abc123';
<String>['git', 'rev-parse', '@{u}'], when(processManager.run(
<String>['git', 'fetch', '--tags'],
environment:anyNamed('environment'), environment:anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')), workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((Invocation invocation) async { ).thenAnswer((Invocation invocation) async {
return FakeProcessResult() return FakeProcessResult()
..exitCode = 0; ..exitCode = 0;
}); });
await realCommandRunner.verifyUpstreamConfigured(); when(processManager.run(
<String>['git', 'rev-parse', '--verify', '@{u}'],
environment:anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((Invocation invocation) async {
return FakeProcessResult()
..exitCode = 0
..stdout = revision;
});
expect(await realCommandRunner.fetchRemoteRevision(), revision);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
Platform: () => fakePlatform, Platform: () => fakePlatform,
...@@ -284,58 +296,6 @@ void main() { ...@@ -284,58 +296,6 @@ void main() {
}); });
}); });
}); });
group('matchesGitLine', () {
setUpAll(() {
Cache.disableLocking();
});
bool _match(String line) => UpgradeCommandRunner.matchesGitLine(line);
test('regex match', () {
expect(_match(' .../flutter_gallery/lib/demo/buttons_demo.dart | 10 +--'), true);
expect(_match(' dev/benchmarks/complex_layout/lib/main.dart | 24 +-'), true);
expect(_match(' rename {packages/flutter/doc => dev/docs}/styles.html (92%)'), true);
expect(_match(' delete mode 100644 doc/index.html'), true);
expect(_match(' create mode 100644 dev/integration_tests/flutter_gallery/lib/gallery/demo.dart'), true);
expect(_match('Fast-forward'), true);
});
test("regex doesn't match", () {
expect(_match('Updating 79cfe1e..5046107'), false);
expect(_match('229 files changed, 6179 insertions(+), 3065 deletions(-)'), false);
});
group('findProjectRoot', () {
Directory tempDir;
setUp(() async {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_upgrade_test.');
});
tearDown(() {
tryToDelete(tempDir);
});
testUsingContext('in project', () async {
final String projectPath = await createProject(tempDir);
expect(findProjectRoot(projectPath), projectPath);
expect(findProjectRoot(globals.fs.path.join(projectPath, 'lib')), projectPath);
final String hello = globals.fs.path.join(Cache.flutterRoot, 'examples', 'hello_world');
expect(findProjectRoot(hello), hello);
expect(findProjectRoot(globals.fs.path.join(hello, 'lib')), hello);
});
testUsingContext('outside project', () async {
final String projectPath = await createProject(tempDir);
expect(findProjectRoot(globals.fs.directory(projectPath).parent.path), null);
expect(findProjectRoot(Cache.flutterRoot), null);
});
});
});
} }
class FakeUpgradeCommandRunner extends UpgradeCommandRunner { class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
...@@ -343,8 +303,10 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { ...@@ -343,8 +303,10 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
bool alreadyUpToDate = false; bool alreadyUpToDate = false;
String remoteRevision = '';
@override @override
Future<void> verifyUpstreamConfigured() async {} Future<String> fetchRemoteRevision() async => remoteRevision;
@override @override
Future<bool> hasUncomittedChanges() async => willHaveUncomittedChanges; Future<bool> hasUncomittedChanges() async => willHaveUncomittedChanges;
...@@ -353,7 +315,7 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { ...@@ -353,7 +315,7 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
Future<void> upgradeChannel(FlutterVersion flutterVersion) async {} Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
@override @override
Future<bool> attemptFastForward(FlutterVersion flutterVersion) async => alreadyUpToDate; Future<bool> attemptReset(FlutterVersion flutterVersion, String newRevision) async => alreadyUpToDate;
@override @override
Future<void> precacheArtifacts() async {} Future<void> precacheArtifacts() async {}
......
...@@ -57,8 +57,7 @@ void main() { ...@@ -57,8 +57,7 @@ void main() {
'git', 'config', '--system', 'core.longpaths', 'true', 'git', 'config', '--system', 'core.longpaths', 'true',
]); ]);
print('Step 1'); print('Step 1 - clone the $_kBranch of flutter into the test directory');
// Step 1. Clone the dev branch of flutter into the test directory.
exitCode = await processUtils.stream(<String>[ exitCode = await processUtils.stream(<String>[
'git', 'git',
'clone', 'clone',
...@@ -66,8 +65,7 @@ void main() { ...@@ -66,8 +65,7 @@ void main() {
], workingDirectory: parentDirectory.path, trace: true); ], workingDirectory: parentDirectory.path, trace: true);
expect(exitCode, 0); expect(exitCode, 0);
print('Step 2'); print('Step 2 - switch to the $_kBranch');
// Step 2. Switch to the dev branch.
exitCode = await processUtils.stream(<String>[ exitCode = await processUtils.stream(<String>[
'git', 'git',
'checkout', 'checkout',
...@@ -78,8 +76,7 @@ void main() { ...@@ -78,8 +76,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true); ], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0); expect(exitCode, 0);
print('Step 3'); print('Step 3 - revert back to $_kInitialVersion');
// Step 3. Revert to a prior version.
exitCode = await processUtils.stream(<String>[ exitCode = await processUtils.stream(<String>[
'git', 'git',
'reset', 'reset',
...@@ -88,9 +85,8 @@ void main() { ...@@ -88,9 +85,8 @@ void main() {
], workingDirectory: testDirectory.path, trace: true); ], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0); expect(exitCode, 0);
print('Step 4'); print('Step 4 - upgrade to the newest $_kBranch');
// Step 4. Upgrade to the newest stable. This should update the persistent // This should update the persistent tool state with the sha for HEAD
// tool state with the sha for v1.14.3
exitCode = await processUtils.stream(<String>[ exitCode = await processUtils.stream(<String>[
flutterBin, flutterBin,
'upgrade', 'upgrade',
...@@ -99,8 +95,7 @@ void main() { ...@@ -99,8 +95,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true); ], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0); expect(exitCode, 0);
print('Step 5'); print('Step 5 - verify that the version is different');
// Step 5. Verify that the version is different.
final RunResult versionResult = await processUtils.run(<String>[ final RunResult versionResult = await processUtils.run(<String>[
'git', 'git',
'describe', 'describe',
...@@ -111,8 +106,9 @@ void main() { ...@@ -111,8 +106,9 @@ void main() {
'--tags', '--tags',
], workingDirectory: testDirectory.path); ], workingDirectory: testDirectory.path);
expect(versionResult.stdout, isNot(contains(_kInitialVersion))); expect(versionResult.stdout, isNot(contains(_kInitialVersion)));
print('current version is ${versionResult.stdout.trim()}\ninitial was $_kInitialVersion');
print('Step 6'); print('Step 6 - downgrade back to the initial version');
// Step 6. Downgrade back to initial version. // Step 6. Downgrade back to initial version.
exitCode = await processUtils.stream(<String>[ exitCode = await processUtils.stream(<String>[
flutterBin, flutterBin,
...@@ -122,7 +118,7 @@ void main() { ...@@ -122,7 +118,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true); ], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0); expect(exitCode, 0);
print('Step 7'); print('Step 7 - verify downgraded version matches original version');
// Step 7. Verify downgraded version matches original version. // Step 7. Verify downgraded version matches original version.
final RunResult oldVersionResult = await processUtils.run(<String>[ final RunResult oldVersionResult = await processUtils.run(<String>[
'git', 'git',
...@@ -134,5 +130,6 @@ void main() { ...@@ -134,5 +130,6 @@ void main() {
'--tags', '--tags',
], workingDirectory: testDirectory.path); ], workingDirectory: testDirectory.path);
expect(oldVersionResult.stdout, contains(_kInitialVersion)); expect(oldVersionResult.stdout, contains(_kInitialVersion));
print('current version is ${oldVersionResult.stdout.trim()}\ninitial was $_kInitialVersion');
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment