##// END OF EJS Templates
largefiles: use the convert extension for 'lfconvert --to-normal'...
Matt Harbison -
r25325:fcd2f9b0 default
parent child Browse files
Show More
@@ -1,563 +1,601 b''
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, errno
12 12 import shutil
13 13
14 14 from mercurial import util, match as match_, hg, node, context, error, \
15 15 cmdutil, scmutil, commands
16 16 from mercurial.i18n import _
17 17 from mercurial.lock import release
18 18
19 from hgext.convert import convcmd
20 from hgext.convert import filemap
21
19 22 import lfutil
20 23 import basestore
21 24
22 25 # -- Commands ----------------------------------------------------------
23 26
24 27 cmdtable = {}
25 28 command = cmdutil.command(cmdtable)
26 29
27 30 @command('lfconvert',
28 31 [('s', 'size', '',
29 32 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
30 33 ('', 'to-normal', False,
31 34 _('convert from a largefiles repo to a normal repo')),
32 35 ],
33 36 _('hg lfconvert SOURCE DEST [FILE ...]'),
34 37 norepo=True,
35 38 inferrepo=True)
36 39 def lfconvert(ui, src, dest, *pats, **opts):
37 40 '''convert a normal repository to a largefiles repository
38 41
39 42 Convert repository SOURCE to a new repository DEST, identical to
40 43 SOURCE except that certain files will be converted as largefiles:
41 44 specifically, any file that matches any PATTERN *or* whose size is
42 45 above the minimum size threshold is converted as a largefile. The
43 46 size used to determine whether or not to track a file as a
44 47 largefile is the size of the first version of the file. The
45 48 minimum size can be specified either with --size or in
46 49 configuration as ``largefiles.size``.
47 50
48 51 After running this command you will need to make sure that
49 52 largefiles is enabled anywhere you intend to push the new
50 53 repository.
51 54
52 55 Use --to-normal to convert largefiles back to normal files; after
53 56 this, the DEST repository can be used without largefiles at all.'''
54 57
55 58 if opts['to_normal']:
56 59 tolfile = False
57 60 else:
58 61 tolfile = True
59 62 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
60 63
61 64 if not hg.islocal(src):
62 65 raise util.Abort(_('%s is not a local Mercurial repo') % src)
63 66 if not hg.islocal(dest):
64 67 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
65 68
66 69 rsrc = hg.repository(ui, src)
67 70 ui.status(_('initializing destination %s\n') % dest)
68 71 rdst = hg.repository(ui, dest, create=True)
69 72
70 73 success = False
71 74 dstwlock = dstlock = None
72 75 try:
73 # Lock destination to prevent modification while it is converted to.
74 # Don't need to lock src because we are just reading from its history
75 # which can't change.
76 dstwlock = rdst.wlock()
77 dstlock = rdst.lock()
78
79 76 # Get a list of all changesets in the source. The easy way to do this
80 77 # is to simply walk the changelog, using changelog.nodesbetween().
81 78 # Take a look at mercurial/revlog.py:639 for more details.
82 79 # Use a generator instead of a list to decrease memory usage
83 80 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
84 81 rsrc.heads())[0])
85 82 revmap = {node.nullid: node.nullid}
86 83 if tolfile:
84 # Lock destination to prevent modification while it is converted to.
85 # Don't need to lock src because we are just reading from its
86 # history which can't change.
87 dstwlock = rdst.wlock()
88 dstlock = rdst.lock()
89
87 90 lfiles = set()
88 91 normalfiles = set()
89 92 if not pats:
90 93 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
91 94 if pats:
92 95 matcher = match_.match(rsrc.root, '', list(pats))
93 96 else:
94 97 matcher = None
95 98
96 99 lfiletohash = {}
97 100 for ctx in ctxs:
98 101 ui.progress(_('converting revisions'), ctx.rev(),
99 102 unit=_('revision'), total=rsrc['tip'].rev())
100 103 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
101 104 lfiles, normalfiles, matcher, size, lfiletohash)
102 105 ui.progress(_('converting revisions'), None)
103 106
104 107 if os.path.exists(rdst.wjoin(lfutil.shortname)):
105 108 shutil.rmtree(rdst.wjoin(lfutil.shortname))
106 109
107 110 for f in lfiletohash.keys():
108 111 if os.path.isfile(rdst.wjoin(f)):
109 112 os.unlink(rdst.wjoin(f))
110 113 try:
111 114 os.removedirs(os.path.dirname(rdst.wjoin(f)))
112 115 except OSError:
113 116 pass
114 117
115 118 # If there were any files converted to largefiles, add largefiles
116 119 # to the destination repository's requirements.
117 120 if lfiles:
118 121 rdst.requirements.add('largefiles')
119 122 rdst._writerequirements()
120 123 else:
121 for ctx in ctxs:
122 ui.progress(_('converting revisions'), ctx.rev(),
123 unit=_('revision'), total=rsrc['tip'].rev())
124 _addchangeset(ui, rsrc, rdst, ctx, revmap)
124 class lfsource(filemap.filemap_source):
125 def __init__(self, ui, source):
126 super(lfsource, self).__init__(ui, source, None)
127 self.filemapper.rename[lfutil.shortname] = '.'
128
129 def getfile(self, name, rev):
130 realname, realrev = rev
131 f = super(lfsource, self).getfile(name, rev)
132
133 if (not realname.startswith(lfutil.shortnameslash)
134 or f[0] is None):
135 return f
136
137 # Substitute in the largefile data for the hash
138 hash = f[0].strip()
139 path = lfutil.findfile(rsrc, hash)
125 140
126 ui.progress(_('converting revisions'), None)
141 if path is None:
142 raise util.Abort(_("missing largefile for \'%s\' in %s")
143 % (realname, realrev))
144 fp = open(path, 'rb')
145
146 try:
147 return (fp.read(), f[1])
148 finally:
149 fp.close()
150
151 class converter(convcmd.converter):
152 def __init__(self, ui, source, dest, revmapfile, opts):
153 src = lfsource(ui, source)
154
155 super(converter, self).__init__(ui, src, dest, revmapfile,
156 opts)
157
158 found, missing = downloadlfiles(ui, rsrc)
159 if missing != 0:
160 raise util.Abort(_("all largefiles must be present locally"))
161
162 convcmd.converter = converter
163 convcmd.convert(ui, src, dest)
127 164 success = True
128 165 finally:
166 if tolfile:
129 167 rdst.dirstate.clear()
130 168 release(dstlock, dstwlock)
131 169 if not success:
132 170 # we failed, remove the new directory
133 171 shutil.rmtree(rdst.root)
134 172
135 173 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
136 174 # Convert src parents to dst parents
137 175 parents = _convertparents(ctx, revmap)
138 176
139 177 # Generate list of changed files
140 178 files = _getchangedfiles(ctx, parents)
141 179
142 180 def getfilectx(repo, memctx, f):
143 181 if lfutil.standin(f) in files:
144 182 # if the file isn't in the manifest then it was removed
145 183 # or renamed, raise IOError to indicate this
146 184 try:
147 185 fctx = ctx.filectx(lfutil.standin(f))
148 186 except error.LookupError:
149 187 return None
150 188 renamed = fctx.renamed()
151 189 if renamed:
152 190 renamed = lfutil.splitstandin(renamed[0])
153 191
154 192 hash = fctx.data().strip()
155 193 path = lfutil.findfile(rsrc, hash)
156 194
157 195 # If one file is missing, likely all files from this rev are
158 196 if path is None:
159 197 cachelfiles(ui, rsrc, ctx.node())
160 198 path = lfutil.findfile(rsrc, hash)
161 199
162 200 if path is None:
163 201 raise util.Abort(
164 202 _("missing largefile \'%s\' from revision %s")
165 203 % (f, node.hex(ctx.node())))
166 204
167 205 data = ''
168 206 fd = None
169 207 try:
170 208 fd = open(path, 'rb')
171 209 data = fd.read()
172 210 finally:
173 211 if fd:
174 212 fd.close()
175 213 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
176 214 'x' in fctx.flags(), renamed)
177 215 else:
178 216 return _getnormalcontext(repo, ctx, f, revmap)
179 217
180 218 dstfiles = []
181 219 for file in files:
182 220 if lfutil.isstandin(file):
183 221 dstfiles.append(lfutil.splitstandin(file))
184 222 else:
185 223 dstfiles.append(file)
186 224 # Commit
187 225 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
188 226
189 227 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
190 228 matcher, size, lfiletohash):
191 229 # Convert src parents to dst parents
192 230 parents = _convertparents(ctx, revmap)
193 231
194 232 # Generate list of changed files
195 233 files = _getchangedfiles(ctx, parents)
196 234
197 235 dstfiles = []
198 236 for f in files:
199 237 if f not in lfiles and f not in normalfiles:
200 238 islfile = _islfile(f, ctx, matcher, size)
201 239 # If this file was renamed or copied then copy
202 240 # the largefile-ness of its predecessor
203 241 if f in ctx.manifest():
204 242 fctx = ctx.filectx(f)
205 243 renamed = fctx.renamed()
206 244 renamedlfile = renamed and renamed[0] in lfiles
207 245 islfile |= renamedlfile
208 246 if 'l' in fctx.flags():
209 247 if renamedlfile:
210 248 raise util.Abort(
211 249 _('renamed/copied largefile %s becomes symlink')
212 250 % f)
213 251 islfile = False
214 252 if islfile:
215 253 lfiles.add(f)
216 254 else:
217 255 normalfiles.add(f)
218 256
219 257 if f in lfiles:
220 258 dstfiles.append(lfutil.standin(f))
221 259 # largefile in manifest if it has not been removed/renamed
222 260 if f in ctx.manifest():
223 261 fctx = ctx.filectx(f)
224 262 if 'l' in fctx.flags():
225 263 renamed = fctx.renamed()
226 264 if renamed and renamed[0] in lfiles:
227 265 raise util.Abort(_('largefile %s becomes symlink') % f)
228 266
229 267 # largefile was modified, update standins
230 268 m = util.sha1('')
231 269 m.update(ctx[f].data())
232 270 hash = m.hexdigest()
233 271 if f not in lfiletohash or lfiletohash[f] != hash:
234 272 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
235 273 executable = 'x' in ctx[f].flags()
236 274 lfutil.writestandin(rdst, lfutil.standin(f), hash,
237 275 executable)
238 276 lfiletohash[f] = hash
239 277 else:
240 278 # normal file
241 279 dstfiles.append(f)
242 280
243 281 def getfilectx(repo, memctx, f):
244 282 if lfutil.isstandin(f):
245 283 # if the file isn't in the manifest then it was removed
246 284 # or renamed, raise IOError to indicate this
247 285 srcfname = lfutil.splitstandin(f)
248 286 try:
249 287 fctx = ctx.filectx(srcfname)
250 288 except error.LookupError:
251 289 return None
252 290 renamed = fctx.renamed()
253 291 if renamed:
254 292 # standin is always a largefile because largefile-ness
255 293 # doesn't change after rename or copy
256 294 renamed = lfutil.standin(renamed[0])
257 295
258 296 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
259 297 'l' in fctx.flags(), 'x' in fctx.flags(),
260 298 renamed)
261 299 else:
262 300 return _getnormalcontext(repo, ctx, f, revmap)
263 301
264 302 # Commit
265 303 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
266 304
267 305 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
268 306 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
269 307 getfilectx, ctx.user(), ctx.date(), ctx.extra())
270 308 ret = rdst.commitctx(mctx)
271 309 lfutil.copyalltostore(rdst, ret)
272 310 rdst.setparents(ret)
273 311 revmap[ctx.node()] = rdst.changelog.tip()
274 312
275 313 # Generate list of changed files
276 314 def _getchangedfiles(ctx, parents):
277 315 files = set(ctx.files())
278 316 if node.nullid not in parents:
279 317 mc = ctx.manifest()
280 318 mp1 = ctx.parents()[0].manifest()
281 319 mp2 = ctx.parents()[1].manifest()
282 320 files |= (set(mp1) | set(mp2)) - set(mc)
283 321 for f in mc:
284 322 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
285 323 files.add(f)
286 324 return files
287 325
288 326 # Convert src parents to dst parents
289 327 def _convertparents(ctx, revmap):
290 328 parents = []
291 329 for p in ctx.parents():
292 330 parents.append(revmap[p.node()])
293 331 while len(parents) < 2:
294 332 parents.append(node.nullid)
295 333 return parents
296 334
297 335 # Get memfilectx for a normal file
298 336 def _getnormalcontext(repo, ctx, f, revmap):
299 337 try:
300 338 fctx = ctx.filectx(f)
301 339 except error.LookupError:
302 340 return None
303 341 renamed = fctx.renamed()
304 342 if renamed:
305 343 renamed = renamed[0]
306 344
307 345 data = fctx.data()
308 346 if f == '.hgtags':
309 347 data = _converttags (repo.ui, revmap, data)
310 348 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
311 349 'x' in fctx.flags(), renamed)
312 350
313 351 # Remap tag data using a revision map
314 352 def _converttags(ui, revmap, data):
315 353 newdata = []
316 354 for line in data.splitlines():
317 355 try:
318 356 id, name = line.split(' ', 1)
319 357 except ValueError:
320 358 ui.warn(_('skipping incorrectly formatted tag %s\n')
321 359 % line)
322 360 continue
323 361 try:
324 362 newid = node.bin(id)
325 363 except TypeError:
326 364 ui.warn(_('skipping incorrectly formatted id %s\n')
327 365 % id)
328 366 continue
329 367 try:
330 368 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
331 369 name))
332 370 except KeyError:
333 371 ui.warn(_('no mapping for id %s\n') % id)
334 372 continue
335 373 return ''.join(newdata)
336 374
337 375 def _islfile(file, ctx, matcher, size):
338 376 '''Return true if file should be considered a largefile, i.e.
339 377 matcher matches it or it is larger than size.'''
340 378 # never store special .hg* files as largefiles
341 379 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
342 380 return False
343 381 if matcher and matcher(file):
344 382 return True
345 383 try:
346 384 return ctx.filectx(file).size() >= size * 1024 * 1024
347 385 except error.LookupError:
348 386 return False
349 387
350 388 def uploadlfiles(ui, rsrc, rdst, files):
351 389 '''upload largefiles to the central store'''
352 390
353 391 if not files:
354 392 return
355 393
356 394 store = basestore._openstore(rsrc, rdst, put=True)
357 395
358 396 at = 0
359 397 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
360 398 retval = store.exists(files)
361 399 files = filter(lambda h: not retval[h], files)
362 400 ui.debug("%d largefiles need to be uploaded\n" % len(files))
363 401
364 402 for hash in files:
365 403 ui.progress(_('uploading largefiles'), at, unit='largefile',
366 404 total=len(files))
367 405 source = lfutil.findfile(rsrc, hash)
368 406 if not source:
369 407 raise util.Abort(_('largefile %s missing from store'
370 408 ' (needs to be uploaded)') % hash)
371 409 # XXX check for errors here
372 410 store.put(source, hash)
373 411 at += 1
374 412 ui.progress(_('uploading largefiles'), None)
375 413
376 414 def verifylfiles(ui, repo, all=False, contents=False):
377 415 '''Verify that every largefile revision in the current changeset
378 416 exists in the central store. With --contents, also verify that
379 417 the contents of each local largefile file revision are correct (SHA-1 hash
380 418 matches the revision ID). With --all, check every changeset in
381 419 this repository.'''
382 420 if all:
383 421 # Pass a list to the function rather than an iterator because we know a
384 422 # list will work.
385 423 revs = range(len(repo))
386 424 else:
387 425 revs = ['.']
388 426
389 427 store = basestore._openstore(repo)
390 428 return store.verify(revs, contents=contents)
391 429
392 430 def cachelfiles(ui, repo, node, filelist=None):
393 431 '''cachelfiles ensures that all largefiles needed by the specified revision
394 432 are present in the repository's largefile cache.
395 433
396 434 returns a tuple (cached, missing). cached is the list of files downloaded
397 435 by this operation; missing is the list of files that were needed but could
398 436 not be found.'''
399 437 lfiles = lfutil.listlfiles(repo, node)
400 438 if filelist:
401 439 lfiles = set(lfiles) & set(filelist)
402 440 toget = []
403 441
404 442 for lfile in lfiles:
405 443 try:
406 444 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
407 445 except IOError, err:
408 446 if err.errno == errno.ENOENT:
409 447 continue # node must be None and standin wasn't found in wctx
410 448 raise
411 449 if not lfutil.findfile(repo, expectedhash):
412 450 toget.append((lfile, expectedhash))
413 451
414 452 if toget:
415 453 store = basestore._openstore(repo)
416 454 ret = store.get(toget)
417 455 return ret
418 456
419 457 return ([], [])
420 458
421 459 def downloadlfiles(ui, repo, rev=None):
422 460 matchfn = scmutil.match(repo[None],
423 461 [repo.wjoin(lfutil.shortname)], {})
424 462 def prepare(ctx, fns):
425 463 pass
426 464 totalsuccess = 0
427 465 totalmissing = 0
428 466 if rev != []: # walkchangerevs on empty list would return all revs
429 467 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
430 468 prepare):
431 469 success, missing = cachelfiles(ui, repo, ctx.node())
432 470 totalsuccess += len(success)
433 471 totalmissing += len(missing)
434 472 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
435 473 if totalmissing > 0:
436 474 ui.status(_("%d largefiles failed to download\n") % totalmissing)
437 475 return totalsuccess, totalmissing
438 476
439 477 def updatelfiles(ui, repo, filelist=None, printmessage=None,
440 478 normallookup=False):
441 479 '''Update largefiles according to standins in the working directory
442 480
443 481 If ``printmessage`` is other than ``None``, it means "print (or
444 482 ignore, for false) message forcibly".
445 483 '''
446 484 statuswriter = lfutil.getstatuswriter(ui, repo, printmessage)
447 485 wlock = repo.wlock()
448 486 try:
449 487 lfdirstate = lfutil.openlfdirstate(ui, repo)
450 488 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
451 489
452 490 if filelist is not None:
453 491 filelist = set(filelist)
454 492 lfiles = [f for f in lfiles if f in filelist]
455 493
456 494 update = {}
457 495 updated, removed = 0, 0
458 496 for lfile in lfiles:
459 497 abslfile = repo.wjoin(lfile)
460 498 absstandin = repo.wjoin(lfutil.standin(lfile))
461 499 if os.path.exists(absstandin):
462 500 if (os.path.exists(absstandin + '.orig') and
463 501 os.path.exists(abslfile)):
464 502 shutil.copyfile(abslfile, abslfile + '.orig')
465 503 util.unlinkpath(absstandin + '.orig')
466 504 expecthash = lfutil.readstandin(repo, lfile)
467 505 if expecthash != '':
468 506 if lfile not in repo[None]: # not switched to normal file
469 507 util.unlinkpath(abslfile, ignoremissing=True)
470 508 # use normallookup() to allocate an entry in largefiles
471 509 # dirstate to prevent lfilesrepo.status() from reporting
472 510 # missing files as removed.
473 511 lfdirstate.normallookup(lfile)
474 512 update[lfile] = expecthash
475 513 else:
476 514 # Remove lfiles for which the standin is deleted, unless the
477 515 # lfile is added to the repository again. This happens when a
478 516 # largefile is converted back to a normal file: the standin
479 517 # disappears, but a new (normal) file appears as the lfile.
480 518 if (os.path.exists(abslfile) and
481 519 repo.dirstate.normalize(lfile) not in repo[None]):
482 520 util.unlinkpath(abslfile)
483 521 removed += 1
484 522
485 523 # largefile processing might be slow and be interrupted - be prepared
486 524 lfdirstate.write()
487 525
488 526 if lfiles:
489 527 statuswriter(_('getting changed largefiles\n'))
490 528 cachelfiles(ui, repo, None, lfiles)
491 529
492 530 for lfile in lfiles:
493 531 update1 = 0
494 532
495 533 expecthash = update.get(lfile)
496 534 if expecthash:
497 535 if not lfutil.copyfromcache(repo, expecthash, lfile):
498 536 # failed ... but already removed and set to normallookup
499 537 continue
500 538 # Synchronize largefile dirstate to the last modified
501 539 # time of the file
502 540 lfdirstate.normal(lfile)
503 541 update1 = 1
504 542
505 543 # copy the state of largefile standin from the repository's
506 544 # dirstate to its state in the lfdirstate.
507 545 abslfile = repo.wjoin(lfile)
508 546 absstandin = repo.wjoin(lfutil.standin(lfile))
509 547 if os.path.exists(absstandin):
510 548 mode = os.stat(absstandin).st_mode
511 549 if mode != os.stat(abslfile).st_mode:
512 550 os.chmod(abslfile, mode)
513 551 update1 = 1
514 552
515 553 updated += update1
516 554
517 555 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
518 556
519 557 lfdirstate.write()
520 558 if lfiles:
521 559 statuswriter(_('%d largefiles updated, %d removed\n') % (updated,
522 560 removed))
523 561 finally:
524 562 wlock.release()
525 563
526 564 @command('lfpull',
527 565 [('r', 'rev', [], _('pull largefiles for these revisions'))
528 566 ] + commands.remoteopts,
529 567 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
530 568 def lfpull(ui, repo, source="default", **opts):
531 569 """pull largefiles for the specified revisions from the specified source
532 570
533 571 Pull largefiles that are referenced from local changesets but missing
534 572 locally, pulling from a remote repository to the local cache.
535 573
536 574 If SOURCE is omitted, the 'default' path will be used.
537 575 See :hg:`help urls` for more information.
538 576
539 577 .. container:: verbose
540 578
541 579 Some examples:
542 580
543 581 - pull largefiles for all branch heads::
544 582
545 583 hg lfpull -r "head() and not closed()"
546 584
547 585 - pull largefiles on the default branch::
548 586
549 587 hg lfpull -r "branch(default)"
550 588 """
551 589 repo.lfpullsource = source
552 590
553 591 revs = opts.get('rev', [])
554 592 if not revs:
555 593 raise util.Abort(_('no revisions specified'))
556 594 revs = scmutil.revrange(repo, revs)
557 595
558 596 numcached = 0
559 597 for rev in revs:
560 598 ui.note(_('pulling largefiles for revision %s\n') % rev)
561 599 (cached, missing) = cachelfiles(ui, repo, rev)
562 600 numcached += len(cached)
563 601 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,351 +1,385 b''
1 1 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
2 2 $ mkdir "${USERCACHE}"
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > largefiles =
6 6 > share =
7 7 > strip =
8 8 > convert =
9 9 > [largefiles]
10 10 > minsize = 0.5
11 11 > patterns = **.other
12 12 > **.dat
13 13 > usercache=${USERCACHE}
14 14 > EOF
15 15
16 16 "lfconvert" works
17 17 $ hg init bigfile-repo
18 18 $ cd bigfile-repo
19 19 $ cat >> .hg/hgrc <<EOF
20 20 > [extensions]
21 21 > largefiles = !
22 22 > EOF
23 23 $ mkdir sub
24 24 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
25 25 $ dd if=/dev/zero bs=1k count=256 > large2 2> /dev/null
26 26 $ echo normal > normal1
27 27 $ echo alsonormal > sub/normal2
28 28 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
29 29 $ hg addremove
30 30 adding large
31 31 adding large2
32 32 adding normal1
33 33 adding sub/maybelarge.dat
34 34 adding sub/normal2
35 35 $ hg commit -m"add large, normal1" large normal1
36 36 $ hg commit -m"add sub/*" sub
37 37
38 38 Test tag parsing
39 39 $ cat >> .hgtags <<EOF
40 40 > IncorrectlyFormattedTag!
41 41 > invalidhash sometag
42 42 > 0123456789abcdef anothertag
43 43 > EOF
44 44 $ hg add .hgtags
45 45 $ hg commit -m"add large2" large2 .hgtags
46 46
47 47 Test link+rename largefile codepath
48 48 $ [ -d .hg/largefiles ] && echo fail || echo pass
49 49 pass
50 50 $ cd ..
51 51 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
52 52 initializing destination largefiles-repo
53 53 skipping incorrectly formatted tag IncorrectlyFormattedTag!
54 54 skipping incorrectly formatted id invalidhash
55 55 no mapping for id 0123456789abcdef
56 56 #if symlink
57 57 $ hg --cwd bigfile-repo rename large2 large3
58 58 $ ln -sf large bigfile-repo/large3
59 59 $ hg --cwd bigfile-repo commit -m"make large2 a symlink" large2 large3
60 60 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo-symlink
61 61 initializing destination largefiles-repo-symlink
62 62 skipping incorrectly formatted tag IncorrectlyFormattedTag!
63 63 skipping incorrectly formatted id invalidhash
64 64 no mapping for id 0123456789abcdef
65 65 abort: renamed/copied largefile large3 becomes symlink
66 66 [255]
67 67 #endif
68 68 $ cd bigfile-repo
69 69 $ hg strip --no-backup 2
70 70 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
71 71 $ cd ..
72 72 $ rm -rf largefiles-repo largefiles-repo-symlink
73 73
74 74 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
75 75 initializing destination largefiles-repo
76 76
77 77 "lfconvert" converts content correctly
78 78 $ cd largefiles-repo
79 79 $ hg up
80 80 getting changed largefiles
81 81 2 largefiles updated, 0 removed
82 82 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 83 $ hg locate
84 84 .hglf/large
85 85 .hglf/sub/maybelarge.dat
86 86 normal1
87 87 sub/normal2
88 88 $ cat normal1
89 89 normal
90 90 $ cat sub/normal2
91 91 alsonormal
92 92 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
93 93 ec87a838931d4d5d2e94a04644788a55 large
94 94 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
95 95
96 96 "lfconvert" adds 'largefiles' to .hg/requires.
97 97 $ cat .hg/requires
98 98 dotencode
99 99 fncache
100 100 largefiles
101 101 revlogv1
102 102 store
103 103
104 104 "lfconvert" includes a newline at the end of the standin files.
105 105 $ cat .hglf/large .hglf/sub/maybelarge.dat
106 106 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
107 107 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
108 108 $ cd ..
109 109
110 110 add some changesets to rename/remove/merge
111 111 $ cd bigfile-repo
112 112 $ hg mv -q sub stuff
113 113 $ hg commit -m"rename sub/ to stuff/"
114 114 $ hg update -q 1
115 115 $ echo blah >> normal3
116 116 $ echo blah >> sub/normal2
117 117 $ echo blah >> sub/maybelarge.dat
118 118 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
119 119 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
120 120 $ hg commit -A -m"add normal3, modify sub/*"
121 121 adding normal3
122 122 created new head
123 123 $ hg rm large normal3
124 124 $ hg commit -q -m"remove large, normal3"
125 125 $ hg merge
126 126 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
127 127 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
128 128 merging stuff/maybelarge.dat incomplete! (edit conflicts, then use 'hg resolve --mark')
129 129 merging sub/normal2 and stuff/normal2 to stuff/normal2
130 130 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
131 131 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
132 132 [1]
133 133 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
134 134 $ hg resolve -m stuff/maybelarge.dat
135 135 (no more unresolved files)
136 136 $ hg commit -m"merge"
137 137 $ hg log -G --template "{rev}:{node|short} {desc|firstline}\n"
138 138 @ 5:4884f215abda merge
139 139 |\
140 140 | o 4:7285f817b77e remove large, normal3
141 141 | |
142 142 | o 3:67e3892e3534 add normal3, modify sub/*
143 143 | |
144 144 o | 2:c96c8beb5d56 rename sub/ to stuff/
145 145 |/
146 146 o 1:020c65d24e11 add sub/*
147 147 |
148 148 o 0:117b8328f97a add large, normal1
149 149
150 150 $ cd ..
151 151
152 152 lfconvert with rename, merge, and remove
153 153 $ rm -rf largefiles-repo
154 154 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
155 155 initializing destination largefiles-repo
156 156 $ cd largefiles-repo
157 157 $ hg log -G --template "{rev}:{node|short} {desc|firstline}\n"
158 158 o 5:8e05f5f2b77e merge
159 159 |\
160 160 | o 4:a5a02de7a8e4 remove large, normal3
161 161 | |
162 162 | o 3:55759520c76f add normal3, modify sub/*
163 163 | |
164 164 o | 2:261ad3f3f037 rename sub/ to stuff/
165 165 |/
166 166 o 1:334e5237836d add sub/*
167 167 |
168 168 o 0:d4892ec57ce2 add large, normal1
169 169
170 170 $ hg locate -r 2
171 171 .hglf/large
172 172 .hglf/stuff/maybelarge.dat
173 173 normal1
174 174 stuff/normal2
175 175 $ hg locate -r 3
176 176 .hglf/large
177 177 .hglf/sub/maybelarge.dat
178 178 normal1
179 179 normal3
180 180 sub/normal2
181 181 $ hg locate -r 4
182 182 .hglf/sub/maybelarge.dat
183 183 normal1
184 184 sub/normal2
185 185 $ hg locate -r 5
186 186 .hglf/stuff/maybelarge.dat
187 187 normal1
188 188 stuff/normal2
189 189 $ hg update
190 190 getting changed largefiles
191 191 1 largefiles updated, 0 removed
192 192 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 193 $ cat stuff/normal2
194 194 alsonormal
195 195 blah
196 196 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
197 197 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
198 198 $ cat .hglf/stuff/maybelarge.dat
199 199 76236b6a2c6102826c61af4297dd738fb3b1de38
200 200 $ cd ..
201 201
202 202 "lfconvert" error cases
203 203 $ hg lfconvert http://localhost/foo foo
204 204 abort: http://localhost/foo is not a local Mercurial repo
205 205 [255]
206 206 $ hg lfconvert foo ssh://localhost/foo
207 207 abort: ssh://localhost/foo is not a local Mercurial repo
208 208 [255]
209 209 $ hg lfconvert nosuchrepo foo
210 210 abort: repository nosuchrepo not found!
211 211 [255]
212 212 $ hg share -q -U bigfile-repo shared
213 213 $ printf 'bogus' > shared/.hg/sharedpath
214 214 $ hg lfconvert shared foo
215 215 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus! (glob)
216 216 [255]
217 217 $ hg lfconvert bigfile-repo largefiles-repo
218 218 initializing destination largefiles-repo
219 219 abort: repository largefiles-repo already exists!
220 220 [255]
221 221
222 222 add another largefile to the new largefiles repo
223 223 $ cd largefiles-repo
224 224 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
225 225 $ hg add --lfsize=1 anotherlarge
226 226 $ hg commit -m "add anotherlarge (should be a largefile)"
227 227 $ cat .hglf/anotherlarge
228 228 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
229 $ hg tag mytag
229 230 $ cd ..
230 231
231 232 round-trip: converting back to a normal (non-largefiles) repo with
232 233 "lfconvert --to-normal" should give the same as ../bigfile-repo
233 234 $ cd largefiles-repo
234 235 $ hg lfconvert --to-normal . ../normal-repo
235 236 initializing destination ../normal-repo
237 0 additional largefiles cached
238 scanning source...
239 sorting...
240 converting...
241 7 add large, normal1
242 6 add sub/*
243 5 rename sub/ to stuff/
244 4 add normal3, modify sub/*
245 3 remove large, normal3
246 2 merge
247 1 add anotherlarge (should be a largefile)
248 0 Added tag mytag for changeset abacddda7028
236 249 $ cd ../normal-repo
237 250 $ cat >> .hg/hgrc <<EOF
238 251 > [extensions]
239 252 > largefiles = !
240 253 > EOF
241 254
242 # Hmmm: the changeset ID for rev 5 is different from the original
243 # normal repo (../bigfile-repo), because the changelog filelist
244 # differs between the two incarnations of rev 5: this repo includes
245 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
246 # removes 'large' relative to the first parent in both repos, it seems
247 # to me that lfconvert is doing a *better* job than
248 # "hg remove" + "hg merge" + "hg commit".
249 # $ hg -R ../bigfile-repo debugdata -c 5
250 # $ hg debugdata -c 5
251 255 $ hg log -G --template "{rev}:{node|short} {desc|firstline}\n"
252 o 6:1635824e6f59 add anotherlarge (should be a largefile)
256 o 7:b5fedc110b9d Added tag mytag for changeset 867ab992ecf4
253 257 |
254 o 5:7215f8deeaaf merge
258 o 6:867ab992ecf4 add anotherlarge (should be a largefile)
259 |
260 o 5:4884f215abda merge
255 261 |\
256 262 | o 4:7285f817b77e remove large, normal3
257 263 | |
258 264 | o 3:67e3892e3534 add normal3, modify sub/*
259 265 | |
260 266 o | 2:c96c8beb5d56 rename sub/ to stuff/
261 267 |/
262 268 o 1:020c65d24e11 add sub/*
263 269 |
264 270 o 0:117b8328f97a add large, normal1
265 271
266 272 $ hg update
267 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
273 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
268 274 $ hg locate
275 .hgtags
269 276 anotherlarge
270 277 normal1
271 278 stuff/maybelarge.dat
272 279 stuff/normal2
273 280 $ [ -d .hg/largefiles ] && echo fail || echo pass
274 281 pass
275 282
276 283 $ cd ..
277 284
278 285 Clearing the usercache ensures that commitctx doesn't try to cache largefiles
279 286 from the working dir on a convert.
280 287 $ rm "${USERCACHE}"/*
281 288 $ hg convert largefiles-repo
282 289 assuming destination largefiles-repo-hg
283 290 initializing destination largefiles-repo-hg repository
284 291 scanning source...
285 292 sorting...
286 293 converting...
287 6 add large, normal1
288 5 add sub/*
289 4 rename sub/ to stuff/
290 3 add normal3, modify sub/*
291 2 remove large, normal3
292 1 merge
293 0 add anotherlarge (should be a largefile)
294 7 add large, normal1
295 6 add sub/*
296 5 rename sub/ to stuff/
297 4 add normal3, modify sub/*
298 3 remove large, normal3
299 2 merge
300 1 add anotherlarge (should be a largefile)
301 0 Added tag mytag for changeset abacddda7028
294 302
295 303 $ hg -R largefiles-repo-hg log -G --template "{rev}:{node|short} {desc|firstline}\n"
304 o 7:2f08f66459b7 Added tag mytag for changeset 17126745edfd
305 |
296 306 o 6:17126745edfd add anotherlarge (should be a largefile)
297 307 |
298 308 o 5:9cc5aa7204f0 merge
299 309 |\
300 310 | o 4:a5a02de7a8e4 remove large, normal3
301 311 | |
302 312 | o 3:55759520c76f add normal3, modify sub/*
303 313 | |
304 314 o | 2:261ad3f3f037 rename sub/ to stuff/
305 315 |/
306 316 o 1:334e5237836d add sub/*
307 317 |
308 318 o 0:d4892ec57ce2 add large, normal1
309 319
310 320 Verify will fail (for now) if the usercache is purged before converting, since
311 321 largefiles are not cached in the converted repo's local store by the conversion
312 322 process.
313 323 $ hg -R largefiles-repo-hg verify --large --lfa
314 324 checking changesets
315 325 checking manifests
316 326 crosschecking files in changesets and manifests
317 327 checking files
318 8 files, 7 changesets, 12 total revisions
319 searching 7 changesets for largefiles
328 9 files, 8 changesets, 13 total revisions
329 searching 8 changesets for largefiles
320 330 changeset 0:d4892ec57ce2: large references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/2e000fa7e85759c7f4c254d4d9c33ef481e459a7 (glob)
321 331 changeset 1:334e5237836d: sub/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c (glob)
322 332 changeset 2:261ad3f3f037: stuff/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c (glob)
323 333 changeset 3:55759520c76f: sub/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/76236b6a2c6102826c61af4297dd738fb3b1de38 (glob)
324 334 changeset 5:9cc5aa7204f0: stuff/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/76236b6a2c6102826c61af4297dd738fb3b1de38 (glob)
325 335 changeset 6:17126745edfd: anotherlarge references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3 (glob)
326 336 verified existence of 6 revisions of 4 largefiles
327 337 [1]
328 338 $ hg -R largefiles-repo-hg showconfig paths
329 339 [1]
330 340
331 341
332 342 Avoid a traceback if a largefile isn't available (issue3519)
333 343
334 344 Ensure the largefile can be cached in the source if necessary
335 345 $ hg clone -U largefiles-repo issue3519
336 346 $ rm -f "${USERCACHE}"/*
337 347 $ hg lfconvert --to-normal issue3519 normalized3519
338 348 initializing destination normalized3519
349 4 additional largefiles cached
350 scanning source...
351 sorting...
352 converting...
353 7 add large, normal1
354 6 add sub/*
355 5 rename sub/ to stuff/
356 4 add normal3, modify sub/*
357 3 remove large, normal3
358 2 merge
359 1 add anotherlarge (should be a largefile)
360 0 Added tag mytag for changeset abacddda7028
339 361
340 362 Ensure the abort message is useful if a largefile is entirely unavailable
341 363 $ rm -rf normalized3519
342 364 $ rm "${USERCACHE}"/*
343 365 $ rm issue3519/.hg/largefiles/*
344 366 $ rm largefiles-repo/.hg/largefiles/*
345 367 $ hg lfconvert --to-normal issue3519 normalized3519
346 368 initializing destination normalized3519
369 anotherlarge: largefile 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3 not available from file:/*/$TESTTMP/largefiles-repo (glob)
370 stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
371 stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
372 sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
347 373 large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
348 abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad
374 sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
375 large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
376 stuff/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob)
377 large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
378 sub/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob)
379 large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
380 0 additional largefiles cached
381 11 largefiles failed to download
382 abort: all largefiles must be present locally
349 383 [255]
350 384
351 385
General Comments 0
You need to be logged in to leave comments. Login now