Unverified Commit 0895130e authored by Gary Qian's avatar Gary Qian Committed by GitHub

Migrate status (#102785)

parent 3da9eee2
...@@ -2,20 +2,34 @@ ...@@ -2,20 +2,34 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart'; import '../base/terminal.dart';
import '../migrate/migrate_utils.dart'; import '../migrate/migrate_utils.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'migrate_status.dart';
/// Base command for the migration tool. /// Base command for the migration tool.
class MigrateCommand extends FlutterCommand { class MigrateCommand extends FlutterCommand {
MigrateCommand({ MigrateCommand({
bool verbose = false,
required this.logger, required this.logger,
// TODO(garyq): Add each parameters in as subcommands land. // TODO(garyq): Add parameter in as they are needed for subcommands.
required FileSystem fileSystem,
required Platform platform,
required ProcessManager processManager,
}) { }) {
// TODO(garyq): Add subcommands. // TODO(garyq): Add each subcommand back in as they land.
addSubcommand(MigrateStatusCommand(
verbose: verbose,
logger: logger,
fileSystem: fileSystem,
platform: platform,
processManager: processManager
));
} }
final Logger logger; final Logger logger;
......
// 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);
}
}
...@@ -14,7 +14,7 @@ import '../base/platform.dart'; ...@@ -14,7 +14,7 @@ import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
/// The default name of the migrate working directory used to stage proposed changes. /// The default name of the migrate working directory used to stage proposed changes.
const String kDefaultMigrateWorkingDirectoryName = 'migrate_working_dir'; const String kDefaultMigrateStagingDirectoryName = 'migrate_staging_dir';
/// Utility class that contains methods that wrap git and other shell commands. /// Utility class that contains methods that wrap git and other shell commands.
class MigrateUtils { class MigrateUtils {
...@@ -179,14 +179,14 @@ class MigrateUtils { ...@@ -179,14 +179,14 @@ class MigrateUtils {
} }
/// Returns true if the workingDirectory git repo has any uncommited changes. /// Returns true if the workingDirectory git repo has any uncommited changes.
Future<bool> hasUncommittedChanges(String workingDirectory, {String? migrateWorkingDir}) async { Future<bool> hasUncommittedChanges(String workingDirectory, {String? migrateStagingDir}) async {
final List<String> cmdArgs = <String>[ final List<String> cmdArgs = <String>[
'git', 'git',
'ls-files', 'ls-files',
'--deleted', '--deleted',
'--modified', '--modified',
'--others', '--others',
'--exclude=${migrateWorkingDir ?? kDefaultMigrateWorkingDirectoryName}' '--exclude=${migrateStagingDir ?? kDefaultMigrateStagingDirectoryName}'
]; ];
final RunResult result = await _processUtils.run(cmdArgs, workingDirectory: workingDirectory); final RunResult result = await _processUtils.run(cmdArgs, workingDirectory: workingDirectory);
checkForErrors(result, allowedExitCodes: <int>[-1], commandDescription: cmdArgs.join(' ')); checkForErrors(result, allowedExitCodes: <int>[-1], commandDescription: cmdArgs.join(' '));
......
// 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.
// @dart = 2.8
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/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() {
FileSystem fileSystem;
BufferLogger logger;
Platform platform;
// TODO(garyq): Add terminal back in when other subcommands land.
ProcessManager processManager;
Directory appDir;
setUp(() {
fileSystem = globals.localFileSystem;
appDir = fileSystem.systemTempDirectory.createTempSync('apptestdir');
logger = BufferLogger.test();
platform = FakePlatform();
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,
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,
});
}
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