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 {
@required FlutterVersion flutterVersion,
@required bool testFlow,
}) 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 the commit is a recognized branch and not master,
// explain that we are avoiding potential damage.
......@@ -132,12 +137,8 @@ class UpgradeCommandRunner {
}
recordState(flutterVersion);
await upgradeChannel(flutterVersion);
final bool alreadyUpToDate = await attemptFastForward(flutterVersion);
if (alreadyUpToDate) {
// 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 attemptReset(flutterVersion, upstreamRevision);
if (!testFlow) {
await flutterUpgradeContinue();
}
}
......@@ -199,16 +200,25 @@ class UpgradeCommandRunner {
return false;
}
/// Check if there is an upstream repository configured.
/// Returns the remote HEAD revision.
///
/// Exits tool if there is no upstream.
Future<void> verifyUpstreamConfigured() async {
Future<String> fetchRemoteRevision() async {
String revision;
try {
// Fetch upstream branch's commits and tags
await processUtils.run(
<String>[ 'git', 'rev-parse', '@{u}'],
<String>['git', 'fetch', '--tags'],
throwOnError: true,
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 {
throwToolExit(
'Unable to upgrade Flutter: no origin repository configured. '
......@@ -216,6 +226,7 @@ class UpgradeCommandRunner {
"https://github.com/flutter/flutter' in $workingDirectory",
);
}
return revision;
}
/// Attempts to upgrade the channel.
......@@ -227,33 +238,20 @@ class UpgradeCommandRunner {
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
/// returns true, otherwise returns false.
Future<bool> attemptFastForward(FlutterVersion oldFlutterVersion) async {
final int code = await processUtils.stream(
<String>['git', 'pull', '--ff-only'],
/// This is a reset instead of fast forward because if we are on a release
/// branch with cherry picks, there may not be a direct fast-forward route
/// to the next release.
Future<void> attemptReset(FlutterVersion oldFlutterVersion, String newRevision) async {
final RunResult result = await processUtils.run(
<String>['git', 'reset', '--hard', newRevision],
throwOnError: true,
workingDirectory: workingDirectory,
mapFunction: (String line) => matchesGitLine(line) ? null : line,
);
if (code != 0) {
throwToolExit(null, exitCode: code);
}
// 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');
if (result.exitCode != 0) {
throwToolExit(null, exitCode: result.exitCode);
}
return alreadyUpToDate;
}
/// Update the engine repository and precache all artifacts.
......@@ -300,18 +298,4 @@ class UpgradeCommandRunner {
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 @@
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/convert.dart';
......@@ -134,7 +133,10 @@ void main() {
});
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.remoteRevision = revision;
final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
force: false,
continueFlow: false,
......@@ -159,16 +161,26 @@ void main() {
Platform: () => fakePlatform,
});
testUsingContext('verifyUpstreamConfigured', () async {
when(globals.processManager.run(
<String>['git', 'rev-parse', '@{u}'],
testUsingContext('fetchRemoteRevision', () async {
const String revision = 'abc123';
when(processManager.run(
<String>['git', 'fetch', '--tags'],
environment:anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((Invocation invocation) async {
return FakeProcessResult()
..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>{
ProcessManager: () => processManager,
Platform: () => fakePlatform,
......@@ -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 {
......@@ -343,8 +303,10 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
bool alreadyUpToDate = false;
String remoteRevision = '';
@override
Future<void> verifyUpstreamConfigured() async {}
Future<String> fetchRemoteRevision() async => remoteRevision;
@override
Future<bool> hasUncomittedChanges() async => willHaveUncomittedChanges;
......@@ -353,7 +315,7 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
@override
Future<bool> attemptFastForward(FlutterVersion flutterVersion) async => alreadyUpToDate;
Future<bool> attemptReset(FlutterVersion flutterVersion, String newRevision) async => alreadyUpToDate;
@override
Future<void> precacheArtifacts() async {}
......
......@@ -57,8 +57,7 @@ void main() {
'git', 'config', '--system', 'core.longpaths', 'true',
]);
print('Step 1');
// Step 1. Clone the dev branch of flutter into the test directory.
print('Step 1 - clone the $_kBranch of flutter into the test directory');
exitCode = await processUtils.stream(<String>[
'git',
'clone',
......@@ -66,8 +65,7 @@ void main() {
], workingDirectory: parentDirectory.path, trace: true);
expect(exitCode, 0);
print('Step 2');
// Step 2. Switch to the dev branch.
print('Step 2 - switch to the $_kBranch');
exitCode = await processUtils.stream(<String>[
'git',
'checkout',
......@@ -78,8 +76,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0);
print('Step 3');
// Step 3. Revert to a prior version.
print('Step 3 - revert back to $_kInitialVersion');
exitCode = await processUtils.stream(<String>[
'git',
'reset',
......@@ -88,9 +85,8 @@ void main() {
], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0);
print('Step 4');
// Step 4. Upgrade to the newest stable. This should update the persistent
// tool state with the sha for v1.14.3
print('Step 4 - upgrade to the newest $_kBranch');
// This should update the persistent tool state with the sha for HEAD
exitCode = await processUtils.stream(<String>[
flutterBin,
'upgrade',
......@@ -99,8 +95,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0);
print('Step 5');
// Step 5. Verify that the version is different.
print('Step 5 - verify that the version is different');
final RunResult versionResult = await processUtils.run(<String>[
'git',
'describe',
......@@ -111,8 +106,9 @@ void main() {
'--tags',
], workingDirectory: testDirectory.path);
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.
exitCode = await processUtils.stream(<String>[
flutterBin,
......@@ -122,7 +118,7 @@ void main() {
], workingDirectory: testDirectory.path, trace: true);
expect(exitCode, 0);
print('Step 7');
print('Step 7 - verify downgraded version matches original version');
// Step 7. Verify downgraded version matches original version.
final RunResult oldVersionResult = await processUtils.run(<String>[
'git',
......@@ -134,5 +130,6 @@ void main() {
'--tags',
], workingDirectory: testDirectory.path);
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