Merge remote-tracking branch 'origin/feat/quiet' into dev

# Conflicts:
#	src/client/client_send.c
This commit is contained in:
2026-09-04 17:14:45 +02:00
7 changed files with 71 additions and 14 deletions
+1
View File
@@ -94,6 +94,7 @@ partial, alternate, and planned behavior.
| `-n, --dry-run` | Scan and print what would be transferred |
| `-p <port>` | SSH port (default: 22) |
| `-v, --verbose` | Enable debug logging |
| `-q, --quiet` | Suppress non-error output |
| `--progress` | Show real-time transfer speed |
| `--delete` | Delete files on receiver not present in source |
| `--exclude <pattern>` | Exclude files matching glob pattern (repeatable) |
+3 -3
View File
@@ -6,11 +6,11 @@ This document maps rsync's full feature set to FastSync's current implementation
| Status | Count | Description |
|--------|-------|-------------|
| ✅ Implemented | 34 | Feature works end-to-end |
| ✅ Implemented | 35 | Feature works end-to-end |
| 🔀 Alt Arg | 3 | Functionality exists but under different flag/semantics |
| ⚠️ Partial | 1 | Flag parsed/stored but behavior incomplete |
| ❌ Not Implemented | 98 | Flag not recognized or no behavior |
| **Total** | **136** | |
| **Total** | **137** | |
---
@@ -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) |
| `-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 |
| `-V`, `--version` | Print version | ✅ Implemented | |
| `--info=FLAGS` | Fine-grained info verbosity | ❌ Not Implemented | Removed because it had no effect |
+5 -1
View File
@@ -212,6 +212,7 @@ static int apply_table_option(Config* config, const OptionEntry* entry, const ch
/* Parse CLI arguments into config. Returns 0 on success, -1 on error, 1 for help/clean-exit. */
int parse_args(Config* config, int argc, char* argv[], int* positional_args,
int* positional_count) {
bool verbose = false;
for (int i = 1; i < argc; i++) {
const OptionEntry* entry = find_table_option(argv[i]);
if (entry) {
@@ -352,7 +353,9 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
0)
return -1;
} else if (opt_is(argv[i], "-v", "--verbose")) {
set_log_level(LOG_LEVEL_DEBUG);
verbose = true;
} else if (opt_is(argv[i], "-q", "--quiet")) {
config->quiet = true;
} else if (opt_is(argv[i], "-T", NULL) && i + 1 < argc) {
if (set_positive_int_option(&config->timeout, argv[++i], "-T") != 0)
return -1;
@@ -377,6 +380,7 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
}
}
}
set_log_level(config->quiet ? LOG_LEVEL_ERROR : (verbose ? LOG_LEVEL_DEBUG : LOG_LEVEL_WARNING));
return 0;
}
+7 -4
View File
@@ -219,9 +219,11 @@ static int send_dry_run_manifest(const Config* config) {
Chunk* chunk;
int file_count = 0;
unsigned long long total_bytes = 0;
if (!config->quiet)
printf("Dry run: files to be transferred\n");
while ((chunk = directory_scanner_next(scanner)) != NULL) {
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);
total_bytes += chunk->items[i]->data->size;
file_count++;
@@ -229,6 +231,7 @@ static int send_dry_run_manifest(const Config* config) {
chunk_destroy(chunk);
}
directory_scanner_destroy(scanner);
if (!config->quiet)
printf("Total: %d files, %.1f MB\n", file_count, total_bytes / 1048576.0);
return 0;
}
@@ -762,7 +765,7 @@ int send_files(Config* config) {
manifest = NULL;
break;
}
if (config->show_progress) {
if (config->show_progress && !config->quiet) {
total_bytes += chunk_bytes;
time_t now = time(NULL);
if (now - last_progress >= 1) {
@@ -786,9 +789,9 @@ int send_files(Config* config) {
bool ok = finalize_transfer(client);
if (ok)
remove_transferred_sources(config, remove_sources);
if (config->show_progress)
if (config->show_progress && !config->quiet)
print_transfer_progress(total_bytes, start, "Done.\n");
if (config->stats) {
if (config->stats && !config->quiet) {
double elapsed_total = difftime(time(NULL), start);
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,
@@ -884,7 +887,7 @@ int send_files_multithreaded(Config** config_ptr) {
thrd_t progress;
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);
if (!progress_created) {
log_perror("Error creating progress thread");
+1
View File
@@ -38,6 +38,7 @@ void print_usage(void) {
printf(" -s Enable chunk serialization\n");
printf(" -f Enable sendfile (TCP only, not with -c or -s)\n");
printf(" -v, --verbose Enable debug logging\n");
printf(" -q, --quiet Suppress non-error output\n");
printf(" -M, --preserve Preserve file metadata\n");
printf(" --chunk-size <n> Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE);
printf(" --source-dir <path> Source directory\n");
+33
View File
@@ -35,6 +35,39 @@ class TestDryRun:
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]}"
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 != ""
@pytest.mark.parametrize("flags", [["-q", "-v"], ["-v", "-q"]])
def test_quiet_successful_transfer_and_verbose_order(self, shared_server, flags):
clean_dir(DEST_DIR)
result, dur = run_client(
SOURCE_DIR, DEST_DIR,
flags=flags,
port=shared_server.port,
)
assert result.returncode == 0, f"Exit {result.returncode}: {result.stderr[:100]}"
assert result.stdout == ""
assert result.stderr == ""
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
mismatches, missing = verify_transfer(SOURCE_DIR, received)
assert not missing, f"Missing: {missing}"
assert not mismatches, f"Mismatch: {mismatches}"
class TestRemoveSourceFiles:
def test_removes_only_transferred_regular_files(self, shared_server):
+18 -3
View File
@@ -283,9 +283,7 @@ static void test_parse_args_unknown_option() {
/* Parsed-but-unimplemented options must fail instead of being silently accepted. */
static void test_parse_args_rejects_unimplemented_options() {
static const char* const options[] = {"-q",
"--quiet",
"--silent",
static const char* const options[] = {"--silent",
"--queue-size",
"-H",
"--hard-links",
@@ -344,6 +342,22 @@ static void test_parse_args_rejects_unimplemented_options() {
}
}
/* Test both rsync-compatible quiet spellings and option ordering. */
static void test_parse_args_quiet() {
static const char* const options[][2] = {
{"-q", "-v"}, {"-v", "-q"}, {"--quiet", "-v"}, {"-v", "--quiet"}};
for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
Config* cfg = config_create();
char* argv[] = {"fastsync", (char*)options[i][0], (char*)options[i][1], "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->quiet);
config_delete(cfg);
}
}
/* Test parse_args with --archive flag */
static void test_parse_args_archive() {
Config* cfg = config_create();
@@ -393,6 +407,7 @@ void test_client_cli() {
test_parse_args_valid_compression_level();
test_parse_args_unknown_option();
test_parse_args_rejects_unimplemented_options();
test_parse_args_quiet();
test_parse_args_archive();
test_parse_args_fsync();
}