# HG changeset patch # User Franz Glasner # Date 1736193790 -3600 # Node ID 27d1aaf5fe3973a466ceb0d92def4bf949efc19c # Parent 481cc9b26861278b0f6b042b9042b4920e6f29a8 Implement "--mode" flag for "treesum.py" to consider file portable mode bits diff -r 481cc9b26861 -r 27d1aaf5fe39 cutils/treesum.py --- a/cutils/treesum.py Mon Jan 06 14:38:07 2025 +0100 +++ b/cutils/treesum.py Mon Jan 06 21:03:10 2025 +0100 @@ -18,6 +18,7 @@ import binascii import datetime import os +import stat import sys import time @@ -78,6 +79,12 @@ help="Use mmap if available. Default is to determine " "automatically from the filesize.") gp.add_argument( + "--mode", action="store_true", dest="metadata_mode", + help="Consider the permission bits of stat using S_IMODE (i.e. " + "all bits without the filetype bits) when " + "computing directory digests. Note that symbolic links " + "are not considered.") + gp.add_argument( "--mtime", action="store_true", dest="metadata_mtime", help="Consider the mtime of files (non-directories) when " "generating digests for directories. Digests for files are " @@ -189,6 +196,7 @@ follow_directory_symlinks=False, logical=None, minimal=None, + mode=False, mmap=None, mtime=False, output=None): @@ -203,6 +211,7 @@ logical=logical, minimal=minimal, mmap=mmap, + metadata_mode=mode, metadata_mtime=mtime, output=output) return opts @@ -240,6 +249,7 @@ generate_treesum_for_directory( outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical, opts.follow_directory_symlinks, + opts.metadata_mode, opts.metadata_mtime, minimal=opts.minimal, comment=opts.comment) @@ -247,7 +257,7 @@ def generate_treesum_for_directory( outfp, root, algorithm, use_mmap, use_base64, handle_root_logical, - follow_directory_symlinks, with_metadata_mtime, + follow_directory_symlinks, with_metadata_mode, with_metadata_mtime, minimal=None, comment=None): """ @@ -321,6 +331,7 @@ linkdgst.update(b"%d:%s," % (len(linktgt), linktgt)) dir_dgst.update(b"1:S,%d:%s," % (len(fso.fsname), fso.fsname)) + # no mtime and no mode for symlinks dir_dgst.update(linkdgst.digest()) opath = "/".join(top) + "/" + fso.name if top else fso.name outfp.write( @@ -335,6 +346,11 @@ dgst = dir_digests[top + (fso.name,)] dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname)) dir_dgst.update(dgst) + if with_metadata_mode: + modestr = normalized_mode_str(fso.stat.st_mode) + if not isinstance(modestr, bytes): + modestr = modestr.encode("ascii") + dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) else: dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname)) if with_metadata_mtime: @@ -344,6 +360,11 @@ if not isinstance(mtime, bytes): mtime = mtime.encode("ascii") dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime)) + if with_metadata_mode: + modestr = normalized_mode_str(fso.stat.st_mode) + if not isinstance(modestr, bytes): + modestr = modestr.encode("ascii") + dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) dgst = digest.compute_digest_file( algorithm[0], fso.path, use_mmap=use_mmap) dir_dgst.update(dgst) @@ -359,6 +380,15 @@ dir_digests[top] = dir_dgst.digest() +def normalized_mode_str(mode): + # XXX FIXME: Windows and "executable" + modebits = stat.S_IMODE(mode) + modestr = "%o" % (modebits,) + if not modestr.startswith("0"): + modestr = "0" + modestr + return modestr + + def format_bsd_line(digestname, value, filename, use_base64): ls = os.linesep if isinstance(os.linesep, bytes) \ else os.linesep.encode("utf-8")