Add --no-print-skip option for suppressing SKIP log output
Introduce a new CLI flag --no-print-skip to hide SKIP status lines during sync/delete operations while preserving existing skip behavior and summary counters. Update help output, README, and AGENTS.md to document the new option.
This commit is contained in:
@ -18,6 +18,7 @@
|
||||
- `2` argument/usage error
|
||||
- Missing `--host`, `--user`, `--password` should still support interactive prompt mode.
|
||||
- `--skip` and `--skip-delete` matching semantics should remain stable.
|
||||
- `--no-print-skip` must suppress only `SKIP` log lines, without changing skip decisions or summary counters.
|
||||
- `--delete-dir` safety guard against dangerous paths (`/`, empty path, dot paths) must remain intact.
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
@ -47,6 +47,7 @@ php src/SFTPsync.php --host <host> --user <user> --password <password> [--port <
|
||||
- `--password <password>`: required (or prompted)
|
||||
- `--port <port>`: optional, default `22`
|
||||
- `--print-relative`: show paths relative to action root in logs
|
||||
- `--no-print-skip`: suppress `SKIP` status lines during execution
|
||||
- `--skip <file_or_dir>`: repeatable, applied to `--sync` and `--sync-down`
|
||||
- `--skip-delete <file_or_dir>`: repeatable, applied to `--delete` and `--delete-dir`
|
||||
- `-h`, `--help`: show help
|
||||
@ -108,3 +109,5 @@ After upload/download, mtime is propagated to the target when possible.
|
||||
## Output
|
||||
|
||||
The script prints status lines (`MKDIR`, `UPLOAD`, `DOWNLOAD`, `DELETE`, `RMDIR`, `SKIP`, `ERROR`) and a final summary with operation counters.
|
||||
|
||||
Use `--no-print-skip` if you want to keep skipped-item accounting in the final summary but hide individual `SKIP` log lines during the run.
|
||||
|
||||
@ -150,7 +150,7 @@ final class SftpAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>}
|
||||
* @return array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,no_print_skip:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>}
|
||||
*/
|
||||
function parseArguments(array $argv): array
|
||||
{
|
||||
@ -161,6 +161,7 @@ function parseArguments(array $argv): array
|
||||
'password' => '',
|
||||
'port' => 22,
|
||||
'print_relative' => false,
|
||||
'no_print_skip' => false,
|
||||
'skip' => [],
|
||||
'skip_delete' => [],
|
||||
'actions' => [],
|
||||
@ -204,6 +205,10 @@ function parseArguments(array $argv): array
|
||||
$parsed['print_relative'] = true;
|
||||
break;
|
||||
|
||||
case '--no-print-skip':
|
||||
$parsed['no_print_skip'] = true;
|
||||
break;
|
||||
|
||||
case '--skip':
|
||||
$skipValue = readCliValue($argv, ++$i, '--skip <file_or_dir>');
|
||||
$parsed['skip'][] = normalizeSkipRule($skipValue);
|
||||
@ -306,6 +311,7 @@ Options:
|
||||
--password <password> Required
|
||||
--port <port> Optional, default 22
|
||||
--print-relative Show logged paths relative to action local/remote roots
|
||||
--no-print-skip Do not print SKIP logs during synchronization
|
||||
--skip <file_or_dir> Repeatable, skip matching names/paths in --sync and --sync-down
|
||||
--skip-delete <file_or_dir> Repeatable, skip matching names/paths in --delete and --delete-dir
|
||||
-h, --help Show this help
|
||||
@ -342,7 +348,7 @@ function run(array $config): int
|
||||
|
||||
foreach ($config['actions'] as $action) {
|
||||
try {
|
||||
runAction($adapter, $action, $config['skip'], $config['skip_delete'], $config['print_relative']);
|
||||
runAction($adapter, $action, $config['skip'], $config['skip_delete'], $config['print_relative'], $config['no_print_skip']);
|
||||
} catch (Throwable $e) {
|
||||
logErrorAction(describeAction($action) . ': ' . $e->getMessage());
|
||||
return EXIT_RUNTIME_ERROR;
|
||||
@ -358,23 +364,24 @@ function runAction(
|
||||
array $action,
|
||||
array $skipRules,
|
||||
array $skipDeleteRules,
|
||||
bool $printRelative
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip
|
||||
): void {
|
||||
switch ($action['type']) {
|
||||
case 'sync':
|
||||
syncUp($adapter, $action['local_dir'], $action['remote_dir'], $skipRules, $printRelative);
|
||||
syncUp($adapter, $action['local_dir'], $action['remote_dir'], $skipRules, $printRelative, $noPrintSkip);
|
||||
return;
|
||||
|
||||
case 'sync-down':
|
||||
syncDown($adapter, $action['remote_dir'], $action['local_dir'], $skipRules, $printRelative);
|
||||
syncDown($adapter, $action['remote_dir'], $action['local_dir'], $skipRules, $printRelative, $noPrintSkip);
|
||||
return;
|
||||
|
||||
case 'delete':
|
||||
deleteRemoteFile($adapter, $action['remote_file'], $skipDeleteRules, $printRelative);
|
||||
deleteRemoteFile($adapter, $action['remote_file'], $skipDeleteRules, $printRelative, $noPrintSkip);
|
||||
return;
|
||||
|
||||
case 'delete-dir':
|
||||
deleteRemoteDir($adapter, $action['remote_dir'], $skipDeleteRules, $printRelative);
|
||||
deleteRemoteDir($adapter, $action['remote_dir'], $skipDeleteRules, $printRelative, $noPrintSkip);
|
||||
return;
|
||||
|
||||
default:
|
||||
@ -387,7 +394,8 @@ function syncUp(
|
||||
string $localDir,
|
||||
string $remoteDir,
|
||||
array $skipRules,
|
||||
bool $printRelative
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip
|
||||
): void {
|
||||
if (!is_dir($localDir)) {
|
||||
throw new RuntimeException('Local directory does not exist: ' . $localDir);
|
||||
@ -412,14 +420,14 @@ function syncUp(
|
||||
$directoryIterator = new RecursiveDirectoryIterator($localBase, FilesystemIterator::SKIP_DOTS);
|
||||
$filteredIterator = new RecursiveCallbackFilterIterator(
|
||||
$directoryIterator,
|
||||
static function (SplFileInfo $current, mixed $key, $iterator) use ($normalizedLocalBase, $baseLen, $skipRules, $localBase, $printRelative): bool {
|
||||
static function (SplFileInfo $current, mixed $key, $iterator) use ($normalizedLocalBase, $baseLen, $skipRules, $localBase, $printRelative, $noPrintSkip): bool {
|
||||
$localPath = normalizeLocalPath($current->getPathname());
|
||||
$relative = ltrim(substr($localPath, $baseLen), '/');
|
||||
if ($relative === '') {
|
||||
return true;
|
||||
}
|
||||
if (isPathSkipped($relative, $skipRules)) {
|
||||
logAction('SKIP', formatLocalLogPath($localPath, $localBase, $printRelative) . ' (matches --skip)');
|
||||
logSkipAction(formatLocalLogPath($localPath, $localBase, $printRelative) . ' (matches --skip)', $noPrintSkip);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -466,7 +474,7 @@ function syncUp(
|
||||
$remoteDisplayPath = formatRemoteLogPath($remoteFile, $remoteBase, $printRelative);
|
||||
|
||||
if (!shouldTransfer((int)$localSize, $localMtime, $remoteSize, $remoteMtime, $remoteStat !== null)) {
|
||||
logAction('SKIP', $localDisplayPath . ' -> ' . $remoteDisplayPath);
|
||||
logSkipAction($localDisplayPath . ' -> ' . $remoteDisplayPath, $noPrintSkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -483,7 +491,8 @@ function syncDown(
|
||||
string $remoteDir,
|
||||
string $localDir,
|
||||
array $skipRules,
|
||||
bool $printRelative
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip
|
||||
): void {
|
||||
$remoteBase = normalizeRemotePath($remoteDir);
|
||||
if ($remoteBase === '') {
|
||||
@ -501,7 +510,7 @@ function syncDown(
|
||||
logAction('MKDIR', formatLocalLogPath($createdPath, $localBase, $printRelative));
|
||||
});
|
||||
|
||||
syncDownRecursive($adapter, $remoteBase, $localDir, $skipRules, $remoteBase, $localBase, $printRelative);
|
||||
syncDownRecursive($adapter, $remoteBase, $localDir, $skipRules, $remoteBase, $localBase, $printRelative, $noPrintSkip);
|
||||
}
|
||||
|
||||
function syncDownRecursive(
|
||||
@ -512,6 +521,7 @@ function syncDownRecursive(
|
||||
string $remoteDisplayBase,
|
||||
string $localDisplayBase,
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip,
|
||||
string $relativeBase = ''
|
||||
): void {
|
||||
foreach ($adapter->listDir($remoteDir) as $item) {
|
||||
@ -520,7 +530,7 @@ function syncDownRecursive(
|
||||
$localPath = rtrim($localDir, DIRECTORY_SEPARATOR . '/\\') . DIRECTORY_SEPARATOR . $item;
|
||||
|
||||
if (isPathSkipped($relativePath, $skipRules)) {
|
||||
logAction('SKIP', formatRemoteLogPath($remotePath, $remoteDisplayBase, $printRelative) . ' (matches --skip)');
|
||||
logSkipAction(formatRemoteLogPath($remotePath, $remoteDisplayBase, $printRelative) . ' (matches --skip)', $noPrintSkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -536,6 +546,7 @@ function syncDownRecursive(
|
||||
$remoteDisplayBase,
|
||||
$localDisplayBase,
|
||||
$printRelative,
|
||||
$noPrintSkip,
|
||||
$relativePath
|
||||
);
|
||||
continue;
|
||||
@ -569,11 +580,11 @@ function syncDownRecursive(
|
||||
}
|
||||
|
||||
if (!shouldTransfer($remoteSize, $remoteMtime, $localSize, $localMtime, $localExists)) {
|
||||
logAction(
|
||||
'SKIP',
|
||||
logSkipAction(
|
||||
formatRemoteLogPath($remotePath, $remoteDisplayBase, $printRelative)
|
||||
. ' -> '
|
||||
. formatLocalLogPath($localPath, $localDisplayBase, $printRelative)
|
||||
. formatLocalLogPath($localPath, $localDisplayBase, $printRelative),
|
||||
$noPrintSkip
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@ -600,7 +611,8 @@ function deleteRemoteFile(
|
||||
SftpAdapter $adapter,
|
||||
string $remoteFile,
|
||||
array $skipDeleteRules,
|
||||
bool $printRelative
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip
|
||||
): void {
|
||||
$remoteFile = normalizeRemotePath($remoteFile);
|
||||
if ($remoteFile === '') {
|
||||
@ -609,13 +621,13 @@ function deleteRemoteFile(
|
||||
$displayBase = dirnameRemotePath($remoteFile);
|
||||
$displayPath = formatRemoteLogPath($remoteFile, $displayBase, $printRelative);
|
||||
if (isPathSkipped($remoteFile, $skipDeleteRules)) {
|
||||
logAction('SKIP', $displayPath . ' (matches --skip-delete)');
|
||||
logSkipAction($displayPath . ' (matches --skip-delete)', $noPrintSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
$stat = $adapter->stat($remoteFile);
|
||||
if ($stat === null) {
|
||||
logAction('SKIP', $displayPath . ' (not found)');
|
||||
logSkipAction($displayPath . ' (not found)', $noPrintSkip);
|
||||
return;
|
||||
}
|
||||
if ($adapter->isDir($remoteFile)) {
|
||||
@ -630,7 +642,8 @@ function deleteRemoteDir(
|
||||
SftpAdapter $adapter,
|
||||
string $remoteDir,
|
||||
array $skipDeleteRules,
|
||||
bool $printRelative
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip
|
||||
): void {
|
||||
$remoteDir = normalizeRemotePath($remoteDir);
|
||||
if (isDangerousRemoteDir($remoteDir)) {
|
||||
@ -639,26 +652,26 @@ function deleteRemoteDir(
|
||||
$displayBase = $remoteDir;
|
||||
$displayPath = formatRemoteLogPath($remoteDir, $displayBase, $printRelative);
|
||||
if (isPathSkipped($remoteDir, $skipDeleteRules)) {
|
||||
logAction('SKIP', $displayPath . ' (matches --skip-delete)');
|
||||
logSkipAction($displayPath . ' (matches --skip-delete)', $noPrintSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$adapter->exists($remoteDir)) {
|
||||
logAction('SKIP', $displayPath . ' (not found)');
|
||||
logSkipAction($displayPath . ' (not found)', $noPrintSkip);
|
||||
return;
|
||||
}
|
||||
if (!$adapter->isDir($remoteDir)) {
|
||||
throw new RuntimeException('Remote path is not a directory: ' . $displayPath);
|
||||
}
|
||||
|
||||
$canRemoveRoot = deleteRemoteDirRecursive($adapter, $remoteDir, $skipDeleteRules, $displayBase, $printRelative);
|
||||
$canRemoveRoot = deleteRemoteDirRecursive($adapter, $remoteDir, $skipDeleteRules, $displayBase, $printRelative, $noPrintSkip);
|
||||
if ($canRemoveRoot) {
|
||||
$adapter->removeEmptyDir($remoteDir);
|
||||
logAction('RMDIR', $displayPath);
|
||||
return;
|
||||
}
|
||||
|
||||
logAction('SKIP', $displayPath . ' (contains --skip-delete entries)');
|
||||
logSkipAction($displayPath . ' (contains --skip-delete entries)', $noPrintSkip);
|
||||
}
|
||||
|
||||
function deleteRemoteDirRecursive(
|
||||
@ -667,6 +680,7 @@ function deleteRemoteDirRecursive(
|
||||
array $skipDeleteRules,
|
||||
string $displayBase,
|
||||
bool $printRelative,
|
||||
bool $noPrintSkip,
|
||||
string $relativeBase = ''
|
||||
): bool {
|
||||
$canRemoveCurrent = true;
|
||||
@ -675,7 +689,7 @@ function deleteRemoteDirRecursive(
|
||||
$child = joinRemotePath($remoteDir, $item);
|
||||
$childDisplay = formatRemoteLogPath($child, $displayBase, $printRelative);
|
||||
if (isPathSkipped($relativePath, $skipDeleteRules)) {
|
||||
logAction('SKIP', $childDisplay . ' (matches --skip-delete)');
|
||||
logSkipAction($childDisplay . ' (matches --skip-delete)', $noPrintSkip);
|
||||
$canRemoveCurrent = false;
|
||||
continue;
|
||||
}
|
||||
@ -687,6 +701,7 @@ function deleteRemoteDirRecursive(
|
||||
$skipDeleteRules,
|
||||
$displayBase,
|
||||
$printRelative,
|
||||
$noPrintSkip,
|
||||
$relativePath
|
||||
);
|
||||
if ($canRemoveChild) {
|
||||
@ -948,6 +963,16 @@ function logAction(string $status, string $message): void
|
||||
writeStatusLine(STDOUT, $status, $message);
|
||||
}
|
||||
|
||||
function logSkipAction(string $message, bool $noPrintSkip): void
|
||||
{
|
||||
incrementStat('SKIP');
|
||||
if ($noPrintSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeStatusLine(STDOUT, 'SKIP', $message);
|
||||
}
|
||||
|
||||
function logError(string $message): void
|
||||
{
|
||||
incrementStat('ERROR');
|
||||
@ -1096,8 +1121,8 @@ function main(array $argv): int
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>} $config
|
||||
* @return array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>}
|
||||
* @param array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,no_print_skip:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>} $config
|
||||
* @return array{help:bool,host:string,user:string,password:string,port:int,print_relative:bool,no_print_skip:bool,skip:list<string>,skip_delete:list<string>,actions:list<array<string,string>>}
|
||||
*/
|
||||
function askForMissingRequiredOptions(array $config): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user