run method
Implementation
Future<int> run() async {
final allFilesResult = await this.allFiles;
if (allFilesResult case (_, final int code)) {
return code;
}
final (allFiles, _) = allFilesResult;
logger.detail('Found ${allFiles.length} files');
for (final file in allFiles) {
logger.detail(' - $file');
}
await _wait(durations.short);
logger.detail('Resolving files');
final pendingHook = PendingHook(hook.resolve(allFiles), logger: logger);
if (!hook.shouldRunOnEmpty &&
!pendingHook.topLevelTasks.any((e) => e.shouldAlwaysRun)) {
if (pendingHook.topLevelTasks.every((e) => e.files.isEmpty)) {
logger.info(
darkGray.wrap('Skipping $hookName hook, no files match any tasks'),
);
return 0;
}
}
final context = await git.prepareFiles();
logger.detail('Starting tasks');
await _wait(durations.short);
final steps = Steps();
final runner = CancelableOperation.fromFuture(
runApp(
HooksmanApp(
pendingHook: pendingHook,
nameOfHook: hookName,
debug: debug,
steps: steps,
),
enableHotReload: false,
),
onCancel: () {
// Stop the nocterm event loop so runApp completes. Do not use
// shutdownApp()/requestExit — that would exit(0) and race the hook's
// intentional non-zero exit after Ctrl+C.
try {
TerminalBinding.instance.shutdown();
} catch (_) {}
},
);
steps.current = Step.running;
await pendingHook.start();
await pendingHook.wait();
final failed = pendingHook.topLevelTasks.any((task) {
final code = task.code;
return code != null && code != 0;
});
if (pendingHook.wasKilled) {
steps.current = Step.quit;
logger.detail('Hook was killed');
} else if (failed) {
steps.current = Step.error;
for (final task in pendingHook.topLevelTasks) {
final code = task.code;
if (code != null && code != 0) {
logger.detail(
'Task failed: ${task.resolvedTask.original.resolvedName}',
);
}
}
} else {
steps.current = Step.complete;
logger.detail('Tasks finished');
}
// Let the final step paint before tearing down the TUI.
await Future<void>.delayed(const Duration(milliseconds: 150));
await runner.cancel();
if (logger.level.index == Level.verbose.index || !pendingHook.wasKilled) {
logger.flush();
}
if (!pendingHook.wasKilled) {
logger.write('\n');
}
if (pendingHook.wasKilled || failed) {
logger.detail('stopping hook tasks');
return 1;
}
if (hook is PreCommitHook) {
logger.detail('Applying modifications');
for (final file in context.nonStagedFiles) {
logger.detail(' - $file');
}
await _wait(durations.short);
await git.applyModifications([
...context.nonStagedFiles,
...context.deletedFiles,
]);
await _wait(durations.long);
} else {
logger.detail('Skipped applying modifications for $hookName');
}
// Only PreCommitHook (without allowEmpty) should fail when tasks left
// nothing to commit. Other hooks succeed after successful task runs.
if (hook case PreCommitHook(allowEmpty: false)) {
final files = await git.diffFiles(
diffArgs: hook.diffArgs,
diffFilters: hook.diffFilters,
);
final code = postSuccessExitCode(hook, files);
if (code != 0) {
logger
..info('No changes to commit')
..detail('--FINISHED--');
return code;
}
}
logger.detail('--FINISHED--');
return 0;
}