Skip to content

[flutter_tools] Make flutter upgrade --verify-only display framework version differences instead of commit hashes#69420

Merged
fluttergithubbot merged 12 commits intoflutter:masterfrom
royarg02:version_in_upgrade_verify_only
Nov 20, 2020
Merged

[flutter_tools] Make flutter upgrade --verify-only display framework version differences instead of commit hashes#69420
fluttergithubbot merged 12 commits intoflutter:masterfrom
royarg02:version_in_upgrade_verify_only

Conversation

@royarg02
Copy link
Contributor

@royarg02 royarg02 commented Oct 30, 2020

Description

This PR changes the display message when one runs flutter upgrade --verify-only, having an available update; namely, replacing the commit hashes with the framework versions.

Related Issues

Fixes #69417.

Tests

I modified:

  • Most tests in upgrade_test.dart which use fetchLatestVersion.
  • Some tests in version_test.dart that run git tag --points-at and git describe...
  • A test in flutter_command_runner_test.dart to check whether Flutter toJson o/p reports the flutter framework version.

Checklist

Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

  • No, no existing tests failed, so this is not a breaking change.

@flutter-dashboard flutter-dashboard bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Oct 30, 2020
@google-cla google-cla bot added the cla: yes label Oct 30, 2020
// TODO(fujino): use a [FlutterVersion] once that class supports arbitrary revisions.
globals.printStatus('The latest revision: $upstreamRevision', emphasis: true);
globals.printStatus('Your current version: ${flutterVersion.frameworkRevision}\n');
globals.printStatus('The latest version: ${gitTagVersion.frameworkVersionFor(upstreamRevision)}', emphasis: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh yeah, we already support that :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, isn't gitTagVersion here equivalent to HEAD?

Copy link
Contributor

@christopherfujino christopherfujino Oct 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is incorrect. .frameworkVersionFor() is a confusingly named method. We would need to compute a new version based on upstreamRevision. I'm pretty sure we need an additional invocation to git to either read the tag on the commit (for release channels) or git describe if we're on master.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something like git describe --tags --abbrev=0 would suffice? Seems working to me running in my cloned repo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something like git describe --tags --abbrev=0 would suffice? Seems working to me running in my cloned repo.

Unfortunately, if the commit in question is not a tagged commit, that will report the nearest tag. However, if a commit is 7 commits past version 1.2.3, it is not accurate to report that it as 1.2.3. What we need is to support constructing a GitTagVersion for a revision other than HEAD. Then I would update the function Future<String> fetchRemoteRevision() to instead be Future<FlutterVersion> fetchLatestVersion().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that GitTagVersion.determine() has this line:

    final List<String> tags = _runGit(
      'git tag --points-at HEAD', processUtils, workingDirectory).trim().split('\n');

which hard-codes HEAD.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then also the fallback at the end implicitly checks HEAD:

    // If we're not currently on a tag, use git describe to find the most
    // recent tag and number of commits past.
    return parse(
      _runGit(
        'git describe --match *.*.* --first-parent --long --tags',
        processUtils,
        workingDirectory,
      )
    );

Copy link
Contributor Author

@royarg02 royarg02 Oct 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we need is to support constructing a GitTagVersion for a revision other than HEAD.

Changing that would break most of the tests in version_test.dart.

Then I would update the function Future<String> fetchRemoteRevision() to instead be Future<FlutterVersion> fetchLatestVersion().

Changing this would break tests at upgrade_test.dart as well.

I think this PR might be a breaking change. I got to know from the chat that this PR doesn't necessarily has to go through the breaking changes policy, but we still need to handle the failing tests due to this change.

@@ -115,8 +115,8 @@ class UpgradeCommandRunner {
} else if (verifyOnly) {
globals.printStatus('A new version of Flutter is available on channel ${flutterVersion.channel}\n');
// TODO(fujino): use a [FlutterVersion] once that class supports arbitrary revisions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can delete this TODO comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

// TODO(fujino): use a [FlutterVersion] once that class supports arbitrary revisions.
globals.printStatus('The latest revision: $upstreamRevision', emphasis: true);
globals.printStatus('Your current version: ${flutterVersion.frameworkRevision}\n');
globals.printStatus('The latest version: ${gitTagVersion.frameworkVersionFor(upstreamRevision)}', emphasis: true);
Copy link
Contributor

@christopherfujino christopherfujino Oct 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is incorrect. .frameworkVersionFor() is a confusingly named method. We would need to compute a new version based on upstreamRevision. I'm pretty sure we need an additional invocation to git to either read the tag on the commit (for release channels) or git describe if we're on master.

expect(testLogger.statusText, contains('A new version of Flutter is available'));
expect(testLogger.statusText, contains(fakeCommandRunner.remoteRevision));
expect(testLogger.statusText, contains(revision));
expect(testLogger.statusText, contains('The latest version: ${gitTagVersion.frameworkVersionFor(fakeCommandRunner.remoteRevision)}'));
Copy link
Contributor

@christopherfujino christopherfujino Oct 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is just a hint for later--first we need to update commands/upgrade.dart with my comments there.

This is not your fault, you were copying an already incorrect test (that I probably wrote), but we should not compute the expectation text, but rather hard-code it. Otherwise, if the functions generating the test is wrong, the test will still pass. For example, when I add print logs to see what we're actually asserting on:

00:04 +3: UpgradeCommandRunner Correctly provides upgrade version on verify only
latest: 0.0.0-unknown
current: null

This seems of minimal value.

When we get the tool doing the right thing I can help you improve these tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything here looks good, I think we just need to address this comment. I think the expectation text here should be raw strings, and not calling the methods. That way if someone inadvertently changes the behavior of these methods, the test will fail, signaling the change.

Copy link
Contributor Author

@royarg02 royarg02 Nov 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockFlutterVersion is not using any defined GitTagVersion, so the tests are showing the "version" as null.

The latest version: null (revision def456)
Your current version: null (revision abc123)

Although the file has this GitTagVersion instance, it is only used as an argument to fakeCommandRunner.runCommand:

const GitTagVersion gitTagVersion = GitTagVersion(

(I don't know whether we could use that).

The revision can be shown if we use when(MockFlutterVersion().frameworkRevisionShort).thenReturn(revision) either in the test or directly in MockFlutterVersion's constructor:

class MockFlutterVersion extends Mock implements FlutterVersion {
  MockFlutterVersion({bool isStable = false, String revision}) : _isStable = isStable {
    //This is only for other tests that depend on FakeUpgradeCommandRunner.remoteRevision
    when(frameworkRevision).thenReturn(revision);
    when(frameworkRevisionShort).thenReturn(revision);
  }

@royarg02
Copy link
Contributor Author

@christopherfujino I've changed GitTagVersion to be able to construct from a given revision, and likewise updated Future<String> fetchRemoteRevision() to Future<FlutterVersion> fetchLatestVersion().

I've not updated the tests for now, which is why they are failing. Let me know your thoughts about the approach and then we can discuss on the tests.

@christopherfujino
Copy link
Contributor

@christopherfujino I've changed GitTagVersion to be able to construct from a given revision, and likewise updated Future<String> fetchRemoteRevision() to Future<FlutterVersion> fetchLatestVersion().

I've not updated the tests for now, which is why they are failing. Let me know your thoughts about the approach and then we can discuss on the tests.

Sorry, I missed this. Taking a look now!

_frameworkRevision = _runGit(
FlutterVersion([this._clock = const SystemClock(), this._workingDirectory, this._frameworkRevision]) {
// If _frameworkRevision is null, assume HEAD
_frameworkRevision = _frameworkRevision ?? _runGit(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the ??= operator here, like: _frameworkRevision ??= _runGit(

final String gitTag;

static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false}) {
static GitTagVersion determine(ProcessUtils processUtils, {String workingDirectory, bool fetchTags = false, String commitHash}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I recommend you name this argument String gitRef, so as to be more general, if in the future we want to pass something other than a hash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if you set the default value to be 'HEAD', then you don't have to do all the fallbacks in the rest of this function.

// TODO(fujino): use a [FlutterVersion] once that class supports arbitrary revisions.
globals.printStatus('The latest revision: $upstreamRevision', emphasis: true);
globals.printStatus('Your current version: ${flutterVersion.frameworkRevision}\n');
globals.printStatus('The latest version: ${upstreamVersion.frameworkVersion}(revision ${upstreamVersion.frameworkRevisionShort})', emphasis: true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a space between the version and the parentheses here and on the next line. This currently looks like:

The latest version: 1.24.0-8.0.pre.5(revision 1f15046b93)
Your current version: 1.24.0-8.0.pre.6(revision bf0fc66cbf)

We should also ensure that in the unit tests we're asserting that this is formatted correctly.

@christopherfujino
Copy link
Contributor

Also it looks like this branch has a merge conflict with upstream.

@royarg02 royarg02 force-pushed the version_in_upgrade_verify_only branch from 1f15046 to 0456782 Compare November 6, 2020 19:41
@karamage
Copy link
Contributor

I saw this PR and understood it.
I think your suggestion looks good.
Amend your suggestion to #68421.
Should I wait until this PR is merged?

@royarg02
Copy link
Contributor Author

Should I wait until this PR is merged?

@karamage I think it would be better if you do.

Copy link
Contributor

@christopherfujino christopherfujino Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this from null to an actual version tag? Your update to FakeUpgradeCommandRunner.fetchLatestVersion is constructing a new MockFlutterVersion on the fly, which is returning null when .frameworkVersion is invoked. Probably the easiest way to fix this is to set a new property on FakeUpgradeCommandRunner called FlutterVersion frameworkVersion; that can be optionally set in the constructor.

Copy link
Contributor Author

@royarg02 royarg02 Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the easiest way to fix this is to set a new property on FakeUpgradeCommandRunner called FlutterVersion frameworkVersion; that can be optionally set in the constructor.

Could you elaborate how this would solve the problem? I think that MockFlutterVersion.frameworkVersion is returning null as it is not assigned, and can be fixed by the when-thenReturn construct just like frameworkRevision:

class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
  bool willHaveUncommittedChanges = false;

  bool alreadyUpToDate = false;

  String remoteRevision = '';

  String remoteVersion = '';

  @override
  Future<FlutterVersion> fetchLatestVersion() async => MockFlutterVersion(revision: remoteRevision, version: remoteVersion);
class MockFlutterVersion extends Mock implements FlutterVersion {
  MockFlutterVersion({String revision, String version}) {
    when(frameworkRevision).thenReturn(revision);
    when(frameworkRevisionShort).thenReturn(revision);
    when(frameworkVersion).thenReturn(version);
  }
}

We would have to then explicitly provide a version number for expectations to be meaningful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the tool code, we explicitly set mock return values in the test code, so that the mocked behavior is obvious when reading the test. It will make updating the test easier if, for example, the behavior of fetchLatestVersion() were to change in a future test.

Something like this:

class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
  bool willHaveUncommittedChanges = false;
  bool alreadyUpToDate = false;

  String remoteRevision;
  FlutterVersion remoteVersion;

  @override
  Future<FlutterVersion> fetchLatestVersion() async => remoteVersion;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the tool code, we explicitly set mock return values in the test code

This snippet alongwith snippets in my previous comment show how that can be done:

const String revision = 'abc123';
const String upstreamRevision = 'def456';
const String version = '1.2.3';
const String upstreamVersion = '4.5.6';
when(flutterVersion.frameworkRevision).thenReturn(revision);
when(flutterVersion.frameworkRevisionShort).thenReturn(revision);
fakeCommandRunner.remoteVersion = upstreamVersion;
fakeCommandRunner.remoteRevision = upstreamRevision;

Also I have some issues with the snippet you've provided:

String remoteRevision;
FlutterVersion remoteVersion;

@override
Future<FlutterVersion> fetchLatestVersion() async => remoteVersion;
  • Making this change would require all tests to explicitly define FakeUpgradeCommandRunner.remoteVersion.
  • What do we assign to FakeUpgradeCommandRunner.remoteVersion in the tests, MockFlutterVersion or FlutterVersion?
  • If we assign MockFlutterVersion, we can just simply, for example, do:
MockFlutterVersion latestVersion = MockFlutterVersion();
when(latestVersion.frameworkRevision).thenReturn(upstreamRevision);
when(latestVersion.frameworkRevisionShort).thenReturn(upstreamRevision);
when(latestVersion.frameworkVersion).thenReturn(upstreamVersion);
fakeCommandRunner.remoteVersion = latestVersion;

or

fakeCommandRunner.remoteVersion = MockFlutterVersion(revision: remoteRevision, version: remoteVersion);

Having FakeUpgradeCommandRunner.remoteRevision will be redundant in either case.

  • If we assign FlutterVersion, processManager would have additional instructions, and the expectation will fail. If we assign MockFlutterVersion, it will not have a "version" tagged by default unless we explicitly provide one using either of the two above.

I'm sorry that I cannot follow your approach, but could you provide me an example on how FakeUpgradeCommandRunner.remoteRevision would be used in the tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the tool code, we explicitly set mock return values in the test code

This snippet alongwith snippets in my previous comment show how that can be done:

const String revision = 'abc123';
const String upstreamRevision = 'def456';
const String version = '1.2.3';
const String upstreamVersion = '4.5.6';
when(flutterVersion.frameworkRevision).thenReturn(revision);
when(flutterVersion.frameworkRevisionShort).thenReturn(revision);
fakeCommandRunner.remoteVersion = upstreamVersion;
fakeCommandRunner.remoteRevision = upstreamRevision;

Also I have some issues with the snippet you've provided:

String remoteRevision;
FlutterVersion remoteVersion;

@override
Future<FlutterVersion> fetchLatestVersion() async => remoteVersion;
  • Making this change would require all tests to explicitly define FakeUpgradeCommandRunner.remoteVersion.

If runner.remoteVersion isn't read in the test, it does not need to be set, right? We can leave it null. If it IS read during the test, we should force the test author to explicitly set the property with a mock object, to ensure that we don't get false positives for new tests. Where possibly, we try to write our tests so that behavior that isn't explicitly set will fail the test, so that the author gets signal that they need to mock a certain behavior.

  • What do we assign to FakeUpgradeCommandRunner.remoteVersion in the tests, MockFlutterVersion or FlutterVersion?
  • If we assign MockFlutterVersion, we can just simply, for example, do:
MockFlutterVersion latestVersion = MockFlutterVersion();
when(latestVersion.frameworkRevision).thenReturn(upstreamRevision);
when(latestVersion.frameworkRevisionShort).thenReturn(upstreamRevision);
when(latestVersion.frameworkVersion).thenReturn(upstreamVersion);
fakeCommandRunner.remoteVersion = latestVersion;

or

fakeCommandRunner.remoteVersion = MockFlutterVersion(revision: remoteRevision, version: remoteVersion);

Yes, the former is more verbose. We favor longer tests with explicit mocked behavior. The former example is easier for a contributor to read and know exactly what is mocked test behavior. Consider, for example, a future test wants to ensure that a frameworkRevisionShort is a subset of frameworkRevision. It will be clear in the former implementation that both values are manually mocked, and how to update the test.

  • If we assign FlutterVersion, processManager would have additional instructions, and the expectation will fail. If we assign MockFlutterVersion, it will not have a "version" tagged by default unless we explicitly provide one using either of the two above.

This is a good point. You can go either way, but passing a MockFlutterVersion and mocking the return value of frameworkVersion would be easier. This would still ensure that we're reading the version label and interpolating it into stdout. If you want to pass a real FlutterVersion, you would need to update FakeProcessManager, with the method processManager.addCommands().

@royarg02 royarg02 force-pushed the version_in_upgrade_verify_only branch from 30a2b4e to 9f63e50 Compare November 15, 2020 13:35
@royarg02
Copy link
Contributor Author

@christopherfujino I've updated the tests, PTAL when you have time.

@christopherfujino
Copy link
Contributor

christopherfujino commented Nov 18, 2020

@christopherfujino I've updated the tests, PTAL when you have time.

it looks like they're failing on CI:

Screen Shot 2020-11-18 at 9 19 17 AM

@royarg02
Copy link
Contributor Author

it looks like they're failing on CI

Could you point out what is causing this problem? Or anything I should do in my fork?

@christopherfujino
Copy link
Contributor

Could you point out what is causing this problem? Or anything I should do in my fork?

I know the CI build logs are not exactly intuitive. This issue has more details about investigating test logs: #64208. Please try to investigate and resolve yourself. If you are still stuck, I will try to make time for this next week.

@jmagman
Copy link
Member

jmagman commented Nov 18, 2020

Could you point out what is causing this problem? Or anything I should do in my fork?

@royarg02 The CI logs are hard to navigate. Your test failure is:

03:02 +425 ~4 -1: test/general.shard/runner/flutter_command_runner_test.dart: FlutterCommandRunner run version checks that Flutter toJson output reports the flutter framework version [E]             
  NoSuchMethodError: The getter 'exitCode' was called on null.
  Receiver: null
  Tried calling: exitCode
  dart:core                                                          Object.noSuchMethod
  package:flutter_tools/src/base/process.dart 153:37                 RunResult.exitCode
  package:flutter_tools/src/base/process.dart 429:47                 _DefaultProcessUtils.runSync
  package:flutter_tools/src/version.dart 656:23                      _runGit
  package:flutter_tools/src/version.dart 746:31                      GitTagVersion.determine
  package:flutter_tools/src/version.dart 70:36                       new FlutterVersion
  test/general.shard/runner/flutter_command_runner_test.dart         new FakeFlutterVersion
  test/general.shard/runner/flutter_command_runner_test.dart 136:44  main.<fn>.<fn>.<fn>.<fn>
  test/general.shard/runner/flutter_command_runner_test.dart 114:99  main.<fn>.<fn>.<fn>.<fn>
  test/src/context.dart 151:42                                       testUsingContext.<fn>.<fn>.<fn>.<fn>.<fn>
  test/src/context.dart 145:23                                       testUsingContext.<fn>.<fn>.<fn>.<fn>.<fn>
  package:flutter_tools/src/base/context.dart 150:29                 AppContext.run.<fn>
  package:flutter_tools/src/base/context.dart 150:7                  AppContext.run.<fn>
  dart:async                                                         runZoned
  package:flutter_tools/src/base/context.dart 149:18                 AppContext.run
  test/src/context.dart 140:30                                       testUsingContext.<fn>.<fn>.<fn>.<fn>
  dart:async                                                         runZoned
  test/src/context.dart 138:18                                       testUsingContext.<fn>.<fn>.<fn>
  package:flutter_tools/src/base/context.dart 150:29                 AppContext.run.<fn>
  package:flutter_tools/src/base/context.dart 150:7                  AppContext.run.<fn>
  dart:async                                                         runZoned
  package:flutter_tools/src/base/context.dart 149:18                 AppContext.run
  test/src/context.dart 105:22                                       testUsingContext.<fn>.<fn>
  package:flutter_tools/src/context_runner.dart 67:18                runInContext.runnerWrapper
  ===== asynchronous gap ===========================
  dart:async                                                         _asyncErrorWrapperHelper
  test/src/context.dart 140:30                                       testUsingContext.<fn>.<fn>.<fn>.<fn>
  dart:async                                                         runZoned
  test/src/context.dart 138:18                                       testUsingContext.<fn>.<fn>.<fn>
  package:flutter_tools/src/base/context.dart 150:29                 AppContext.run.<fn>
  package:flutter_tools/src/base/context.dart 150:7                  AppContext.run.<fn>
  dart:async                                                         runZoned
  package:flutter_tools/src/base/context.dart 149:18                 AppContext.run
  test/src/context.dart 105:22                                       testUsingContext.<fn>.<fn>
  package:flutter_tools/src/context_runner.dart 67:18                runInContext.runnerWrapper
  ===== asynchronous gap ===========================
  dart:async                                                         _asyncThenWrapperHelper
  package:flutter_tools/src/context_runner.dart                      runInContext.runnerWrapper
  package:flutter_tools/src/base/context.dart 150:29                 AppContext.run.<fn>
  package:flutter_tools/src/base/context.dart 150:7                  AppContext.run.<fn>
  dart:async                                                         runZoned
  package:flutter_tools/src/base/context.dart 149:18                 AppContext.run
  package:flutter_tools/src/context_runner.dart 70:24                runInContext
  test/src/context.dart 104:11                                       testUsingContext.<fn>
  test/src/context.dart 103:21                                       testUsingContext.<fn>
  test/src/common.dart 192:18                                        test.<fn>
  test/src/common.dart 188:5                                         test.<fn>

You should be able to reproduce locally:

cd packages/flutter_tools 
flutter test test/general.shard

The getter 'exitCode' was called on null. usually means a process call was changed, but a testing mock wasn't updated.

'ENV1': 'irrelevant',
'ENV2': 'irrelevant',
});
})..version = '1.2.3-dev-4.5';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary? The tests pass without it. And I'm not familiar the version property of Platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it isn't. Probably a leftover I overlooked. I removed it.

Copy link
Contributor

@christopherfujino christopherfujino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks so much for making this change, I know it wasn't easy to wrangle the tests.

@royarg02
Copy link
Contributor Author

royarg02 commented Nov 20, 2020

Thanks @christopherfujino and @jmagman for assisting me in landing this PR.

@royarg02 royarg02 deleted the version_in_upgrade_verify_only branch March 4, 2021 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[flutter_tools] flutter upgrade --verify-only shows commit hashes instead of framework version

5 participants