forked from flutter/packages
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[flutter_migrate] Reland apply, abandon, and status commands and add …
…environment.dart (flutter#2723) * Checkout commands files * fix licences and tests * Skip apply test on old flutter version * Formatting * Fix version booleans * Move flutter version test tcheck first * Working tests * Formatting * Env test * Docs and fix windows test * Null safe, extra test * Improve cleanliness checking * Regex and fake process manager * Formatting * Change env test contents * format * Nits
- Loading branch information
Showing
10 changed files
with
1,320 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Copyright 2013 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/command.dart'; | ||
import '../base/file_system.dart'; | ||
import '../base/logger.dart'; | ||
import '../base/project.dart'; | ||
import '../base/terminal.dart'; | ||
|
||
import '../utils.dart'; | ||
|
||
/// Abandons the existing migration by deleting the migrate working directory. | ||
class MigrateAbandonCommand extends MigrateCommand { | ||
MigrateAbandonCommand({ | ||
required this.logger, | ||
required this.fileSystem, | ||
required this.terminal, | ||
required ProcessManager processManager, | ||
}) : migrateUtils = MigrateUtils( | ||
logger: logger, | ||
fileSystem: fileSystem, | ||
processManager: processManager, | ||
) { | ||
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.', | ||
); | ||
argParser.addFlag( | ||
'flutter-subcommand', | ||
help: | ||
'Enable when using the flutter tool as a subcommand. This changes the ' | ||
'wording of log messages to indicate the correct suggested commands to use.', | ||
); | ||
} | ||
|
||
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 | ||
Future<CommandResult> runCommand() async { | ||
final String? projectDirectory = stringArg('project-directory'); | ||
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); | ||
final FlutterProject project = projectDirectory == null | ||
? FlutterProject.current(fileSystem) | ||
: flutterProjectFactory | ||
.fromDirectory(fileSystem.directory(projectDirectory)); | ||
final bool isSubcommand = boolArg('flutter-subcommand') ?? 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.printError( | ||
'Provided staging directory `$customStagingDirectoryPath` ' | ||
'does not exist or is not valid.'); | ||
return const CommandResult(ExitStatus.fail); | ||
} | ||
} | ||
if (!stagingDirectory.existsSync()) { | ||
logger | ||
.printStatus('No migration in progress. Start a new migration with:'); | ||
printCommandText('start', logger, standalone: !isSubcommand); | ||
return const CommandResult(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 CommandResult(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('start', logger, standalone: !isSubcommand); | ||
return const CommandResult(ExitStatus.success); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// Copyright 2013 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/command.dart'; | ||
import '../base/file_system.dart'; | ||
import '../base/logger.dart'; | ||
import '../base/project.dart'; | ||
import '../base/terminal.dart'; | ||
import '../environment.dart'; | ||
import '../flutter_project_metadata.dart'; | ||
|
||
import '../manifest.dart'; | ||
import '../update_locks.dart'; | ||
import '../utils.dart'; | ||
|
||
/// Migrate subcommand that checks the migrate working directory for unresolved conflicts and | ||
/// applies the staged changes to the project. | ||
class MigrateApplyCommand extends MigrateCommand { | ||
MigrateApplyCommand({ | ||
bool verbose = false, | ||
required this.logger, | ||
required this.fileSystem, | ||
required this.terminal, | ||
required ProcessManager processManager, | ||
}) : _verbose = verbose, | ||
_processManager = processManager, | ||
migrateUtils = MigrateUtils( | ||
logger: logger, | ||
fileSystem: fileSystem, | ||
processManager: processManager, | ||
) { | ||
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.', | ||
); | ||
argParser.addFlag( | ||
'flutter-subcommand', | ||
help: | ||
'Enable when using the flutter tool as a subcommand. This changes the ' | ||
'wording of log messages to indicate the correct suggested commands to use.', | ||
); | ||
} | ||
|
||
final bool _verbose; | ||
|
||
final ProcessManager _processManager; | ||
|
||
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 | ||
Future<CommandResult> runCommand() async { | ||
final String? projectDirectory = stringArg('project-directory'); | ||
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(); | ||
final FlutterProject project = projectDirectory == null | ||
? FlutterProject.current(fileSystem) | ||
: flutterProjectFactory | ||
.fromDirectory(fileSystem.directory(projectDirectory)); | ||
final FlutterToolsEnvironment environment = | ||
await FlutterToolsEnvironment.initializeFlutterToolsEnvironment( | ||
_processManager, logger); | ||
final bool isSubcommand = boolArg('flutter-subcommand') ?? false; | ||
|
||
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, standalone: null); | ||
return const CommandResult(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('start', logger, standalone: !isSubcommand); | ||
return const CommandResult(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('resolve-conflicts', logger, standalone: !isSubcommand); | ||
return const CommandResult(ExitStatus.fail); | ||
} | ||
|
||
if (await hasUncommittedChanges( | ||
project.directory.path, logger, migrateUtils) && | ||
!force) { | ||
return const CommandResult(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 = project.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 String currentGitHash = | ||
environment.getString('FlutterVersion.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 CommandResult(ExitStatus.success); | ||
} | ||
} |
Oops, something went wrong.