| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:typed_data'; | |
| import '../git.dart' as git; | |
| import '../log.dart' as log; | |
| import '../path.dart'; | |
| import '../utils.dart'; | |
| import '../validator.dart'; | |
| /// A validator that validates that no checked in files are modified in git. | |
| /// | |
| /// Doesn't report on newly added files, as generated files might not be checked | |
| /// in to git. | |
| class GitStatusValidator extends Validator { | |
| @override | |
| Future<void> validate() async { | |
| if (!package.inGitRepo) { | |
| return; | |
| } | |
| final Uint8List output; | |
| final String reporoot; | |
| try { | |
| final maybeReporoot = git.repoRoot(package.dir); | |
| if (maybeReporoot == null) { | |
| log.fine( | |
| 'Could not determine the repository root from ${package.dir}.', | |
| ); | |
| // This validation is only a warning. | |
| return; | |
| } | |
| reporoot = maybeReporoot; | |
| output = git.runSyncBytes([ | |
| 'status', | |
| '-z', // Machine parsable | |
| '--no-renames', // We don't care about renames. | |
| '--untracked-files=no', // Don't show untracked files. | |
| ], workingDir: package.dir); | |
| } on git.GitException catch (e) { | |
| log.fine('Could not run `git status` files in repo (${e.message}).'); | |
| // This validation is only a warning. | |
| // If git is not supported on the platform, we just continue silently. | |
| return; | |
| } | |
| final List<String> modifiedFiles; | |
| try { | |
| modifiedFiles = | |
| git | |
| .splitZeroTerminated(output, skipPrefix: 3) | |
| .map((bytes) { | |
| try { | |
| final filename = utf8.decode(bytes); | |
| final fullPath = p.join(reporoot, filename); | |
| if (!files.any((f) => p.equals(fullPath, f))) { | |
| // File is not in the published set - ignore. | |
| return null; | |
| } | |
| return p.relative(fullPath); | |
| } on FormatException catch (e) { | |
| // Filename is not utf8 - ignore. | |
| log.fine('Cannot decode file name: $e'); | |
| return null; | |
| } | |
| }) | |
| .nonNulls | |
| .toList(); | |
| } on FormatException catch (e) { | |
| // Malformed output from `git status`. Skip this validation. | |
| log.fine('Malformed output from `git status -z`: $e'); | |
| return; | |
| } | |
| if (modifiedFiles.isNotEmpty) { | |
| warnings.add(''' | |
| ${modifiedFiles.length} checked-in ${pluralize('file', modifiedFiles.length)} ${modifiedFiles.length == 1 ? 'is' : 'are'} modified in git. | |
| Usually you want to publish from a clean git state. | |
| Consider committing these files or reverting the changes. | |
| Modified files: | |
| ${modifiedFiles.take(10).map(p.relative).join('\n')} | |
| ${modifiedFiles.length > 10 ? '...\n' : ''} | |
| Run `git status` for more information. | |
| '''); | |
| } | |
| } | |
| } |