feat: add quiet client option
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 37s
CI / sanitizers (undefined) (pull_request) Successful in 36s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 32s
CI / build-and-test (pull_request) Successful in 1m16s
CI / valgrind (pull_request) Successful in 33s
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 37s
CI / sanitizers (undefined) (pull_request) Successful in 36s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 32s
CI / build-and-test (pull_request) Successful in 1m16s
CI / valgrind (pull_request) Successful in 33s
This commit is contained in:
@@ -94,6 +94,7 @@ partial, alternate, and planned behavior.
|
|||||||
| `-n, --dry-run` | Scan and print what would be transferred |
|
| `-n, --dry-run` | Scan and print what would be transferred |
|
||||||
| `-p <port>` | SSH port (default: 22) |
|
| `-p <port>` | SSH port (default: 22) |
|
||||||
| `-v, --verbose` | Enable debug logging |
|
| `-v, --verbose` | Enable debug logging |
|
||||||
|
| `-q, --quiet` | Suppress non-error output |
|
||||||
| `--progress` | Show real-time transfer speed |
|
| `--progress` | Show real-time transfer speed |
|
||||||
| `--delete` | Delete files on receiver not present in source |
|
| `--delete` | Delete files on receiver not present in source |
|
||||||
| `--exclude <pattern>` | Exclude files matching glob pattern (repeatable) |
|
| `--exclude <pattern>` | Exclude files matching glob pattern (repeatable) |
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `-a`, `--archive` | Archive mode is -rlptgoD | 🔀 Alt Arg | Maps to -c -m -M (compression + multithread + metadata) |
|
| `-a`, `--archive` | Archive mode is -rlptgoD | 🔀 Alt Arg | Maps to -c -m -M (compression + multithread + metadata) |
|
||||||
| `-v`, `--verbose` | Increase verbosity | ✅ Implemented | Sets `log_level=DEBUG` |
|
| `-v`, `--verbose` | Increase verbosity | ✅ Implemented | Sets `log_level=DEBUG` |
|
||||||
| `-q`, `--quiet` | Suppress non-error messages | ❌ Not Implemented | Removed because it had no effect |
|
| `-q`, `--quiet` | Suppress non-error messages | ✅ Implemented | Suppresses client output while preserving errors |
|
||||||
| `--help` | Show help | ✅ Implemented | Prints usage and exits; `-h` is not accepted |
|
| `--help` | Show help | ✅ Implemented | Prints usage and exits; `-h` is not accepted |
|
||||||
| `-V`, `--version` | Print version | ✅ Implemented | |
|
| `-V`, `--version` | Print version | ✅ Implemented | |
|
||||||
| `--info=FLAGS` | Fine-grained info verbosity | ❌ Not Implemented | Removed because it had no effect |
|
| `--info=FLAGS` | Fine-grained info verbosity | ❌ Not Implemented | Removed because it had no effect |
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
|||||||
return -1;
|
return -1;
|
||||||
} else if (opt_is(argv[i], "-v", "--verbose")) {
|
} else if (opt_is(argv[i], "-v", "--verbose")) {
|
||||||
set_log_level(LOG_LEVEL_DEBUG);
|
set_log_level(LOG_LEVEL_DEBUG);
|
||||||
|
} else if (opt_is(argv[i], "-q", "--quiet")) {
|
||||||
|
config->quiet = true;
|
||||||
|
set_log_level(LOG_LEVEL_ERROR);
|
||||||
} else if (opt_is(argv[i], "-T", NULL) && i + 1 < argc) {
|
} else if (opt_is(argv[i], "-T", NULL) && i + 1 < argc) {
|
||||||
if (set_positive_int_option(&config->timeout, argv[++i], "-T") != 0)
|
if (set_positive_int_option(&config->timeout, argv[++i], "-T") != 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -134,9 +134,11 @@ static int send_dry_run_manifest(const Config* config) {
|
|||||||
Chunk* chunk;
|
Chunk* chunk;
|
||||||
int file_count = 0;
|
int file_count = 0;
|
||||||
unsigned long long total_bytes = 0;
|
unsigned long long total_bytes = 0;
|
||||||
|
if (!config->quiet)
|
||||||
printf("Dry run: files to be transferred\n");
|
printf("Dry run: files to be transferred\n");
|
||||||
while ((chunk = directory_scanner_next(scanner)) != NULL) {
|
while ((chunk = directory_scanner_next(scanner)) != NULL) {
|
||||||
for (int i = 0; i < chunk->element_count; i++) {
|
for (int i = 0; i < chunk->element_count; i++) {
|
||||||
|
if (!config->quiet)
|
||||||
printf(" %s (%zu bytes)\n", chunk->items[i]->path, chunk->items[i]->data->size);
|
printf(" %s (%zu bytes)\n", chunk->items[i]->path, chunk->items[i]->data->size);
|
||||||
total_bytes += chunk->items[i]->data->size;
|
total_bytes += chunk->items[i]->data->size;
|
||||||
file_count++;
|
file_count++;
|
||||||
@@ -144,6 +146,7 @@ static int send_dry_run_manifest(const Config* config) {
|
|||||||
chunk_destroy(chunk);
|
chunk_destroy(chunk);
|
||||||
}
|
}
|
||||||
directory_scanner_destroy(scanner);
|
directory_scanner_destroy(scanner);
|
||||||
|
if (!config->quiet)
|
||||||
printf("Total: %d files, %.1f MB\n", file_count, total_bytes / 1048576.0);
|
printf("Total: %d files, %.1f MB\n", file_count, total_bytes / 1048576.0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -650,7 +653,7 @@ int send_files(Config* config) {
|
|||||||
manifest = NULL;
|
manifest = NULL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (config->show_progress) {
|
if (config->show_progress && !config->quiet) {
|
||||||
total_bytes += chunk_bytes;
|
total_bytes += chunk_bytes;
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
if (now - last_progress >= 1) {
|
if (now - last_progress >= 1) {
|
||||||
@@ -672,9 +675,9 @@ int send_files(Config* config) {
|
|||||||
manifest = NULL;
|
manifest = NULL;
|
||||||
}
|
}
|
||||||
bool ok = finalize_transfer(client);
|
bool ok = finalize_transfer(client);
|
||||||
if (config->show_progress)
|
if (config->show_progress && !config->quiet)
|
||||||
print_transfer_progress(total_bytes, start, "Done.\n");
|
print_transfer_progress(total_bytes, start, "Done.\n");
|
||||||
if (config->stats) {
|
if (config->stats && !config->quiet) {
|
||||||
double elapsed_total = difftime(time(NULL), start);
|
double elapsed_total = difftime(time(NULL), start);
|
||||||
double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0;
|
double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0;
|
||||||
fprintf(stderr, "Stats: %d files, %.1f MB, %.1f MB/s\n", total_files, total_bytes / 1048576.0,
|
fprintf(stderr, "Stats: %d files, %.1f MB, %.1f MB/s\n", total_files, total_bytes / 1048576.0,
|
||||||
@@ -765,7 +768,7 @@ int send_files_multithreaded(Config** config_ptr) {
|
|||||||
|
|
||||||
thrd_t progress;
|
thrd_t progress;
|
||||||
bool progress_created = false;
|
bool progress_created = false;
|
||||||
if (config->show_progress) {
|
if (config->show_progress && !config->quiet) {
|
||||||
progress_created = (thrd_create(&progress, progress_thread_fn, context) == thrd_success);
|
progress_created = (thrd_create(&progress, progress_thread_fn, context) == thrd_success);
|
||||||
if (!progress_created) {
|
if (!progress_created) {
|
||||||
log_perror("Error creating progress thread");
|
log_perror("Error creating progress thread");
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ void print_usage(void) {
|
|||||||
printf(" -s Enable chunk serialization\n");
|
printf(" -s Enable chunk serialization\n");
|
||||||
printf(" -f Enable sendfile (TCP only, not with -c or -s)\n");
|
printf(" -f Enable sendfile (TCP only, not with -c or -s)\n");
|
||||||
printf(" -v, --verbose Enable debug logging\n");
|
printf(" -v, --verbose Enable debug logging\n");
|
||||||
|
printf(" -q, --quiet Suppress non-error output\n");
|
||||||
printf(" -M, --preserve Preserve file metadata\n");
|
printf(" -M, --preserve Preserve file metadata\n");
|
||||||
printf(" --chunk-size <n> Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE);
|
printf(" --chunk-size <n> Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE);
|
||||||
printf(" --source-dir <path> Source directory\n");
|
printf(" --source-dir <path> Source directory\n");
|
||||||
|
|||||||
@@ -35,6 +35,23 @@ class TestDryRun:
|
|||||||
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr[:100]}"
|
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr[:100]}"
|
||||||
assert "Dry run:" in result.stdout, f"No dry run output: {result.stdout[:200]}"
|
assert "Dry run:" in result.stdout, f"No dry run output: {result.stdout[:200]}"
|
||||||
|
|
||||||
|
def test_quiet_suppresses_dry_run_output(self):
|
||||||
|
result, dur = run_client(
|
||||||
|
SOURCE_DIR, DEST_DIR,
|
||||||
|
flags=["-q", "-n", "--progress", "--stats"],
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr[:100]}"
|
||||||
|
assert result.stdout == ""
|
||||||
|
assert result.stderr == ""
|
||||||
|
|
||||||
|
def test_quiet_preserves_errors(self):
|
||||||
|
result, dur = run_client(
|
||||||
|
SOURCE_DIR, DEST_DIR,
|
||||||
|
flags=["--quiet", "--server-port", "1"],
|
||||||
|
)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert result.stderr != ""
|
||||||
|
|
||||||
|
|
||||||
class TestArchiveMode:
|
class TestArchiveMode:
|
||||||
def test_archive_mode(self, shared_server):
|
def test_archive_mode(self, shared_server):
|
||||||
|
|||||||
+17
-3
@@ -262,9 +262,7 @@ static void test_parse_args_unknown_option() {
|
|||||||
|
|
||||||
/* Parsed-but-unimplemented options must fail instead of being silently accepted. */
|
/* Parsed-but-unimplemented options must fail instead of being silently accepted. */
|
||||||
static void test_parse_args_rejects_unimplemented_options() {
|
static void test_parse_args_rejects_unimplemented_options() {
|
||||||
static const char* const options[] = {"-q",
|
static const char* const options[] = {"--silent",
|
||||||
"--quiet",
|
|
||||||
"--silent",
|
|
||||||
"--queue-size",
|
"--queue-size",
|
||||||
"-H",
|
"-H",
|
||||||
"--hard-links",
|
"--hard-links",
|
||||||
@@ -323,6 +321,21 @@ static void test_parse_args_rejects_unimplemented_options() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Test both rsync-compatible quiet spellings. */
|
||||||
|
static void test_parse_args_quiet() {
|
||||||
|
static const char* const options[] = {"-q", "--quiet"};
|
||||||
|
for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
|
||||||
|
Config* cfg = config_create();
|
||||||
|
char* argv[] = {"fastsync", (char*)options[i], "/src", "/dst"};
|
||||||
|
int positional_args[2];
|
||||||
|
int positional_count = 0;
|
||||||
|
|
||||||
|
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
||||||
|
EXPECT_TRUE(cfg->quiet);
|
||||||
|
config_delete(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Test parse_args with --archive flag */
|
/* Test parse_args with --archive flag */
|
||||||
static void test_parse_args_archive() {
|
static void test_parse_args_archive() {
|
||||||
Config* cfg = config_create();
|
Config* cfg = config_create();
|
||||||
@@ -359,5 +372,6 @@ void test_client_cli() {
|
|||||||
test_parse_args_valid_compression_level();
|
test_parse_args_valid_compression_level();
|
||||||
test_parse_args_unknown_option();
|
test_parse_args_unknown_option();
|
||||||
test_parse_args_rejects_unimplemented_options();
|
test_parse_args_rejects_unimplemented_options();
|
||||||
|
test_parse_args_quiet();
|
||||||
test_parse_args_archive();
|
test_parse_args_archive();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user