##// END OF EJS Templates
largefiles: don't reference uninitialized variable (issue3092)
Levi Bard -
r15808:62098aeb default
parent child Browse files
Show More
@@ -1,485 +1,487
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''High-level command function for lfconvert, plus the cmdtable.'''
10 10
11 11 import os
12 12 import shutil
13 13
14 14 from mercurial import util, match as match_, hg, node, context, error
15 15 from mercurial.i18n import _
16 16
17 17 import lfutil
18 18 import basestore
19 19
20 20 # -- Commands ----------------------------------------------------------
21 21
22 22 def lfconvert(ui, src, dest, *pats, **opts):
23 23 '''convert a normal repository to a largefiles repository
24 24
25 25 Convert repository SOURCE to a new repository DEST, identical to
26 26 SOURCE except that certain files will be converted as largefiles:
27 27 specifically, any file that matches any PATTERN *or* whose size is
28 28 above the minimum size threshold is converted as a largefile. The
29 29 size used to determine whether or not to track a file as a
30 30 largefile is the size of the first version of the file. The
31 31 minimum size can be specified either with --size or in
32 32 configuration as ``largefiles.size``.
33 33
34 34 After running this command you will need to make sure that
35 35 largefiles is enabled anywhere you intend to push the new
36 36 repository.
37 37
38 38 Use --to-normal to convert largefiles back to normal files; after
39 39 this, the DEST repository can be used without largefiles at all.'''
40 40
41 41 if opts['to_normal']:
42 42 tolfile = False
43 43 else:
44 44 tolfile = True
45 45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
46 46
47 47 if not hg.islocal(src):
48 48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 49 if not hg.islocal(dest):
50 50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51 51
52 52 rsrc = hg.repository(ui, src)
53 53 ui.status(_('initializing destination %s\n') % dest)
54 54 rdst = hg.repository(ui, dest, create=True)
55 55
56 56 success = False
57 57 try:
58 58 # Lock destination to prevent modification while it is converted to.
59 59 # Don't need to lock src because we are just reading from its history
60 60 # which can't change.
61 61 dst_lock = rdst.lock()
62 62
63 63 # Get a list of all changesets in the source. The easy way to do this
64 64 # is to simply walk the changelog, using changelog.nodesbewteen().
65 65 # Take a look at mercurial/revlog.py:639 for more details.
66 66 # Use a generator instead of a list to decrease memory usage
67 67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
68 68 rsrc.heads())[0])
69 69 revmap = {node.nullid: node.nullid}
70 70 if tolfile:
71 71 lfiles = set()
72 72 normalfiles = set()
73 73 if not pats:
74 74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
75 75 if pats:
76 76 matcher = match_.match(rsrc.root, '', list(pats))
77 77 else:
78 78 matcher = None
79 79
80 80 lfiletohash = {}
81 81 for ctx in ctxs:
82 82 ui.progress(_('converting revisions'), ctx.rev(),
83 83 unit=_('revision'), total=rsrc['tip'].rev())
84 84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
85 85 lfiles, normalfiles, matcher, size, lfiletohash)
86 86 ui.progress(_('converting revisions'), None)
87 87
88 88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
89 89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
90 90
91 91 for f in lfiletohash.keys():
92 92 if os.path.isfile(rdst.wjoin(f)):
93 93 os.unlink(rdst.wjoin(f))
94 94 try:
95 95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
96 96 except OSError:
97 97 pass
98 98
99 99 # If there were any files converted to largefiles, add largefiles
100 100 # to the destination repository's requirements.
101 101 if lfiles:
102 102 rdst.requirements.add('largefiles')
103 103 rdst._writerequirements()
104 104 else:
105 105 for ctx in ctxs:
106 106 ui.progress(_('converting revisions'), ctx.rev(),
107 107 unit=_('revision'), total=rsrc['tip'].rev())
108 108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
109 109
110 110 ui.progress(_('converting revisions'), None)
111 111 success = True
112 112 finally:
113 113 if not success:
114 114 # we failed, remove the new directory
115 115 shutil.rmtree(rdst.root)
116 116 dst_lock.release()
117 117
118 118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
119 119 # Convert src parents to dst parents
120 120 parents = []
121 121 for p in ctx.parents():
122 122 parents.append(revmap[p.node()])
123 123 while len(parents) < 2:
124 124 parents.append(node.nullid)
125 125
126 126 # Generate list of changed files
127 127 files = set(ctx.files())
128 128 if node.nullid not in parents:
129 129 mc = ctx.manifest()
130 130 mp1 = ctx.parents()[0].manifest()
131 131 mp2 = ctx.parents()[1].manifest()
132 132 files |= (set(mp1) | set(mp2)) - set(mc)
133 133 for f in mc:
134 134 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
135 135 files.add(f)
136 136
137 137 def getfilectx(repo, memctx, f):
138 138 if lfutil.standin(f) in files:
139 139 # if the file isn't in the manifest then it was removed
140 140 # or renamed, raise IOError to indicate this
141 141 try:
142 142 fctx = ctx.filectx(lfutil.standin(f))
143 143 except error.LookupError:
144 144 raise IOError()
145 145 renamed = fctx.renamed()
146 146 if renamed:
147 147 renamed = lfutil.splitstandin(renamed[0])
148 148
149 149 hash = fctx.data().strip()
150 150 path = lfutil.findfile(rsrc, hash)
151 151 ### TODO: What if the file is not cached?
152 152 data = ''
153 153 fd = None
154 154 try:
155 155 fd = open(path, 'rb')
156 156 data = fd.read()
157 157 finally:
158 158 if fd:
159 159 fd.close()
160 160 return context.memfilectx(f, data, 'l' in fctx.flags(),
161 161 'x' in fctx.flags(), renamed)
162 162 else:
163 163 try:
164 164 fctx = ctx.filectx(f)
165 165 except error.LookupError:
166 166 raise IOError()
167 167 renamed = fctx.renamed()
168 168 if renamed:
169 169 renamed = renamed[0]
170 170 data = fctx.data()
171 171 if f == '.hgtags':
172 172 newdata = []
173 173 for line in data.splitlines():
174 174 id, name = line.split(' ', 1)
175 175 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
176 176 name))
177 177 data = ''.join(newdata)
178 178 return context.memfilectx(f, data, 'l' in fctx.flags(),
179 179 'x' in fctx.flags(), renamed)
180 180
181 181 dstfiles = []
182 182 for file in files:
183 183 if lfutil.isstandin(file):
184 184 dstfiles.append(lfutil.splitstandin(file))
185 185 else:
186 186 dstfiles.append(file)
187 187 # Commit
188 188 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
189 189 getfilectx, ctx.user(), ctx.date(), ctx.extra())
190 190 ret = rdst.commitctx(mctx)
191 191 rdst.dirstate.setparents(ret)
192 192 revmap[ctx.node()] = rdst.changelog.tip()
193 193
194 194 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
195 195 matcher, size, lfiletohash):
196 196 # Convert src parents to dst parents
197 197 parents = []
198 198 for p in ctx.parents():
199 199 parents.append(revmap[p.node()])
200 200 while len(parents) < 2:
201 201 parents.append(node.nullid)
202 202
203 203 # Generate list of changed files
204 204 files = set(ctx.files())
205 205 if node.nullid not in parents:
206 206 mc = ctx.manifest()
207 207 mp1 = ctx.parents()[0].manifest()
208 208 mp2 = ctx.parents()[1].manifest()
209 209 files |= (set(mp1) | set(mp2)) - set(mc)
210 210 for f in mc:
211 211 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
212 212 files.add(f)
213 213
214 214 dstfiles = []
215 215 for f in files:
216 216 if f not in lfiles and f not in normalfiles:
217 217 islfile = _islfile(f, ctx, matcher, size)
218 218 # If this file was renamed or copied then copy
219 219 # the lfileness of its predecessor
220 220 if f in ctx.manifest():
221 221 fctx = ctx.filectx(f)
222 222 renamed = fctx.renamed()
223 223 renamedlfile = renamed and renamed[0] in lfiles
224 224 islfile |= renamedlfile
225 225 if 'l' in fctx.flags():
226 226 if renamedlfile:
227 227 raise util.Abort(
228 228 _('renamed/copied largefile %s becomes symlink')
229 229 % f)
230 230 islfile = False
231 231 if islfile:
232 232 lfiles.add(f)
233 233 else:
234 234 normalfiles.add(f)
235 235
236 236 if f in lfiles:
237 237 dstfiles.append(lfutil.standin(f))
238 238 # largefile in manifest if it has not been removed/renamed
239 239 if f in ctx.manifest():
240 if 'l' in ctx.filectx(f).flags():
240 fctx = ctx.filectx(f)
241 if 'l' in fctx.flags():
242 renamed = fctx.renamed()
241 243 if renamed and renamed[0] in lfiles:
242 244 raise util.Abort(_('largefile %s becomes symlink') % f)
243 245
244 246 # largefile was modified, update standins
245 247 fullpath = rdst.wjoin(f)
246 248 util.makedirs(os.path.dirname(fullpath))
247 249 m = util.sha1('')
248 250 m.update(ctx[f].data())
249 251 hash = m.hexdigest()
250 252 if f not in lfiletohash or lfiletohash[f] != hash:
251 253 try:
252 254 fd = open(fullpath, 'wb')
253 255 fd.write(ctx[f].data())
254 256 finally:
255 257 if fd:
256 258 fd.close()
257 259 executable = 'x' in ctx[f].flags()
258 260 os.chmod(fullpath, lfutil.getmode(executable))
259 261 lfutil.writestandin(rdst, lfutil.standin(f), hash,
260 262 executable)
261 263 lfiletohash[f] = hash
262 264 else:
263 265 # normal file
264 266 dstfiles.append(f)
265 267
266 268 def getfilectx(repo, memctx, f):
267 269 if lfutil.isstandin(f):
268 270 # if the file isn't in the manifest then it was removed
269 271 # or renamed, raise IOError to indicate this
270 272 srcfname = lfutil.splitstandin(f)
271 273 try:
272 274 fctx = ctx.filectx(srcfname)
273 275 except error.LookupError:
274 276 raise IOError()
275 277 renamed = fctx.renamed()
276 278 if renamed:
277 279 # standin is always a largefile because largefile-ness
278 280 # doesn't change after rename or copy
279 281 renamed = lfutil.standin(renamed[0])
280 282
281 283 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
282 284 fctx.flags(), 'x' in fctx.flags(), renamed)
283 285 else:
284 286 try:
285 287 fctx = ctx.filectx(f)
286 288 except error.LookupError:
287 289 raise IOError()
288 290 renamed = fctx.renamed()
289 291 if renamed:
290 292 renamed = renamed[0]
291 293
292 294 data = fctx.data()
293 295 if f == '.hgtags':
294 296 newdata = []
295 297 for line in data.splitlines():
296 298 id, name = line.split(' ', 1)
297 299 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
298 300 name))
299 301 data = ''.join(newdata)
300 302 return context.memfilectx(f, data, 'l' in fctx.flags(),
301 303 'x' in fctx.flags(), renamed)
302 304
303 305 # Commit
304 306 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
305 307 getfilectx, ctx.user(), ctx.date(), ctx.extra())
306 308 ret = rdst.commitctx(mctx)
307 309 rdst.dirstate.setparents(ret)
308 310 revmap[ctx.node()] = rdst.changelog.tip()
309 311
310 312 def _islfile(file, ctx, matcher, size):
311 313 '''Return true if file should be considered a largefile, i.e.
312 314 matcher matches it or it is larger than size.'''
313 315 # never store special .hg* files as largefiles
314 316 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
315 317 return False
316 318 if matcher and matcher(file):
317 319 return True
318 320 try:
319 321 return ctx.filectx(file).size() >= size * 1024 * 1024
320 322 except error.LookupError:
321 323 return False
322 324
323 325 def uploadlfiles(ui, rsrc, rdst, files):
324 326 '''upload largefiles to the central store'''
325 327
326 328 if not files:
327 329 return
328 330
329 331 store = basestore._openstore(rsrc, rdst, put=True)
330 332
331 333 at = 0
332 334 files = filter(lambda h: not store.exists(h), files)
333 335 for hash in files:
334 336 ui.progress(_('uploading largefiles'), at, unit='largefile',
335 337 total=len(files))
336 338 source = lfutil.findfile(rsrc, hash)
337 339 if not source:
338 340 raise util.Abort(_('largefile %s missing from store'
339 341 ' (needs to be uploaded)') % hash)
340 342 # XXX check for errors here
341 343 store.put(source, hash)
342 344 at += 1
343 345 ui.progress(_('uploading largefiles'), None)
344 346
345 347 def verifylfiles(ui, repo, all=False, contents=False):
346 348 '''Verify that every big file revision in the current changeset
347 349 exists in the central store. With --contents, also verify that
348 350 the contents of each big file revision are correct (SHA-1 hash
349 351 matches the revision ID). With --all, check every changeset in
350 352 this repository.'''
351 353 if all:
352 354 # Pass a list to the function rather than an iterator because we know a
353 355 # list will work.
354 356 revs = range(len(repo))
355 357 else:
356 358 revs = ['.']
357 359
358 360 store = basestore._openstore(repo)
359 361 return store.verify(revs, contents=contents)
360 362
361 363 def cachelfiles(ui, repo, node):
362 364 '''cachelfiles ensures that all largefiles needed by the specified revision
363 365 are present in the repository's largefile cache.
364 366
365 367 returns a tuple (cached, missing). cached is the list of files downloaded
366 368 by this operation; missing is the list of files that were needed but could
367 369 not be found.'''
368 370 lfiles = lfutil.listlfiles(repo, node)
369 371 toget = []
370 372
371 373 for lfile in lfiles:
372 374 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
373 375 # if it exists and its hash matches, it might have been locally
374 376 # modified before updating and the user chose 'local'. in this case,
375 377 # it will not be in any store, so don't look for it.
376 378 if ((not os.path.exists(repo.wjoin(lfile)) or
377 379 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
378 380 not lfutil.findfile(repo, expectedhash)):
379 381 toget.append((lfile, expectedhash))
380 382
381 383 if toget:
382 384 store = basestore._openstore(repo)
383 385 ret = store.get(toget)
384 386 return ret
385 387
386 388 return ([], [])
387 389
388 390 def updatelfiles(ui, repo, filelist=None, printmessage=True):
389 391 wlock = repo.wlock()
390 392 try:
391 393 lfdirstate = lfutil.openlfdirstate(ui, repo)
392 394 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
393 395
394 396 if filelist is not None:
395 397 lfiles = [f for f in lfiles if f in filelist]
396 398
397 399 printed = False
398 400 if printmessage and lfiles:
399 401 ui.status(_('getting changed largefiles\n'))
400 402 printed = True
401 403 cachelfiles(ui, repo, '.')
402 404
403 405 updated, removed = 0, 0
404 406 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
405 407 # increment the appropriate counter according to _updatelfile's
406 408 # return value
407 409 updated += i > 0 and i or 0
408 410 removed -= i < 0 and i or 0
409 411 if printmessage and (removed or updated) and not printed:
410 412 ui.status(_('getting changed largefiles\n'))
411 413 printed = True
412 414
413 415 lfdirstate.write()
414 416 if printed and printmessage:
415 417 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
416 418 removed))
417 419 finally:
418 420 wlock.release()
419 421
420 422 def _updatelfile(repo, lfdirstate, lfile):
421 423 '''updates a single largefile and copies the state of its standin from
422 424 the repository's dirstate to its state in the lfdirstate.
423 425
424 426 returns 1 if the file was modified, -1 if the file was removed, 0 if the
425 427 file was unchanged, and None if the needed largefile was missing from the
426 428 cache.'''
427 429 ret = 0
428 430 abslfile = repo.wjoin(lfile)
429 431 absstandin = repo.wjoin(lfutil.standin(lfile))
430 432 if os.path.exists(absstandin):
431 433 if os.path.exists(absstandin+'.orig'):
432 434 shutil.copyfile(abslfile, abslfile+'.orig')
433 435 expecthash = lfutil.readstandin(repo, lfile)
434 436 if (expecthash != '' and
435 437 (not os.path.exists(abslfile) or
436 438 expecthash != lfutil.hashfile(abslfile))):
437 439 if not lfutil.copyfromcache(repo, expecthash, lfile):
438 440 # use normallookup() to allocate entry in largefiles dirstate,
439 441 # because lack of it misleads lfiles_repo.status() into
440 442 # recognition that such cache missing files are REMOVED.
441 443 lfdirstate.normallookup(lfile)
442 444 return None # don't try to set the mode
443 445 ret = 1
444 446 mode = os.stat(absstandin).st_mode
445 447 if mode != os.stat(abslfile).st_mode:
446 448 os.chmod(abslfile, mode)
447 449 ret = 1
448 450 else:
449 451 # Remove lfiles for which the standin is deleted, unless the
450 452 # lfile is added to the repository again. This happens when a
451 453 # largefile is converted back to a normal file: the standin
452 454 # disappears, but a new (normal) file appears as the lfile.
453 455 if os.path.exists(abslfile) and lfile not in repo[None]:
454 456 os.unlink(abslfile)
455 457 ret = -1
456 458 state = repo.dirstate[lfutil.standin(lfile)]
457 459 if state == 'n':
458 460 # When rebasing, we need to synchronize the standin and the largefile,
459 461 # because otherwise the largefile will get reverted. But for commit's
460 462 # sake, we have to mark the file as unclean.
461 463 if getattr(repo, "_isrebasing", False):
462 464 lfdirstate.normallookup(lfile)
463 465 else:
464 466 lfdirstate.normal(lfile)
465 467 elif state == 'r':
466 468 lfdirstate.remove(lfile)
467 469 elif state == 'a':
468 470 lfdirstate.add(lfile)
469 471 elif state == '?':
470 472 lfdirstate.drop(lfile)
471 473 return ret
472 474
473 475 # -- hg commands declarations ------------------------------------------------
474 476
475 477 cmdtable = {
476 478 'lfconvert': (lfconvert,
477 479 [('s', 'size', '',
478 480 _('minimum size (MB) for files to be converted '
479 481 'as largefiles'),
480 482 'SIZE'),
481 483 ('', 'to-normal', False,
482 484 _('convert from a largefiles repo to a normal repo')),
483 485 ],
484 486 _('hg lfconvert SOURCE DEST [FILE ...]')),
485 487 }
General Comments 0
You need to be logged in to leave comments. Login now