Mercurial > hgrepos > Python > apps > py-cutils
comparison cutils/treesum.py @ 157:27d1aaf5fe39
Implement "--mode" flag for "treesum.py" to consider file portable mode bits
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 06 Jan 2025 21:03:10 +0100 |
| parents | 481cc9b26861 |
| children | d8cdd1985d43 |
comparison
equal
deleted
inserted
replaced
| 156:481cc9b26861 | 157:27d1aaf5fe39 |
|---|---|
| 16 import argparse | 16 import argparse |
| 17 import base64 | 17 import base64 |
| 18 import binascii | 18 import binascii |
| 19 import datetime | 19 import datetime |
| 20 import os | 20 import os |
| 21 import stat | |
| 21 import sys | 22 import sys |
| 22 import time | 23 import time |
| 23 | 24 |
| 24 from . import (__version__, __revision__) | 25 from . import (__version__, __revision__) |
| 25 from . import util | 26 from . import util |
| 75 "empty use it as the leading \"ROOT (<TAG>)\" output.") | 76 "empty use it as the leading \"ROOT (<TAG>)\" output.") |
| 76 gp.add_argument( | 77 gp.add_argument( |
| 77 "--mmap", action="store_true", dest="mmap", default=None, | 78 "--mmap", action="store_true", dest="mmap", default=None, |
| 78 help="Use mmap if available. Default is to determine " | 79 help="Use mmap if available. Default is to determine " |
| 79 "automatically from the filesize.") | 80 "automatically from the filesize.") |
| 81 gp.add_argument( | |
| 82 "--mode", action="store_true", dest="metadata_mode", | |
| 83 help="Consider the permission bits of stat using S_IMODE (i.e. " | |
| 84 "all bits without the filetype bits) when " | |
| 85 "computing directory digests. Note that symbolic links " | |
| 86 "are not considered.") | |
| 80 gp.add_argument( | 87 gp.add_argument( |
| 81 "--mtime", action="store_true", dest="metadata_mtime", | 88 "--mtime", action="store_true", dest="metadata_mtime", |
| 82 help="Consider the mtime of files (non-directories) when " | 89 help="Consider the mtime of files (non-directories) when " |
| 83 "generating digests for directories. Digests for files are " | 90 "generating digests for directories. Digests for files are " |
| 84 "not affected.") | 91 "not affected.") |
| 187 base64=False, | 194 base64=False, |
| 188 comment=[], | 195 comment=[], |
| 189 follow_directory_symlinks=False, | 196 follow_directory_symlinks=False, |
| 190 logical=None, | 197 logical=None, |
| 191 minimal=None, | 198 minimal=None, |
| 199 mode=False, | |
| 192 mmap=None, | 200 mmap=None, |
| 193 mtime=False, | 201 mtime=False, |
| 194 output=None): | 202 output=None): |
| 195 opts = argparse.Namespace( | 203 opts = argparse.Namespace( |
| 196 directories=directories, | 204 directories=directories, |
| 201 comment=comment, | 209 comment=comment, |
| 202 follow_directory_symlinks=follow_directory_symlinks, | 210 follow_directory_symlinks=follow_directory_symlinks, |
| 203 logical=logical, | 211 logical=logical, |
| 204 minimal=minimal, | 212 minimal=minimal, |
| 205 mmap=mmap, | 213 mmap=mmap, |
| 214 metadata_mode=mode, | |
| 206 metadata_mtime=mtime, | 215 metadata_mtime=mtime, |
| 207 output=output) | 216 output=output) |
| 208 return opts | 217 return opts |
| 209 | 218 |
| 210 | 219 |
| 238 with out_cm as outfp: | 247 with out_cm as outfp: |
| 239 for d in opts.directories: | 248 for d in opts.directories: |
| 240 generate_treesum_for_directory( | 249 generate_treesum_for_directory( |
| 241 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical, | 250 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical, |
| 242 opts.follow_directory_symlinks, | 251 opts.follow_directory_symlinks, |
| 252 opts.metadata_mode, | |
| 243 opts.metadata_mtime, | 253 opts.metadata_mtime, |
| 244 minimal=opts.minimal, | 254 minimal=opts.minimal, |
| 245 comment=opts.comment) | 255 comment=opts.comment) |
| 246 | 256 |
| 247 | 257 |
| 248 def generate_treesum_for_directory( | 258 def generate_treesum_for_directory( |
| 249 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical, | 259 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical, |
| 250 follow_directory_symlinks, with_metadata_mtime, | 260 follow_directory_symlinks, with_metadata_mode, with_metadata_mtime, |
| 251 minimal=None, comment=None): | 261 minimal=None, comment=None): |
| 252 """ | 262 """ |
| 253 | 263 |
| 254 :param outfp: a *binary* file with a "write()" and a "flush()" method | 264 :param outfp: a *binary* file with a "write()" and a "flush()" method |
| 255 | 265 |
| 319 linktgt = util.fsencode(os.readlink(fso.path)) | 329 linktgt = util.fsencode(os.readlink(fso.path)) |
| 320 linkdgst = algorithm[0]() | 330 linkdgst = algorithm[0]() |
| 321 linkdgst.update(b"%d:%s," % (len(linktgt), linktgt)) | 331 linkdgst.update(b"%d:%s," % (len(linktgt), linktgt)) |
| 322 dir_dgst.update(b"1:S,%d:%s," | 332 dir_dgst.update(b"1:S,%d:%s," |
| 323 % (len(fso.fsname), fso.fsname)) | 333 % (len(fso.fsname), fso.fsname)) |
| 334 # no mtime and no mode for symlinks | |
| 324 dir_dgst.update(linkdgst.digest()) | 335 dir_dgst.update(linkdgst.digest()) |
| 325 opath = "/".join(top) + "/" + fso.name if top else fso.name | 336 opath = "/".join(top) + "/" + fso.name if top else fso.name |
| 326 outfp.write( | 337 outfp.write( |
| 327 format_bsd_line( | 338 format_bsd_line( |
| 328 algorithm[1], | 339 algorithm[1], |
| 333 continue | 344 continue |
| 334 # fetch from dir_digests | 345 # fetch from dir_digests |
| 335 dgst = dir_digests[top + (fso.name,)] | 346 dgst = dir_digests[top + (fso.name,)] |
| 336 dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname)) | 347 dir_dgst.update(b"1:d,%d:%s," % (len(fso.fsname), fso.fsname)) |
| 337 dir_dgst.update(dgst) | 348 dir_dgst.update(dgst) |
| 349 if with_metadata_mode: | |
| 350 modestr = normalized_mode_str(fso.stat.st_mode) | |
| 351 if not isinstance(modestr, bytes): | |
| 352 modestr = modestr.encode("ascii") | |
| 353 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) | |
| 338 else: | 354 else: |
| 339 dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname)) | 355 dir_dgst.update(b"1:f,%d:%s," % (len(fso.fsname), fso.fsname)) |
| 340 if with_metadata_mtime: | 356 if with_metadata_mtime: |
| 341 mtime = datetime.datetime.utcfromtimestamp( | 357 mtime = datetime.datetime.utcfromtimestamp( |
| 342 int(fso.stat.st_mtime)) | 358 int(fso.stat.st_mtime)) |
| 343 mtime = mtime.isoformat("T") + "Z" | 359 mtime = mtime.isoformat("T") + "Z" |
| 344 if not isinstance(mtime, bytes): | 360 if not isinstance(mtime, bytes): |
| 345 mtime = mtime.encode("ascii") | 361 mtime = mtime.encode("ascii") |
| 346 dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime)) | 362 dir_dgst.update(b"5:mtime,%d:%s," % (len(mtime), mtime)) |
| 363 if with_metadata_mode: | |
| 364 modestr = normalized_mode_str(fso.stat.st_mode) | |
| 365 if not isinstance(modestr, bytes): | |
| 366 modestr = modestr.encode("ascii") | |
| 367 dir_dgst.update(b"4:mode,%d:%s," % (len(modestr), modestr)) | |
| 347 dgst = digest.compute_digest_file( | 368 dgst = digest.compute_digest_file( |
| 348 algorithm[0], fso.path, use_mmap=use_mmap) | 369 algorithm[0], fso.path, use_mmap=use_mmap) |
| 349 dir_dgst.update(dgst) | 370 dir_dgst.update(dgst) |
| 350 opath = "/".join(top) + "/" + fso.name if top else fso.name | 371 opath = "/".join(top) + "/" + fso.name if top else fso.name |
| 351 outfp.write( | 372 outfp.write( |
| 355 opath = "/".join(top) + "/" if top else "" | 376 opath = "/".join(top) + "/" if top else "" |
| 356 outfp.write(format_bsd_line( | 377 outfp.write(format_bsd_line( |
| 357 algorithm[1], dir_dgst.digest(), opath, use_base64)) | 378 algorithm[1], dir_dgst.digest(), opath, use_base64)) |
| 358 outfp.flush() | 379 outfp.flush() |
| 359 dir_digests[top] = dir_dgst.digest() | 380 dir_digests[top] = dir_dgst.digest() |
| 381 | |
| 382 | |
| 383 def normalized_mode_str(mode): | |
| 384 # XXX FIXME: Windows and "executable" | |
| 385 modebits = stat.S_IMODE(mode) | |
| 386 modestr = "%o" % (modebits,) | |
| 387 if not modestr.startswith("0"): | |
| 388 modestr = "0" + modestr | |
| 389 return modestr | |
| 360 | 390 |
| 361 | 391 |
| 362 def format_bsd_line(digestname, value, filename, use_base64): | 392 def format_bsd_line(digestname, value, filename, use_base64): |
| 363 ls = os.linesep if isinstance(os.linesep, bytes) \ | 393 ls = os.linesep if isinstance(os.linesep, bytes) \ |
| 364 else os.linesep.encode("utf-8") | 394 else os.linesep.encode("utf-8") |
