diff options
author | Adam Spragg <adam@spra.gg> | 2022-05-18 16:35:35 +0100 |
---|---|---|
committer | Adam Spragg <adam@spra.gg> | 2022-05-24 10:12:33 +0100 |
commit | 7292804c1bafd43389defc0d7b4352ada666d626 (patch) | |
tree | 9e0390c7f45dd20b7db92d3f64bdcecaa3d9c1dc | |
parent | ce62a76e4570b6368384b3995c1ba106389df454 (diff) |
Add ability to not save mtime in metadata files
If you're storing metadata in a version control system with multiple
branches, mtime differences are going to produce a whole bunch of
conflicts that you likely don't care about. This allows you to not
save mtime and avoid those.
Note that we use a sentinel value of -1 for the mentry `mtimensec` field
to indicate this in the data, as all values of `mtime` are theoretically
valid, but `mtimensec` must always be between 0 and 999,999,999 in the
real world.
I'm not 100% sure about the mechanism for selecting this feature. The
legacy behaviour for metastore was to save mtimes in the metadata files,
but ignore them for compare/apply by default, with a `--mtime` option to
use the mtime data.
Keeping the legacy behaviour for backwards compatibility, but adding a
`--no-mtime` option to ignore mtimes when saving felt like a reasonable
way of making this happen, but something about it doesn't feel great.
Maybe I just didn't figure out how to make the documentation clear
enough. ¯\_(ツ)_/¯
-rw-r--r-- | FILEFORMAT_1 | 1 | ||||
-rw-r--r-- | man1/metastore.1 | 5 | ||||
-rw-r--r-- | metastore.txt | 5 | ||||
-rw-r--r-- | src/metaentry.c | 145 | ||||
-rw-r--r-- | src/metaentry.h | 2 | ||||
-rw-r--r-- | src/metastore.c | 15 | ||||
-rw-r--r-- | src/metastore.h | 3 | ||||
-rw-r--r-- | src/settings.h | 2 |
8 files changed, 109 insertions, 69 deletions
diff --git a/FILEFORMAT_1 b/FILEFORMAT_1 index 189cc84..89d2a86 100644 --- a/FILEFORMAT_1 +++ b/FILEFORMAT_1 @@ -29,6 +29,7 @@ Following sections explain internals of metastore file (.metadata), version 1 i.e. File type and mode, as per inode(7) "\t" URLSTRING - Mtime (including nanoseconds) in ISO-8601 format, UTC. "YYYY-mm-ddTHH:MM:SS.nnnnnnnnnZ" + Or a literal "0" if mtime is not saved m * ("\t" URLSTRING "\t" URLSTRING) - xattr name/value pairs. `m` may be 0. diff --git a/man1/metastore.1 b/man1/metastore.1 index 1e01daf..ee01dd8 100644 --- a/man1/metastore.1 +++ b/man1/metastore.1 @@ -51,6 +51,9 @@ once for even less verbosity. .B \-m, \-\-mtime Causes metastore to also take mtime into account for the compare or apply actions. .TP +.B \-M, \-\-no\-mtime +Causes metastore to not save mtime in the metadata +.TP .B \-e, \-\-empty\-dirs Also attempts to recreate missing empty directories. May be useful where empty directories are not tracked (e.g. by git or cvs). @@ -108,7 +111,7 @@ in and .I mtime is the ISO-8601 extended format representation of the last-modified time in UTC, -with nanosecond precision. +with nanosecond precision, or a literal "0" if mtime is not saved. Strings are URL-encoded, and all characters from 0x00 to 0x20 (inclusive), 0x25 (%) and 0x7F \fBmust\fR be encoded. diff --git a/metastore.txt b/metastore.txt index 39fdf5e..ba81a05 100644 --- a/metastore.txt +++ b/metastore.txt @@ -54,6 +54,9 @@ OPTIONS Causes metastore to also take mtime into account for the compare or apply actions. + -M, --no-mtime + Causes metastore to not save mtime in the metadata + -e, --empty-dirs Also attempts to recreate missing empty directories. May be use‐ ful where empty directories are not tracked (e.g. by git or @@ -100,7 +103,7 @@ FORMATS resentation of the 16-bit "file type and mode" field described in inode(7), and mtime is the ISO-8601 extended format represen‐ tation of the last-modified time in UTC, with nanosecond preci‐ - sion. + sion, or a literal "0" if mtime is not saved. Strings are URL-encoded, and all characters from 0x00 to 0x20 (inclusive), 0x25 (%) and 0x7F must be encoded. diff --git a/src/metaentry.c b/src/metaentry.c index 7f1012f..9956f7f 100644 --- a/src/metaentry.c +++ b/src/metaentry.c @@ -193,7 +193,7 @@ mentries_print(const struct metahash *mhash) /* Creates a metaentry for the file/dir/etc at path */ struct metaentry * -mentry_create(const char *path) +mentry_create(const char *path, int with_mtime) { #if !defined(NO_XATTR) || !(NO_XATTR+0) ssize_t lsize, vsize; @@ -233,8 +233,14 @@ mentry_create(const char *path) mentry->owner = xstrdup(pbuf->pw_name); mentry->group = xstrdup(gbuf->gr_name); mentry->mode = sbuf.st_mode & 0177777; - mentry->mtime = sbuf.st_mtim.tv_sec; - mentry->mtimensec = sbuf.st_mtim.tv_nsec; + if (with_mtime) { + mentry->mtime = sbuf.st_mtim.tv_sec; + mentry->mtimensec = sbuf.st_mtim.tv_nsec; + } + else { + mentry->mtime = 0; + mentry->mtimensec = MTIME_NONE; + } /* symlinks have no xattrs */ if (S_ISLNK(mentry->mode)) @@ -364,7 +370,7 @@ mentries_recurse(const char *path, struct metahash *mhash, msettings *st) return; } - mentry = mentry_create(path); + mentry = mentry_create(path, st->do_mtime >= 0); if (!mentry) return; @@ -491,12 +497,17 @@ mentries_tofile_v1(const struct metahash *mhash, FILE * to) fprintf(to, "%.6o", mentry->mode); fputc('\t', to); - gmtime_r(&mentry->mtime, &tm); - strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%dT%H:%M:%S", &tm); - fputs(tmbuf, to); + if (mentry->mtimensec != MTIME_NONE) { + gmtime_r(&mentry->mtime, &tm); + strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%dT%H:%M:%S", &tm); + fputs(tmbuf, to); - fputc('.', to); - fprintf(to, "%.9ldZ", mentry->mtimensec); + fputc('.', to); + fprintf(to, "%.9ldZ", mentry->mtimensec); + } + else { + fputc('0', to); + } for (i = 0; i < mentry->xattrs; i++) { fputc('\t', to); @@ -625,50 +636,55 @@ mentries_fromfile_v1(struct metahash **mhash, const char *ptr, const char *max) goto err; if ((mtime = read_string_url(&ptr, max)) == NULL) goto err; - - /* Get the time_t part of `mtime` into mentry->mtime */ - if ((nsec = strptime(mtime, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL) - goto err; - tm.tm_isdst = 0; - mentry->mtime = timegm(&tm); - - /* Check if there's a nanosecond portion of `mtime` */ - if (*nsec == '.') { - /* There is a decimal point in mtime. Get nanoseconds. */ - ++nsec; - mentry->mtimensec = strtol(nsec, &tz, 10); - i = tz - nsec; - while (i < 9) { - /* Too few digits for nanosecond precision. - * e.g. "...T10:15:23.5", which we've parsed as - * 5 nanoseconds, but is actually half a second - * or 500000000 nanoseconds. - * Scale to correct value. - */ - mentry->mtimensec *= 10; - ++i; - } - while (i > 9) { - /* Too many digits for nanosecond precision. - * Scale to correct value - */ - mentry->mtimensec /= 10; - --i; - } + if (strcmp(mtime, "0") == 0) { + mentry->mtime = 0; + mentry->mtimensec = MTIME_NONE; } else { - /* No decimal point. Set nanoseconds to zero */ - mentry->mtimensec = 0; - tz = nsec; - } + /* Get the time_t part of `mtime` into mentry->mtime */ + if ((nsec = strptime(mtime, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL) + goto err; + tm.tm_isdst = 0; + mentry->mtime = timegm(&tm); + + /* Check if there's a nanosecond portion of `mtime` */ + if (*nsec == '.') { + /* There is a decimal point in mtime. Get nanoseconds. */ + ++nsec; + mentry->mtimensec = strtol(nsec, &tz, 10); + i = tz - nsec; + while (i < 9) { + /* Too few digits for nanosecond precision. + * e.g. "...T10:15:23.5", which we've parsed as + * 5 nanoseconds, but is actually half a second + * or 500000000 nanoseconds. + * Scale to correct value. + */ + mentry->mtimensec *= 10; + ++i; + } + while (i > 9) { + /* Too many digits for nanosecond precision. + * Scale to correct value + */ + mentry->mtimensec /= 10; + --i; + } + } + else { + /* No decimal point. Set nanoseconds to zero */ + mentry->mtimensec = 0; + tz = nsec; + } - /* Check for a 'Z' (Zulu/UTC) timesone specifier in `mtime`. */ - if (*tz == 'Z') - ++tz; + /* Check for a 'Z' (Zulu/UTC) timesone specifier in `mtime`. */ + if (*tz == 'Z') + ++tz; - /* Check that we've reached the end of `mtime` */ - if (*tz != '\0') - goto err; + /* Check that we've reached the end of `mtime` */ + if (*tz != '\0') + goto err; + } free(mtime); mtime = NULL; @@ -842,10 +858,12 @@ mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st) if ((left->mode & S_IFMT) != (right->mode & S_IFMT)) retval |= DIFF_TYPE; - if (st->do_mtime && strcmp(left->path, st->metafile) && - ( left->mtime != right->mtime - || left->mtimensec != right->mtimensec) - ) + if (st->do_mtime > 0 + && left->mtimensec != MTIME_NONE + && right->mtimensec != MTIME_NONE + && strcmp(left->path, st->metafile) + && (left->mtime != right->mtime + || left->mtimensec != right->mtimensec)) retval |= DIFF_MTIME; if (mentry_compare_xattr(left, right)) { @@ -898,19 +916,28 @@ mentries_dump(struct metahash *mhash) const struct metaentry *mentry; char mode[11 + 1] = ""; char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = ""; - char zone[5 + 1] = ""; + char nsec[1 + 9 + 1] = ""; + char zone[1 + 5 + 1] = ""; struct tm cal; for (int key = 0; key < HASH_INDEXES; key++) { for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) { strmode(mentry->mode, mode); - localtime_r(&mentry->mtime, &cal); - strftime(date, sizeof(date), "%F %T", &cal); - strftime(zone, sizeof(zone), "%z", &cal); - printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n", + if (mentry->mtimensec == MTIME_NONE) { + snprintf(date, sizeof(date), "%19s", ""); + snprintf(nsec, sizeof(nsec), "%10s", ""); + snprintf(zone, sizeof(zone), "%6s", ""); + } + else { + localtime_r(&mentry->mtime, &cal); + strftime(date, sizeof(date), "%F %T", &cal); + snprintf(nsec, sizeof(nsec), ".%09ld", mentry->mtimensec); + strftime(zone, sizeof(zone), " %z", &cal); + } + printf("%s\t%s\t%s\t%s%s%s\t%s%s\n", mode, mentry->owner, mentry->group, - date, mentry->mtimensec, zone, + date, nsec, zone, mentry->path, S_ISDIR(mentry->mode) ? "/" : ""); for (unsigned i = 0; i < mentry->xattrs; i++) { printf("\t\t\t\t%s%s\t%s=", diff --git a/src/metaentry.h b/src/metaentry.h index a6de66b..6c97961 100644 --- a/src/metaentry.h +++ b/src/metaentry.h @@ -54,7 +54,7 @@ struct metahash { }; /* Create a metaentry for the file/dir/etc at path */ -struct metaentry *mentry_create(const char *path); +struct metaentry *mentry_create(const char *path, int with_mtime); /* Recurses opath and adds metadata entries to the metaentry list */ void mentries_recurse_path(const char *opath, struct metahash **mhash, diff --git a/src/metastore.c b/src/metastore.c index 2bc2a89..bc5787e 100644 --- a/src/metastore.c +++ b/src/metastore.c @@ -43,7 +43,7 @@ /* metastore settings */ static struct metasettings settings = { .metafile = METAFILE, - .do_mtime = false, + .do_mtime = 0, .do_emptydirs = false, .do_removeemptydirs = false, .do_git = false, @@ -286,7 +286,7 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) * recreating them. */ static void -fixup_emptydirs(void) +fixup_emptydirs(int do_mtime) { struct metaentry *entry; struct metaentry *cur; @@ -364,7 +364,7 @@ fixup_emptydirs(void) } msg(MSG_QUIET, "ok\n"); - new = mentry_create(cur->path); + new = mentry_create(cur->path, do_mtime >= 0); if (!new) { msg(MSG_QUIET, "Failed to get metadata for %s\n", cur->path); continue; @@ -459,6 +459,7 @@ usage(const char *arg0, const char *message) " -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" +" -M, --no-mtime Do not save mtime into metadata\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" @@ -480,6 +481,7 @@ static struct option long_options[] = { { "verbose", no_argument, NULL, 'v' }, { "quiet", no_argument, NULL, 'q' }, { "mtime", no_argument, NULL, 'm' }, + { "no-mtime", no_argument, NULL, 'M' }, { "empty-dirs", no_argument, NULL, 'e' }, { "remove-empty-dirs", no_argument, NULL, 'E' }, { "git", no_argument, NULL, 'g' }, @@ -501,7 +503,7 @@ main(int argc, char **argv) i = 0; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "csadVhvqmeEgf:r:", + c = getopt_long(argc, argv, "csadVhvqmMeEgf:r:", long_options, &option_index); if (c == -1) break; @@ -514,7 +516,8 @@ main(int argc, char **argv) case 'h': /* help */ action |= ACTION_HELP; i++; break; case 'v': /* verbose */ adjust_verbosity(1); break; case 'q': /* quiet */ adjust_verbosity(-1); break; - case 'm': /* mtime */ settings.do_mtime = true; break; + case 'm': /* mtime */ settings.do_mtime = 1; break; + case 'M': /* no-mtime */ settings.do_mtime = -1; break; case 'e': /* empty-dirs */ settings.do_emptydirs = true; break; case 'E': /* remove-empty-dirs */ settings.do_removeemptydirs = true; break; @@ -577,7 +580,7 @@ main(int argc, char **argv) case ACTION_APPLY: mentries_compare(real, stored, compare_fix, &settings); if (settings.do_emptydirs) - fixup_emptydirs(); + fixup_emptydirs(settings.do_mtime); if (settings.do_removeemptydirs) fixup_newemptydirs(); break; diff --git a/src/metastore.h b/src/metastore.h index bd79416..de51292 100644 --- a/src/metastore.h +++ b/src/metastore.h @@ -28,6 +28,9 @@ #define VERSION_1 "00000001" #define VERSIONLEN 8 +/* Sentinel value to determine if mtime is not in use */ +#define MTIME_NONE -1 + /* Default filename */ #define METAFILE "./.metadata" diff --git a/src/settings.h b/src/settings.h index 048f281..fd0a0a3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,7 +23,7 @@ /* Data structure to hold metastore settings */ struct metasettings { char *metafile; /* path to the file containing the metadata */ - bool do_mtime; /* should mtimes be corrected? */ + int do_mtime; /* deal with mtimes? -1 = no, 0 = default, 1 = yes */ 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? */ |