Skip to content

Add option --skip-dir-with to skip directories containing a marker file #747

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions flist.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/

#include "rsync.h"
#include <unistd.h>
#include "ifuncs.h"
#include "rounding.h"
#include "inums.h"
Expand Down Expand Up @@ -277,6 +278,18 @@ static inline int path_is_daemon_excluded(char *path, int ignore_filename)

static inline int is_excluded(const char *fname, int is_dir, int filter_level)
{
/* Check for marker file in directories */
if (skip_dir_with_file && is_dir) {
char marker_path[MAXPATHLEN];
int len = snprintf(marker_path, MAXPATHLEN, "%s/%s", fname, skip_dir_with_file);

if (len > 0 && len < MAXPATHLEN && access(marker_path, F_OK) == 0) {
if (INFO_GTE(FLIST, 2))
rprintf(FINFO, "skipping directory %s containing %s marker\n",
full_fname(fname), skip_dir_with_file);
return 1;
}
}
return name_is_excluded(fname, is_dir ? NAME_IS_DIR : NAME_IS_FILE, filter_level);
}

Expand Down
15 changes: 14 additions & 1 deletion options.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ int32 block_size = 0;
time_t stop_at_utime = 0;
char *skip_compress = NULL;
char *copy_as = NULL;
const char *skip_dir_with_file = NULL;
item_list dparam_list = EMPTY_ITEM_LIST;

/** Network address family. **/
Expand Down Expand Up @@ -584,7 +585,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
OPT_STOP_AFTER, OPT_STOP_AT,
OPT_STOP_AFTER, OPT_STOP_AT, OPT_SKIP_DIR_WITH,
OPT_REFUSED_BASE = 9000};

static struct poptOption long_options[] = {
Expand Down Expand Up @@ -841,6 +842,8 @@ static struct poptOption long_options[] = {
{"dparam", 0, POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
{"detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 },
{"no-detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 },
{"skip-compress", 0, POPT_ARG_STRING, &skip_compress, 0, 0, 0 },
{"skip-dir-with", 0, POPT_ARG_STRING, &skip_dir_with_file, OPT_SKIP_DIR_WITH, 0, 0 },
{0,0,0,0, 0, 0, 0}
};

Expand Down Expand Up @@ -1893,6 +1896,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
break;
#endif

case OPT_SKIP_DIR_WITH:
arg = poptGetOptArg(pc);
if (arg && *arg)
skip_dir_with_file = arg;
else {
snprintf(err_buf, sizeof err_buf, "skip-dir-with requires a FILE argument\n");
goto cleanup;
}
break;

case OPT_STDERR: {
int len;
arg = poptGetOptArg(pc);
Expand Down
14 changes: 14 additions & 0 deletions rsync.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ has its own detailed description later in this manpage.
--compress-choice=STR choose the compression algorithm (aka --zc)
--compress-level=NUM explicitly set compression level (aka --zl)
--skip-compress=LIST skip compressing files with suffix in LIST
--skip-dir-with=FILE skip directories containing specified FILE
--cvs-exclude, -C auto-ignore files in the same way CVS does
--filter=RULE, -f add a file-filtering RULE
-F same as --filter='dir-merge /.rsync-filter'
Expand Down Expand Up @@ -2951,6 +2952,19 @@ expand it.
list of non-compressing files (and its list may be configured to a
different default).

0. `--skip-dir-with=FILE`

Skip directories that contain the specified FILE.

This option is useful for skipping transfer of directories that contain
a marker file indicating the directory should not be transferred. For
example, you could use it to skip directories containing temporary files or
cached data.

The FILE argument is required and specifies the name of the marker file to
look for. rsync will check each directory for the presence of a file with
this name, and if found, will skip the directory entirely.

0. `--numeric-ids`

With this option rsync will transfer numeric group and user IDs rather than
Expand Down
2 changes: 2 additions & 0 deletions rsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -1487,3 +1487,5 @@ const char *get_panic_action(void);
#elif defined HAVE_MALLINFO
#define MEM_ALLOC_INFO mallinfo
#endif

extern const char *skip_dir_with_file;
69 changes: 69 additions & 0 deletions testsuite/skip-dir-with.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/sh

# Copyright (C) 2025 Allan Clark

# This program is distributable under the terms of the GNU GPL (see
# COPYING).

# Test rsync handling of --skip-dir-with option, which skips directories
# containing a specified marker file.

. "$suitedir/rsync.fns"

# Build some files/dirs/links to copy
makepath "$fromdir/dir1"
makepath "$fromdir/dir2"
makepath "$fromdir/dir3"
makepath "$fromdir/dir4/subdir"

# Add content to the directories
echo "file1" > "$fromdir/dir1/file1.txt"
echo "file2" > "$fromdir/dir2/file2.txt"
echo "file3" > "$fromdir/dir3/file3.txt"
echo "file4" > "$fromdir/dir4/file4.txt"
echo "subfile" > "$fromdir/dir4/subdir/subfile.txt"

# Create marker files
touch "$fromdir/dir2/.marker"
touch "$fromdir/dir4/subdir/.different_marker"

# Test 1: Using .marker to skip dir2
$RSYNC -a --skip-dir-with=.marker "$fromdir/" "$todir/"

# Verify that dir2 was skipped
test -d "$todir/dir2" && test_fail "dir2 with .marker was not skipped"
test -d "$todir/dir1" || test_fail "dir1 should have been copied"
test -d "$todir/dir3" || test_fail "dir3 should have been copied"
test -d "$todir/dir4" || test_fail "dir4 should have been copied"

# Cleanup
rm -rf "$todir"/*

# Test 2: Using .different_marker to skip subdir
$RSYNC -a --skip-dir-with=.different_marker "$fromdir/" "$todir/"

# Verify that dir4/subdir was skipped
test -d "$todir/dir4/subdir" && test_fail "dir4/subdir with .different_marker was not skipped"
test -d "$todir/dir1" || test_fail "dir1 should have been copied"
test -d "$todir/dir2" || test_fail "dir2 should have been copied"
test -d "$todir/dir3" || test_fail "dir3 should have been copied"
test -f "$todir/dir4/file4.txt" || test_fail "dir4/file4.txt should have been copied"

# Cleanup
rm -rf "$todir"/*

# Add more marker files for third test
touch "$fromdir/dir1/.common_marker"
touch "$fromdir/dir3/.common_marker"

# Test 3: Using .common_marker to skip multiple directories
$RSYNC -a --skip-dir-with=.common_marker "$fromdir/" "$todir/"

# Verify that both dir1 and dir3 were skipped
test -d "$todir/dir1" && test_fail "dir1 with .common_marker was not skipped"
test -d "$todir/dir3" && test_fail "dir3 with .common_marker was not skipped"
test -d "$todir/dir2" || test_fail "dir2 should have been copied"
test -d "$todir/dir4" || test_fail "dir4 should have been copied"

# Success!
exit 0