Unverified Commit 2396616e authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Support old and new git release tag formats (#53715)

parent ab14307e
......@@ -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<void> 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(
<String>['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
......
......@@ -45,14 +45,14 @@ class VersionCommand extends FlutterCommand {
RunResult runResult;
try {
runResult = await processUtils.run(
<String>['git', 'tag', '-l', 'v*', '--sort=-creatordate'],
<String>['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<String> 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(
<String>['git', 'checkout', 'v$version'],
<String>['git', 'checkout', matchingTag],
throwOnError: true,
workingDirectory: Cache.flutterRoot,
);
......
......@@ -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<String> parts = versionPattern.matchAsPrefix(version)?.groups(<int>[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<int> parsedParts = parts.take(5).map<int>((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<String> parts = versionPattern.matchAsPrefix(version)?.groups(<int>[1, 2, 3, 4, 5, 6]);
if (parts == null) {
return const GitTagVersion.unknown();
}
final List<int> parsedParts = parts.take(5).map<int>((String source) => source == null ? null : int.tryParse(source)).toList();
List<int> devParts = <int>[null, null];
if (parts[3] != null) {
devParts = RegExp(r'^-dev\.(\d+)\.(\d+)')
.matchAsPrefix(parts[3])
?.groups(<int>[1, 2])
?.map<int>(
(String source) => source == null ? null : int.tryParse(source)
)?.toList() ?? <int>[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) {
......
......@@ -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: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
Stdio: () => mockStdio,
......@@ -187,7 +187,7 @@ void main() {
await createTestCommandRunner(command).run(<String>[
'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: <Type, Generator>{
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', '');
}
......
......@@ -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(<FakeCommand>[
const FakeCommand(
command: <String>[
'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<bool> hasUncomittedChanges() async => willHaveUncomittedChanges;
@override
Future<void> resetChanges(GitTagVersion gitTagVersion) async {}
@override
Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
......
......@@ -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);
......
......@@ -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, '', ''), <String>['git', 'fetch']));
when(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''), <String>['git', 'describe']));
......@@ -439,7 +459,7 @@ void main() {
environment: anyNamed('environment'),
));
verify(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['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, '', ''), <String>['git', 'fetch']));
when(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(107, 0, 'v0.1.2-3-1234abcd', ''), <String>['git', 'describe']));
......@@ -476,7 +496,7 @@ void main() {
environment: anyNamed('environment'),
));
verify(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['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, '', ''), <String>['git', 'fetch']));
when(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(RunResult(ProcessResult(110, 0, 'v0.1.2-3-1234abcd', ''), <String>['git', 'describe']));
......@@ -513,7 +533,7 @@ void main() {
environment: anyNamed('environment'),
)).called(1);
verify(processUtils.runSync(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['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(
<String>['git', 'describe', '--match', 'v*.*.*', '--first-parent', '--long', '--tags'],
<String>['git', 'describe', '--match', '*.*.*', '--first-parent', '--long', '--tags'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(106, 0, 'v0.1.2-3-1234abcd', ''));
......
......@@ -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(<String>[
'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(<String>[
'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(<String>[
'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(<String>[
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(<String>[
'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(<String>[
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(<String>[
'git',
......
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