import 'package:path/path.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/project.dart';
import 'custom_merge.dart';
import 'environment.dart';
import 'flutter_project_metadata.dart';
import 'migrate_logger.dart';
import 'result.dart';
import 'utils.dart';
const List<String> _skippedFiles = <String>[
'ios/Runner.xcodeproj/project.pbxproj',
'README.md',
];
const List<String> _skippedDirectories = <String>[
'.dart_tool',
'.git',
'assets',
'build',
'lib',
'test',
];
final Iterable<String> canonicalizedSkippedFiles = _skippedFiles.map<String>(
(String path) => canonicalize(path),
);
bool _skipped(String localPath, FileSystem fileSystem,
{Set<String>? skippedPrefixes}) {
final String canonicalizedLocalPath = canonicalize(localPath);
final Iterable<String> canonicalizedSkippedFiles =
_skippedFiles.map<String>((String path) => canonicalize(path));
if (canonicalizedSkippedFiles.contains(canonicalizedLocalPath)) {
return true;
}
final Iterable<String> canonicalizedSkippedDirectories =
_skippedDirectories.map<String>((String path) => canonicalize(path));
for (final String dir in canonicalizedSkippedDirectories) {
if (canonicalizedLocalPath.startsWith('$dir${fileSystem.path.separator}')) {
return true;
}
}
if (skippedPrefixes != null) {
return skippedPrefixes.any((String prefix) => localPath.startsWith(
'${normalize(prefix.replaceAll(r'\', fileSystem.path.separator))}${fileSystem.path.separator}'));
}
return false;
}
const List<String> _doNotMergeFileExtensions = <String>[
'.bmp',
'.gif',
'.jpg',
'.jpeg',
'.png',
'.svg',
'.dll',
'.exe',
'.jar',
'.so',
];
const Set<String> _alwaysMigrateFiles = <String>{
'.metadata',
'android/gradle/wrapper/gradle-wrapper.jar',
'.gitignore',
};
bool _mergable(String localPath) {
return _alwaysMigrateFiles.contains(localPath) ||
!_doNotMergeFileExtensions.any((String ext) => localPath.endsWith(ext));
}
Set<String> _getSkippedPrefixes(List<SupportedPlatform> platforms) {
final Set<String> skippedPrefixes = <String>{};
for (final SupportedPlatform platform in SupportedPlatform.values) {
skippedPrefixes.add(platformToSubdirectoryPrefix(platform));
}
for (final SupportedPlatform platform in platforms) {
skippedPrefixes.remove(platformToSubdirectoryPrefix(platform));
}
return skippedPrefixes;
}
class MigrateContext {
MigrateContext({
required this.flutterProject,
required this.skippedPrefixes,
required this.fileSystem,
required this.migrateLogger,
required this.migrateUtils,
required this.environment,
this.baseProject,
this.targetProject,
});
final FlutterProject flutterProject;
final Set<String> skippedPrefixes;
final FileSystem fileSystem;
final MigrateLogger migrateLogger;
final MigrateUtils migrateUtils;
final FlutterToolsEnvironment environment;
MigrateBaseFlutterProject? baseProject;
MigrateTargetFlutterProject? targetProject;
}
String getLocalPath(String path, String basePath, FileSystem fileSystem) {
return path.replaceFirst(basePath + fileSystem.path.separator, '');
}
String platformToSubdirectoryPrefix(SupportedPlatform platform) {
switch (platform) {
case SupportedPlatform.android:
return 'android';
case SupportedPlatform.ios:
return 'ios';
case SupportedPlatform.linux:
return 'linux';
case SupportedPlatform.macos:
return 'macos';
case SupportedPlatform.web:
return 'web';
case SupportedPlatform.windows:
return 'windows';
case SupportedPlatform.fuchsia:
return 'fuchsia';
}
}
class MigrateCommandParameters {
MigrateCommandParameters({
this.baseAppPath,
this.targetAppPath,
this.baseRevision,
this.targetRevision,
this.preferTwoWayMerge = false,
this.verbose = false,
this.allowFallbackBaseRevision = false,
this.deleteTempDirectories = true,
this.platforms,
});
final String? baseAppPath;
final String? targetAppPath;
final String? baseRevision;
final String? targetRevision;
final bool preferTwoWayMerge;
final bool verbose;
final bool allowFallbackBaseRevision;
final bool deleteTempDirectories;
final List<SupportedPlatform>? platforms;
}
Future<MigrateResult?> computeMigration({
FlutterProject? flutterProject,
required MigrateCommandParameters commandParameters,
required FileSystem fileSystem,
required Logger logger,
required MigrateUtils migrateUtils,
required FlutterToolsEnvironment environment,
}) async {
flutterProject ??= FlutterProject.current(fileSystem);
final MigrateLogger migrateLogger =
MigrateLogger(logger: logger, verbose: commandParameters.verbose);
migrateLogger.logStep('start');
final List<SupportedPlatform> platforms =
commandParameters.platforms ?? flutterProject.getSupportedPlatforms();
final Set<String> skippedPrefixes = _getSkippedPrefixes(platforms);
final MigrateResult result = MigrateResult.empty();
final MigrateContext context = MigrateContext(
flutterProject: flutterProject,
skippedPrefixes: skippedPrefixes,
migrateLogger: migrateLogger,
fileSystem: fileSystem,
migrateUtils: migrateUtils,
environment: environment,
);
migrateLogger.logStep('revisions');
final MigrateRevisions revisionConfig = MigrateRevisions(
context: context,
baseRevision: commandParameters.baseRevision,
allowFallbackBaseRevision: commandParameters.allowFallbackBaseRevision,
platforms: platforms,
environment: environment,
);
migrateLogger.logStep('unmanaged');
final List<String> unmanagedFiles = <String>[];
final List<String> unmanagedDirectories = <String>[];
final String basePath = flutterProject.directory.path;
for (final String localPath in revisionConfig.config.unmanagedFiles) {
if (localPath.endsWith(fileSystem.path.separator)) {
unmanagedDirectories.add(fileSystem.path.join(basePath, localPath));
} else {
unmanagedFiles.add(fileSystem.path.join(basePath, localPath));
}
}
migrateLogger.logStep('generating_base');
final ReferenceProjects referenceProjects =
await _generateBaseAndTargetReferenceProjects(
context: context,
result: result,
revisionConfig: revisionConfig,
platforms: platforms,
commandParameters: commandParameters,
);
migrateLogger.logStep('diff');
result.diffMap.addAll(await referenceProjects.baseProject
.diff(context, referenceProjects.targetProject));
migrateLogger.logStep('new_files');
result.addedFiles.addAll(await referenceProjects.baseProject
.computeNewlyAddedFiles(
context, result, referenceProjects.targetProject));
migrateLogger.logStep('merging');
await MigrateFlutterProject.merge(
context,
result,
referenceProjects.baseProject,
referenceProjects.targetProject,
unmanagedFiles,
unmanagedDirectories,
commandParameters.preferTwoWayMerge,
);
migrateLogger.logStep('cleaning');
_registerTempDirectoriesForCleaning(
commandParameters: commandParameters,
result: result,
referenceProjects: referenceProjects);
migrateLogger.stop();
return result;
}
String _getFallbackBaseRevision(
bool allowFallbackBaseRevision, MigrateLogger migrateLogger) {
if (!allowFallbackBaseRevision) {
migrateLogger.stop();
migrateLogger.printError(
'Could not determine base revision this app was created with:');
migrateLogger.printError(
'.metadata file did not exist or did not contain a valid revision.',
indent: 2);
migrateLogger.printError(
'Run this command again with the `--allow-fallback-base-revision` flag to use Flutter v1.0.0 as the base revision or manually pass a revision with `--base-revision=<revision>`',
indent: 2);
throwToolExit('Failed to resolve base revision');
}
migrateLogger.printIfVerbose(
'Could not determine base revision, falling back on `v1.0.0`, revision 5391447fae6209bb21a89e6a5a6583cac1af9b4b');
return '5391447fae6209bb21a89e6a5a6583cac1af9b4b';
}
class ReferenceProjects {
ReferenceProjects({
required this.baseProject,
required this.targetProject,
required this.customBaseProjectDir,
required this.customTargetProjectDir,
});
MigrateBaseFlutterProject baseProject;
MigrateTargetFlutterProject targetProject;
bool customBaseProjectDir;
bool customTargetProjectDir;
}
Future<ReferenceProjects> _generateBaseAndTargetReferenceProjects({
required MigrateContext context,
required MigrateResult result,
required MigrateRevisions revisionConfig,
required List<SupportedPlatform> platforms,
required MigrateCommandParameters commandParameters,
}) async {
final bool customBaseProjectDir = commandParameters.baseAppPath != null;
final bool customTargetProjectDir = commandParameters.targetAppPath != null;
Directory baseProjectDir =
context.fileSystem.systemTempDirectory.createTempSync('baseProject');
Directory targetProjectDir =
context.fileSystem.systemTempDirectory.createTempSync('targetProject');
if (customBaseProjectDir) {
baseProjectDir =
context.fileSystem.directory(commandParameters.baseAppPath);
} else {
baseProjectDir =
context.fileSystem.systemTempDirectory.createTempSync('baseProject');
context.migrateLogger
.printIfVerbose('Created temporary directory: ${baseProjectDir.path}');
}
if (customTargetProjectDir) {
targetProjectDir =
context.fileSystem.directory(commandParameters.targetAppPath);
} else {
targetProjectDir =
context.fileSystem.systemTempDirectory.createTempSync('targetProject');
context.migrateLogger.printIfVerbose(
'Created temporary directory: ${targetProjectDir.path}');
}
await context.migrateUtils.gitInit(baseProjectDir.absolute.path);
await context.migrateUtils.gitInit(targetProjectDir.absolute.path);
result.generatedBaseTemplateDirectory = baseProjectDir;
result.generatedTargetTemplateDirectory = targetProjectDir;
final String name =
context.environment['FlutterProject.manifest.appname']! as String;
final String androidLanguage =
context.environment['FlutterProject.android.isKotlin']! as bool
? 'kotlin'
: 'java';
final String iosLanguage =
context.environment['FlutterProject.ios.isSwift']! as bool
? 'swift'
: 'objc';
final Directory targetFlutterDirectory = context.fileSystem
.directory(context.environment.getString('Cache.flutterRoot'));
final MigrateBaseFlutterProject baseProject = MigrateBaseFlutterProject(
path: commandParameters.baseAppPath,
directory: baseProjectDir,
name: name,
androidLanguage: androidLanguage,
iosLanguage: iosLanguage,
platformWhitelist: platforms,
);
context.baseProject = baseProject;
await baseProject.createProject(
context,
result,
revisionConfig.revisionsList,
revisionConfig.revisionToConfigs,
commandParameters.baseRevision ??
revisionConfig.metadataRevision ??
_getFallbackBaseRevision(
commandParameters.allowFallbackBaseRevision, context.migrateLogger),
revisionConfig.targetRevision,
targetFlutterDirectory,
);
final MigrateTargetFlutterProject targetProject = MigrateTargetFlutterProject(
path: commandParameters.targetAppPath,
directory: targetProjectDir,
name: name,
androidLanguage: androidLanguage,
iosLanguage: iosLanguage,
platformWhitelist: platforms,
);
context.targetProject = targetProject;
await targetProject.createProject(
context,
result,
revisionConfig.targetRevision,
targetFlutterDirectory,
);
return ReferenceProjects(
baseProject: baseProject,
targetProject: targetProject,
customBaseProjectDir: customBaseProjectDir,
customTargetProjectDir: customTargetProjectDir,
);
}
void _registerTempDirectoriesForCleaning({
required MigrateCommandParameters commandParameters,
required MigrateResult result,
required ReferenceProjects referenceProjects,
}) {
if (commandParameters.deleteTempDirectories) {
if (!referenceProjects.customBaseProjectDir) {
result.tempDirectories.add(result.generatedBaseTemplateDirectory!);
}
if (!referenceProjects.customTargetProjectDir) {
result.tempDirectories.add(result.generatedTargetTemplateDirectory!);
}
result.tempDirectories.addAll(result.sdkDirs.values);
}
}
abstract class MigrateFlutterProject {
MigrateFlutterProject({
required this.path,
required this.directory,
required this.name,
required this.androidLanguage,
required this.iosLanguage,
this.platformWhitelist,
});
final String? path;
final Directory directory;
final String name;
final String androidLanguage;
final String iosLanguage;
final List<SupportedPlatform>? platformWhitelist;
Future<Map<String, DiffResult>> diff(
MigrateContext context,
MigrateFlutterProject other,
) async {
final Map<String, DiffResult> diffMap = <String, DiffResult>{};
final List<FileSystemEntity> thisFiles =
directory.listSync(recursive: true);
int modifiedFilesCount = 0;
for (final FileSystemEntity entity in thisFiles) {
if (entity is! File) {
continue;
}
final File thisFile = entity.absolute;
final String localPath = getLocalPath(
thisFile.path, directory.absolute.path, context.fileSystem);
if (_skipped(localPath, context.fileSystem,
skippedPrefixes: context.skippedPrefixes)) {
continue;
}
if (await context.migrateUtils
.isGitIgnored(thisFile.absolute.path, directory.absolute.path)) {
diffMap[localPath] = DiffResult(diffType: DiffType.ignored);
}
final File otherFile = other.directory.childFile(localPath);
if (otherFile.existsSync()) {
final DiffResult diff =
await context.migrateUtils.diffFiles(thisFile, otherFile);
diffMap[localPath] = diff;
if (diff.diff != '') {
context.migrateLogger.printIfVerbose(
'Found ${diff.exitCode} changes in $localPath',
indent: 4);
modifiedFilesCount++;
}
} else {
diffMap[localPath] = DiffResult(diffType: DiffType.deletion);
}
}
context.migrateLogger.printIfVerbose(
'$modifiedFilesCount files were modified between base and target apps.');
return diffMap;
}
Future<List<FilePendingMigration>> computeNewlyAddedFiles(
MigrateContext context,
MigrateResult result,
MigrateFlutterProject other,
) async {
final List<FilePendingMigration> addedFiles = <FilePendingMigration>[];
final List<FileSystemEntity> otherFiles =
other.directory.listSync(recursive: true);
for (final FileSystemEntity entity in otherFiles) {
if (entity is! File) {
continue;
}
final File otherFile = entity.absolute;
final String localPath = getLocalPath(
otherFile.path, other.directory.absolute.path, context.fileSystem);
if (directory.childFile(localPath).existsSync() ||
_skipped(localPath, context.fileSystem,
skippedPrefixes: context.skippedPrefixes)) {
continue;
}
if (await context.migrateUtils.isGitIgnored(
otherFile.absolute.path, other.directory.absolute.path)) {
result.diffMap[localPath] = DiffResult(diffType: DiffType.ignored);
}
result.diffMap[localPath] = DiffResult(diffType: DiffType.addition);
if (context.flutterProject.directory.childFile(localPath).existsSync()) {
continue;
}
addedFiles.add(FilePendingMigration(localPath, otherFile));
}
context.migrateLogger.printIfVerbose(
'${addedFiles.length} files were newly added in the target app.');
return addedFiles;
}
static Future<void> merge(
MigrateContext context,
MigrateResult result,
MigrateFlutterProject baseProject,
MigrateFlutterProject targetProject,
List<String> unmanagedFiles,
List<String> unmanagedDirectories,
bool preferTwoWayMerge,
) async {
final List<CustomMerge> customMerges = <CustomMerge>[
MetadataCustomMerge(logger: context.migrateLogger.logger),
];
final List<FileSystemEntity> currentFiles =
context.flutterProject.directory.listSync(recursive: true);
final String projectRootPath =
context.flutterProject.directory.absolute.path;
final Set<String> missingAlwaysMigrateFiles =
Set<String>.of(_alwaysMigrateFiles);
for (final FileSystemEntity entity in currentFiles) {
if (entity is! File) {
continue;
}
bool ignored = false;
ignored = unmanagedFiles.contains(entity.absolute.path);
for (final String path in unmanagedDirectories) {
if (entity.absolute.path.startsWith(path)) {
ignored = true;
break;
}
}
if (ignored) {
continue;
}
final File currentFile = entity.absolute;
final String localPath =
getLocalPath(currentFile.path, projectRootPath, context.fileSystem);
missingAlwaysMigrateFiles.remove(localPath);
if (result.diffMap.containsKey(localPath) &&
result.diffMap[localPath]!.diffType == DiffType.ignored ||
await context.migrateUtils.isGitIgnored(currentFile.path,
context.flutterProject.directory.absolute.path) ||
_skipped(localPath, context.fileSystem,
skippedPrefixes: context.skippedPrefixes) ||
!_mergable(localPath)) {
continue;
}
final File baseTemplateFile = baseProject.directory.childFile(localPath);
final File targetTemplateFile =
targetProject.directory.childFile(localPath);
final DiffResult userDiff =
await context.migrateUtils.diffFiles(currentFile, baseTemplateFile);
final DiffResult targetDiff =
await context.migrateUtils.diffFiles(currentFile, targetTemplateFile);
if (targetDiff.exitCode == 0) {
continue;
}
final bool alwaysMigrate = _alwaysMigrateFiles.contains(localPath);
if (userDiff.exitCode == 0 || alwaysMigrate) {
if ((result.diffMap.containsKey(localPath) || alwaysMigrate) &&
result.diffMap[localPath] != null) {
if (result.diffMap[localPath]!.diffType == DiffType.deletion) {
result.deletedFiles
.add(FilePendingMigration(localPath, currentFile));
continue;
}
if (result.diffMap[localPath]!.exitCode != 0 || alwaysMigrate) {
MergeResult mergeResult;
try {
mergeResult = StringMergeResult.explicit(
mergedString: targetTemplateFile.readAsStringSync(),
hasConflict: false,
exitCode: 0,
localPath: localPath,
);
} on FileSystemException {
mergeResult = BinaryMergeResult.explicit(
mergedBytes: targetTemplateFile.readAsBytesSync(),
hasConflict: false,
exitCode: 0,
localPath: localPath,
);
}
result.mergeResults.add(mergeResult);
continue;
}
}
continue;
}
if (result.diffMap.containsKey(localPath)) {
MergeResult? mergeResult;
MergeType mergeType =
result.mergeTypeMap[localPath] ?? MergeType.twoWay;
for (final CustomMerge customMerge in customMerges) {
if (customMerge.localPath == localPath) {
mergeResult = customMerge.merge(
currentFile, baseTemplateFile, targetTemplateFile);
mergeType = MergeType.custom;
break;
}
}
if (mergeResult == null) {
late String basePath;
late String currentPath;
late String targetPath;
if (preferTwoWayMerge) {
mergeType = MergeType.twoWay;
}
switch (mergeType) {
case MergeType.twoWay:
{
basePath = currentFile.path;
currentPath = currentFile.path;
targetPath = context.fileSystem.path.join(
result.generatedTargetTemplateDirectory!.path, localPath);
break;
}
case MergeType.threeWay:
{
basePath = context.fileSystem.path.join(
result.generatedBaseTemplateDirectory!.path, localPath);
currentPath = currentFile.path;
targetPath = context.fileSystem.path.join(
result.generatedTargetTemplateDirectory!.path, localPath);
break;
}
case MergeType.custom:
{
break;
}
}
if (mergeType != MergeType.custom) {
mergeResult = await context.migrateUtils.gitMergeFile(
base: basePath,
current: currentPath,
target: targetPath,
localPath: localPath,
);
}
}
if (mergeResult != null) {
if (mergeResult is StringMergeResult) {
if (mergeResult.mergedString == currentFile.readAsStringSync()) {
context.migrateLogger
.printIfVerbose('$localPath was merged with a $mergeType.');
continue;
}
} else {
if ((mergeResult as BinaryMergeResult).mergedBytes ==
currentFile.readAsBytesSync()) {
continue;
}
}
result.mergeResults.add(mergeResult);
}
context.migrateLogger
.printStatus('$localPath was merged with a $mergeType.');
continue;
}
}
for (final String localPath in missingAlwaysMigrateFiles) {
final File targetTemplateFile =
result.generatedTargetTemplateDirectory!.childFile(localPath);
if (targetTemplateFile.existsSync() &&
!_skipped(localPath, context.fileSystem,
skippedPrefixes: context.skippedPrefixes)) {
result.addedFiles
.add(FilePendingMigration(localPath, targetTemplateFile));
}
}
}
}
class MigrateBaseFlutterProject extends MigrateFlutterProject {
MigrateBaseFlutterProject({
required super.path,
required super.directory,
required super.name,
required super.androidLanguage,
required super.iosLanguage,
super.platformWhitelist,
});
Future<void> createProject(
MigrateContext context,
MigrateResult result,
List<String> revisionsList,
Map<String, List<MigratePlatformConfig>> revisionToConfigs,
String fallbackRevision,
String targetRevision,
Directory targetFlutterDirectory,
) async {
if (path == null) {
final Map<String, Directory> revisionToFlutterSdkDir =
<String, Directory>{};
for (final String revision in revisionsList) {
final List<String> platforms = <String>[];
for (final MigratePlatformConfig config
in revisionToConfigs[revision]!) {
if (config.component == FlutterProjectComponent.root) {
continue;
}
platforms.add(config.component.toString().split('.').last);
}
late Directory sdkDir;
final List<String> revisionsToTry = <String>[revision];
if (revision != fallbackRevision) {
revisionsToTry.add(fallbackRevision);
}
bool sdkAvailable = false;
int index = 0;
do {
if (index < revisionsToTry.length) {
final String activeRevision = revisionsToTry[index++];
if (activeRevision != revision &&
revisionToFlutterSdkDir.containsKey(activeRevision)) {
sdkDir = revisionToFlutterSdkDir[activeRevision]!;
revisionToFlutterSdkDir[revision] = sdkDir;
sdkAvailable = true;
} else {
sdkDir = context.fileSystem.systemTempDirectory
.createTempSync('flutter_$activeRevision');
result.sdkDirs[activeRevision] = sdkDir;
context.migrateLogger.printStatus('Cloning SDK $activeRevision');
sdkAvailable = await context.migrateUtils
.cloneFlutter(activeRevision, sdkDir.absolute.path);
revisionToFlutterSdkDir[revision] = sdkDir;
}
} else {
sdkDir = targetFlutterDirectory;
revisionToFlutterSdkDir[revision] = sdkDir;
sdkAvailable = true;
}
} while (!sdkAvailable);
context.migrateLogger.printStatus(
'Creating base app for $platforms with revision $revision.');
final String newDirectoryPath =
await context.migrateUtils.createFromTemplates(
sdkDir.childDirectory('bin').absolute.path,
name: name,
androidLanguage: androidLanguage,
iosLanguage: iosLanguage,
outputDirectory: result.generatedBaseTemplateDirectory!.absolute.path,
platforms: platforms,
);
if (newDirectoryPath != result.generatedBaseTemplateDirectory?.path) {
result.generatedBaseTemplateDirectory =
context.fileSystem.directory(newDirectoryPath);
}
final List<FileSystemEntity> generatedBaseFiles =
result.generatedBaseTemplateDirectory!.listSync(recursive: true);
for (final FileSystemEntity entity in generatedBaseFiles) {
if (entity is! File) {
continue;
}
final File baseTemplateFile = entity.absolute;
final String localPath = getLocalPath(
baseTemplateFile.path,
result.generatedBaseTemplateDirectory!.absolute.path,
context.fileSystem);
if (!result.mergeTypeMap.containsKey(localPath)) {
result.mergeTypeMap[localPath] = revision == targetRevision
? MergeType.twoWay
: MergeType.threeWay;
}
}
if (newDirectoryPath != result.generatedBaseTemplateDirectory?.path) {
result.generatedBaseTemplateDirectory =
context.fileSystem.directory(newDirectoryPath);
break;
}
}
}
}
}
class MigrateTargetFlutterProject extends MigrateFlutterProject {
MigrateTargetFlutterProject({
required super.path,
required super.directory,
required super.name,
required super.androidLanguage,
required super.iosLanguage,
super.platformWhitelist,
});
Future<void> createProject(
MigrateContext context,
MigrateResult result,
String targetRevision,
Directory targetFlutterDirectory,
) async {
if (path == null) {
context.migrateLogger
.printStatus('Creating target app with revision $targetRevision.');
context.migrateLogger.printIfVerbose('Creating target app.');
await context.migrateUtils.createFromTemplates(
targetFlutterDirectory.childDirectory('bin').absolute.path,
name: name,
androidLanguage: androidLanguage,
iosLanguage: iosLanguage,
outputDirectory: result.generatedTargetTemplateDirectory!.absolute.path,
);
}
}
}
class MigrateRevisions {
MigrateRevisions({
required MigrateContext context,
required String? baseRevision,
required bool allowFallbackBaseRevision,
required List<SupportedPlatform> platforms,
required FlutterToolsEnvironment environment,
}) {
_computeRevisions(context, baseRevision, allowFallbackBaseRevision,
platforms, environment);
}
late List<String> revisionsList;
late Map<String, List<MigratePlatformConfig>> revisionToConfigs;
late String fallbackRevision;
late String targetRevision;
late String? metadataRevision;
late MigrateConfig config;
void _computeRevisions(
MigrateContext context,
String? baseRevision,
bool allowFallbackBaseRevision,
List<SupportedPlatform> platforms,
FlutterToolsEnvironment environment,
) {
final List<FlutterProjectComponent> components =
<FlutterProjectComponent>[];
for (final SupportedPlatform platform in platforms) {
components.add(platform.toFlutterProjectComponent());
}
components.add(FlutterProjectComponent.root);
final FlutterProjectMetadata metadata = FlutterProjectMetadata(
context.flutterProject.directory.childFile('.metadata'),
context.migrateLogger.logger);
config = metadata.migrateConfig;
config.populate(
projectDirectory: context.flutterProject.directory,
update: false,
logger: context.migrateLogger.logger,
);
metadataRevision = metadata.versionRevision;
if (environment.getString('FlutterVersion.frameworkRevision') == null) {
throwToolExit('Flutter framework revision was null');
}
targetRevision = environment.getString('FlutterVersion.frameworkRevision')!;
String rootBaseRevision = '';
revisionToConfigs = <String, List<MigratePlatformConfig>>{};
final Set<String> revisions = <String>{};
if (baseRevision == null) {
for (final MigratePlatformConfig platform
in config.platformConfigs.values) {
final String effectiveRevision = platform.baseRevision == null
? metadataRevision ??
_getFallbackBaseRevision(
allowFallbackBaseRevision, context.migrateLogger)
: platform.baseRevision!;
if (!components.contains(platform.component)) {
continue;
}
if (platform.component == FlutterProjectComponent.root) {
rootBaseRevision = effectiveRevision;
}
revisions.add(effectiveRevision);
if (revisionToConfigs[effectiveRevision] == null) {
revisionToConfigs[effectiveRevision] = <MigratePlatformConfig>[];
}
revisionToConfigs[effectiveRevision]!.add(platform);
}
} else {
rootBaseRevision = baseRevision;
revisionToConfigs[baseRevision] = <MigratePlatformConfig>[];
for (final FlutterProjectComponent component in components) {
revisionToConfigs[baseRevision]!.add(MigratePlatformConfig(
component: component, baseRevision: baseRevision));
}
}
revisions.remove(rootBaseRevision);
revisionsList = List<String>.from(revisions);
if (rootBaseRevision != '') {
revisionsList.insert(0, rootBaseRevision);
}
context.migrateLogger
.printIfVerbose('Potential base revisions: $revisionsList');
fallbackRevision = _getFallbackBaseRevision(true, context.migrateLogger);
if (revisionsList.contains(fallbackRevision) &&
baseRevision != fallbackRevision &&
metadataRevision != fallbackRevision) {
context.migrateLogger.printStatus(
'Using Flutter v1.0.0 ($fallbackRevision) as the base revision since a valid base revision could not be found in the .metadata file. This may result in more merge conflicts than normally expected.',
indent: 4);
}
}
}