comparison hgext/relink.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children eef9a2d67051
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
24 command = registrar.command(cmdtable) 24 command = registrar.command(cmdtable)
25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for 25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should 26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 # be specifying the version(s) of Mercurial they are tested with, or 27 # be specifying the version(s) of Mercurial they are tested with, or
28 # leave the attribute unspecified. 28 # leave the attribute unspecified.
29 testedwith = 'ships-with-hg-core' 29 testedwith = b'ships-with-hg-core'
30 30
31 31
32 @command('relink', [], _('[ORIGIN]'), helpcategory=command.CATEGORY_MAINTENANCE) 32 @command(
33 b'relink', [], _(b'[ORIGIN]'), helpcategory=command.CATEGORY_MAINTENANCE
34 )
33 def relink(ui, repo, origin=None, **opts): 35 def relink(ui, repo, origin=None, **opts):
34 """recreate hardlinks between two repositories 36 """recreate hardlinks between two repositories
35 37
36 When repositories are cloned locally, their data files will be 38 When repositories are cloned locally, their data files will be
37 hardlinked so that they only use the space of a single repository. 39 hardlinked so that they only use the space of a single repository.
53 55
54 Do not attempt any read operations on this repository while the 56 Do not attempt any read operations on this repository while the
55 command is running. (Both repositories will be locked against 57 command is running. (Both repositories will be locked against
56 writes.) 58 writes.)
57 """ 59 """
58 if not util.safehasattr(util, 'samefile') or not util.safehasattr( 60 if not util.safehasattr(util, b'samefile') or not util.safehasattr(
59 util, 'samedevice' 61 util, b'samedevice'
60 ): 62 ):
61 raise error.Abort(_('hardlinks are not supported on this system')) 63 raise error.Abort(_(b'hardlinks are not supported on this system'))
62 src = hg.repository( 64 src = hg.repository(
63 repo.baseui, 65 repo.baseui,
64 ui.expandpath(origin or 'default-relink', origin or 'default'), 66 ui.expandpath(origin or b'default-relink', origin or b'default'),
65 ) 67 )
66 ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path)) 68 ui.status(_(b'relinking %s to %s\n') % (src.store.path, repo.store.path))
67 if repo.root == src.root: 69 if repo.root == src.root:
68 ui.status(_('there is nothing to relink\n')) 70 ui.status(_(b'there is nothing to relink\n'))
69 return 71 return
70 72
71 if not util.samedevice(src.store.path, repo.store.path): 73 if not util.samedevice(src.store.path, repo.store.path):
72 # No point in continuing 74 # No point in continuing
73 raise error.Abort(_('source and destination are on different devices')) 75 raise error.Abort(_(b'source and destination are on different devices'))
74 76
75 with repo.lock(), src.lock(): 77 with repo.lock(), src.lock():
76 candidates = sorted(collect(src, ui)) 78 candidates = sorted(collect(src, ui))
77 targets = prune(candidates, src.store.path, repo.store.path, ui) 79 targets = prune(candidates, src.store.path, repo.store.path, ui)
78 do_relink(src.store.path, repo.store.path, targets, ui) 80 do_relink(src.store.path, repo.store.path, targets, ui)
79 81
80 82
81 def collect(src, ui): 83 def collect(src, ui):
82 seplen = len(os.path.sep) 84 seplen = len(os.path.sep)
83 candidates = [] 85 candidates = []
84 live = len(src['tip'].manifest()) 86 live = len(src[b'tip'].manifest())
85 # Your average repository has some files which were deleted before 87 # Your average repository has some files which were deleted before
86 # the tip revision. We account for that by assuming that there are 88 # the tip revision. We account for that by assuming that there are
87 # 3 tracked files for every 2 live files as of the tip version of 89 # 3 tracked files for every 2 live files as of the tip version of
88 # the repository. 90 # the repository.
89 # 91 #
90 # mozilla-central as of 2010-06-10 had a ratio of just over 7:5. 92 # mozilla-central as of 2010-06-10 had a ratio of just over 7:5.
91 total = live * 3 // 2 93 total = live * 3 // 2
92 src = src.store.path 94 src = src.store.path
93 progress = ui.makeprogress(_('collecting'), unit=_('files'), total=total) 95 progress = ui.makeprogress(_(b'collecting'), unit=_(b'files'), total=total)
94 pos = 0 96 pos = 0
95 ui.status( 97 ui.status(
96 _("tip has %d files, estimated total number of files: %d\n") 98 _(b"tip has %d files, estimated total number of files: %d\n")
97 % (live, total) 99 % (live, total)
98 ) 100 )
99 for dirpath, dirnames, filenames in os.walk(src): 101 for dirpath, dirnames, filenames in os.walk(src):
100 dirnames.sort() 102 dirnames.sort()
101 relpath = dirpath[len(src) + seplen :] 103 relpath = dirpath[len(src) + seplen :]
102 for filename in sorted(filenames): 104 for filename in sorted(filenames):
103 if filename[-2:] not in ('.d', '.i'): 105 if filename[-2:] not in (b'.d', b'.i'):
104 continue 106 continue
105 st = os.stat(os.path.join(dirpath, filename)) 107 st = os.stat(os.path.join(dirpath, filename))
106 if not stat.S_ISREG(st.st_mode): 108 if not stat.S_ISREG(st.st_mode):
107 continue 109 continue
108 pos += 1 110 pos += 1
109 candidates.append((os.path.join(relpath, filename), st)) 111 candidates.append((os.path.join(relpath, filename), st))
110 progress.update(pos, item=filename) 112 progress.update(pos, item=filename)
111 113
112 progress.complete() 114 progress.complete()
113 ui.status(_('collected %d candidate storage files\n') % len(candidates)) 115 ui.status(_(b'collected %d candidate storage files\n') % len(candidates))
114 return candidates 116 return candidates
115 117
116 118
117 def prune(candidates, src, dst, ui): 119 def prune(candidates, src, dst, ui):
118 def linkfilter(src, dst, st): 120 def linkfilter(src, dst, st):
124 if util.samefile(src, dst): 126 if util.samefile(src, dst):
125 return False 127 return False
126 if not util.samedevice(src, dst): 128 if not util.samedevice(src, dst):
127 # No point in continuing 129 # No point in continuing
128 raise error.Abort( 130 raise error.Abort(
129 _('source and destination are on different devices') 131 _(b'source and destination are on different devices')
130 ) 132 )
131 if st.st_size != ts.st_size: 133 if st.st_size != ts.st_size:
132 return False 134 return False
133 return st 135 return st
134 136
135 targets = [] 137 targets = []
136 progress = ui.makeprogress( 138 progress = ui.makeprogress(
137 _('pruning'), unit=_('files'), total=len(candidates) 139 _(b'pruning'), unit=_(b'files'), total=len(candidates)
138 ) 140 )
139 pos = 0 141 pos = 0
140 for fn, st in candidates: 142 for fn, st in candidates:
141 pos += 1 143 pos += 1
142 srcpath = os.path.join(src, fn) 144 srcpath = os.path.join(src, fn)
143 tgt = os.path.join(dst, fn) 145 tgt = os.path.join(dst, fn)
144 ts = linkfilter(srcpath, tgt, st) 146 ts = linkfilter(srcpath, tgt, st)
145 if not ts: 147 if not ts:
146 ui.debug('not linkable: %s\n' % fn) 148 ui.debug(b'not linkable: %s\n' % fn)
147 continue 149 continue
148 targets.append((fn, ts.st_size)) 150 targets.append((fn, ts.st_size))
149 progress.update(pos, item=fn) 151 progress.update(pos, item=fn)
150 152
151 progress.complete() 153 progress.complete()
152 ui.status(_('pruned down to %d probably relinkable files\n') % len(targets)) 154 ui.status(
155 _(b'pruned down to %d probably relinkable files\n') % len(targets)
156 )
153 return targets 157 return targets
154 158
155 159
156 def do_relink(src, dst, files, ui): 160 def do_relink(src, dst, files, ui):
157 def relinkfile(src, dst): 161 def relinkfile(src, dst):
158 bak = dst + '.bak' 162 bak = dst + b'.bak'
159 os.rename(dst, bak) 163 os.rename(dst, bak)
160 try: 164 try:
161 util.oslink(src, dst) 165 util.oslink(src, dst)
162 except OSError: 166 except OSError:
163 os.rename(bak, dst) 167 os.rename(bak, dst)
167 CHUNKLEN = 65536 171 CHUNKLEN = 65536
168 relinked = 0 172 relinked = 0
169 savedbytes = 0 173 savedbytes = 0
170 174
171 progress = ui.makeprogress( 175 progress = ui.makeprogress(
172 _('relinking'), unit=_('files'), total=len(files) 176 _(b'relinking'), unit=_(b'files'), total=len(files)
173 ) 177 )
174 pos = 0 178 pos = 0
175 for f, sz in files: 179 for f, sz in files:
176 pos += 1 180 pos += 1
177 source = os.path.join(src, f) 181 source = os.path.join(src, f)
178 tgt = os.path.join(dst, f) 182 tgt = os.path.join(dst, f)
179 # Binary mode, so that read() works correctly, especially on Windows 183 # Binary mode, so that read() works correctly, especially on Windows
180 sfp = open(source, 'rb') 184 sfp = open(source, b'rb')
181 dfp = open(tgt, 'rb') 185 dfp = open(tgt, b'rb')
182 sin = sfp.read(CHUNKLEN) 186 sin = sfp.read(CHUNKLEN)
183 while sin: 187 while sin:
184 din = dfp.read(CHUNKLEN) 188 din = dfp.read(CHUNKLEN)
185 if sin != din: 189 if sin != din:
186 break 190 break
187 sin = sfp.read(CHUNKLEN) 191 sin = sfp.read(CHUNKLEN)
188 sfp.close() 192 sfp.close()
189 dfp.close() 193 dfp.close()
190 if sin: 194 if sin:
191 ui.debug('not linkable: %s\n' % f) 195 ui.debug(b'not linkable: %s\n' % f)
192 continue 196 continue
193 try: 197 try:
194 relinkfile(source, tgt) 198 relinkfile(source, tgt)
195 progress.update(pos, item=f) 199 progress.update(pos, item=f)
196 relinked += 1 200 relinked += 1
197 savedbytes += sz 201 savedbytes += sz
198 except OSError as inst: 202 except OSError as inst:
199 ui.warn('%s: %s\n' % (tgt, stringutil.forcebytestr(inst))) 203 ui.warn(b'%s: %s\n' % (tgt, stringutil.forcebytestr(inst)))
200 204
201 progress.complete() 205 progress.complete()
202 206
203 ui.status( 207 ui.status(
204 _('relinked %d files (%s reclaimed)\n') 208 _(b'relinked %d files (%s reclaimed)\n')
205 % (relinked, util.bytecount(savedbytes)) 209 % (relinked, util.bytecount(savedbytes))
206 ) 210 )