diff options
-rw-r--r-- | NEWS | 8 | ||||
-rw-r--r-- | examples/git-metapull | 51 | ||||
-rw-r--r-- | examples/post-checkout | 24 | ||||
-rw-r--r-- | examples/pre-commit | 28 | ||||
-rw-r--r-- | metastore.1 | 8 | ||||
-rw-r--r-- | metastore.c | 121 | ||||
-rw-r--r-- | settings.h | 9 |
7 files changed, 186 insertions, 63 deletions
@@ -1,6 +1,12 @@ -Latest stuff (planned release date: 2015-03-??) +Latest stuff (planned release date: 2015-09-??) ------------------------------------------------------------------------ + * Empty directories not present in metadata can be now removed when + applying stored metadata if -E / --remove-empty-dirs option is used. + + * Scripts in example/ directory do not require bash anymore and there + is a new example for post checkout hook. + * Dependency on libattr's xattr.h header has been removed. As long as your libc provides sys/xattr.h (glibc does it since v2.3), everything should be fine. diff --git a/examples/git-metapull b/examples/git-metapull index 789149f..8842b11 100644 --- a/examples/git-metapull +++ b/examples/git-metapull @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # This script can be used instead of git-pull when updating a remote # repo to make sure the metadata matches what is stored in the repo. @@ -6,44 +6,41 @@ # and after getting confirmation, apply the changes. DO_MTIME=yes +MSFILE=".metadata" umask 0077 +[ "$DO_MTIME" = "yes" ] && MSFLAGS="-m" -if ! git-pull; then - echo "Failed to execute git-pull" >&2 - exit 1 -fi +exit_on_fail() { + "$@" + if [ $? -ne 0 ]; then + echo "Failed to execute: $@" >&2 + exit 1 + fi +} + +exit_on_fail \ + git-pull -if [ ! -e ".metadata" ]; then - echo ".metadata missing" >&2 - exit 1 +if [ ! -e "$MSFILE" ]; then + echo "\"$MSFILE\" missing" >&2 + exit 1 fi echo "Going to apply the following metadata changes" >&2 -if [ "$DO_MTIME" = "yes" ]; then - metastore -c -m >&2 -else - metastore -c >&2 -fi -echo -n "Ok to apply? (y/n): " >&2 -read -n1 REPLY +metastore -c $MSFLAGS -f "$MSFILE" >&2 + +printf "%s" "Ok to apply? (y/n): " >&2 +read REPLY echo "" if [ "$REPLY" != "y" ]; then - echo "Aborted" >&2 - exit 1 -fi - -if [ "$DO_MTIME" = "yes" ]; then - flags="-a -m" -else - flags="-a" + echo "Aborted" >&2 + exit 1 fi -if ! metastore $flags; then - echo "Failed to execute metastore $flags" >&2 - exit 1 -fi +exit_on_fail \ + metastore -a $MSFLAGS -f "$MSFILE" exit 0 diff --git a/examples/post-checkout b/examples/post-checkout new file mode 100644 index 0000000..bd4de15 --- /dev/null +++ b/examples/post-checkout @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to set metadata information using +# metastore after each checkout. + +MSFILE=".metadata" + +exit_on_fail() { + "$@" + if [ $? -ne 0 ]; then + echo "Failed to execute: $@" >&2 + exit 1 + fi +} + +if [ ! -e "$MSFILE" ]; then + echo "\"$MSFILE\" missing" >&2 + exit 1 +fi + +exit_on_fail \ + metastore -a -m -e -E -q -f "$MSFILE" + +exit 0 diff --git a/examples/pre-commit b/examples/pre-commit index 1b5d5c6..b91635d 100644 --- a/examples/pre-commit +++ b/examples/pre-commit @@ -1,21 +1,27 @@ -#!/bin/bash +#!/bin/sh # # An example hook script to store metadata information using # metastore on each commit. -if ! metastore -s; then - echo "Failed to execute metastore -s" >&2 - exit 1 -fi +MSFILE=".metadata" -if [ ! -e ".metadata" ]; then - echo ".metadata missing" >&2 - exit 1 -fi +exit_on_fail() { + "$@" + if [ $? -ne 0 ]; then + echo "Failed to execute: $@" >&2 + exit 1 + fi +} + +exit_on_fail \ + metastore -s -f "$MSFILE" -if ! git-add .metadata; then - echo "Failed to execute git-add .metadata" >&2 +if [ ! -e "$MSFILE" ]; then + echo "\"$MSFILE\" missing" >&2 exit 1 fi +exit_on_fail \ + git-add "$MSFILE" + exit 0 diff --git a/metastore.1 b/metastore.1 index d628d3c..d6c561e 100644 --- a/metastore.1 +++ b/metastore.1 @@ -1,4 +1,4 @@ -.TH metastore "1" "February 2012" +.TH metastore "2" "September 2015" .\" .SH NAME metastore \- stores and restores filesystem metadata @@ -46,7 +46,11 @@ Causes metastore to also take mtime into account for the compare or apply action Also attempts to recreate missing empty directories. May be useful where empty directories are not tracked (e.g. by git or cvs). Only works in combination with the \fBapply\fR option. -This is currently an experimental feature. +.TP +.B -E, --remove-empty-dirs +Also attempts to remove empty directories missing from the metadata. May be +useful where empty directories are not tracked (e.g. by git or cvs). Only +works in combination with the \fBapply\fR option. .TP .B \-g, \-\-git Prevents metastore from omitting .git directories. diff --git a/metastore.c b/metastore.c index 83f8555..38901de 100644 --- a/metastore.c +++ b/metastore.c @@ -39,6 +39,7 @@ static struct metasettings settings = { .metafile = METAFILE, .do_mtime = false, .do_emptydirs = false, + .do_removeemptydirs = false, .do_git = false, }; @@ -46,6 +47,9 @@ static struct metasettings settings = { static struct metaentry *missingdirs = NULL; static struct metaentry *missingothers = NULL; +/* Used to create lists of dirs / other files which are missing in metadata */ +static struct metaentry *extradirs = NULL; + /* * Inserts an entry in a linked list ordered by pathlen */ @@ -64,6 +68,23 @@ insert_entry_plist(struct metaentry **list, struct metaentry *entry) } /* + * Inserts an entry in a linked list ordered by pathlen descendingly + */ +static void +insert_entry_pdlist(struct metaentry **list, struct metaentry *entry) +{ + struct metaentry **parent; + + for (parent = list; *parent; parent = &((*parent)->list)) { + if ((*parent)->pathlen < entry->pathlen) + break; + } + + entry->list = *parent; + *parent = entry; +} + +/* * Prints differences between real and stored actual metadata * - for use in mentries_compare */ @@ -132,6 +153,8 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) } if (!stored) { + if (S_ISDIR(real->mode)) + insert_entry_pdlist(&extradirs, real); msg(MSG_NORMAL, "%s:\tadded\n", real->path); return; } @@ -233,10 +256,8 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) } /* - * Tries to fix any empty dirs which are missing by recreating them. - * An "empty" dir is one which either: - * - is empty; or - * - only contained empty dirs + * Tries to fix any empty dirs which are missing from the filesystem by + * recreating them. */ static void fixup_emptydirs(struct metahash *real, struct metahash *stored) @@ -327,25 +348,76 @@ fixup_emptydirs(struct metahash *real, struct metahash *stored) } } +/* + * Deletes any empty dirs present in the filesystem that are missing + * from the metadata. + * An "empty" dir is one which either: + * - is empty; or + * - only contains empty dirs + */ +static void +fixup_newemptydirs(void) +{ + struct metaentry **cur; + int removed_dirs = 1; + + if (!extradirs) + return; + + /* This is a simpleminded algorithm that attempts to rmdir() all + * directories discovered missing from the metadata. Naturally, this will + * succeed only on the truly empty directories, but depending on the order, + * it may mean that parent directory removal are attempted to be removed + * *before* the children. To circumvent this, keep looping around all the + * directories until none have been successfully removed. This is a + * O(N**2) algorithm, so don't try to remove too many nested directories + * at once (e.g. thousands). + * + * Note that this will succeed only if each parent directory is writable. + */ + while (removed_dirs) { + removed_dirs = 0; + msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n"); + for (cur = &extradirs; *cur;) { + msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path); + if (rmdir((*cur)->path)) { + msg(MSG_QUIET, "failed (%s)\n", strerror(errno)); + cur = &(*cur)->list; + continue; + } + /* No freeing, because OS will do the job at the end. */ + *cur = (*cur)->list; + removed_dirs++; + msg(MSG_QUIET, "ok\n"); + } + } +} + /* Prints usage message and exits */ static void usage(const char *arg0, const char *message) { if (message) msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message); - msg(MSG_CRITICAL, "Usage: %s ACTION [OPTION...] [PATH...]\n\n", arg0); - msg(MSG_CRITICAL, "Where ACTION is one of:\n" - " -c, --compare\t\tShow differences between stored and real metadata\n" - " -s, --save\t\tSave current metadata\n" - " -a, --apply\t\tApply stored metadata\n" - " -h, --help\t\tHelp message (this text)\n\n" - "Valid OPTIONS are:\n" - " -v, --verbose\t\tPrint more verbose messages\n" - " -q, --quiet\t\tPrint less verbose messages\n" - " -m, --mtime\t\tAlso take mtime into account for diff or apply\n" - " -e, --empty-dirs\tRecreate missing empty directories (experimental)\n" - " -g, --git\t\tDo not omit .git directories\n" - " -f, --file <file>\tSet metadata file\n" + msg(MSG_CRITICAL, +"Usage: %s ACTION [OPTION...] [PATH...]\n", + arg0); + msg(MSG_CRITICAL, +"\n" +"Where ACTION is one of:\n" +" -c, --compare Show differences between stored and real metadata\n" +" -s, --save Save current metadata\n" +" -a, --apply Apply stored metadata\n" +" -h, --help Help message (this text)\n" +"\n" +"Valid OPTIONS are:\n" +" -v, --verbose Print more verbose messages\n" +" -q, --quiet Print less verbose messages\n" +" -m, --mtime Also take mtime into account for diff or apply\n" +" -e, --empty-dirs Recreate missing empty directories\n" +" -E, --remove-empty-dirs Remove extra empty directories\n" +" -g, --git Do not omit .git directories\n" +" -f, --file=FILE Set metadata file to FILE\n" ); exit(message ? EXIT_FAILURE : EXIT_SUCCESS); @@ -361,6 +433,7 @@ static struct option long_options[] = { {"quiet", 0, 0, 0}, {"mtime", 0, 0, 0}, {"empty-dirs", 0, 0, 0}, + {"remove-empty-dirs", 0, 0, 0}, {"git", 0, 0, 0}, {"file", required_argument, 0, 0}, {0, 0, 0, 0} @@ -379,7 +452,7 @@ main(int argc, char **argv, char **envp) i = 0; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "csahvqmegf:", + c = getopt_long(argc, argv, "csahvqmeEgf:", long_options, &option_index); if (c == -1) break; @@ -397,6 +470,9 @@ main(int argc, char **argv, char **envp) } else if (!strcmp("empty-dirs", long_options[option_index].name)) { settings.do_emptydirs = true; + } else if (!strcmp("remove-empty-dirs", + long_options[option_index].name)) { + settings.do_removeemptydirs = true; } else if (!strcmp("git", long_options[option_index].name)) { settings.do_git = true; @@ -436,6 +512,9 @@ main(int argc, char **argv, char **envp) case 'e': settings.do_emptydirs = true; break; + case 'E': + settings.do_removeemptydirs = true; + break; case 'g': settings.do_git = true; break; @@ -455,6 +534,10 @@ main(int argc, char **argv, char **envp) if (settings.do_emptydirs && action != ACTION_APPLY) usage(argv[0], "--empty-dirs is only valid with --apply"); + /* Make sure --remove-empty-dirs is only used with apply */ + if (settings.do_removeemptydirs && action != ACTION_APPLY) + usage(argv[0], "--remove-empty-dirs is only valid with --apply"); + /* Perform action */ switch (action) { case ACTION_DIFF: @@ -523,6 +606,8 @@ main(int argc, char **argv, char **envp) if (settings.do_emptydirs) fixup_emptydirs(real, stored); + if (settings.do_removeemptydirs) + fixup_newemptydirs(); break; case ACTION_HELP: @@ -22,10 +22,11 @@ /* Data structure to hold metastore settings */ struct metasettings { - char *metafile; /* path to the file containing the metadata */ - bool do_mtime; /* should mtimes be corrected? */ - bool do_emptydirs; /* should empty dirs be recreated? */ - bool do_git; /* should .git dirs be processed? */ + char *metafile; /* path to the file containing the metadata */ + bool do_mtime; /* should mtimes be corrected? */ + bool do_emptydirs; /* should empty dirs be recreated? */ + bool do_removeemptydirs; /* should new empty dirs be removed? */ + bool do_git; /* should .git dirs be processed? */ }; /* Convenient typedef for immutable settings */ |