diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 021c5d785e..3d11dcb37e 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -131,7 +131,6 @@ class UpgradeCommandRunner { ); } recordState(flutterVersion); - await resetChanges(gitTagVersion); await upgradeChannel(flutterVersion); final bool alreadyUpToDate = await attemptFastForward(flutterVersion); if (alreadyUpToDate) { @@ -219,34 +218,6 @@ class UpgradeCommandRunner { } } - /// Attempts to reset to the last non-hotfix tag. - /// - /// If the git history is on a hotfix, doing a fast forward will not pick up - /// major or minor version upgrades. By resetting to the point before the - /// hotfix, doing a git fast forward should succeed. - Future resetChanges(GitTagVersion gitTagVersion) async { - String tag; - if (gitTagVersion == const GitTagVersion.unknown()) { - tag = 'v0.0.0'; - } else { - tag = 'v${gitTagVersion.x}.${gitTagVersion.y}.${gitTagVersion.z}'; - } - try { - await processUtils.run( - ['git', 'reset', '--hard', tag], - throwOnError: true, - workingDirectory: workingDirectory, - ); - } on ProcessException catch (error) { - throwToolExit( - 'Unable to upgrade Flutter: The tool could not update to the version $tag. ' - 'This may be due to git not being installed or an internal error. ' - 'Please ensure that git is installed on your computer and retry again.' - '\nError: $error.' - ); - } - } - /// Attempts to upgrade the channel. /// /// If the user is on a deprecated channel, attempts to migrate them off of diff --git a/packages/flutter_tools/lib/src/commands/version.dart b/packages/flutter_tools/lib/src/commands/version.dart index 6282698b35..6727a469fa 100644 --- a/packages/flutter_tools/lib/src/commands/version.dart +++ b/packages/flutter_tools/lib/src/commands/version.dart @@ -45,14 +45,14 @@ class VersionCommand extends FlutterCommand { RunResult runResult; try { runResult = await processUtils.run( - ['git', 'tag', '-l', 'v*', '--sort=-creatordate'], + ['git', 'tag', '-l', '*.*.*', '--sort=-creatordate'], throwOnError: true, workingDirectory: Cache.flutterRoot, ); } on ProcessException catch (error) { throwToolExit( 'Unable to get the tags. ' - 'This might be due to git not being installed or an internal error' + 'This is likely due to an internal git error.' '\nError: $error.' ); } @@ -88,8 +88,14 @@ class VersionCommand extends FlutterCommand { } final String version = argResults.rest[0].replaceFirst('v', ''); - if (!tags.contains('v$version')) { + final List matchingTags = tags.where((String tag) => tag.contains(version)).toList(); + String matchingTag; + // TODO(fujino): make this a tool exit and fix tests + if (matchingTags.isEmpty) { globals.printError('There is no version: $version'); + matchingTag = version; + } else { + matchingTag = matchingTags.first.trim(); } // check min supported version @@ -113,7 +119,7 @@ class VersionCommand extends FlutterCommand { try { await processUtils.run( - ['git', 'checkout', 'v$version'], + ['git', 'checkout', matchingTag], throwOnError: true, workingDirectory: Cache.flutterRoot, ); diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index 85e4fd7405..fa899e1aae 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -692,14 +692,26 @@ String _shortGitRevision(String revision) { return revision.length > 10 ? revision.substring(0, 10) : revision; } +/// Version of Flutter SDK parsed from git class GitTagVersion { - const GitTagVersion(this.x, this.y, this.z, this.hotfix, this.commits, this.hash); + const GitTagVersion({ + this.x, + this.y, + this.z, + this.hotfix, + this.devVersion, + this.devPatch, + this.commits, + this.hash, + }); const GitTagVersion.unknown() : x = null, y = null, z = null, hotfix = null, commits = 0, + devVersion = null, + devPatch = null, hash = ''; /// The X in vX.Y.Z. @@ -720,6 +732,12 @@ class GitTagVersion { /// The git hash (or an abbreviation thereof) for this commit. final String hash; + /// The N in X.Y.Z-dev.N.M + final int devVersion; + + /// The M in X.Y.Z-dev.N.M + final int devPatch; + static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false}) { if (fetchTags) { final String channel = _runGit('git rev-parse --abbrev-ref HEAD', processUtils, workingDirectory); @@ -729,18 +747,73 @@ class GitTagVersion { _runGit('git fetch $_flutterGit --tags', processUtils, workingDirectory); } } - return parse(_runGit('git describe --match v*.*.* --first-parent --long --tags', processUtils, workingDirectory)); + // `--match` glob must match old version tag `v1.2.3` and new `1.2.3-dev.4.5` + return parse(_runGit('git describe --match *.*.* --first-parent --long --tags', processUtils, workingDirectory)); } - static GitTagVersion parse(String version) { - final RegExp versionPattern = RegExp(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\+hotfix\.([0-9]+))?-([0-9]+)-g([a-f0-9]+)$'); + // TODO(fujino): Deprecate this https://github.com/flutter/flutter/issues/53850 + /// Check for the release tag format pre-v1.17.0 + static GitTagVersion parseLegacyVersion(String version) { + final RegExp versionPattern = RegExp( + r'^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\+hotfix\.([0-9]+))?-([0-9]+)-g([a-f0-9]+)$'); + final List parts = versionPattern.matchAsPrefix(version)?.groups([1, 2, 3, 4, 5, 6]); if (parts == null) { - globals.printTrace('Could not interpret results of "git describe": $version'); return const GitTagVersion.unknown(); } final List parsedParts = parts.take(5).map((String source) => source == null ? null : int.tryParse(source)).toList(); - return GitTagVersion(parsedParts[0], parsedParts[1], parsedParts[2], parsedParts[3], parsedParts[4], parts[5]); + return GitTagVersion( + x: parsedParts[0], + y: parsedParts[1], + z: parsedParts[2], + hotfix: parsedParts[3], + commits: parsedParts[4], + hash: parts[5], + ); + } + + /// Check for the release tag format from v1.17.0 on + static GitTagVersion parseVersion(String version) { + final RegExp versionPattern = RegExp( + r'^([0-9]+)\.([0-9]+)\.([0-9]+)(-dev\.[0-9]+\.[0-9]+)?-([0-9]+)-g([a-f0-9]+)$'); + final List parts = versionPattern.matchAsPrefix(version)?.groups([1, 2, 3, 4, 5, 6]); + if (parts == null) { + return const GitTagVersion.unknown(); + } + final List parsedParts = parts.take(5).map((String source) => source == null ? null : int.tryParse(source)).toList(); + List devParts = [null, null]; + if (parts[3] != null) { + devParts = RegExp(r'^-dev\.(\d+)\.(\d+)') + .matchAsPrefix(parts[3]) + ?.groups([1, 2]) + ?.map( + (String source) => source == null ? null : int.tryParse(source) + )?.toList() ?? [null, null]; + } + return GitTagVersion( + x: parsedParts[0], + y: parsedParts[1], + z: parsedParts[2], + devVersion: devParts[0], + devPatch: devParts[1], + commits: parsedParts[4], + hash: parts[5], + ); + } + + static GitTagVersion parse(String version) { + GitTagVersion gitTagVersion; + + gitTagVersion = parseLegacyVersion(version); + if (gitTagVersion != const GitTagVersion.unknown()) { + return gitTagVersion; + } + gitTagVersion = parseVersion(version); + if (gitTagVersion != const GitTagVersion.unknown()) { + return gitTagVersion; + } + globals.printTrace('Could not interpret results of "git describe": $version'); + return const GitTagVersion.unknown(); } String frameworkVersionFor(String revision) { 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 711c82a962..cdbed8666b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart @@ -42,7 +42,7 @@ void main() { 'version', '--no-pub', ]); - expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n')); + expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\r\n30.0.0-dev.0.0\n')); }, overrides: { ProcessManager: () => MockProcessManager(), Stdio: () => mockStdio, @@ -187,7 +187,7 @@ void main() { await createTestCommandRunner(command).run([ 'version', ]); - expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n')); + expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\r\n30.0.0-dev.0.0\n')); }, overrides: { ProcessManager: () => MockProcessManager(), Stdio: () => mockStdio, @@ -236,7 +236,7 @@ class MockProcessManager extends Mock implements ProcessManager { if (failGitTag) { return ProcessResult(0, 1, '', ''); } - return ProcessResult(0, 0, 'v10.0.0\r\nv20.0.0', ''); + return ProcessResult(0, 0, 'v10.0.0\r\nv20.0.0\r\n30.0.0-dev.0.0', ''); } if (command[0] == 'git' && command[1] == 'checkout') { version = command[2] as String; @@ -259,7 +259,7 @@ class MockProcessManager extends Mock implements ProcessManager { return ProcessResult(0, 0, '000000000000000000000', ''); } if (commandStr == - 'git describe --match v*.*.* --first-parent --long --tags') { + 'git describe --match *.*.* --first-parent --long --tags') { if (version.isNotEmpty) { return ProcessResult(0, 0, '$version-0-g00000000', ''); } diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index 8b616c4953..150dc266c9 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -29,7 +29,14 @@ void main() { MockProcessManager processManager; FakePlatform fakePlatform; final MockFlutterVersion flutterVersion = MockFlutterVersion(); - const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd'); + const GitTagVersion gitTagVersion = GitTagVersion( + x: 1, + y: 2, + z: 3, + hotfix: 4, + commits: 5, + hash: 'asd', + ); when(flutterVersion.channel).thenReturn('dev'); setUp(() { @@ -231,7 +238,7 @@ void main() { fakeProcessManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags', + 'git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags', ], stdout: 'v1.12.16-19-gb45b676af', ), @@ -336,9 +343,6 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { @override Future hasUncomittedChanges() async => willHaveUncomittedChanges; - @override - Future resetChanges(GitTagVersion gitTagVersion) async {} - @override Future upgradeChannel(FlutterVersion flutterVersion) async {} diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart index 476f245b58..3779ff895a 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart @@ -179,7 +179,7 @@ void main() { workingDirectory: Cache.flutterRoot)).thenReturn(result); when(processManager.runSync('git fetch https://github.com/flutter/flutter.git --tags'.split(' '), workingDirectory: Cache.flutterRoot)).thenReturn(result); - when(processManager.runSync('git describe --match v*.*.* --first-parent --long --tags'.split(' '), + when(processManager.runSync('git describe --match *.*.* --first-parent --long --tags'.split(' '), workingDirectory: Cache.flutterRoot)).thenReturn(result); when(processManager.runSync(FlutterVersion.gitLog('-n 1 --pretty=format:%ad --date=iso'.split(' ')), workingDirectory: Cache.flutterRoot)).thenReturn(result); diff --git a/packages/flutter_tools/test/general.shard/version_test.dart b/packages/flutter_tools/test/general.shard/version_test.dart index 2a71c5edab..83e8cab309 100644 --- a/packages/flutter_tools/test/general.shard/version_test.dart +++ b/packages/flutter_tools/test/general.shard/version_test.dart @@ -392,24 +392,44 @@ void main() { testUsingContext('GitTagVersion', () { const String hash = 'abcdef'; - expect(GitTagVersion.parse('v1.2.3-4-g$hash').frameworkVersionFor(hash), '1.2.4-pre.4'); - expect(GitTagVersion.parse('v98.76.54-32-g$hash').frameworkVersionFor(hash), '98.76.55-pre.32'); - expect(GitTagVersion.parse('v10.20.30-0-g$hash').frameworkVersionFor(hash), '10.20.30'); + GitTagVersion gitTagVersion; + + // legacy tag release format + gitTagVersion = GitTagVersion.parse('v1.2.3-4-g$hash'); + expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.4'); + expect(gitTagVersion.devVersion, null); + expect(gitTagVersion.devPatch, null); + + // new dev tag release format + gitTagVersion = GitTagVersion.parse('1.2.3-dev.4.5-13-g$hash'); + expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.13'); + expect(gitTagVersion.devVersion, 4); + expect(gitTagVersion.devPatch, 5); + + // new stable tag release format + gitTagVersion = GitTagVersion.parse('1.2.3-13-g$hash'); + expect(gitTagVersion.frameworkVersionFor(hash), '1.2.4-pre.13'); + expect(gitTagVersion.devVersion, null); + expect(gitTagVersion.devPatch, null); + + expect(GitTagVersion.parse('98.76.54-32-g$hash').frameworkVersionFor(hash), '98.76.55-pre.32'); + expect(GitTagVersion.parse('10.20.30-0-g$hash').frameworkVersionFor(hash), '10.20.30'); expect(GitTagVersion.parse('v1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash), '1.2.3+hotfix.2-pre.4'); - expect(GitTagVersion.parse('v7.2.4+hotfix.8-0-g$hash').frameworkVersionFor(hash), '7.2.4+hotfix.8'); expect(testLogger.traceText, ''); + expect(GitTagVersion.parse('1.2.3+hotfix.1-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown'); expect(GitTagVersion.parse('x1.2.3-4-g$hash').frameworkVersionFor(hash), '0.0.0-unknown'); - expect(GitTagVersion.parse('v1.0.0-unknown-0-g$hash').frameworkVersionFor(hash), '0.0.0-unknown'); + expect(GitTagVersion.parse('1.0.0-unknown-0-g$hash').frameworkVersionFor(hash), '0.0.0-unknown'); expect(GitTagVersion.parse('beta-1-g$hash').frameworkVersionFor(hash), '0.0.0-unknown'); - expect(GitTagVersion.parse('v1.2.3-4-gx$hash').frameworkVersionFor(hash), '0.0.0-unknown'); + expect(GitTagVersion.parse('1.2.3-4-gx$hash').frameworkVersionFor(hash), '0.0.0-unknown'); expect(testLogger.statusText, ''); expect(testLogger.errorText, ''); expect( testLogger.traceText, + 'Could not interpret results of "git describe": 1.2.3+hotfix.1-4-gabcdef\n' 'Could not interpret results of "git describe": x1.2.3-4-gabcdef\n' - 'Could not interpret results of "git describe": v1.0.0-unknown-0-gabcdef\n' + 'Could not interpret results of "git describe": 1.0.0-unknown-0-gabcdef\n' 'Could not interpret results of "git describe": beta-1-gabcdef\n' - 'Could not interpret results of "git describe": v1.2.3-4-gxabcdef\n', + 'Could not interpret results of "git describe": 1.2.3-4-gxabcdef\n', ); }); @@ -421,7 +441,7 @@ void main() { environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(105, 0, '', ''), ['git', 'fetch'])); when(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''), ['git', 'describe'])); @@ -439,7 +459,7 @@ void main() { environment: anyNamed('environment'), )); verify(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).called(1); @@ -458,7 +478,7 @@ void main() { environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(106, 0, '', ''), ['git', 'fetch'])); when(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(107, 0, 'v0.1.2-3-1234abcd', ''), ['git', 'describe'])); @@ -476,7 +496,7 @@ void main() { environment: anyNamed('environment'), )); verify(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).called(1); @@ -495,7 +515,7 @@ void main() { environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(109, 0, '', ''), ['git', 'fetch'])); when(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenReturn(RunResult(ProcessResult(110, 0, 'v0.1.2-3-1234abcd', ''), ['git', 'describe'])); @@ -513,7 +533,7 @@ void main() { environment: anyNamed('environment'), )).called(1); verify(processUtils.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).called(1); @@ -634,7 +654,7 @@ void fakeData( environment: anyNamed('environment'), )).thenReturn(ProcessResult(105, 0, '', '')); when(pm.runSync( - ['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'], + ['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'], workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenReturn(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', '')); diff --git a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart index 1e42316fd7..438efc3343 100644 --- a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart +++ b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart @@ -57,6 +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. exitCode = await processUtils.stream([ 'git', @@ -65,6 +66,7 @@ void main() { ], workingDirectory: parentDirectory.path, trace: true); expect(exitCode, 0); + print('Step 2'); // Step 2. Switch to the dev branch. exitCode = await processUtils.stream([ 'git', @@ -76,6 +78,7 @@ void main() { ], workingDirectory: testDirectory.path, trace: true); expect(exitCode, 0); + print('Step 3'); // Step 3. Revert to a prior version. exitCode = await processUtils.stream([ 'git', @@ -85,7 +88,8 @@ void main() { ], workingDirectory: testDirectory.path, trace: true); expect(exitCode, 0); - // Step 4. Upgrade to the newest dev. This should update the persistent + print('Step 4'); + // Step 4. Upgrade to the newest stable. This should update the persistent // tool state with the sha for v1.14.3 exitCode = await processUtils.stream([ flutterBin, @@ -95,6 +99,7 @@ void main() { ], workingDirectory: testDirectory.path, trace: true); expect(exitCode, 0); + print('Step 5'); // Step 5. Verify that the version is different. final RunResult versionResult = await processUtils.run([ 'git', @@ -107,6 +112,7 @@ void main() { ], workingDirectory: testDirectory.path); expect(versionResult.stdout, isNot(contains(_kInitialVersion))); + print('Step 6'); // Step 6. Downgrade back to initial version. exitCode = await processUtils.stream([ flutterBin, @@ -116,6 +122,7 @@ void main() { ], workingDirectory: testDirectory.path, trace: true); expect(exitCode, 0); + print('Step 7'); // Step 7. Verify downgraded version matches original version. final RunResult oldVersionResult = await processUtils.run([ 'git',