fshash.in, fshash.1: Don't reuse virtual inode numbers spuriously.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 7 Feb 2024 19:03:33 +0000 (19:03 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 7 Feb 2024 19:03:33 +0000 (19:03 +0000)
There was code to prevent collisions this when generating new virtual
inodes, but the code which was supposed to record which ones had been
generated so far was missing.

Of course, changing this breaks compatibility with old manifests, so we
need a mechanism to support that.

I wouldn't usually make two apparently unrelated changes like this in
one commit: I'd, say, introduce the compatibility machinery first, and
then fix the virtual-inode bug.  But that won't work: one of the things
compatibility version 2 should do is print a comment (so that you can
tell that a mismatch is because of this setting), but that needs to
arrive in the same change as the bug fix.

Sorry.

fshash.1
fshash.in

index 788a7d8..8577d65 100644 (file)
--- a/fshash.1
+++ b/fshash.1
@@ -12,6 +12,8 @@
 .IR cache ]
 .RB [ \-f
 .IR format ]
+.RB [ \-C
+.IR version ]
 .RB [ \-H
 .IR hash ]
 .RI [ file
@@ -95,6 +97,20 @@ format doesn't work well: see
 .B BUGS
 below.)
 .TP
+.B \-C, \-\-compat=\fIversion
+Produce a manifest with the given compatibility
+.IR version .
+Alas,
+.B fshash
+has bugs in the way it produces manifests.  Fixing the bugs makes the
+output better, but now it can't be compared with old manifests which
+were made with the bugs.  By default,
+.B fshash
+produces manifests in the most recent format, but this option will force
+it to be compatible with old versions.  The original version was 1; all
+later versions print a comment reporting the version number at the start
+of the manifest.  The current version is 2.
+.TP
 .B \-H, \-\-hash=\fIhash
 Use the
 .I hash
index 525b0f0..dcfd229 100644 (file)
--- a/fshash.in
+++ b/fshash.in
@@ -449,6 +449,7 @@ class Reporter (object):
           suffix = '\0%d' % seq
           seq += 1
         me._inomap[inoidx] = vino
+        if OPTS.compat >= 2: me._vinomap[vino] = inoidx
     if h: info = h
     else: info = '[%-*s]' % (2*me._hsz - 2, fmt.info())
     print '%s %8s %6s %-12s %-20s %20s %s' % (
@@ -586,33 +587,39 @@ for short, long, props in [
                       'help': 'read files to report in the given FORMAT' }),
   ('-u', '--udiff', { 'action': 'store_true', 'dest': 'udiff',
                       'help': 'read diff from stdin, clear cache entries' }),
+  ('-C', '--compat', { 'dest': 'compat', 'metavar': 'VERSION',
+                       'type': 'int', 'default': 2,
+                       'help': 'produce output with given compatibility VERSION' }),
   ('-H', '--hash', { 'dest': 'hash', 'metavar': 'HASH',
                      ##'type': 'choice', 'choices': H.algorithms,
                      'help': 'use HASH as the hash function' })]:
   op.add_option(short, long, **props)
-opts, args = op.parse_args(argv)
-
-if opts.udiff:
-  if opts.cache is None or opts.all or opts.files or len(args) > 2:
+OPTS, args = op.parse_args(argv)
+if not 1 <= OPTS.compat <= 2:
+  die("unknown compatibility version %d" % OPTS.compat)
+if OPTS.udiff:
+  if OPTS.cache is None or OPTS.all or OPTS.files or len(args) > 2:
     die("incompatible options: `-u' requires `-c CACHE', forbids others")
-  db = HashCache(opts.cache, opts.hash)
+  db = HashCache(OPTS.cache, OPTS.hash)
   if len(args) == 2: OS.chdir(args[1])
   good = True
   if not clear_cache(db): good = False
   if good: db.flush()
   else: exit(2)
 else:
-  if not opts.files and len(args) <= 1:
+  if not OPTS.files and len(args) <= 1:
     die("no filename sources: nothing to do")
-  db = HashCache(opts.cache, opts.hash)
-  if opts.all:
+  db = HashCache(OPTS.cache, OPTS.hash)
+  if OPTS.all:
     db.reset()
+  if OPTS.compat >= 2:
+    print "## fshash report format version %d" % OPTS.compat
   rep = Reporter(db)
-  if opts.files:
-    FMTMAP[opts.files](rep.file)
+  if OPTS.files:
+    FMTMAP[OPTS.files](rep.file)
   for dir in args[1:]:
     enum_walk(dir, rep.file)
-  if opts.all:
+  if OPTS.all:
     db.prune()
   db.flush()