Unverified Commit 7ab2bf8f authored by Gary Qian's avatar Gary Qian Committed by GitHub

Delete flutter_migrate code (#114253)

parent 0c7ee586
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../migrate/migrate_utils.dart';
import '../runner/flutter_command.dart';
import 'migrate_abandon.dart';
import 'migrate_apply.dart';
import 'migrate_status.dart';
/// Base command for the migration tool.
class MigrateCommand extends FlutterCommand {
MigrateCommand({
required bool verbose,
required this.logger,
required FileSystem fileSystem,
required Terminal terminal,
required Platform platform,
required ProcessManager processManager,
}) {
addSubcommand(MigrateStatusCommand(
verbose: verbose,
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager
));
addSubcommand(MigrateAbandonCommand(
logger: logger,
fileSystem: fileSystem,
terminal: terminal,
platform: platform,
processManager: processManager
));
addSubcommand(MigrateApplyCommand(
verbose: verbose,
logger: logger,
fileSystem: fileSystem,
terminal: terminal,
platform: platform,
processManager: processManager
));
}
final Logger logger;
@override
final String name = 'migrate';
@override
final String description = 'Migrates flutter generated project files to the current flutter version';
@override
String get category => FlutterCommandCategory.project;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
@override
Future<FlutterCommandResult> runCommand() async {
return const FlutterCommandResult(ExitStatus.fail);
}
}
Future<bool> gitRepoExists(String projectDirectory, Logger logger, MigrateUtils migrateUtils) async {
if (await migrateUtils.isGitRepo(projectDirectory)) {
return true;
}
logger.printStatus('Project is not a git repo. Please initialize a git repo and try again.');
printCommandText('git init', logger);
return false;
}
Future<bool> hasUncommittedChanges(String projectDirectory, Logger logger, MigrateUtils migrateUtils) async {
if (await migrateUtils.hasUncommittedChanges(projectDirectory)) {
logger.printStatus('There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.');
return true;
}
return false;
}
/// Prints a command to logger with appropriate formatting.
void printCommandText(String command, Logger logger) {
logger.printStatus(
'\n\$ $command\n',
color: TerminalColor.grey,
indent: 4,
newline: false,
);
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../migrate/migrate_utils.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import 'migrate.dart';
/// Abandons the existing migration by deleting the migrate working directory.
class MigrateAbandonCommand extends FlutterCommand {
MigrateAbandonCommand({
required this.logger,
required this.fileSystem,
required this.terminal,
required Platform platform,
required ProcessManager processManager,
}) : migrateUtils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
) {
requiresPubspecYaml();
argParser.addOption(
'staging-directory',
help: 'Specifies the custom migration working directory used to stage '
'and edit proposed changes. This path can be absolute or relative '
'to the flutter project root. This defaults to '
'`$kDefaultMigrateStagingDirectoryName`',
valueHelp: 'path',
);
argParser.addOption(
'project-directory',
help: 'The root directory of the flutter project. This defaults to the '
'current working directory if omitted.',
valueHelp: 'path',
);
argParser.addFlag(
'force',
abbr: 'f',
help: 'Delete the migrate working directory without asking for confirmation.',
);
}
final Logger logger;
final FileSystem fileSystem;
final Terminal terminal;
final MigrateUtils migrateUtils;
@override
final String name = 'abandon';
@override
final String description = 'Deletes the current active migration working directory.';
@override
String get category => FlutterCommandCategory.project;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
@override
Future<FlutterCommandResult> runCommand() async {
final String? projectDirectory = stringArg('project-directory');
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(logger: logger, fileSystem: fileSystem);
final FlutterProject project = projectDirectory == null
? FlutterProject.current()
: flutterProjectFactory.fromDirectory(fileSystem.directory(projectDirectory));
Directory stagingDirectory = project.directory.childDirectory(kDefaultMigrateStagingDirectoryName);
final String? customStagingDirectoryPath = stringArg('staging-directory');
if (customStagingDirectoryPath != null) {
if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) {
stagingDirectory = fileSystem.directory(customStagingDirectoryPath);
} else {
stagingDirectory = project.directory.childDirectory(customStagingDirectoryPath);
}
if (!stagingDirectory.existsSync()) {
logger.printError('Provided staging directory `$customStagingDirectoryPath` '
'does not exist or is not valid.');
return const FlutterCommandResult(ExitStatus.fail);
}
}
if (!stagingDirectory.existsSync()) {
logger.printStatus('No migration in progress. Start a new migration with:');
printCommandText('flutter migrate start', logger);
return const FlutterCommandResult(ExitStatus.fail);
}
logger.printStatus('\nAbandoning the existing migration will delete the '
'migration staging directory at ${stagingDirectory.path}');
final bool force = boolArg('force') ?? false;
if (!force) {
String selection = 'y';
terminal.usesTerminalUi = true;
try {
selection = await terminal.promptForCharInput(
<String>['y', 'n'],
logger: logger,
prompt: 'Are you sure you wish to continue with abandoning? (y)es, (N)o',
defaultChoiceIndex: 1,
);
} on StateError catch(e) {
logger.printError(
e.message,
indent: 0,
);
}
if (selection != 'y') {
return const FlutterCommandResult(ExitStatus.success);
}
}
try {
stagingDirectory.deleteSync(recursive: true);
} on FileSystemException catch (e) {
logger.printError('Deletion failed with: $e');
logger.printError('Please manually delete the staging directory at `${stagingDirectory.path}`');
}
logger.printStatus('\nAbandon complete. Start a new migration with:');
printCommandText('flutter migrate start', logger);
return const FlutterCommandResult(ExitStatus.success);
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../flutter_project_metadata.dart';
import '../migrate/migrate_manifest.dart';
import '../migrate/migrate_update_locks.dart';
import '../migrate/migrate_utils.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import '../version.dart';
import 'migrate.dart';
/// Migrate subcommand that checks the migrate working directory for unresolved conflicts and
/// applies the staged changes to the project.
class MigrateApplyCommand extends FlutterCommand {
MigrateApplyCommand({
bool verbose = false,
required this.logger,
required this.fileSystem,
required this.terminal,
required Platform platform,
required ProcessManager processManager,
}) : _verbose = verbose,
migrateUtils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
) {
requiresPubspecYaml();
argParser.addOption(
'staging-directory',
help: 'Specifies the custom migration working directory used to stage '
'and edit proposed changes. This path can be absolute or relative '
'to the flutter project root. This defaults to '
'`$kDefaultMigrateStagingDirectoryName`',
valueHelp: 'path',
);
argParser.addOption(
'project-directory',
help: 'The root directory of the flutter project. This defaults to the '
'current working directory if omitted.',
valueHelp: 'path',
);
argParser.addFlag(
'force',
abbr: 'f',
help: 'Ignore unresolved merge conflicts and uncommitted changes and '
'apply staged changes by force.',
);
argParser.addFlag(
'keep-working-directory',
help: 'Do not delete the working directory.',
);
}
final bool _verbose;
final Logger logger;
final FileSystem fileSystem;
final Terminal terminal;
final MigrateUtils migrateUtils;
@override
final String name = 'apply';
@override
final String description = r'Accepts the changes produced by `$ flutter '
'migrate start` and copies the changed files into '
'your project files. All merge conflicts should '
'be resolved before apply will complete '
'successfully. If conflicts still exist, this '
'command will print the remaining conflicted files.';
@override
String get category => FlutterCommandCategory.project;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
@override
Future<FlutterCommandResult> runCommand() async {
final String? projectDirectory = stringArg('project-directory');
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(logger: logger, fileSystem: fileSystem);
final FlutterProject project = projectDirectory == null
? FlutterProject.current()
: flutterProjectFactory.fromDirectory(fileSystem.directory(projectDirectory));
if (!await gitRepoExists(project.directory.path, logger, migrateUtils)) {
logger.printStatus('No git repo found. Please run in a project with an '
'initialized git repo or initialize one with:');
printCommandText('git init', logger);
return const FlutterCommandResult(ExitStatus.fail);
}
final bool force = boolArg('force') ?? false;
Directory stagingDirectory = project.directory.childDirectory(kDefaultMigrateStagingDirectoryName);
final String? customStagingDirectoryPath = stringArg('staging-directory');
if (customStagingDirectoryPath != null) {
if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) {
stagingDirectory = fileSystem.directory(customStagingDirectoryPath);
} else {
stagingDirectory = project.directory.childDirectory(customStagingDirectoryPath);
}
}
if (!stagingDirectory.existsSync()) {
logger.printStatus('No migration in progress at $stagingDirectory. Please run:');
printCommandText('flutter migrate start', logger);
return const FlutterCommandResult(ExitStatus.fail);
}
final File manifestFile = MigrateManifest.getManifestFileFromDirectory(stagingDirectory);
final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile);
if (!checkAndPrintMigrateStatus(manifest, stagingDirectory, warnConflict: true, logger: logger) && !force) {
logger.printStatus('Conflicting files found. Resolve these conflicts and try again.');
logger.printStatus('Guided conflict resolution wizard:');
printCommandText('flutter migrate resolve-conflicts', logger);
return const FlutterCommandResult(ExitStatus.fail);
}
if (await hasUncommittedChanges(project.directory.path, logger, migrateUtils) && !force) {
return const FlutterCommandResult(ExitStatus.fail);
}
logger.printStatus('Applying migration.');
// Copy files from working directory to project root
final List<String> allFilesToCopy = <String>[];
allFilesToCopy.addAll(manifest.mergedFiles);
allFilesToCopy.addAll(manifest.conflictFiles);
allFilesToCopy.addAll(manifest.addedFiles);
if (allFilesToCopy.isNotEmpty && _verbose) {
logger.printStatus('Modifying ${allFilesToCopy.length} files.', indent: 2);
}
for (final String localPath in allFilesToCopy) {
if (_verbose) {
logger.printStatus('Writing $localPath');
}
final File workingFile = stagingDirectory.childFile(localPath);
final File targetFile = project.directory.childFile(localPath);
if (!workingFile.existsSync()) {
continue;
}
if (!targetFile.existsSync()) {
targetFile.createSync(recursive: true);
}
try {
targetFile.writeAsStringSync(workingFile.readAsStringSync(), flush: true);
} on FileSystemException {
targetFile.writeAsBytesSync(workingFile.readAsBytesSync(), flush: true);
}
}
// Delete files slated for deletion.
if (manifest.deletedFiles.isNotEmpty) {
logger.printStatus('Deleting ${manifest.deletedFiles.length} files.', indent: 2);
}
for (final String localPath in manifest.deletedFiles) {
final File targetFile = FlutterProject.current().directory.childFile(localPath);
targetFile.deleteSync();
}
// Update the migrate config files to reflect latest migration.
if (_verbose) {
logger.printStatus('Updating .migrate_configs');
}
final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.directory.childFile('.metadata'), logger);
final FlutterVersion version = FlutterVersion(workingDirectory: project.directory.absolute.path);
final String currentGitHash = version.frameworkRevision;
metadata.migrateConfig.populate(
projectDirectory: project.directory,
currentRevision: currentGitHash,
logger: logger,
);
// Clean up the working directory
final bool keepWorkingDirectory = boolArg('keep-working-directory') ?? false;
if (!keepWorkingDirectory) {
stagingDirectory.deleteSync(recursive: true);
}
// Detect pub dependency locking. Run flutter pub upgrade --major-versions
await updatePubspecDependencies(project, migrateUtils, logger, terminal);
// Detect gradle lockfiles in android directory. Delete lockfiles and regenerate with ./gradlew tasks (any gradle task that requires a build).
await updateGradleDependencyLocking(project, migrateUtils, logger, terminal, _verbose, fileSystem);
logger.printStatus('Migration complete. You may use commands like `git '
'status`, `git diff` and `git restore <file>` to continue '
'working with the migrated files.');
return const FlutterCommandResult(ExitStatus.success);
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../migrate/migrate_manifest.dart';
import '../migrate/migrate_utils.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import 'migrate.dart';
/// Flutter migrate subcommand to check the migration status of the project.
class MigrateStatusCommand extends FlutterCommand {
MigrateStatusCommand({
bool verbose = false,
required this.logger,
required this.fileSystem,
required Platform platform,
required ProcessManager processManager,
}) : _verbose = verbose,
migrateUtils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
) {
requiresPubspecYaml();
argParser.addOption(
'staging-directory',
help: 'Specifies the custom migration working directory used to stage '
'and edit proposed changes. This path can be absolute or relative '
'to the flutter project root. This defaults to '
'`$kDefaultMigrateStagingDirectoryName`',
valueHelp: 'path',
);
argParser.addOption(
'project-directory',
help: 'The root directory of the flutter project. This defaults to the '
'current working directory if omitted.',
valueHelp: 'path',
);
argParser.addFlag(
'diff',
defaultsTo: true,
help: 'Shows the diff output when enabled. Enabled by default.',
);
argParser.addFlag(
'show-added-files',
help: 'Shows the contents of added files. Disabled by default.',
);
}
final bool _verbose;
final Logger logger;
final FileSystem fileSystem;
final MigrateUtils migrateUtils;
@override
final String name = 'status';
@override
final String description = 'Prints the current status of the in progress migration.';
@override
String get category => FlutterCommandCategory.project;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
/// Manually marks the lines in a diff that should be printed unformatted for visbility.
///
/// This is used to ensure the initial lines that display the files being diffed and the
/// git revisions are printed and never skipped.
final Set<int> _initialDiffLines = <int>{0, 1};
@override
Future<FlutterCommandResult> runCommand() async {
final String? projectDirectory = stringArg('project-directory');
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(logger: logger, fileSystem: fileSystem);
final FlutterProject project = projectDirectory == null
? FlutterProject.current()
: flutterProjectFactory.fromDirectory(fileSystem.directory(projectDirectory));
Directory stagingDirectory = project.directory.childDirectory(kDefaultMigrateStagingDirectoryName);
final String? customStagingDirectoryPath = stringArg('staging-directory');
if (customStagingDirectoryPath != null) {
if (fileSystem.path.isAbsolute(customStagingDirectoryPath)) {
stagingDirectory = fileSystem.directory(customStagingDirectoryPath);
} else {
stagingDirectory = project.directory.childDirectory(customStagingDirectoryPath);
}
}
if (!stagingDirectory.existsSync()) {
logger.printStatus('No migration in progress in $stagingDirectory. Start a new migration with:');
printCommandText('flutter migrate start', logger);
return const FlutterCommandResult(ExitStatus.fail);
}
final File manifestFile = MigrateManifest.getManifestFileFromDirectory(stagingDirectory);
if (!manifestFile.existsSync()) {
logger.printError('No migrate manifest in the migrate working directory '
'at ${stagingDirectory.path}. Fix the working directory '
'or abandon and restart the migration.');
return const FlutterCommandResult(ExitStatus.fail);
}
final MigrateManifest manifest = MigrateManifest.fromFile(manifestFile);
final bool showDiff = boolArg('diff') ?? true;
final bool showAddedFiles = boolArg('show-added-files') ?? true;
if (showDiff || _verbose) {
if (showAddedFiles || _verbose) {
for (final String localPath in manifest.addedFiles) {
logger.printStatus('Newly added file at $localPath:\n');
try {
logger.printStatus(stagingDirectory.childFile(localPath).readAsStringSync(), color: TerminalColor.green);
} on FileSystemException {
logger.printStatus('Contents are byte data\n', color: TerminalColor.grey);
}
}
}
final List<String> files = <String>[];
files.addAll(manifest.mergedFiles);
files.addAll(manifest.resolvedConflictFiles(stagingDirectory));
files.addAll(manifest.remainingConflictFiles(stagingDirectory));
for (final String localPath in files) {
final DiffResult result = await migrateUtils.diffFiles(project.directory.childFile(localPath), stagingDirectory.childFile(localPath));
if (result.diff != '' && result.diff != null) {
// Print with different colors for better visibility.
int lineNumber = -1;
for (final String line in result.diff!.split('\n')) {
lineNumber++;
if (line.startsWith('---') || line.startsWith('+++') || line.startsWith('&&') || _initialDiffLines.contains(lineNumber)) {
logger.printStatus(line);
continue;
}
if (line.startsWith('-')) {
logger.printStatus(line, color: TerminalColor.red);
continue;
}
if (line.startsWith('+')) {
logger.printStatus(line, color: TerminalColor.green);
continue;
}
logger.printStatus(line, color: TerminalColor.grey);
}
}
}
}
logger.printBox('Working directory at `${stagingDirectory.path}`');
checkAndPrintMigrateStatus(manifest, stagingDirectory, logger: logger);
final bool readyToApply = manifest.remainingConflictFiles(stagingDirectory).isEmpty;
if (!readyToApply) {
logger.printStatus('Guided conflict resolution wizard:');
printCommandText('flutter migrate resolve-conflicts', logger);
logger.printStatus('Resolve conflicts and accept changes with:');
} else {
logger.printStatus('All conflicts resolved. Review changes above and '
'apply the migration with:',
color: TerminalColor.green);
}
printCommandText('flutter migrate apply', logger);
return const FlutterCommandResult(ExitStatus.success);
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:yaml/yaml.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart';
import 'migrate_result.dart';
import 'migrate_utils.dart';
const String _kMergedFilesKey = 'merged_files';
const String _kConflictFilesKey = 'conflict_files';
const String _kAddedFilesKey = 'added_files';
const String _kDeletedFilesKey = 'deleted_files';
/// Represents the manifest file that tracks the contents of the current
/// migration working directory.
///
/// This manifest file is created with the MigrateResult of a computeMigration run.
class MigrateManifest {
/// Creates a new manifest from a MigrateResult.
MigrateManifest({
required this.migrateRootDir,
required this.migrateResult,
});
/// Parses an existing migrate manifest.
MigrateManifest.fromFile(File manifestFile) : migrateResult = MigrateResult.empty(), migrateRootDir = manifestFile.parent {
final Object? yamlContents = loadYaml(manifestFile.readAsStringSync());
if (yamlContents is! YamlMap) {
throw Exception('Invalid .migrate_manifest file in the migrate working directory. File is not a Yaml map.');
}
final YamlMap map = yamlContents;
bool valid = map.containsKey(_kMergedFilesKey) && map.containsKey(_kConflictFilesKey) && map.containsKey(_kAddedFilesKey) && map.containsKey(_kDeletedFilesKey);
if (!valid) {
throw Exception('Invalid .migrate_manifest file in the migrate working directory. File is missing an entry.');
}
final Object? mergedFilesYaml = map[_kMergedFilesKey];
final Object? conflictFilesYaml = map[_kConflictFilesKey];
final Object? addedFilesYaml = map[_kAddedFilesKey];
final Object? deletedFilesYaml = map[_kDeletedFilesKey];
valid = valid && (mergedFilesYaml is YamlList || mergedFilesYaml == null);
valid = valid && (conflictFilesYaml is YamlList || conflictFilesYaml == null);
valid = valid && (addedFilesYaml is YamlList || addedFilesYaml == null);
valid = valid && (deletedFilesYaml is YamlList || deletedFilesYaml == null);
if (!valid) {
throw Exception('Invalid .migrate_manifest file in the migrate working directory. Entry is not a Yaml list.');
}
if (mergedFilesYaml != null) {
for (final Object? localPath in mergedFilesYaml as YamlList) {
if (localPath is String) {
// We can fill the maps with partially dummy data as not all properties are used by the manifest.
migrateResult.mergeResults.add(StringMergeResult.explicit(mergedString: '', hasConflict: false, exitCode: 0, localPath: localPath));
}
}
}
if (conflictFilesYaml != null) {
for (final Object? localPath in conflictFilesYaml as YamlList) {
if (localPath is String) {
migrateResult.mergeResults.add(StringMergeResult.explicit(mergedString: '', hasConflict: true, exitCode: 1, localPath: localPath));
}
}
}
if (addedFilesYaml != null) {
for (final Object? localPath in addedFilesYaml as YamlList) {
if (localPath is String) {
migrateResult.addedFiles.add(FilePendingMigration(localPath, migrateRootDir.childFile(localPath)));
}
}
}
if (deletedFilesYaml != null) {
for (final Object? localPath in deletedFilesYaml as YamlList) {
if (localPath is String) {
migrateResult.deletedFiles.add(FilePendingMigration(localPath, migrateRootDir.childFile(localPath)));
}
}
}
}
final Directory migrateRootDir;
final MigrateResult migrateResult;
/// A list of local paths of files that require conflict resolution.
List<String> get conflictFiles {
final List<String> output = <String>[];
for (final MergeResult result in migrateResult.mergeResults) {
if (result.hasConflict) {
output.add(result.localPath);
}
}
return output;
}
/// A list of local paths of files that require conflict resolution.
List<String> remainingConflictFiles(Directory workingDir) {
final List<String> output = <String>[];
for (final String localPath in conflictFiles) {
if (!_conflictsResolved(workingDir.childFile(localPath).readAsStringSync())) {
output.add(localPath);
}
}
return output;
}
// A list of local paths of files that had conflicts and are now fully resolved.
List<String> resolvedConflictFiles(Directory workingDir) {
final List<String> output = <String>[];
for (final String localPath in conflictFiles) {
if (_conflictsResolved(workingDir.childFile(localPath).readAsStringSync())) {
output.add(localPath);
}
}
return output;
}
/// A list of local paths of files that were automatically merged.
List<String> get mergedFiles {
final List<String> output = <String>[];
for (final MergeResult result in migrateResult.mergeResults) {
if (!result.hasConflict) {
output.add(result.localPath);
}
}
return output;
}
/// A list of local paths of files that were newly added.
List<String> get addedFiles {
final List<String> output = <String>[];
for (final FilePendingMigration file in migrateResult.addedFiles) {
output.add(file.localPath);
}
return output;
}
/// A list of local paths of files that are marked for deletion.
List<String> get deletedFiles {
final List<String> output = <String>[];
for (final FilePendingMigration file in migrateResult.deletedFiles) {
output.add(file.localPath);
}
return output;
}
/// Returns the manifest file given a migration workind directory.
static File getManifestFileFromDirectory(Directory workingDir) {
return workingDir.childFile('.migrate_manifest');
}
/// Writes the manifest yaml file in the working directory.
void writeFile() {
final StringBuffer mergedFileManifestContents = StringBuffer();
final StringBuffer conflictFilesManifestContents = StringBuffer();
for (final MergeResult result in migrateResult.mergeResults) {
if (result.hasConflict) {
conflictFilesManifestContents.write(' - ${result.localPath}\n');
} else {
mergedFileManifestContents.write(' - ${result.localPath}\n');
}
}
final StringBuffer newFileManifestContents = StringBuffer();
for (final String localPath in addedFiles) {
newFileManifestContents.write(' - $localPath\n)');
}
final StringBuffer deletedFileManifestContents = StringBuffer();
for (final String localPath in deletedFiles) {
deletedFileManifestContents.write(' - $localPath\n');
}
final String migrateManifestContents = 'merged_files:\n${mergedFileManifestContents}conflict_files:\n${conflictFilesManifestContents}added_files:\n${newFileManifestContents}deleted_files:\n$deletedFileManifestContents';
final File migrateManifest = getManifestFileFromDirectory(migrateRootDir);
migrateManifest.createSync(recursive: true);
migrateManifest.writeAsStringSync(migrateManifestContents, flush: true);
}
}
/// Returns true if the file does not contain any git conflict markers.
bool _conflictsResolved(String contents) {
if (contents.contains('>>>>>>>') && contents.contains('=======') && contents.contains('<<<<<<<')) {
return false;
}
return true;
}
/// Returns true if the migration working directory has all conflicts resolved and prints the migration status.
///
/// The migration status printout lists all added, deleted, merged, and conflicted files.
bool checkAndPrintMigrateStatus(MigrateManifest manifest, Directory workingDir, {bool warnConflict = false, Logger? logger}) {
final StringBuffer printout = StringBuffer();
final StringBuffer redPrintout = StringBuffer();
bool result = true;
final List<String> remainingConflicts = <String>[];
final List<String> mergedFiles = <String>[];
for (final String localPath in manifest.conflictFiles) {
if (!_conflictsResolved(workingDir.childFile(localPath).readAsStringSync())) {
remainingConflicts.add(localPath);
} else {
mergedFiles.add(localPath);
}
}
mergedFiles.addAll(manifest.mergedFiles);
if (manifest.addedFiles.isNotEmpty) {
printout.write('Added files:\n');
for (final String localPath in manifest.addedFiles) {
printout.write(' - $localPath\n');
}
}
if (manifest.deletedFiles.isNotEmpty) {
printout.write('Deleted files:\n');
for (final String localPath in manifest.deletedFiles) {
printout.write(' - $localPath\n');
}
}
if (mergedFiles.isNotEmpty) {
printout.write('Modified files:\n');
for (final String localPath in mergedFiles) {
printout.write(' - $localPath\n');
}
}
if (remainingConflicts.isNotEmpty) {
if (warnConflict) {
printout.write('Unable to apply migration. The following files in the migration working directory still have unresolved conflicts:');
} else {
printout.write('Merge conflicted files:');
}
for (final String localPath in remainingConflicts) {
redPrintout.write(' - $localPath\n');
}
result = false;
}
if (logger != null) {
logger.printStatus(printout.toString());
logger.printStatus(redPrintout.toString(), color: TerminalColor.red, newline: false);
}
return result;
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/file_system.dart';
import 'migrate_utils.dart';
/// Data class that holds all results and generated directories from a computeMigration run.
///
/// mergeResults, addedFiles, and deletedFiles includes the sets of files to be migrated while
/// the other members track the temporary sdk and generated app directories created by the tool.
///
/// The compute function does not clean up the temp directories, as the directories may be reused,
/// so this must be done manually afterwards.
class MigrateResult {
/// Explicitly initialize the MigrateResult.
MigrateResult({
required this.mergeResults,
required this.addedFiles,
required this.deletedFiles,
required this.tempDirectories,
required this.sdkDirs,
required this.mergeTypeMap,
required this.diffMap,
this.generatedBaseTemplateDirectory,
this.generatedTargetTemplateDirectory});
/// Creates a MigrateResult with all empty members.
MigrateResult.empty()
: mergeResults = <MergeResult>[],
addedFiles = <FilePendingMigration>[],
deletedFiles = <FilePendingMigration>[],
tempDirectories = <Directory>[],
mergeTypeMap = <String, MergeType>{},
diffMap = <String, DiffResult>{},
sdkDirs = <String, Directory>{};
/// The results of merging existing files with the target files.
final List<MergeResult> mergeResults;
/// Tracks the files that are to be newly added to the project.
final List<FilePendingMigration> addedFiles;
/// Tracks the files that are to be deleted from the project.
final List<FilePendingMigration> deletedFiles;
/// Tracks the temporary directories created during the migrate compute process.
final List<Directory> tempDirectories;
/// Mapping between the local path of a file and the type of merge that should be used.
final Map<String, MergeType> mergeTypeMap;
/// Mapping between the local path of a file and the diff between the base and target
/// versions of the file.
final Map<String, DiffResult> diffMap;
/// The root directory of the base app.
Directory? generatedBaseTemplateDirectory;
/// The root directory of the target app.
Directory? generatedTargetTemplateDirectory;
/// The root directories of the Flutter SDK for each revision.
Map<String, Directory> sdkDirs;
}
/// Defines available merge techniques.
enum MergeType {
/// A standard three-way merge.
threeWay,
/// A two way merge that ignores the base version of the file.
twoWay,
/// A `CustomMerge` manually handles the merge.
custom,
}
/// Stores a file that has been marked for migration and metadata about the file.
class FilePendingMigration {
FilePendingMigration(this.localPath, this.file);
String localPath;
File file;
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart';
import '../project.dart';
import 'migrate_utils.dart';
/// Checks if the project uses pubspec dependency locking and prompts if
/// the pub upgrade should be run.
Future<void> updatePubspecDependencies(
FlutterProject flutterProject,
MigrateUtils migrateUtils,
Logger logger,
Terminal terminal
) async {
final File pubspecFile = flutterProject.directory.childFile('pubspec.yaml');
if (!pubspecFile.existsSync()) {
return;
}
if (!pubspecFile.readAsStringSync().contains('# THIS LINE IS AUTOGENERATED')) {
return;
}
logger.printStatus('\nDart dependency locking detected in pubspec.yaml.');
terminal.usesTerminalUi = true;
String selection = 'y';
selection = await terminal.promptForCharInput(
<String>['y', 'n'],
logger: logger,
prompt: 'Do you want the tool to run `flutter pub upgrade --major-versions`? (y)es, (n)o',
defaultChoiceIndex: 1,
);
if (selection == 'y') {
// Runs `flutter pub upgrade --major-versions`
await migrateUtils.flutterPubUpgrade(flutterProject.directory.path);
}
}
/// Checks if gradle dependency locking is used and prompts the developer to
/// remove and back up the gradle dependency lockfile.
Future<void> updateGradleDependencyLocking(
FlutterProject flutterProject,
MigrateUtils migrateUtils,
Logger logger,
Terminal terminal,
bool verbose,
FileSystem fileSystem
) async {
final Directory androidDir = flutterProject.directory.childDirectory('android');
if (!androidDir.existsSync()) {
return;
}
final List<FileSystemEntity> androidFiles = androidDir.listSync();
final List<File> lockfiles = <File>[];
final List<String> backedUpFilePaths = <String>[];
for (final FileSystemEntity entity in androidFiles) {
if (entity is! File) {
continue;
}
final File file = entity.absolute;
// Don't re-handle backed up lockfiles.
if (file.path.contains('_backup_')) {
continue;
}
try {
// lockfiles generated by gradle start with this prefix.
if (file.readAsStringSync().startsWith(
'# This is a Gradle generated file for dependency locking.\n# '
'Manual edits can break the build and are not advised.\n# This '
'file is expected to be part of source control.')) {
lockfiles.add(file);
}
} on FileSystemException {
if (verbose) {
logger.printStatus('Unable to check ${file.path}');
}
}
}
if (lockfiles.isNotEmpty) {
logger.printStatus('\nGradle dependency locking detected.');
logger.printStatus('Flutter can backup the lockfiles and regenerate updated '
'lockfiles.');
terminal.usesTerminalUi = true;
String selection = 'y';
selection = await terminal.promptForCharInput(
<String>['y', 'n'],
logger: logger,
prompt: 'Do you want the tool to update locked dependencies? (y)es, (n)o',
defaultChoiceIndex: 1,
);
if (selection == 'y') {
for (final File file in lockfiles) {
int counter = 0;
while (true) {
final String newPath = '${file.absolute.path}_backup_$counter';
if (!fileSystem.file(newPath).existsSync()) {
file.renameSync(newPath);
backedUpFilePaths.add(newPath);
break;
} else {
counter++;
}
}
}
// Runs `./gradlew tasks`in the project's android directory.
await migrateUtils.gradlewTasks(flutterProject.directory.childDirectory('android').path);
logger.printStatus('Old lockfiles renamed to:');
for (final String path in backedUpFilePaths) {
logger.printStatus(path, color: TerminalColor.grey, indent: 2);
}
}
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/migrate.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/migrate/migrate_utils.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
late FileSystem fileSystem;
late BufferLogger logger;
late Platform platform;
late Terminal terminal;
late ProcessManager processManager;
late Directory appDir;
setUp(() {
fileSystem = globals.localFileSystem;
appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir');
logger = BufferLogger.test();
platform = FakePlatform();
terminal = Terminal.test();
processManager = globals.processManager;
});
setUpAll(() {
Cache.disableLocking();
});
tearDown(() async {
tryToDelete(appDir);
});
testUsingContext('abandon deletes staging directory', () async {
final MigrateCommand command = MigrateCommand(
verbose: true,
logger: logger,
fileSystem: fileSystem,
terminal: terminal,
platform: platform,
processManager: processManager,
);
final Directory stagingDir = appDir.childDirectory(kDefaultMigrateStagingDirectoryName);
appDir.childFile('lib/main.dart').createSync(recursive: true);
final File pubspecOriginal = appDir.childFile('pubspec.yaml');
pubspecOriginal.createSync();
pubspecOriginal.writeAsStringSync('''
name: originalname
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true''', flush: true);
expect(stagingDir.existsSync(), false);
await createTestCommandRunner(command).run(
<String>[
'migrate',
'abandon',
'--staging-directory=${stagingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.errorText, contains('Provided staging directory'));
expect(logger.errorText, contains('migrate_staging_dir` does not exist or is not valid.'));
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'abandon',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('No migration in progress. Start a new migration with:'));
final File pubspecModified = stagingDir.childFile('pubspec.yaml');
pubspecModified.createSync(recursive: true);
pubspecModified.writeAsStringSync('''
name: newname
description: new description of the test project
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: false
EXTRALINE''', flush: true);
final File addedFile = stagingDir.childFile('added.file');
addedFile.createSync(recursive: true);
addedFile.writeAsStringSync('new file contents');
final File manifestFile = stagingDir.childFile('.migrate_manifest');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
merged_files:
- pubspec.yaml
conflict_files:
added_files:
- added.file
deleted_files:
''');
expect(appDir.childFile('lib/main.dart').existsSync(), true);
expect(stagingDir.existsSync(), true);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'abandon',
'--staging-directory=${stagingDir.path}',
'--project-directory=${appDir.path}',
'--force',
]
);
expect(logger.statusText, contains('Abandon complete. Start a new migration with:'));
expect(stagingDir.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => platform,
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/migrate.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/migrate/migrate_utils.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
late FileSystem fileSystem;
late BufferLogger logger;
late Platform platform;
late Terminal terminal;
late ProcessManager processManager;
late Directory appDir;
setUp(() {
fileSystem = globals.localFileSystem;
appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir');
logger = BufferLogger.test();
platform = FakePlatform();
terminal = Terminal.test();
processManager = globals.processManager;
});
setUpAll(() {
Cache.disableLocking();
});
tearDown(() async {
tryToDelete(appDir);
});
testUsingContext('Apply produces all outputs', () async {
final MigrateCommand command = MigrateCommand(
verbose: true,
logger: logger,
fileSystem: fileSystem,
terminal: terminal,
platform: platform,
processManager: processManager,
);
final Directory workingDir = appDir.childDirectory(kDefaultMigrateStagingDirectoryName);
appDir.childFile('lib/main.dart').createSync(recursive: true);
final File pubspecOriginal = appDir.childFile('pubspec.yaml');
pubspecOriginal.createSync();
pubspecOriginal.writeAsStringSync('''
name: originalname
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true''', flush: true);
final File gitignore = appDir.childFile('.gitignore');
gitignore.createSync();
gitignore.writeAsStringSync(kDefaultMigrateStagingDirectoryName, flush: true);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'apply',
'--staging-directory=${workingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('Project is not a git repo. Please initialize a git repo and try again.'));
await processManager.run(<String>['git', 'init'], workingDirectory: appDir.path);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'apply',
'--staging-directory=${workingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('No migration in progress'));
final File pubspecModified = workingDir.childFile('pubspec.yaml');
pubspecModified.createSync(recursive: true);
pubspecModified.writeAsStringSync('''
name: newname
description: new description of the test project
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: false
# EXTRALINE:''', flush: true);
final File addedFile = workingDir.childFile('added.file');
addedFile.createSync(recursive: true);
addedFile.writeAsStringSync('new file contents');
final File manifestFile = workingDir.childFile('.migrate_manifest');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
merged_files:
- pubspec.yaml
conflict_files:
- conflict/conflict.file
added_files:
- added.file
deleted_files:
''');
// Add conflict file
final File conflictFile = workingDir.childDirectory('conflict').childFile('conflict.file');
conflictFile.createSync(recursive: true);
conflictFile.writeAsStringSync('''
line1
<<<<<<< /conflcit/conflict.file
line2
=======
linetwo
>>>>>>> /var/folders/md/gm0zgfcj07vcsj6jkh_mp_wh00ff02/T/flutter_tools.4Xdep8/generatedTargetTemplatetlN44S/conflict/conflict.file
line3
''', flush: true);
final File conflictFileOriginal = appDir.childDirectory('conflict').childFile('conflict.file');
conflictFileOriginal.createSync(recursive: true);
conflictFileOriginal.writeAsStringSync('''
line1
line2
line3
''', flush: true);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'apply',
'--staging-directory=${workingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains(r'''
Added files:
- added.file
Modified files:
- pubspec.yaml
Unable to apply migration. The following files in the migration working directory still have unresolved conflicts:
- conflict/conflict.file
Conflicting files found. Resolve these conflicts and try again.
Guided conflict resolution wizard:
$ flutter migrate resolve-conflicts'''));
conflictFile.writeAsStringSync('''
line1
linetwo
line3
''', flush: true);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'apply',
'--staging-directory=${workingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('There are uncommitted changes in your project. Please git commit, abandon, or stash your changes before trying again.'));
await processManager.run(<String>['git', 'add', '.'], workingDirectory: appDir.path);
await processManager.run(<String>['git', 'commit', '-m', 'Initial commit'], workingDirectory: appDir.path);
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'apply',
'--staging-directory=${workingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains(r'''
Added files:
- added.file
Modified files:
- conflict/conflict.file
- pubspec.yaml
Applying migration.
Modifying 3 files.
Writing pubspec.yaml
Writing conflict/conflict.file
Writing added.file
Updating .migrate_configs
Migration complete. You may use commands like `git status`, `git diff` and `git restore <file>` to continue working with the migrated files.'''));
expect(pubspecOriginal.readAsStringSync(), contains('# EXTRALINE'));
expect(conflictFileOriginal.readAsStringSync(), contains('linetwo'));
expect(appDir.childFile('added.file').existsSync(), true);
expect(appDir.childFile('added.file').readAsStringSync(), contains('new file contents'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => platform,
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/migrate.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/migrate/migrate_utils.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
late FileSystem fileSystem;
late BufferLogger logger;
late Platform platform;
late Terminal terminal;
late ProcessManager processManager;
late Directory appDir;
setUp(() {
fileSystem = globals.localFileSystem;
appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir');
logger = BufferLogger.test();
platform = FakePlatform();
terminal = Terminal.test();
processManager = globals.processManager;
});
setUpAll(() {
Cache.disableLocking();
});
tearDown(() async {
tryToDelete(appDir);
});
testUsingContext('Status produces all outputs', () async {
final MigrateCommand command = MigrateCommand(
verbose: true,
logger: logger,
fileSystem: fileSystem,
terminal: terminal,
platform: platform,
processManager: processManager,
);
final Directory stagingDir = appDir.childDirectory(kDefaultMigrateStagingDirectoryName);
final File pubspecOriginal = appDir.childFile('pubspec.yaml');
pubspecOriginal.createSync();
pubspecOriginal.writeAsStringSync('''
name: originalname
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true''', flush: true);
final File pubspecModified = stagingDir.childFile('pubspec.yaml');
pubspecModified.createSync(recursive: true);
pubspecModified.writeAsStringSync('''
name: newname
description: new description of the test project
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: false
EXTRALINE''', flush: true);
final File addedFile = stagingDir.childFile('added.file');
addedFile.createSync(recursive: true);
addedFile.writeAsStringSync('new file contents');
final File manifestFile = stagingDir.childFile('.migrate_manifest');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
merged_files:
- pubspec.yaml
conflict_files:
added_files:
- added.file
deleted_files:
''');
await createTestCommandRunner(command).run(
<String>[
'migrate',
'status',
'--staging-directory=${stagingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('''
Newly added file at added.file:
new file contents'''));
expect(logger.statusText, contains(r'''
Added files:
- added.file
Modified files:
- pubspec.yaml
All conflicts resolved. Review changes above and apply the migration with:
$ flutter migrate apply
'''));
expect(logger.statusText, contains(r'''
@@ -1,5 +1,5 @@
-name: originalname
-description: A new Flutter project.
+name: newname
+description: new description of the test project
version: 1.0.0+1
environment:
sdk: '>=2.18.0-58.0.dev <3.0.0'
@@ -10,4 +10,5 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter:
- uses-material-design: true
\ No newline at end of file
+ uses-material-design: false
+ EXTRALINE'''));
// Add conflict file
final File conflictFile = stagingDir.childDirectory('conflict').childFile('conflict.file');
conflictFile.createSync(recursive: true);
conflictFile.writeAsStringSync('''
line1
<<<<<<< /conflcit/conflict.file
line2
=======
linetwo
>>>>>>> /var/folders/md/gm0zgfcj07vcsj6jkh_mp_wh00ff02/T/flutter_tools.4Xdep8/generatedTargetTemplatetlN44S/conflict/conflict.file
line3
''', flush: true);
final File conflictFileOriginal = appDir.childDirectory('conflict').childFile('conflict.file');
conflictFileOriginal.createSync(recursive: true);
conflictFileOriginal.writeAsStringSync('''
line1
line2
line3
''', flush: true);
manifestFile.writeAsStringSync('''
merged_files:
- pubspec.yaml
conflict_files:
- conflict/conflict.file
added_files:
- added.file
deleted_files:
''');
logger.clear();
await createTestCommandRunner(command).run(
<String>[
'migrate',
'status',
'--staging-directory=${stagingDir.path}',
'--project-directory=${appDir.path}',
]
);
expect(logger.statusText, contains('''
@@ -1,3 +1,7 @@
line1
+<<<<<<< /conflcit/conflict.file
line2
+=======
+linetwo
+>>>>>>>'''));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => platform,
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/commands/migrate.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/migrate/migrate_utils.dart';
import '../src/common.dart';
void main() {
late BufferLogger logger;
late FileSystem fileSystem;
late Directory projectRoot;
late String projectRootPath;
late ProcessUtils processUtils;
late MigrateUtils utils;
setUpAll(() async {
fileSystem = globals.localFileSystem;
logger = BufferLogger.test();
utils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
platform: globals.platform,
processManager: globals.processManager,
);
processUtils = ProcessUtils(processManager: globals.processManager, logger: logger);
});
group('git', () {
setUp(() async {
projectRoot = fileSystem.systemTempDirectory.createTempSync('flutter_migrate_command_test');
projectRoot.createSync(recursive: true);
projectRootPath = projectRoot.path;
});
tearDown(() async {
tryToDelete(projectRoot);
});
testWithoutContext('isGitRepo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
expect(await gitRepoExists(projectRootPath, logger, utils), false);
expect(logger.statusText, contains('Project is not a git repo. Please initialize a git repo and try again.'));
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
expect(await gitRepoExists(projectRootPath, logger, utils), true);
});
testWithoutContext('printCommandText produces formatted output', () async {
printCommandText('some command --help', logger);
expect(logger.statusText, contains(r' $ some command --help'));
});
testWithoutContext('hasUncommittedChanges false on clean repo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
projectRoot.childFile('.gitignore')
..createSync()
..writeAsStringSync('ignored_file.dart', flush: true);
await processUtils.run(<String>['git', 'add', '.'], workingDirectory: projectRootPath);
await processUtils.run(<String>['git', 'commit', '-m', 'Initial commit'], workingDirectory: projectRootPath);
expect(await hasUncommittedChanges(projectRootPath, logger, utils), false);
});
testWithoutContext('hasUncommittedChanges true on dirty repo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
projectRoot.childFile('some_file.dart')
..createSync()
..writeAsStringSync('void main() {}', flush: true);
expect(await hasUncommittedChanges(projectRootPath, logger, utils), true);
});
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/flutter_project_metadata.dart';
import 'package:flutter_tools/src/project.dart';
import '../src/common.dart';
import '../src/context.dart';
import 'test_data/migrate_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
late FlutterRunTestDriver flutter;
late Logger logger;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
flutter = FlutterRunTestDriver(tempDir);
logger = BufferLogger.test();
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext('parse simple config file', () async {
final File metadataFile = tempDir.childFile('.metadata');
metadataFile.createSync(recursive: true);
metadataFile.writeAsStringSync('''
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: fj19vkla9vnlka9vni3n808v3nch8cd
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: fj19vkla9vnlka9vni3n808v3nch8cd
base_revision: 93kf9v3njfa90vnidfjvn39nvi3vnie
- platform: android
create_revision: abfj19vkla9vnlka9vni3n808v3nch8cd
base_revision: ab93kf9v3njfa90vnidfjvn39nvi3vnie
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- lib/main.dart
- ios/Runner.xcodeproj/project.pbxproj
- lib/file1/etc.dart
- android/my_file.java
''', flush: true);
FlutterProjectMetadata metadata = FlutterProjectMetadata(metadataFile, logger);
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.root]!.createRevision, equals('fj19vkla9vnlka9vni3n808v3nch8cd'));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.root]!.baseRevision, equals('93kf9v3njfa90vnidfjvn39nvi3vnie'));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.android]!.createRevision, equals('abfj19vkla9vnlka9vni3n808v3nch8cd'));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.android]!.baseRevision, equals('ab93kf9v3njfa90vnidfjvn39nvi3vnie'));
expect(metadata.migrateConfig.unmanagedFiles[0], equals('lib/main.dart'));
expect(metadata.migrateConfig.unmanagedFiles[1], equals('ios/Runner.xcodeproj/project.pbxproj'));
expect(metadata.migrateConfig.unmanagedFiles[2], equals('lib/file1/etc.dart'));
expect(metadata.migrateConfig.unmanagedFiles[3], equals('android/my_file.java'));
metadataFile.writeAsStringSync('''
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: fj19vkla9vnlka9vni3n808v3nch8cd
channel: stable
project_type: app
''', flush: true);
metadata = FlutterProjectMetadata(metadataFile, logger);
expect(metadata.migrateConfig.isEmpty, equals(true));
expect(metadata.versionRevision, equals('fj19vkla9vnlka9vni3n808v3nch8cd'));
expect(metadata.versionChannel, equals('stable'));
});
testUsingContext('write simple config file', () async {
const String testCreateRevision = 'testmc9skl32nlnf23lnakcs9njr3';
const String testBaseRevision = 'testanas9anlnq9ba7bjhavan3kma';
MigrateConfig config = MigrateConfig(
platformConfigs: <SupportedPlatform, MigratePlatformConfig>{
SupportedPlatform.android: MigratePlatformConfig(platform: SupportedPlatform.android, createRevision: testCreateRevision, baseRevision: testBaseRevision),
SupportedPlatform.ios: MigratePlatformConfig(platform: SupportedPlatform.ios, createRevision: testCreateRevision, baseRevision: testBaseRevision),
SupportedPlatform.root: MigratePlatformConfig(platform: SupportedPlatform.root, createRevision: testCreateRevision, baseRevision: testBaseRevision),
SupportedPlatform.windows: MigratePlatformConfig(platform: SupportedPlatform.windows, createRevision: testCreateRevision, baseRevision: testBaseRevision),
},
unmanagedFiles: <String>[
'lib/main.dart',
'ios/Runner.xcodeproj/project.pbxproj',
'lib/file1/etc.dart',
],
);
String outputString = config.getOutputFileString();
expect(outputString, equals('''
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: android
create_revision: $testCreateRevision
base_revision: $testBaseRevision
- platform: ios
create_revision: $testCreateRevision
base_revision: $testBaseRevision
- platform: root
create_revision: $testCreateRevision
base_revision: $testBaseRevision
- platform: windows
create_revision: $testCreateRevision
base_revision: $testBaseRevision
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
- 'lib/file1/etc.dart'
'''));
config = MigrateConfig();
outputString = config.getOutputFileString();
expect(outputString, equals(''));
});
testUsingContext('populate migrate config', () async {
// Flutter Stable 1.22.6 hash: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
final MigrateProject project = MigrateProject('version:1.22.6_stable');
await project.setUpIn(tempDir);
final File metadataFile = tempDir.childFile('.metadata');
const String currentRevision = 'test_base_revision';
const String createRevision = 'test_create_revision';
final FlutterProjectMetadata metadata = FlutterProjectMetadata(metadataFile, logger);
metadata.migrateConfig.populate(
projectDirectory: tempDir,
currentRevision: currentRevision,
createRevision: createRevision,
logger: logger,
);
expect(metadata.migrateConfig.platformConfigs.length, equals(3));
final List<SupportedPlatform> keyList = List<SupportedPlatform>.from(metadata.migrateConfig.platformConfigs.keys);
expect(keyList[0], equals(SupportedPlatform.root));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.root]!.baseRevision, equals(currentRevision));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.root]!.createRevision, equals(createRevision));
expect(keyList[1], equals(SupportedPlatform.android));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.android]!.baseRevision, equals(currentRevision));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.android]!.createRevision, equals(createRevision));
expect(keyList[2], equals(SupportedPlatform.ios));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.ios]!.baseRevision, equals(currentRevision));
expect(metadata.migrateConfig.platformConfigs[SupportedPlatform.ios]!.createRevision, equals(createRevision));
final File metadataFileOutput = tempDir.childFile('.metadata_output');
metadata.writeFile(outputFile: metadataFileOutput);
expect(metadataFileOutput.readAsStringSync(), equals('''
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
channel: unknown
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: $createRevision
base_revision: $currentRevision
- platform: android
create_revision: $createRevision
base_revision: $currentRevision
- platform: ios
create_revision: $createRevision
base_revision: $currentRevision
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
'''));
});
testUsingContext('equality compares platform', () async {
const String testCreateRevision = 'testmc9skl32nlnf23lnakcs9njr3';
const String testBaseRevision = 'testanas9anlnq9ba7bjhavan3kma';
final MigratePlatformConfig configAndroid = MigratePlatformConfig(platform: SupportedPlatform.android, createRevision: testCreateRevision, baseRevision: testBaseRevision);
final MigratePlatformConfig configIos = MigratePlatformConfig(platform: SupportedPlatform.ios, createRevision: testCreateRevision, baseRevision: testBaseRevision);
expect(configAndroid.equals(configIos), false);
expect(configAndroid.equals(configAndroid), true);
expect(configIos.equals(configIos), true);
});
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/migrate/migrate_utils.dart';
import '../src/common.dart';
void main() {
late BufferLogger logger;
late FileSystem fileSystem;
late Directory projectRoot;
late String projectRootPath;
late MigrateUtils utils;
late ProcessUtils processUtils;
setUpAll(() async {
fileSystem = globals.localFileSystem;
logger = BufferLogger.test();
utils = MigrateUtils(
logger: logger,
fileSystem: fileSystem,
platform: globals.platform,
processManager: globals.processManager,
);
processUtils = ProcessUtils(processManager: globals.processManager, logger: logger);
});
group('git', () {
setUp(() async {
projectRoot = fileSystem.systemTempDirectory.createTempSync('flutter_migrate_utils_test');
projectRoot.createSync(recursive: true);
projectRootPath = projectRoot.path;
});
tearDown(() async {
tryToDelete(projectRoot);
});
testWithoutContext('init', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
});
testWithoutContext('isGitIgnored', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
projectRoot.childFile('.gitignore')
..createSync()
..writeAsStringSync('ignored_file.dart', flush: true);
expect(await utils.isGitIgnored('ignored_file.dart', projectRootPath), true);
expect(await utils.isGitIgnored('other_file.dart', projectRootPath), false);
});
testWithoutContext('isGitRepo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
expect(await utils.isGitRepo(projectRootPath), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
expect(await utils.isGitRepo(projectRootPath), true);
expect(await utils.isGitRepo(projectRoot.parent.path), false);
});
testWithoutContext('hasUncommittedChanges false on clean repo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
projectRoot.childFile('.gitignore')
..createSync()
..writeAsStringSync('ignored_file.dart', flush: true);
await processUtils.run(<String>['git', 'add', '.'], workingDirectory: projectRootPath);
await processUtils.run(<String>['git', 'commit', '-m', 'Initial commit'], workingDirectory: projectRootPath);
expect(await utils.hasUncommittedChanges(projectRootPath), false);
});
testWithoutContext('hasUncommittedChanges true on dirty repo', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
projectRoot.childFile('some_file.dart')
..createSync()
..writeAsStringSync('void main() {}', flush: true);
expect(await utils.hasUncommittedChanges(projectRootPath), true);
});
testWithoutContext('diffFiles', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
final File file1 = projectRoot.childFile('some_file.dart')
..createSync()
..writeAsStringSync('void main() {}\n', flush: true);
final File file2 = projectRoot.childFile('some_other_file.dart');
DiffResult result = await utils.diffFiles(file1, file2);
expect(result.diff, null);
expect(result.diffType, DiffType.deletion);
expect(result.exitCode, null);
result = await utils.diffFiles(file2, file1);
expect(result.diff, null);
expect(result.diffType, DiffType.addition);
expect(result.exitCode, null);
file2.createSync();
file2.writeAsStringSync('void main() {}\n', flush: true);
result = await utils.diffFiles(file1, file2);
expect(result.diff, '');
expect(result.diffType, DiffType.command);
expect(result.exitCode, 0);
file2.writeAsStringSync('void main() {}\na second line\na third line\n', flush: true);
result = await utils.diffFiles(file1, file2);
expect(result.diff, contains('@@ -1 +1,3 @@\n void main() {}\n+a second line\n+a third line'));
expect(result.diffType, DiffType.command);
expect(result.exitCode, 1);
});
testWithoutContext('merge', () async {
expect(projectRoot.existsSync(), true);
expect(projectRoot.childDirectory('.git').existsSync(), false);
await utils.gitInit(projectRootPath);
expect(projectRoot.childDirectory('.git').existsSync(), true);
final File file1 = projectRoot.childFile('some_file.dart');
file1.createSync();
file1.writeAsStringSync('void main() {}\n\nline1\nline2\nline3\nline4\nline5\n', flush: true);
final File file2 = projectRoot.childFile('some_other_file.dart');
file2.createSync();
file2.writeAsStringSync('void main() {}\n\nline1\nline2\nline3.0\nline3.5\nline4\nline5\n', flush: true);
final File file3 = projectRoot.childFile('some_other_third_file.dart');
file3.createSync();
file3.writeAsStringSync('void main() {}\n\nline2\nline3\nline4\nline5\n', flush: true);
StringMergeResult result = await utils.gitMergeFile(
base: file1.path,
current: file2.path,
target: file3.path,
localPath: 'some_file.dart',
) as StringMergeResult;
expect(result.mergedString, 'void main() {}\n\nline2\nline3.0\nline3.5\nline4\nline5\n');
expect(result.hasConflict, false);
expect(result.exitCode, 0);
file3.writeAsStringSync('void main() {}\n\nline1\nline2\nline3.1\nline3.5\nline4\nline5\n', flush: true);
result = await utils.gitMergeFile(
base: file1.path,
current: file2.path,
target: file3.path,
localPath: 'some_file.dart',
) as StringMergeResult;
expect(result.mergedString, contains('line3.0\n=======\nline3.1\n>>>>>>>'));
expect(result.hasConflict, true);
expect(result.exitCode, 1);
// Two way merge
result = await utils.gitMergeFile(
base: file1.path,
current: file1.path,
target: file3.path,
localPath: 'some_file.dart',
) as StringMergeResult;
expect(result.mergedString, 'void main() {}\n\nline1\nline2\nline3.1\nline3.5\nline4\nline5\n');
expect(result.hasConflict, false);
expect(result.exitCode, 0);
});
});
group('legacy app creation', () {
testWithoutContext('clone and create', () async {
projectRoot = fileSystem.systemTempDirectory.createTempSync('flutter_sdk_test');
const String revision = '5391447fae6209bb21a89e6a5a6583cac1af9b4b';
expect(await utils.cloneFlutter(revision, projectRoot.path), true);
expect(projectRoot.childFile('README.md').existsSync(), true);
final Directory appDir = fileSystem.systemTempDirectory.createTempSync('flutter_app');
await utils.createFromTemplates(
projectRoot.childDirectory('bin').path,
name: 'testapp',
androidLanguage: 'java',
iosLanguage: 'objc',
outputDirectory: appDir.path,
);
expect(appDir.childFile('pubspec.yaml').existsSync(), true);
expect(appDir.childFile('.metadata').existsSync(), true);
expect(appDir.childFile('.metadata').readAsStringSync(), contains(revision));
expect(appDir.childDirectory('android').existsSync(), true);
expect(appDir.childDirectory('ios').existsSync(), true);
expect(appDir.childDirectory('web').existsSync(), false);
projectRoot.deleteSync(recursive: true);
});
});
testWithoutContext('conflictsResolved', () async {
expect(utils.conflictsResolved(''), true);
expect(utils.conflictsResolved('hello'), true);
expect(utils.conflictsResolved('hello\n'), true);
expect(utils.conflictsResolved('hello\nwow a bunch of lines\n\nhi\n'), true);
expect(utils.conflictsResolved('hello\nwow a bunch of lines\n>>>>>>>\nhi\n'), false);
expect(utils.conflictsResolved('hello\nwow a bunch of lines\n=======\nhi\n'), false);
expect(utils.conflictsResolved('hello\nwow a bunch of lines\n<<<<<<<\nhi\n'), false);
expect(utils.conflictsResolved('hello\nwow a bunch of lines\n<<<<<<<\n=======\n<<<<<<<\nhi\n'), false);
});
}
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