Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 11 additions & 69 deletions packages/flutter_tools/lib/src/commands/upgrade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -229,74 +229,6 @@ class UpgradeCommandRunner {
}
}

/// Checks if the Flutter git repository is tracking a standard remote.
///
/// A "standard remote" is one having the same url as [globals.flutterGit].
/// The upgrade process only supports standard remotes.
///
/// Exits tool if the tracking remote is not standard.
void verifyStandardRemote(FlutterVersion localVersion) {
// If repositoryUrl of the local version is null, exit
final String? repositoryUrl = localVersion.repositoryUrl;
if (repositoryUrl == null) {
throwToolExit(
'Unable to upgrade Flutter: The tool could not determine the remote '
'upstream which is being tracked by the SDK.\n'
'Re-install Flutter by going to $_flutterInstallDocs.'
);
}

// Strip `.git` suffix before comparing the remotes
final String trackingUrl = stripDotGit(repositoryUrl);
final String flutterGitUrl = stripDotGit(globals.flutterGit);

// Exempt the official flutter git SSH remote from this check
if (trackingUrl == '[email protected]:flutter/flutter') {
return;
}

if (trackingUrl != flutterGitUrl) {
if (globals.platform.environment.containsKey('FLUTTER_GIT_URL')) {
// If `FLUTTER_GIT_URL` is set, inform the user to either remove the
// `FLUTTER_GIT_URL` environment variable or set it to the current
// tracking remote to continue.
throwToolExit(
'Unable to upgrade Flutter: The Flutter SDK is tracking '
'"${localVersion.repositoryUrl}" but "FLUTTER_GIT_URL" is set to '
'"${globals.flutterGit}".\n'
'Either remove "FLUTTER_GIT_URL" from the environment or set it to '
'"${localVersion.repositoryUrl}", and retry. '
'Alternatively, re-install Flutter by going to $_flutterInstallDocs.\n'
'If this is intentional, it is recommended to use "git" directly to '
'keep Flutter SDK up-to date.'
);
}
// If `FLUTTER_GIT_URL` is unset, inform that the user has to set the
// environment variable to continue.
throwToolExit(
'Unable to upgrade Flutter: The Flutter SDK is tracking a non-standard '
'remote "${localVersion.repositoryUrl}".\n'
'Set the environment variable "FLUTTER_GIT_URL" to '
'"${localVersion.repositoryUrl}", and retry. '
'Alternatively, re-install Flutter by going to $_flutterInstallDocs.\n'
'If this is intentional, it is recommended to use "git" directly to '
'keep Flutter SDK up-to date.'
);
}
}

// Strips ".git" suffix from a given string, preferably an url.
// For example, changes 'https://github.com/flutter/flutter.git' to 'https://github.com/flutter/flutter'.
// URLs without ".git" suffix will remain unaffected.
String stripDotGit(String url) {
final RegExp pattern = RegExp(r'(.*)(\.git)$');
final RegExpMatch? match = pattern.firstMatch(url);
if (match == null) {
return url;
}
return match.group(1)!;
}

/// Returns the remote HEAD flutter version.
///
/// Exits tool if HEAD isn't pointing to a branch, or there is no upstream.
Expand Down Expand Up @@ -337,7 +269,17 @@ class UpgradeCommandRunner {
throwToolExit(errorString);
}
}
verifyStandardRemote(localVersion);
// At this point the current checkout should be on HEAD of a branch having
// an upstream. Check whether this upstream is "standard".
final VersionCheckError? error = VersionUpstreamValidator(version: localVersion, platform: globals.platform).run();
if (error != null) {
throwToolExit(
Copy link
Contributor

@christopherfujino christopherfujino Mar 21, 2022

Choose a reason for hiding this comment

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

I think it's a little unusual for the API of this method to be void while it is the responsibility of the caller to catch a VersionCheckError. Could you instead have this method return VersionCheckError? and then in the method, instead of throwing, simply return the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

'Unable to upgrade Flutter: '
'${error.message}\n'
'Reinstalling Flutter may fix this issue. Visit $_flutterInstallDocs '
'for instructions.'
);
}
return FlutterVersion(workingDirectory: workingDirectory, frameworkRevision: revision);
}

Expand Down
92 changes: 91 additions & 1 deletion packages/flutter_tools/lib/src/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'base/common.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/platform.dart';
import 'base/process.dart';
import 'base/time.dart';
import 'cache.dart';
Expand Down Expand Up @@ -239,11 +240,15 @@ class FlutterVersion {
if (!kOfficialChannels.contains(channel)) {
return;
}
// Don't perform the update check if the tracking remote is not standard.
if (VersionUpstreamValidator(version: this, platform: globals.platform).run() != null) {
return;
}
DateTime localFrameworkCommitDate;
try {
// Don't perform the update check if fetching the latest local commit failed.
localFrameworkCommitDate = DateTime.parse(_latestGitCommitDate());
} on VersionCheckError {
// Don't perform the update check if the version check failed.
return;
}
final DateTime? latestFlutterCommitDate = await _getLatestAvailableFlutterDate();
Expand Down Expand Up @@ -405,6 +410,91 @@ class FlutterVersion {
}
}

/// Checks if the provided [version] is tracking a standard remote.
///
/// A "standard remote" is one having the same url as(in order of precedence):
/// * The value of `FLUTTER_GIT_URL` environment variable.
/// * The HTTPS or SSH url of the Flutter repository as provided by GitHub.
///
/// To initiate the validation check, call [run].
///
/// This prevents the tool to check for version freshness from the standard
/// remote but fetch updates from the upstream of current branch/channel, both
/// of which can be different.
///
/// This also prevents unnecessary freshness check from a forked repo unless the
/// user explicitly configures the environment to do so.
class VersionUpstreamValidator {
VersionUpstreamValidator({
required this.version,
required this.platform,
});

final Platform platform;
final FlutterVersion version;

/// Performs the validation against the tracking remote of the [version].
///
/// Returns [VersionCheckError] if the tracking remote is not standard.
VersionCheckError? run(){
final String? flutterGit = platform.environment['FLUTTER_GIT_URL'];
final String? repositoryUrl = version.repositoryUrl;

if (repositoryUrl == null) {
return VersionCheckError(
'The tool could not determine the remote upstream which is being '
'tracked by the SDK.'
);
}

// Strip `.git` suffix before comparing the remotes
final List<String> sanitizedStandardRemotes = <String>[
if (flutterGit != null) flutterGit,
'https://github.com/flutter/flutter.git',
'[email protected]:flutter/flutter.git',
].map((String remote) => stripDotGit(remote)).toList();

final String sanitizedRepositoryUrl = stripDotGit(repositoryUrl);

if (!sanitizedStandardRemotes.contains(sanitizedRepositoryUrl)) {
if (platform.environment.containsKey('FLUTTER_GIT_URL')) {
// If `FLUTTER_GIT_URL` is set, inform to either remove the
// `FLUTTER_GIT_URL` environment variable or set it to the current
// tracking remote.
return VersionCheckError(
'The Flutter SDK is tracking "$repositoryUrl" but "FLUTTER_GIT_URL" '
'is set to "$flutterGit".\n'
'Either remove "FLUTTER_GIT_URL" from the environment or set it to '
'"$repositoryUrl". '
'If this is intentional, it is recommended to use "git" directly to '
'manage the SDK.'
);
}
// If `FLUTTER_GIT_URL` is unset, inform to set the environment variable.
return VersionCheckError(
'The Flutter SDK is tracking a non-standard remote "$repositoryUrl".\n'
'Set the environment variable "FLUTTER_GIT_URL" to '
'"$repositoryUrl". '
'If this is intentional, it is recommended to use "git" directly to '
'manage the SDK.'
);
}
return null;
}

// Strips ".git" suffix from a given string, preferably an url.
// For example, changes 'https://github.com/flutter/flutter.git' to 'https://github.com/flutter/flutter'.
// URLs without ".git" suffix will remain unaffected.
static final RegExp _patternUrlDotGit = RegExp(r'(.*)(\.git)$');
static String stripDotGit(String url) {
final RegExpMatch? match = _patternUrlDotGit.firstMatch(url);
if (match == null) {
return url;
}
return match.group(1)!;
}
}

/// Contains data and load/save logic pertaining to Flutter version checks.
@visibleForTesting
class VersionCheckStamp {
Expand Down
124 changes: 0 additions & 124 deletions packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,130 +239,6 @@ void main() {
Platform: () => fakePlatform,
});

group('verifyStandardRemote', () {
const String flutterStandardUrlDotGit = 'https://github.com/flutter/flutter.git';
const String flutterNonStandardUrlDotGit = 'https://githubmirror.com/flutter/flutter.git';
const String flutterStandardSshUrl = '[email protected]:flutter/flutter';

testUsingContext('throws toolExit if repository url is null', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
repositoryUrl: null,
);

await expectLater(
() async => realCommandRunner.verifyStandardRemote(flutterVersion),
throwsToolExit(message: 'Unable to upgrade Flutter: The tool could not '
'determine the remote upstream which is being tracked by the SDK.\n'
'Re-install Flutter by going to https://flutter.dev/docs/get-started/install.'
),
);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform,
});

testUsingContext('does not throw toolExit at standard remote url with FLUTTER_GIT_URL unset', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
);
expect(() => realCommandRunner.verifyStandardRemote(flutterVersion), returnsNormally);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform,
});

testUsingContext('throws toolExit at non-standard remote url with FLUTTER_GIT_URL unset', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
repositoryUrl: flutterNonStandardUrlDotGit,
);

await expectLater(
() async => realCommandRunner.verifyStandardRemote(flutterVersion),
throwsToolExit(message: 'Unable to upgrade Flutter: The Flutter SDK '
'is tracking a non-standard remote "$flutterNonStandardUrlDotGit".\n'
'Set the environment variable "FLUTTER_GIT_URL" to '
'"$flutterNonStandardUrlDotGit", and retry. '
'Alternatively, re-install Flutter by going to '
'https://flutter.dev/docs/get-started/install.\n'
'If this is intentional, it is recommended to use "git" directly to '
'keep Flutter SDK up-to date.'
),
);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform,
});

testUsingContext('does not throw toolExit at non-standard remote url with FLUTTER_GIT_URL set', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
repositoryUrl: flutterNonStandardUrlDotGit,
);

expect(() => realCommandRunner.verifyStandardRemote(flutterVersion), returnsNormally);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform..environment = Map<String, String>.unmodifiable(<String, String> {
'FLUTTER_GIT_URL': flutterNonStandardUrlDotGit,
}),
});

testUsingContext('throws toolExit at remote url and FLUTTER_GIT_URL set to different urls', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
repositoryUrl: flutterNonStandardUrlDotGit,
);

await expectLater(
() async => realCommandRunner.verifyStandardRemote(flutterVersion),
throwsToolExit(message: 'Unable to upgrade Flutter: The Flutter SDK '
'is tracking "$flutterNonStandardUrlDotGit" but "FLUTTER_GIT_URL" '
'is set to "$flutterStandardUrlDotGit".\n'
'Either remove "FLUTTER_GIT_URL" from the environment or set it to '
'"$flutterNonStandardUrlDotGit", and retry. '
'Alternatively, re-install Flutter by going to '
'https://flutter.dev/docs/get-started/install.\n'
'If this is intentional, it is recommended to use "git" directly to '
'keep Flutter SDK up-to date.'
),
);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform..environment = Map<String, String>.unmodifiable(<String, String> {
'FLUTTER_GIT_URL': flutterStandardUrlDotGit,
}),
});

testUsingContext('exempts standard ssh url from check with FLUTTER_GIT_URL unset', () async {
final FakeFlutterVersion flutterVersion = FakeFlutterVersion(
channel: 'beta',
repositoryUrl: flutterStandardSshUrl,
);

expect(() => realCommandRunner.verifyStandardRemote(flutterVersion), returnsNormally);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator> {
ProcessManager: () => processManager,
Platform: () => fakePlatform,
});

testUsingContext('stripDotGit removes ".git" suffix if any', () async {
expect(realCommandRunner.stripDotGit('https://github.com/flutter/flutter.git'), 'https://github.com/flutter/flutter');
expect(realCommandRunner.stripDotGit('https://github.com/flutter/flutter'), 'https://github.com/flutter/flutter');
expect(realCommandRunner.stripDotGit('[email protected]:flutter/flutter.git'), '[email protected]:flutter/flutter');
expect(realCommandRunner.stripDotGit('[email protected]:flutter/flutter'), '[email protected]:flutter/flutter');
expect(realCommandRunner.stripDotGit('https://githubmirror.com/flutter/flutter.git.git'), 'https://githubmirror.com/flutter/flutter.git');
expect(realCommandRunner.stripDotGit('https://githubmirror.com/flutter/flutter.gitgit'), 'https://githubmirror.com/flutter/flutter.gitgit');
});
});

testUsingContext('git exception during attemptReset throwsToolExit', () async {
const String revision = 'abc123';
const String errorMessage = 'fatal: Could not parse object ´$revision´';
Expand Down
Loading