diff --git a/README b/README index 5f8e9c0..2879bdf 100644 --- a/README +++ b/README @@ -35,6 +35,11 @@ Usage: fdupes [options] DIRECTORY... prompting the user -p --permissions don't consider files with different owner/group or permission bits as duplicates + -b --basename don't consider files with different basenames (that's + the base file name with directory path excluded) as duplicates + -P --strip=N strip the indicated number of leading directory + components (at slashes), and consider files potential + matches only if the remaining pathnames are the same -o --order=BY select sort order for output, linking and deleting; by mtime (BY='time'; default) or filename (BY='name') -i --reverse reverse order while sorting diff --git a/fdupes.1 b/fdupes.1 index bdad958..dbc42c2 100644 --- a/fdupes.1 +++ b/fdupes.1 @@ -66,6 +66,15 @@ set of duplicates and delete the others without prompting the user .B -p --permissions don't consider files with different owner/group or permission bits as duplicates .TP +.B -b --basename +don't consider files with different file basenames (that's the base file name with +the directory path excluded) as duplicates +.TP +.B -P --strip=N +strip the indicated number of leading directory components (at slashes), +and consider files potential matches only if the remaining pathnames +are the same +.TP .B -o --order\fR=\fIWORD\fR order files according to WORD: time - sort by mtime, name - sort by filename diff --git a/fdupes.c b/fdupes.c index 7745209..50fec3a 100644 --- a/fdupes.c +++ b/fdupes.c @@ -53,6 +53,7 @@ #define F_EXCLUDEHIDDEN 0x1000 #define F_PERMISSIONS 0x2000 #define F_REVERSE 0x4000 +#define F_BASENAME 0x8000 typedef enum { ORDER_TIME = 0, @@ -63,6 +64,8 @@ char *program_name; unsigned long flags = 0; +int stripdirs = 0; + #define CHUNK_SIZE 8192 #define INPUT_SIZE 256 @@ -91,6 +94,8 @@ typedef struct _signatures typedef struct _file { char *d_name; + char *d_basename; + char *d_strippedname; off_t size; md5_byte_t *crcpartial; md5_byte_t *crcsignature; @@ -249,7 +254,6 @@ int grokdir(char *dir, file_t **filelistp) struct stat linfo; static int progress = 0; static char indicator[] = "-\\|/"; - char *fullname, *name; cd = opendir(dir); @@ -291,19 +295,29 @@ int grokdir(char *dir, file_t **filelistp) strcpy(newfile->d_name, dir); lastchar = strlen(dir) - 1; - if (lastchar >= 0 && dir[lastchar] != '/') - strcat(newfile->d_name, "/"); - strcat(newfile->d_name, dirinfo->d_name); + if (lastchar >= 0 && dir[lastchar] != '/') { + newfile->d_name[++lastchar] = '/'; + } + newfile->d_basename = newfile->d_name + lastchar + 1; + strcpy(newfile->d_basename, dirinfo->d_name); + + newfile->d_strippedname = newfile->d_name; + for (int i = 0 ; i < stripdirs ; ++i) { + newfile->d_strippedname = strchr(newfile->d_strippedname, '/'); + if (! newfile->d_strippedname) { + break; + } + ++newfile->d_strippedname; + } if (ISFLAG(flags, F_EXCLUDEHIDDEN)) { - fullname = strdup(newfile->d_name); - name = basename(fullname); - if (name[0] == '.' && strcmp(name, ".") && strcmp(name, "..") ) { + if (newfile->d_basename[0] == '.' + && strcmp(newfile->d_basename, ".") + && strcmp(newfile->d_basename, "..") ) { free(newfile->d_name); free(newfile); continue; } - free(fullname); } if (filesize(newfile->d_name) == 0 && ISFLAG(flags, F_EXCLUDEEMPTY)) { @@ -516,10 +530,20 @@ file_t **checkmatch(filetree_t **root, filetree_t *checktree, file_t *file) cmpresult = -1; else if (fsize > checktree->file->size) cmpresult = 1; + else + if (ISFLAG(flags, F_BASENAME) && + strcmp(file->d_basename, checktree->file->d_basename) != 0) + cmpresult = -1; else if (ISFLAG(flags, F_PERMISSIONS) && !same_permissions(file->d_name, checktree->file->d_name)) cmpresult = -1; + else + if (stripdirs > 0 && + file->d_strippedname && + checktree->file->d_strippedname && + strcmp(file->d_strippedname, checktree->file->d_strippedname) != 0) + cmpresult = -1; else { if (checktree->file->crcpartial == NULL) { crcsignature = getcrcpartialsignature(checktree->file->d_name); @@ -1003,6 +1027,11 @@ void help_text() printf(" \tprompting the user\n"); printf(" -p --permissions \tdon't consider files with different owner/group or\n"); printf(" \tpermission bits as duplicates\n"); + printf(" -b --basename \tdon't consider files with different basenames (that's\n"); + printf(" \tthe base file name with directory path excluded) as duplicates\n"); + printf(" -P --strip=N \tstrip the indicated number of leading directory\n"); + printf(" \tcomponents (at slashes), and consider files potential\n"); + printf(" \tmatches only if the remaining pathnames are the same\n"); printf(" -o --order=BY \tselect sort order for output, linking and deleting; by\n"); printf(" \tmtime (BY='time'; default) or filename (BY='name')\n"); printf(" -i --reverse \treverse order while sorting\n"); @@ -1051,7 +1080,9 @@ int main(int argc, char **argv) { { "summarize", 0, 0, 'm'}, { "summary", 0, 0, 'm' }, { "permissions", 0, 0, 'p' }, - { "order", 1, 0, 'o' }, + { "basename", 0, 0, 'b' }, + { "strip", required_argument, 0, 'P' }, + { "order", required_argument, 0, 'o' }, { "reverse", 0, 0, 'i' }, { 0, 0, 0, 0 } }; @@ -1064,7 +1095,7 @@ int main(int argc, char **argv) { oldargv = cloneargs(argc, argv); - while ((opt = GETOPT(argc, argv, "frRq1SsHlnAdvhNmpo:i" + while ((opt = GETOPT(argc, argv, "frRq1SsHlnAdvhNmpbP:o:i" #ifndef OMIT_GETOPT_LONG , long_options, NULL #endif @@ -1118,6 +1149,16 @@ int main(int argc, char **argv) { case 'p': SETFLAG(flags, F_PERMISSIONS); break; + case 'b': + SETFLAG(flags, F_BASENAME); + break; + case 'P': + stripdirs = atoi(optarg); + if (stripdirs == 0 && strcmp(optarg, "0") != 0) { + errormsg("invalid value for --strip: '%s'\n", optarg); + exit(1); + } + break; case 'o': if (!strcasecmp("name", optarg)) { ordertype = ORDER_NAME; diff --git a/testdir/basename_test/dir1/included_with_b_option b/testdir/basename_test/dir1/included_with_b_option new file mode 100644 index 0000000..17c8020 --- /dev/null +++ b/testdir/basename_test/dir1/included_with_b_option @@ -0,0 +1 @@ +Integer condimentum, massa non malesuada cursus, arcu tellus cursus tellus, vitae sodales diam erat eget sem. Nulla pulvinar lacus sed pulvinar sodales. Curabitur blandit, orci sed elementum efficitur, ipsum libero finibus lacus, nec varius nunc est at eros. Morbi feugiat libero quis lectus dictum pellentesque. Mauris scelerisque placerat neque non sodales. Proin elementum velit eget lacinia mollis. Aenean varius nunc lorem, id malesuada sapien mattis eu. Mauris sagittis diam ante, eget semper ipsum posuere in. Vivamus cursus ante vitae arcu vulputate, non feugiat mauris sollicitudin. Nam nec nisl at nunc elementum rhoncus. Suspendisse a ante et mi hendrerit vehicula. Aliquam diam ex, porta bibendum bibendum at, sagittis tempor velit. Aenean mattis enim nec velit vehicula feugiat. Sed non facilisis tortor, eget interdum neque. Donec malesuada, felis quis egestas accumsan, neque lectus scelerisque lectus, sit amet elementum tortor metus sit amet neque. Fusce efficitur gravida turpis ut pharetra. diff --git a/testdir/basename_test/dir2/excluded_with_b_option b/testdir/basename_test/dir2/excluded_with_b_option new file mode 100644 index 0000000..17c8020 --- /dev/null +++ b/testdir/basename_test/dir2/excluded_with_b_option @@ -0,0 +1 @@ +Integer condimentum, massa non malesuada cursus, arcu tellus cursus tellus, vitae sodales diam erat eget sem. Nulla pulvinar lacus sed pulvinar sodales. Curabitur blandit, orci sed elementum efficitur, ipsum libero finibus lacus, nec varius nunc est at eros. Morbi feugiat libero quis lectus dictum pellentesque. Mauris scelerisque placerat neque non sodales. Proin elementum velit eget lacinia mollis. Aenean varius nunc lorem, id malesuada sapien mattis eu. Mauris sagittis diam ante, eget semper ipsum posuere in. Vivamus cursus ante vitae arcu vulputate, non feugiat mauris sollicitudin. Nam nec nisl at nunc elementum rhoncus. Suspendisse a ante et mi hendrerit vehicula. Aliquam diam ex, porta bibendum bibendum at, sagittis tempor velit. Aenean mattis enim nec velit vehicula feugiat. Sed non facilisis tortor, eget interdum neque. Donec malesuada, felis quis egestas accumsan, neque lectus scelerisque lectus, sit amet elementum tortor metus sit amet neque. Fusce efficitur gravida turpis ut pharetra. diff --git a/testdir/basename_test/dir2/included_with_b_option b/testdir/basename_test/dir2/included_with_b_option new file mode 100644 index 0000000..17c8020 --- /dev/null +++ b/testdir/basename_test/dir2/included_with_b_option @@ -0,0 +1 @@ +Integer condimentum, massa non malesuada cursus, arcu tellus cursus tellus, vitae sodales diam erat eget sem. Nulla pulvinar lacus sed pulvinar sodales. Curabitur blandit, orci sed elementum efficitur, ipsum libero finibus lacus, nec varius nunc est at eros. Morbi feugiat libero quis lectus dictum pellentesque. Mauris scelerisque placerat neque non sodales. Proin elementum velit eget lacinia mollis. Aenean varius nunc lorem, id malesuada sapien mattis eu. Mauris sagittis diam ante, eget semper ipsum posuere in. Vivamus cursus ante vitae arcu vulputate, non feugiat mauris sollicitudin. Nam nec nisl at nunc elementum rhoncus. Suspendisse a ante et mi hendrerit vehicula. Aliquam diam ex, porta bibendum bibendum at, sagittis tempor velit. Aenean mattis enim nec velit vehicula feugiat. Sed non facilisis tortor, eget interdum neque. Donec malesuada, felis quis egestas accumsan, neque lectus scelerisque lectus, sit amet elementum tortor metus sit amet neque. Fusce efficitur gravida turpis ut pharetra. diff --git a/testdir/pathstrip_test/subdir1/excluded_if_P_option_le_2 b/testdir/pathstrip_test/subdir1/excluded_if_P_option_le_2 new file mode 100644 index 0000000..c9aaf70 --- /dev/null +++ b/testdir/pathstrip_test/subdir1/excluded_if_P_option_le_2 @@ -0,0 +1 @@ +Praesent nec placerat velit. Integer vel scelerisque sapien, in suscipit tellus. Mauris congue nisl quis mi maximus, eleifend pharetra sem lobortis. Aenean luctus enim a orci varius, non tincidunt justo vehicula. Quisque mattis quam faucibus lectus bibendum sagittis. Integer lobortis nulla nec ligula rutrum, vitae ultrices turpis porttitor. Quisque at lorem mollis, placerat purus nec, vestibulum mauris. Proin interdum posuere tempor. Proin blandit felis ut purus consequat, vitae laoreet sem ultrices. diff --git a/testdir/pathstrip_test/subdir2/excluded_if_P_option_le_2 b/testdir/pathstrip_test/subdir2/excluded_if_P_option_le_2 new file mode 100644 index 0000000..c9aaf70 --- /dev/null +++ b/testdir/pathstrip_test/subdir2/excluded_if_P_option_le_2 @@ -0,0 +1 @@ +Praesent nec placerat velit. Integer vel scelerisque sapien, in suscipit tellus. Mauris congue nisl quis mi maximus, eleifend pharetra sem lobortis. Aenean luctus enim a orci varius, non tincidunt justo vehicula. Quisque mattis quam faucibus lectus bibendum sagittis. Integer lobortis nulla nec ligula rutrum, vitae ultrices turpis porttitor. Quisque at lorem mollis, placerat purus nec, vestibulum mauris. Proin interdum posuere tempor. Proin blandit felis ut purus consequat, vitae laoreet sem ultrices.