Skip to content
Merged
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
12 changes: 10 additions & 2 deletions docs/rmlint.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,20 @@ Traversal Options
:``-b --match-basename``:

Only consider those files as dupes that have the same basename. See also
``man 1 basename``. The comparison of the basenames is case-insensitive.
``man 1 basename``. The comparison of the basenames is case-insensitive for
ASCII characters only; non-ASCII UTF-8 characters must match exactly.

:``-B --unmatched-basename``:

Only consider those files as dupes that do not share the same basename.
See also ``man 1 basename``. The comparison of the basenames is case-insensitive.
See also ``man 1 basename``. The comparison of the basenames is case-insensitive for
ASCII characters only; non-ASCII UTF-8 characters must match exactly.

:``--match-relative-path``:

Only consider those files as dupes that have the same relative path.
Path components are compared case-insensitive for ASCII characters only;
non-ASCII UTF-8 characters must match exactly.
Comment thread
dictcp marked this conversation as resolved.

:``-e --match-extension`` / ``-E --no-match-extension`` (**default**):

Expand Down
1 change: 1 addition & 0 deletions lib/cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ typedef struct RmCfg {
gboolean limits_specified;
gboolean filter_mtime;
gboolean match_basename;
gboolean match_relative_path;
gboolean unmatched_basenames;
gboolean match_with_extension;
gboolean match_without_extension;
Expand Down
1 change: 1 addition & 0 deletions lib/cmdline.c
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,7 @@ bool rm_cmd_parse_args(int argc, char **argv, RmSession *session) {
{"must-match-untagged" , 'M' , 0 , G_OPTION_ARG_NONE , &cfg->must_match_untagged , _("Must have twin in untagged dir") , NULL} ,
{"match-basename" , 'b' , 0 , G_OPTION_ARG_NONE , &cfg->match_basename , _("Only find twins with same basename") , NULL} ,
{"match-extension" , 'e' , 0 , G_OPTION_ARG_NONE , &cfg->match_with_extension , _("Only find twins with same extension") , NULL} ,
{"match-relative-path" , 0 , 0 , G_OPTION_ARG_NONE , &cfg->match_relative_path , _("Only find twins with same relative path") , NULL} ,
{"match-without-extension" , 'i' , 0 , G_OPTION_ARG_NONE , &cfg->match_without_extension , _("Only find twins with same basename minus extension") , NULL} ,
{"merge-directories" , 'D' , EMPTY , G_OPTION_ARG_CALLBACK , FUNC(merge_directories) , _("Find duplicate directories") , NULL} ,
{"honour-dir-layout" , 'j' , EMPTY , G_OPTION_ARG_CALLBACK , FUNC(honour_dir_layout) , _("Only find directories with same file layout") , NULL} ,
Expand Down
24 changes: 24 additions & 0 deletions lib/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,30 @@ gint rm_file_basenames_cmp(const RmFile *file_a, const RmFile *file_b) {
return g_ascii_strcasecmp(file_a->folder->basename, file_b->folder->basename);
}

gint rm_file_relpaths_cmp(const RmFile *file_a, const RmFile *file_b) {
gint diff = file_a->depth - file_b->depth;
RETURN_IF_NONZERO(diff);

Comment thread
dictcp marked this conversation as resolved.
/* If both files have depth 0, we still need to distinguish them by at
* least their basenames; otherwise, they would always compare equal. */
if(file_a->depth == 0) {
return rm_file_basenames_cmp(file_a, file_b);
}
RmNode *node_a = file_a->folder;
RmNode *node_b = file_b->folder;
for(gint depth = file_a->depth; depth > 0; --depth) {
g_assert(node_a);
g_assert(node_b);

diff = g_ascii_strcasecmp(node_a->basename, node_b->basename);
Comment thread
dictcp marked this conversation as resolved.
RETURN_IF_NONZERO(diff);

node_a = node_a->parent;
node_b = node_b->parent;
}
return 0;
}

void rm_file_hardlink_add(RmFile *head, RmFile *link) {
if(!head->hardlinks) {
head->hardlinks = g_queue_new();
Expand Down
6 changes: 6 additions & 0 deletions lib/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,10 @@ RmFile *rm_file_copy(RmFile *file);
*/
gint rm_file_basenames_cmp(const RmFile *file_a, const RmFile *file_b);

/**
* @brief Compare relative paths of two files
* @return 0 if relative paths match, non-zero otherwise.
*/
gint rm_file_relpaths_cmp(const RmFile *file_a, const RmFile *file_b);

#endif /* end of include guard */
6 changes: 6 additions & 0 deletions lib/preprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ gint rm_file_cmp(const RmFile *file_a, const RmFile *file_b) {

if(cfg->match_without_extension) {
result = rm_file_cmp_without_extension(file_a, file_b);
RETURN_IF_NONZERO(result);
}

if(cfg->match_relative_path) {
result = rm_file_relpaths_cmp(file_a, file_b);
RETURN_IF_NONZERO(result);
}

return result;
Expand Down
68 changes: 68 additions & 0 deletions tests/test_options/test_match_relative_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
# encoding: utf-8
from tests.utils import *


def test_positive_match_relative_path(usual_setup_usual_teardown):
create_file('xxx', 'base_a/snap/test')
create_file('xxx', 'base_b/snap/test')

head, *data, footer = run_rmlint(
'--match-relative-path {t}/base_a {t}/base_b'.format(t=TESTDIR_NAME),
use_default_dir=False
)
Comment thread
dictcp marked this conversation as resolved.
assert footer['total_files'] == 2
assert footer['total_lint_size'] == 3
assert footer['duplicates'] == 1


def test_negative_match_relative_path(usual_setup_usual_teardown):
Comment thread
dictcp marked this conversation as resolved.
create_file('xxx', 'base_a/snap1/test')
create_file('xxx', 'base_b/snap2/test')

head, *data, footer = run_rmlint(
'--match-relative-path {t}/base_a {t}/base_b'.format(t=TESTDIR_NAME),
use_default_dir=False
)
assert footer['total_files'] == 2
assert footer['total_lint_size'] == 0
assert footer['duplicates'] == 0


def test_negative_match_relative_path_unequal_depth(usual_setup_usual_teardown):
create_file('xxx', 'base_a/snap/test')
create_file('xxx', 'base_b/deeper/snap/test')

head, *data, footer = run_rmlint(
'--match-relative-path {t}/base_a {t}/base_b'.format(t=TESTDIR_NAME),
use_default_dir=False
)
assert footer['total_files'] == 2
assert footer['total_lint_size'] == 0
assert footer['duplicates'] == 0


def test_positive_match_relative_path_depth_4(usual_setup_usual_teardown):
create_file('xxx', 'base_a/l1/l2/l3/test')
create_file('xxx', 'base_b/l1/l2/l3/test')

head, *data, footer = run_rmlint(
'--match-relative-path {t}/base_a {t}/base_b'.format(t=TESTDIR_NAME),
use_default_dir=False
)
assert footer['total_files'] == 2
assert footer['total_lint_size'] == 3
assert footer['duplicates'] == 1


def test_negative_match_relative_path_explicit_files(usual_setup_usual_teardown):
path_a = create_file('xxx', 'base_a/file_a')
path_b = create_file('xxx', 'base_b/file_b')

head, *data, footer = run_rmlint(
'--match-relative-path', path_a, path_b,
use_default_dir=False
)
assert footer['total_files'] == 2
assert footer['total_lint_size'] == 0
assert footer['duplicates'] == 0