summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Spragg <adam@spra.gg>2022-05-18 16:35:35 +0100
committerAdam Spragg <adam@spra.gg>2022-05-24 10:12:33 +0100
commit7292804c1bafd43389defc0d7b4352ada666d626 (patch)
tree9e0390c7f45dd20b7db92d3f64bdcecaa3d9c1dc
parentce62a76e4570b6368384b3995c1ba106389df454 (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_11
-rw-r--r--man1/metastore.15
-rw-r--r--metastore.txt5
-rw-r--r--src/metaentry.c145
-rw-r--r--src/metaentry.h2
-rw-r--r--src/metastore.c15
-rw-r--r--src/metastore.h3
-rw-r--r--src/settings.h2
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? */