Use an alternative to git describe for master version resolution (#174088)

Closes https://github.com/flutter/flutter/issues/173904.

It's not clear to me how `git describe --tags HEAD` ever ... worked to
determine a fallback. From what I can tell, the _intent_ was to use the
latest (newest? closest?) tag as the base version, and then append
`-{commitCount}-{shortHash}` as the fallback version number when on
`master` (or any non-published branch).

So, I rewrote the implementation, unfortunately with 4 separate calls
out to `git ...` instead of a single one.

There are 20+ tests that fail as a result of this change, mostly because
they make specific expectations around `git describe` being invoked, and
of course that is no longer the case - putting those aside, I'd like to
double check that:

1. I understand what the original command was _intending_ to do
2. We like the _output_ of the updated command
3. ... we either are okay with the implementation, or have an
alternative in mind (I'm no `git` master)

Wdyt?

At this commit:
```sh
$ flutter-dev --version                                                            
Flutter 3.36.0-1.0.pre-170 • channel [user-branch] • https://github.com/matanlurey/flutter
Framework • revision 250381a185 (5 minutes ago) • 2025-08-19 18:57:36 -0700
Engine • hash f278b0aa3b8c6732ab636563eb8e896c35fc9c79 (revision 9ac4facf7e) (2 hours ago) • 2025-08-19 23:42:28.000Z
Tools • Dart 3.10.0 (build 3.10.0-115.0.dev) • DevTools 2.49.0
```

/cc @zanderso @jmagman for historics.
This commit is contained in:
Matan Lurey 2025-08-21 13:04:32 -07:00 committed by GitHub
parent 4d4a69f14f
commit e45fd36aac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 124 additions and 74 deletions

View File

@ -1049,23 +1049,47 @@ class GitTagVersion {
}
}
// If we're not currently on a tag, use git describe to find the most
// recent tag and number of commits past.
return parse(
git
.runSync([
'describe',
'--match',
'*.*.*',
'--long',
'--tags',
gitRef,
], workingDirectory: workingDirectory)
.stdout
.trim(),
// If we don't exist in a tag, use git to find the latest tag.
return _useNewestTagAndCommitsPastFallback(
git: git,
workingDirectory: workingDirectory,
gitRef: gitRef,
);
}
static GitTagVersion _useNewestTagAndCommitsPastFallback({
required Git git,
required String? workingDirectory,
required String gitRef,
}) {
final String latestTag = git
.runSync([
'for-each-ref',
'--sort=-v:refname',
'--count=1',
'--format=%(refname:short)',
'refs/tags/[0-9]*.*.*',
], workingDirectory: workingDirectory)
.stdout
.trim();
final String ancestorRef = git
.runSync(['merge-base', gitRef, latestTag], workingDirectory: workingDirectory)
.stdout
.trim();
final String commitCount = git
.runSync([
'rev-list',
'--count',
'$ancestorRef..$gitRef',
], workingDirectory: workingDirectory)
.stdout
.trim();
return parse('$latestTag-$commitCount');
}
/// Parse a version string.
///
/// The version string can either be an exact release tag (e.g. '1.2.3' for
@ -1109,6 +1133,7 @@ class GitTagVersion {
);
}
@visibleForTesting
static GitTagVersion parse(String version) {
GitTagVersion gitTagVersion;

View File

@ -291,9 +291,8 @@ void main() {
command: <String>['git', 'rev-parse', '--verify', '@{upstream}'],
stdout: revision,
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', revision]),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', revision],
command: <String>['git', 'tag', '--points-at', revision],
stdout: version,
),
]);

View File

@ -16,6 +16,7 @@ import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/git.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/version.dart';
import 'package:meta/meta.dart';
import 'package:test/fake.dart';
import '../src/common.dart';
@ -57,6 +58,34 @@ void main() {
);
});
/// Mocks the series of commands used to determine the Flutter version for `master`.
@useResult
List<FakeCommand> mockGitTagHistory({
required String latestTag,
required String headRef,
required String ancestorRef,
required int commitsBetweenRefs,
}) {
return [
FakeCommand(
command: const [
'git',
'for-each-ref',
'--sort=-v:refname',
'--count=1',
'--format=%(refname:short)',
'refs/tags/[0-9]*.*.*',
],
stdout: latestTag,
),
FakeCommand(command: ['git', 'merge-base', headRef, latestTag], stdout: ancestorRef),
FakeCommand(
command: ['git', 'rev-list', '--count', '$ancestorRef..$headRef'],
stdout: '$commitsBetweenRefs',
),
];
}
for (final String channel in kOfficialChannels) {
DateTime getChannelUpToDateVersion() {
return _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate(channel) ~/ 2);
@ -100,17 +129,11 @@ void main() {
stdout: '1234abcd',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>[
'git',
'describe',
'--match',
'*.*.*',
'--long',
'--tags',
'1234abcd',
],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '',
headRef: '1234abcd',
ancestorRef: '',
commitsBetweenRefs: 0,
),
FakeCommand(
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -278,17 +301,11 @@ void main() {
],
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>[
'git',
'describe',
'--match',
'*.*.*',
'--long',
'--tags',
'1234abcd',
],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '0.1.2-3',
headRef: '1234abcd',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 170,
),
FakeCommand(
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -467,17 +484,11 @@ void main() {
stdout: '1234abcd',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>[
'git',
'describe',
'--match',
'*.*.*',
'--long',
'--tags',
'1234abcd',
],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '0.1.2-3',
headRef: '1234abcd',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 170,
),
FakeCommand(
command: const <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -859,9 +870,11 @@ void main() {
stdout: '1234abcd',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '0.1.2-3',
headRef: '1234abcd',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 170,
),
const FakeCommand(
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -907,9 +920,11 @@ void main() {
stdout: '1234abcd',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '0.1.2-3',
headRef: '1234abcd',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 170,
),
const FakeCommand(
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -1148,9 +1163,11 @@ void main() {
stdout: '1234abcd',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', '1234abcd']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'],
stdout: '0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: '0.1.2-3',
headRef: '1234abcd',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 170,
),
const FakeCommand(
command: <String>['git', 'symbolic-ref', '--short', 'HEAD'],
@ -1400,15 +1417,16 @@ void main() {
testUsingContext('determine reports correct git describe version if HEAD is not at a tag', () {
const devTag = '1.2.0-2.0.pre';
const headRevision = 'abcd1234';
const commitsAhead = '12';
processManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>['git', 'tag', '--points-at', 'HEAD'],
// no output, since there's no tag
),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD'],
stdout: '$devTag-$commitsAhead-g$headRevision',
...mockGitTagHistory(
latestTag: devTag,
headRef: 'HEAD',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 12,
),
]);
final platform = FakePlatform();
@ -1425,9 +1443,11 @@ void main() {
testUsingContext('determine does not call fetch --tags', () {
processManager.addCommands(<FakeCommand>[
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD'],
stdout: 'v0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: 'v0.1.2-3',
headRef: 'HEAD',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 12,
),
]);
final platform = FakePlatform();
@ -1443,9 +1463,11 @@ void main() {
stdout: 'beta',
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD'],
stdout: 'v0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: 'v0.1.2-3',
headRef: 'HEAD',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 12,
),
]);
final platform = FakePlatform();
@ -1464,9 +1486,11 @@ void main() {
command: <String>['git', 'fetch', 'https://github.com/flutter/flutter.git', '--tags', '-f'],
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD'],
stdout: 'v0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: 'v0.1.2-3',
headRef: 'HEAD',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 12,
),
]);
final platform = FakePlatform();
@ -1485,9 +1509,11 @@ void main() {
command: <String>['git', 'fetch', 'https://githubmirror.com/flutter.git', '--tags', '-f'],
),
const FakeCommand(command: <String>['git', 'tag', '--points-at', 'HEAD']),
const FakeCommand(
command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', 'HEAD'],
stdout: 'v0.1.2-3-1234abcd',
...mockGitTagHistory(
latestTag: 'v0.1.2-3',
headRef: 'HEAD',
ancestorRef: 'abcd1234',
commitsBetweenRefs: 12,
),
]);
final platform = FakePlatform(