comparison cutils/treesum.py @ 158:d8cdd1985d43

Implement "--full-mode" for "treesum.py"
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 07 Jan 2025 12:48:37 +0100
parents 27d1aaf5fe39
children 5c23ad9a3f8f
comparison
equal deleted inserted replaced
157:27d1aaf5fe39 158:d8cdd1985d43
63 help="Follow symbolic links to directories when walking a " 63 help="Follow symbolic links to directories when walking a "
64 "directory tree. Note that this is different from using " 64 "directory tree. Note that this is different from using "
65 "\"--logical\" or \"--physical\" for arguments given " 65 "\"--logical\" or \"--physical\" for arguments given "
66 "directly on the command line") 66 "directly on the command line")
67 gp.add_argument( 67 gp.add_argument(
68 "--full-mode", action="store_true", dest="metadata_full_mode",
69 help="Consider all mode bits as returned from stat(2) when "
70 "computing directory digests. "
71 "Note that mode bits on symbolic links itself are not "
72 "considered.")
73 gp.add_argument(
68 "--logical", "-L", dest="logical", action="store_true", 74 "--logical", "-L", dest="logical", action="store_true",
69 default=None, 75 default=None,
70 help="Follow symbolic links given on command line arguments." 76 help="Follow symbolic links given on command line arguments."
71 " Note that this is a different setting as to follow symbolic" 77 " Note that this is a different setting as to follow symbolic"
72 " links to directories when traversing a directory tree.") 78 " links to directories when traversing a directory tree.")
78 "--mmap", action="store_true", dest="mmap", default=None, 84 "--mmap", action="store_true", dest="mmap", default=None,
79 help="Use mmap if available. Default is to determine " 85 help="Use mmap if available. Default is to determine "
80 "automatically from the filesize.") 86 "automatically from the filesize.")
81 gp.add_argument( 87 gp.add_argument(
82 "--mode", action="store_true", dest="metadata_mode", 88 "--mode", action="store_true", dest="metadata_mode",
83 help="Consider the permission bits of stat using S_IMODE (i.e. " 89 help="Consider the permission bits of stat(2) using S_IMODE (i.e. "
84 "all bits without the filetype bits) when " 90 "all bits without the filetype bits) when "
85 "computing directory digests. Note that symbolic links " 91 "computing directory digests. Note that mode bits on "
86 "are not considered.") 92 "symbolic links itself are not considered.")
87 gp.add_argument( 93 gp.add_argument(
88 "--mtime", action="store_true", dest="metadata_mtime", 94 "--mtime", action="store_true", dest="metadata_mtime",
89 help="Consider the mtime of files (non-directories) when " 95 help="Consider the mtime of files (non-directories) when "
90 "generating digests for directories. Digests for files are " 96 "generating digests for directories. Digests for files are "
91 "not affected.") 97 "not affected.")
192 algorithm="BLAKE2b-256", 198 algorithm="BLAKE2b-256",
193 append_output=False, 199 append_output=False,
194 base64=False, 200 base64=False,
195 comment=[], 201 comment=[],
196 follow_directory_symlinks=False, 202 follow_directory_symlinks=False,
203 full_mode=False,
197 logical=None, 204 logical=None,
198 minimal=None, 205 minimal=None,
199 mode=False, 206 mode=False,
200 mmap=None, 207 mmap=None,
201 mtime=False, 208 mtime=False,
209 comment=comment, 216 comment=comment,
210 follow_directory_symlinks=follow_directory_symlinks, 217 follow_directory_symlinks=follow_directory_symlinks,
211 logical=logical, 218 logical=logical,
212 minimal=minimal, 219 minimal=minimal,
213 mmap=mmap, 220 mmap=mmap,
221 metadata_full_mode=full_mode,
214 metadata_mode=mode, 222 metadata_mode=mode,
215 metadata_mtime=mtime, 223 metadata_mtime=mtime,
216 output=output) 224 output=output)
217 return opts 225 return opts
218 226
248 for d in opts.directories: 256 for d in opts.directories:
249 generate_treesum_for_directory( 257 generate_treesum_for_directory(
250 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical, 258 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical,
251 opts.follow_directory_symlinks, 259 opts.follow_directory_symlinks,
252 opts.metadata_mode, 260 opts.metadata_mode,
261 opts.metadata_full_mode,
253 opts.metadata_mtime, 262 opts.metadata_mtime,
254 minimal=opts.minimal, 263 minimal=opts.minimal,
255 comment=opts.comment) 264 comment=opts.comment)
256 265
257 266
258 def generate_treesum_for_directory( 267 def generate_treesum_for_directory(
259 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical, 268 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical,
260 follow_directory_symlinks, with_metadata_mode, with_metadata_mtime, 269 follow_directory_symlinks, with_metadata_mode, with_metadata_full_mode,
270 with_metadata_mtime,
261 minimal=None, comment=None): 271 minimal=None, comment=None):
262 """ 272 """
263 273
264 :param outfp: a *binary* file with a "write()" and a "flush()" method 274 :param outfp: a *binary* file with a "write()" and a "flush()" method
265 275
320 for top, fsobjects in walk.walk( 330 for top, fsobjects in walk.walk(
321 root, 331 root,
322 follow_symlinks=follow_directory_symlinks): 332 follow_symlinks=follow_directory_symlinks):
323 dir_dgst = algorithm[0]() 333 dir_dgst = algorithm[0]()
324 for fso in fsobjects: 334 for fso in fsobjects:
325 # print("NNNNNNNN", fso.name, fso.stat, 335 print("NNNNNNNN", fso.name, fso.stat,
326 # "%o (%o)" % (fso.stat.st_mode, stat.S_IMODE(fso.stat.st_mode))) 336 "%o (%o)"
337 % (fso.stat.st_mode, stat.S_IMODE(fso.stat.st_mode)))
327 if fso.is_dir: 338 if fso.is_dir:
328 if fso.is_symlink and not follow_directory_symlinks: 339 if fso.is_symlink and not follow_directory_symlinks:
329 linktgt = util.fsencode(os.readlink(fso.path)) 340 linktgt = util.fsencode(os.readlink(fso.path))
330 linkdgst = algorithm[0]() 341 linkdgst = algorithm[0]()
331 linkdgst.update(b"%d:%s," % (len(linktgt), linktgt)) 342 linkdgst.update(b"%d:%s," % (len(linktgt), linktgt))
344 continue 355 continue
345 # fetch from dir_digests 356 # fetch from dir_digests
346 dgst = dir_digests[top + (fso.name,)] 357 dgst = dir_digests[top + (fso.name,)]
347 dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname)) 358 dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname))
348 dir_dgst.update(dgst) 359 dir_dgst.update(dgst)
349 if with_metadata_mode: 360 if with_metadata_full_mode:
350 modestr = normalized_mode_str(fso.stat.st_mode) 361 modestr = normalized_mode_str(fso.stat.st_mode)
362 if not isinstance(modestr, bytes):
363 modestr = modestr.encode("ascii")
364 dir_dgst.update(b"8:fullmode,%d:%s,"
365 % (len(modestr), modestr))
366 elif with_metadata_mode:
367 modestr = normalized_compatible_mode_str(fso.stat.st_mode)
351 if not isinstance(modestr, bytes): 368 if not isinstance(modestr, bytes):
352 modestr = modestr.encode("ascii") 369 modestr = modestr.encode("ascii")
353 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) 370 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr))
354 else: 371 else:
355 dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname)) 372 dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname))
358 int(fso.stat.st_mtime)) 375 int(fso.stat.st_mtime))
359 mtime = mtime.isoformat("T") + "Z" 376 mtime = mtime.isoformat("T") + "Z"
360 if not isinstance(mtime, bytes): 377 if not isinstance(mtime, bytes):
361 mtime = mtime.encode("ascii") 378 mtime = mtime.encode("ascii")
362 dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime)) 379 dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime))
363 if with_metadata_mode: 380 if with_metadata_full_mode:
364 modestr = normalized_mode_str(fso.stat.st_mode) 381 modestr = normalized_mode_str(fso.stat.st_mode)
382 if not isinstance(modestr, bytes):
383 modestr = modestr.encode("ascii")
384 dir_dgst.update(b"8:fullmode,%d:%s,"
385 % (len(modestr), modestr))
386 elif with_metadata_mode:
387 modestr = normalized_compatible_mode_str(fso.stat.st_mode)
365 if not isinstance(modestr, bytes): 388 if not isinstance(modestr, bytes):
366 modestr = modestr.encode("ascii") 389 modestr = modestr.encode("ascii")
367 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) 390 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr))
368 dgst = digest.compute_digest_file( 391 dgst = digest.compute_digest_file(
369 algorithm[0], fso.path, use_mmap=use_mmap) 392 algorithm[0], fso.path, use_mmap=use_mmap)
378 algorithm[1], dir_dgst.digest(), opath, use_base64)) 401 algorithm[1], dir_dgst.digest(), opath, use_base64))
379 outfp.flush() 402 outfp.flush()
380 dir_digests[top] = dir_dgst.digest() 403 dir_digests[top] = dir_dgst.digest()
381 404
382 405
383 def normalized_mode_str(mode): 406 def normalized_compatible_mode_str(mode):
384 # XXX FIXME: Windows and "executable" 407 # XXX FIXME: Windows and "executable"
385 modebits = stat.S_IMODE(mode) 408 modebits = stat.S_IMODE(mode)
386 modestr = "%o" % (modebits,) 409 modestr = "%o" % (modebits,)
410 if not modestr.startswith("0"):
411 modestr = "0" + modestr
412 return modestr
413
414
415 def normalized_mode_str(mode):
416 modestr = "%o" % (mode,)
387 if not modestr.startswith("0"): 417 if not modestr.startswith("0"):
388 modestr = "0" + modestr 418 modestr = "0" + modestr
389 return modestr 419 return modestr
390 420
391 421