Mercurial > hgrepos > Python > apps > py-cutils
comparison cutils/treesum.py @ 131:3a18d71d7c50
Implement --follow-directory-symlinks when walking a directory tree
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Thu, 02 Jan 2025 20:52:49 +0100 |
| parents | d5621028ce39 |
| children | 8b73dca5db97 |
comparison
equal
deleted
inserted
replaced
| 130:d5621028ce39 | 131:3a18d71d7c50 |
|---|---|
| 44 help="Append to the output file instead of overwriting it.") | 44 help="Append to the output file instead of overwriting it.") |
| 45 aparser.add_argument( | 45 aparser.add_argument( |
| 46 "--base64", action="store_true", | 46 "--base64", action="store_true", |
| 47 help="Output checksums in base64 notation, not hexadecimal (OpenBSD).") | 47 help="Output checksums in base64 notation, not hexadecimal (OpenBSD).") |
| 48 aparser.add_argument( | 48 aparser.add_argument( |
| 49 "--follow-directory-symlinks", action="store_true", | |
| 50 dest="follow_directory_symlinks", | |
| 51 help="Follow symbolic links to directories when walking a directory" | |
| 52 " tree. Note that this is different from using \"--logical\" or" | |
| 53 " \"--physical\" for arguments given directly on the command" | |
| 54 " line") | |
| 55 aparser.add_argument( | |
| 49 "--logical", "-L", dest="logical", action="store_true", default=None, | 56 "--logical", "-L", dest="logical", action="store_true", default=None, |
| 50 help="Follow symbolic links given on command line arguments." | 57 help="Follow symbolic links given on command line arguments." |
| 51 " Note that this is a different setting as to follow symbolic" | 58 " Note that this is a different setting as to follow symbolic" |
| 52 " links to directories when traversing a directory tree.") | 59 " links to directories when traversing a directory tree.") |
| 53 aparser.add_argument( | 60 aparser.add_argument( |
| 81 | 88 |
| 82 def gen_opts(directories=[], | 89 def gen_opts(directories=[], |
| 83 algorithm="BLAKE2b-256", | 90 algorithm="BLAKE2b-256", |
| 84 append_output=False, | 91 append_output=False, |
| 85 base64=False, | 92 base64=False, |
| 93 follow_directory_symlinks=False, | |
| 86 logical=None, | 94 logical=None, |
| 87 mmap=None, | 95 mmap=None, |
| 88 output=None): | 96 output=None): |
| 89 opts = argparse.Namespace(directories=directories, | 97 opts = argparse.Namespace( |
| 90 algorithm=(util.algotag2algotype(algorithm), | 98 directories=directories, |
| 91 algorithm), | 99 algorithm=(util.algotag2algotype(algorithm), |
| 92 append_output=append_output, | 100 algorithm), |
| 93 base64=base64, | 101 append_output=append_output, |
| 94 logical=logical, | 102 base64=base64, |
| 95 mmap=mmap, | 103 follow_directory_symlinks=follow_directory_symlinks, |
| 96 output=output) | 104 logical=logical, |
| 105 mmap=mmap, | |
| 106 output=output) | |
| 97 return opts | 107 return opts |
| 98 | 108 |
| 99 | 109 |
| 100 def treesum(opts): | 110 def treesum(opts): |
| 101 # XXX TBD: opts.check and opts.checklist (as in shasum.py) | 111 # XXX TBD: opts.check and opts.checklist (as in shasum.py) |
| 118 out_cm = open(opts.output, "wb") | 128 out_cm = open(opts.output, "wb") |
| 119 | 129 |
| 120 with out_cm as outfp: | 130 with out_cm as outfp: |
| 121 for d in opts.directories: | 131 for d in opts.directories: |
| 122 generate_treesum_for_directory( | 132 generate_treesum_for_directory( |
| 123 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical) | 133 outfp, d, opts.algorithm, opts.mmap, opts.base64, opts.logical, |
| 134 opts.follow_directory_symlinks) | |
| 124 | 135 |
| 125 | 136 |
| 126 def generate_treesum_for_directory( | 137 def generate_treesum_for_directory( |
| 127 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical): | 138 outfp, root, algorithm, use_mmap, use_base64, handle_root_logical, |
| 139 follow_directory_symlinks): | |
| 128 """ | 140 """ |
| 129 | 141 |
| 130 :param outfp: a *binary* file with a "write()" and a "flush()" method | 142 :param outfp: a *binary* file with a "write()" and a "flush()" method |
| 131 | 143 |
| 132 """ | 144 """ |
| 135 | 147 |
| 136 # Note given non-default flags that are relevant for directory traversal | 148 # Note given non-default flags that are relevant for directory traversal |
| 137 flags = [] | 149 flags = [] |
| 138 if handle_root_logical: | 150 if handle_root_logical: |
| 139 flags.append("logical") | 151 flags.append("logical") |
| 152 if follow_directory_symlinks: | |
| 153 flags.append("follow-directory-symlinks") | |
| 140 if flags: | 154 if flags: |
| 141 outfp.write(format_bsd_line("FLAGS", ",".join(flags), None, False)) | 155 outfp.write(format_bsd_line("FLAGS", ",".join(flags), None, False)) |
| 142 | 156 |
| 143 # Write execution timestamps in POSIX epoch and ISO format | 157 # Write execution timestamps in POSIX epoch and ISO format |
| 144 ts = time.time() | 158 ts = time.time() |
| 161 "./@", | 175 "./@", |
| 162 use_base64)) | 176 use_base64)) |
| 163 outfp.flush() | 177 outfp.flush() |
| 164 return | 178 return |
| 165 | 179 |
| 166 for top, dirs, nondirs in walk.walk(root, follow_symlinks=False): | 180 for top, dirs, nondirs in walk.walk( |
| 181 root, | |
| 182 follow_symlinks=follow_directory_symlinks): | |
| 167 dir_dgst = algorithm[0]() | 183 dir_dgst = algorithm[0]() |
| 168 for dn in dirs: | 184 for dn in dirs: |
| 169 if dn.is_symlink: | 185 if dn.is_symlink and not follow_directory_symlinks: |
| 170 linktgt = util.fsencode(os.readlink(dn.path)) | 186 linktgt = util.fsencode(os.readlink(dn.path)) |
| 171 linkdgst = algorithm[0]() | 187 linkdgst = algorithm[0]() |
| 172 linkdgst.update(linktgt) | 188 linkdgst.update(linktgt) |
| 173 dir_dgst.update(b"1:S,%d:%s," % (len(dn.fsname), dn.fsname)) | 189 dir_dgst.update(b"1:S,%d:%s," % (len(dn.fsname), dn.fsname)) |
| 174 dir_dgst.update(linkdgst.digest()) | 190 dir_dgst.update(linkdgst.digest()) |
| 178 algorithm[1], | 194 algorithm[1], |
| 179 linkdgst.digest(), | 195 linkdgst.digest(), |
| 180 "%s/./@" % (opath,), | 196 "%s/./@" % (opath,), |
| 181 use_base64)) | 197 use_base64)) |
| 182 outfp.flush() | 198 outfp.flush() |
| 183 else: | 199 continue |
| 184 # fetch from dir_digests | 200 # fetch from dir_digests |
| 185 dgst = dir_digests[top + (dn.name,)] | 201 dgst = dir_digests[top + (dn.name,)] |
| 186 dir_dgst.update(b"1:d,%d:%s," % (len(dn.fsname), dn.fsname)) | 202 dir_dgst.update(b"1:d,%d:%s," % (len(dn.fsname), dn.fsname)) |
| 187 dir_dgst.update(dgst) | 203 dir_dgst.update(dgst) |
| 188 for fn in nondirs: | 204 for fn in nondirs: |
| 189 dir_dgst.update(b"1:f,%d:%s," % (len(fn.fsname), fn.fsname)) | 205 dir_dgst.update(b"1:f,%d:%s," % (len(fn.fsname), fn.fsname)) |
| 190 dgst = digest.compute_digest_file( | 206 dgst = digest.compute_digest_file( |
| 191 algorithm[0], fn.path, use_mmap=use_mmap) | 207 algorithm[0], fn.path, use_mmap=use_mmap) |
| 192 dir_dgst.update(dgst) | 208 dir_dgst.update(dgst) |
