##// END OF EJS Templates
largefiles: use "normallookup" on "lfdirstate" while reverting...
FUJIWARA Katsunori -
r21934:0cb34b39 stable
parent child Browse files
Show More
@@ -1,577 +1,578 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 19 import lfutil
20 20 import basestore
21 21
22 22 # -- Commands ----------------------------------------------------------
23 23
24 24 cmdtable = {}
25 25 command = cmdutil.command(cmdtable)
26 26
27 27 @command('lfconvert',
28 28 [('s', 'size', '',
29 29 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
30 30 ('', 'to-normal', False,
31 31 _('convert from a largefiles repo to a normal repo')),
32 32 ],
33 33 _('hg lfconvert SOURCE DEST [FILE ...]'),
34 34 norepo=True,
35 35 inferrepo=True)
36 36 def lfconvert(ui, src, dest, *pats, **opts):
37 37 '''convert a normal repository to a largefiles repository
38 38
39 39 Convert repository SOURCE to a new repository DEST, identical to
40 40 SOURCE except that certain files will be converted as largefiles:
41 41 specifically, any file that matches any PATTERN *or* whose size is
42 42 above the minimum size threshold is converted as a largefile. The
43 43 size used to determine whether or not to track a file as a
44 44 largefile is the size of the first version of the file. The
45 45 minimum size can be specified either with --size or in
46 46 configuration as ``largefiles.size``.
47 47
48 48 After running this command you will need to make sure that
49 49 largefiles is enabled anywhere you intend to push the new
50 50 repository.
51 51
52 52 Use --to-normal to convert largefiles back to normal files; after
53 53 this, the DEST repository can be used without largefiles at all.'''
54 54
55 55 if opts['to_normal']:
56 56 tolfile = False
57 57 else:
58 58 tolfile = True
59 59 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
60 60
61 61 if not hg.islocal(src):
62 62 raise util.Abort(_('%s is not a local Mercurial repo') % src)
63 63 if not hg.islocal(dest):
64 64 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
65 65
66 66 rsrc = hg.repository(ui, src)
67 67 ui.status(_('initializing destination %s\n') % dest)
68 68 rdst = hg.repository(ui, dest, create=True)
69 69
70 70 success = False
71 71 dstwlock = dstlock = None
72 72 try:
73 73 # Lock destination to prevent modification while it is converted to.
74 74 # Don't need to lock src because we are just reading from its history
75 75 # which can't change.
76 76 dstwlock = rdst.wlock()
77 77 dstlock = rdst.lock()
78 78
79 79 # Get a list of all changesets in the source. The easy way to do this
80 80 # is to simply walk the changelog, using changelog.nodesbetween().
81 81 # Take a look at mercurial/revlog.py:639 for more details.
82 82 # Use a generator instead of a list to decrease memory usage
83 83 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
84 84 rsrc.heads())[0])
85 85 revmap = {node.nullid: node.nullid}
86 86 if tolfile:
87 87 lfiles = set()
88 88 normalfiles = set()
89 89 if not pats:
90 90 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
91 91 if pats:
92 92 matcher = match_.match(rsrc.root, '', list(pats))
93 93 else:
94 94 matcher = None
95 95
96 96 lfiletohash = {}
97 97 for ctx in ctxs:
98 98 ui.progress(_('converting revisions'), ctx.rev(),
99 99 unit=_('revision'), total=rsrc['tip'].rev())
100 100 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
101 101 lfiles, normalfiles, matcher, size, lfiletohash)
102 102 ui.progress(_('converting revisions'), None)
103 103
104 104 if os.path.exists(rdst.wjoin(lfutil.shortname)):
105 105 shutil.rmtree(rdst.wjoin(lfutil.shortname))
106 106
107 107 for f in lfiletohash.keys():
108 108 if os.path.isfile(rdst.wjoin(f)):
109 109 os.unlink(rdst.wjoin(f))
110 110 try:
111 111 os.removedirs(os.path.dirname(rdst.wjoin(f)))
112 112 except OSError:
113 113 pass
114 114
115 115 # If there were any files converted to largefiles, add largefiles
116 116 # to the destination repository's requirements.
117 117 if lfiles:
118 118 rdst.requirements.add('largefiles')
119 119 rdst._writerequirements()
120 120 else:
121 121 for ctx in ctxs:
122 122 ui.progress(_('converting revisions'), ctx.rev(),
123 123 unit=_('revision'), total=rsrc['tip'].rev())
124 124 _addchangeset(ui, rsrc, rdst, ctx, revmap)
125 125
126 126 ui.progress(_('converting revisions'), None)
127 127 success = True
128 128 finally:
129 129 rdst.dirstate.clear()
130 130 release(dstlock, dstwlock)
131 131 if not success:
132 132 # we failed, remove the new directory
133 133 shutil.rmtree(rdst.root)
134 134
135 135 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
136 136 # Convert src parents to dst parents
137 137 parents = _convertparents(ctx, revmap)
138 138
139 139 # Generate list of changed files
140 140 files = _getchangedfiles(ctx, parents)
141 141
142 142 def getfilectx(repo, memctx, f):
143 143 if lfutil.standin(f) in files:
144 144 # if the file isn't in the manifest then it was removed
145 145 # or renamed, raise IOError to indicate this
146 146 try:
147 147 fctx = ctx.filectx(lfutil.standin(f))
148 148 except error.LookupError:
149 149 raise IOError
150 150 renamed = fctx.renamed()
151 151 if renamed:
152 152 renamed = lfutil.splitstandin(renamed[0])
153 153
154 154 hash = fctx.data().strip()
155 155 path = lfutil.findfile(rsrc, hash)
156 156
157 157 # If one file is missing, likely all files from this rev are
158 158 if path is None:
159 159 cachelfiles(ui, rsrc, ctx.node())
160 160 path = lfutil.findfile(rsrc, hash)
161 161
162 162 if path is None:
163 163 raise util.Abort(
164 164 _("missing largefile \'%s\' from revision %s")
165 165 % (f, node.hex(ctx.node())))
166 166
167 167 data = ''
168 168 fd = None
169 169 try:
170 170 fd = open(path, 'rb')
171 171 data = fd.read()
172 172 finally:
173 173 if fd:
174 174 fd.close()
175 175 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
176 176 'x' in fctx.flags(), renamed)
177 177 else:
178 178 return _getnormalcontext(repo, ctx, f, revmap)
179 179
180 180 dstfiles = []
181 181 for file in files:
182 182 if lfutil.isstandin(file):
183 183 dstfiles.append(lfutil.splitstandin(file))
184 184 else:
185 185 dstfiles.append(file)
186 186 # Commit
187 187 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
188 188
189 189 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
190 190 matcher, size, lfiletohash):
191 191 # Convert src parents to dst parents
192 192 parents = _convertparents(ctx, revmap)
193 193
194 194 # Generate list of changed files
195 195 files = _getchangedfiles(ctx, parents)
196 196
197 197 dstfiles = []
198 198 for f in files:
199 199 if f not in lfiles and f not in normalfiles:
200 200 islfile = _islfile(f, ctx, matcher, size)
201 201 # If this file was renamed or copied then copy
202 202 # the largefile-ness of its predecessor
203 203 if f in ctx.manifest():
204 204 fctx = ctx.filectx(f)
205 205 renamed = fctx.renamed()
206 206 renamedlfile = renamed and renamed[0] in lfiles
207 207 islfile |= renamedlfile
208 208 if 'l' in fctx.flags():
209 209 if renamedlfile:
210 210 raise util.Abort(
211 211 _('renamed/copied largefile %s becomes symlink')
212 212 % f)
213 213 islfile = False
214 214 if islfile:
215 215 lfiles.add(f)
216 216 else:
217 217 normalfiles.add(f)
218 218
219 219 if f in lfiles:
220 220 dstfiles.append(lfutil.standin(f))
221 221 # largefile in manifest if it has not been removed/renamed
222 222 if f in ctx.manifest():
223 223 fctx = ctx.filectx(f)
224 224 if 'l' in fctx.flags():
225 225 renamed = fctx.renamed()
226 226 if renamed and renamed[0] in lfiles:
227 227 raise util.Abort(_('largefile %s becomes symlink') % f)
228 228
229 229 # largefile was modified, update standins
230 230 m = util.sha1('')
231 231 m.update(ctx[f].data())
232 232 hash = m.hexdigest()
233 233 if f not in lfiletohash or lfiletohash[f] != hash:
234 234 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
235 235 executable = 'x' in ctx[f].flags()
236 236 lfutil.writestandin(rdst, lfutil.standin(f), hash,
237 237 executable)
238 238 lfiletohash[f] = hash
239 239 else:
240 240 # normal file
241 241 dstfiles.append(f)
242 242
243 243 def getfilectx(repo, memctx, f):
244 244 if lfutil.isstandin(f):
245 245 # if the file isn't in the manifest then it was removed
246 246 # or renamed, raise IOError to indicate this
247 247 srcfname = lfutil.splitstandin(f)
248 248 try:
249 249 fctx = ctx.filectx(srcfname)
250 250 except error.LookupError:
251 251 raise IOError
252 252 renamed = fctx.renamed()
253 253 if renamed:
254 254 # standin is always a largefile because largefile-ness
255 255 # doesn't change after rename or copy
256 256 renamed = lfutil.standin(renamed[0])
257 257
258 258 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
259 259 'l' in fctx.flags(), 'x' in fctx.flags(),
260 260 renamed)
261 261 else:
262 262 return _getnormalcontext(repo, ctx, f, revmap)
263 263
264 264 # Commit
265 265 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
266 266
267 267 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
268 268 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
269 269 getfilectx, ctx.user(), ctx.date(), ctx.extra())
270 270 ret = rdst.commitctx(mctx)
271 271 rdst.setparents(ret)
272 272 revmap[ctx.node()] = rdst.changelog.tip()
273 273
274 274 # Generate list of changed files
275 275 def _getchangedfiles(ctx, parents):
276 276 files = set(ctx.files())
277 277 if node.nullid not in parents:
278 278 mc = ctx.manifest()
279 279 mp1 = ctx.parents()[0].manifest()
280 280 mp2 = ctx.parents()[1].manifest()
281 281 files |= (set(mp1) | set(mp2)) - set(mc)
282 282 for f in mc:
283 283 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
284 284 files.add(f)
285 285 return files
286 286
287 287 # Convert src parents to dst parents
288 288 def _convertparents(ctx, revmap):
289 289 parents = []
290 290 for p in ctx.parents():
291 291 parents.append(revmap[p.node()])
292 292 while len(parents) < 2:
293 293 parents.append(node.nullid)
294 294 return parents
295 295
296 296 # Get memfilectx for a normal file
297 297 def _getnormalcontext(repo, ctx, f, revmap):
298 298 try:
299 299 fctx = ctx.filectx(f)
300 300 except error.LookupError:
301 301 raise IOError
302 302 renamed = fctx.renamed()
303 303 if renamed:
304 304 renamed = renamed[0]
305 305
306 306 data = fctx.data()
307 307 if f == '.hgtags':
308 308 data = _converttags (repo.ui, revmap, data)
309 309 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
310 310 'x' in fctx.flags(), renamed)
311 311
312 312 # Remap tag data using a revision map
313 313 def _converttags(ui, revmap, data):
314 314 newdata = []
315 315 for line in data.splitlines():
316 316 try:
317 317 id, name = line.split(' ', 1)
318 318 except ValueError:
319 319 ui.warn(_('skipping incorrectly formatted tag %s\n')
320 320 % line)
321 321 continue
322 322 try:
323 323 newid = node.bin(id)
324 324 except TypeError:
325 325 ui.warn(_('skipping incorrectly formatted id %s\n')
326 326 % id)
327 327 continue
328 328 try:
329 329 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
330 330 name))
331 331 except KeyError:
332 332 ui.warn(_('no mapping for id %s\n') % id)
333 333 continue
334 334 return ''.join(newdata)
335 335
336 336 def _islfile(file, ctx, matcher, size):
337 337 '''Return true if file should be considered a largefile, i.e.
338 338 matcher matches it or it is larger than size.'''
339 339 # never store special .hg* files as largefiles
340 340 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
341 341 return False
342 342 if matcher and matcher(file):
343 343 return True
344 344 try:
345 345 return ctx.filectx(file).size() >= size * 1024 * 1024
346 346 except error.LookupError:
347 347 return False
348 348
349 349 def uploadlfiles(ui, rsrc, rdst, files):
350 350 '''upload largefiles to the central store'''
351 351
352 352 if not files:
353 353 return
354 354
355 355 store = basestore._openstore(rsrc, rdst, put=True)
356 356
357 357 at = 0
358 358 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
359 359 retval = store.exists(files)
360 360 files = filter(lambda h: not retval[h], files)
361 361 ui.debug("%d largefiles need to be uploaded\n" % len(files))
362 362
363 363 for hash in files:
364 364 ui.progress(_('uploading largefiles'), at, unit='largefile',
365 365 total=len(files))
366 366 source = lfutil.findfile(rsrc, hash)
367 367 if not source:
368 368 raise util.Abort(_('largefile %s missing from store'
369 369 ' (needs to be uploaded)') % hash)
370 370 # XXX check for errors here
371 371 store.put(source, hash)
372 372 at += 1
373 373 ui.progress(_('uploading largefiles'), None)
374 374
375 375 def verifylfiles(ui, repo, all=False, contents=False):
376 376 '''Verify that every largefile revision in the current changeset
377 377 exists in the central store. With --contents, also verify that
378 378 the contents of each local largefile file revision are correct (SHA-1 hash
379 379 matches the revision ID). With --all, check every changeset in
380 380 this repository.'''
381 381 if all:
382 382 # Pass a list to the function rather than an iterator because we know a
383 383 # list will work.
384 384 revs = range(len(repo))
385 385 else:
386 386 revs = ['.']
387 387
388 388 store = basestore._openstore(repo)
389 389 return store.verify(revs, contents=contents)
390 390
391 391 def cachelfiles(ui, repo, node, filelist=None):
392 392 '''cachelfiles ensures that all largefiles needed by the specified revision
393 393 are present in the repository's largefile cache.
394 394
395 395 returns a tuple (cached, missing). cached is the list of files downloaded
396 396 by this operation; missing is the list of files that were needed but could
397 397 not be found.'''
398 398 lfiles = lfutil.listlfiles(repo, node)
399 399 if filelist:
400 400 lfiles = set(lfiles) & set(filelist)
401 401 toget = []
402 402
403 403 for lfile in lfiles:
404 404 try:
405 405 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
406 406 except IOError, err:
407 407 if err.errno == errno.ENOENT:
408 408 continue # node must be None and standin wasn't found in wctx
409 409 raise
410 410 if not lfutil.findfile(repo, expectedhash):
411 411 toget.append((lfile, expectedhash))
412 412
413 413 if toget:
414 414 store = basestore._openstore(repo)
415 415 ret = store.get(toget)
416 416 return ret
417 417
418 418 return ([], [])
419 419
420 420 def downloadlfiles(ui, repo, rev=None):
421 421 matchfn = scmutil.match(repo[None],
422 422 [repo.wjoin(lfutil.shortname)], {})
423 423 def prepare(ctx, fns):
424 424 pass
425 425 totalsuccess = 0
426 426 totalmissing = 0
427 427 if rev != []: # walkchangerevs on empty list would return all revs
428 428 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
429 429 prepare):
430 430 success, missing = cachelfiles(ui, repo, ctx.node())
431 431 totalsuccess += len(success)
432 432 totalmissing += len(missing)
433 433 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
434 434 if totalmissing > 0:
435 435 ui.status(_("%d largefiles failed to download\n") % totalmissing)
436 436 return totalsuccess, totalmissing
437 437
438 def updatelfiles(ui, repo, filelist=None, printmessage=True):
438 def updatelfiles(ui, repo, filelist=None, printmessage=True,
439 normallookup=False):
439 440 wlock = repo.wlock()
440 441 try:
441 442 lfdirstate = lfutil.openlfdirstate(ui, repo)
442 443 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
443 444
444 445 if filelist is not None:
445 446 lfiles = [f for f in lfiles if f in filelist]
446 447
447 448 update = {}
448 449 updated, removed = 0, 0
449 450 for lfile in lfiles:
450 451 abslfile = repo.wjoin(lfile)
451 452 absstandin = repo.wjoin(lfutil.standin(lfile))
452 453 if os.path.exists(absstandin):
453 454 if (os.path.exists(absstandin + '.orig') and
454 455 os.path.exists(abslfile)):
455 456 shutil.copyfile(abslfile, abslfile + '.orig')
456 457 util.unlinkpath(absstandin + '.orig')
457 458 expecthash = lfutil.readstandin(repo, lfile)
458 459 if (expecthash != '' and
459 460 (not os.path.exists(abslfile) or
460 461 expecthash != lfutil.hashfile(abslfile))):
461 462 if lfile not in repo[None]: # not switched to normal file
462 463 util.unlinkpath(abslfile, ignoremissing=True)
463 464 # use normallookup() to allocate entry in largefiles
464 465 # dirstate, because lack of it misleads
465 466 # lfilesrepo.status() into recognition that such cache
466 467 # missing files are REMOVED.
467 468 lfdirstate.normallookup(lfile)
468 469 update[lfile] = expecthash
469 470 else:
470 471 # Remove lfiles for which the standin is deleted, unless the
471 472 # lfile is added to the repository again. This happens when a
472 473 # largefile is converted back to a normal file: the standin
473 474 # disappears, but a new (normal) file appears as the lfile.
474 475 if (os.path.exists(abslfile) and
475 476 repo.dirstate.normalize(lfile) not in repo[None]):
476 477 util.unlinkpath(abslfile)
477 478 removed += 1
478 479
479 480 # largefile processing might be slow and be interrupted - be prepared
480 481 lfdirstate.write()
481 482
482 483 if lfiles:
483 484 if printmessage:
484 485 ui.status(_('getting changed largefiles\n'))
485 486 cachelfiles(ui, repo, None, lfiles)
486 487
487 488 for lfile in lfiles:
488 489 update1 = 0
489 490
490 491 expecthash = update.get(lfile)
491 492 if expecthash:
492 493 if not lfutil.copyfromcache(repo, expecthash, lfile):
493 494 # failed ... but already removed and set to normallookup
494 495 continue
495 496 # Synchronize largefile dirstate to the last modified
496 497 # time of the file
497 498 lfdirstate.normal(lfile)
498 499 update1 = 1
499 500
500 501 # copy the state of largefile standin from the repository's
501 502 # dirstate to its state in the lfdirstate.
502 503 abslfile = repo.wjoin(lfile)
503 504 absstandin = repo.wjoin(lfutil.standin(lfile))
504 505 if os.path.exists(absstandin):
505 506 mode = os.stat(absstandin).st_mode
506 507 if mode != os.stat(abslfile).st_mode:
507 508 os.chmod(abslfile, mode)
508 509 update1 = 1
509 510
510 511 updated += update1
511 512
512 513 standin = lfutil.standin(lfile)
513 514 if standin in repo.dirstate:
514 515 stat = repo.dirstate._map[standin]
515 516 state, mtime = stat[0], stat[3]
516 517 else:
517 518 state, mtime = '?', -1
518 519 if state == 'n':
519 if mtime < 0:
520 if normallookup or mtime < 0:
520 521 # state 'n' doesn't ensure 'clean' in this case
521 522 lfdirstate.normallookup(lfile)
522 523 else:
523 524 lfdirstate.normal(lfile)
524 525 elif state == 'm':
525 526 lfdirstate.normallookup(lfile)
526 527 elif state == 'r':
527 528 lfdirstate.remove(lfile)
528 529 elif state == 'a':
529 530 lfdirstate.add(lfile)
530 531 elif state == '?':
531 532 lfdirstate.drop(lfile)
532 533
533 534 lfdirstate.write()
534 535 if printmessage and lfiles:
535 536 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
536 537 removed))
537 538 finally:
538 539 wlock.release()
539 540
540 541 @command('lfpull',
541 542 [('r', 'rev', [], _('pull largefiles for these revisions'))
542 543 ] + commands.remoteopts,
543 544 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
544 545 def lfpull(ui, repo, source="default", **opts):
545 546 """pull largefiles for the specified revisions from the specified source
546 547
547 548 Pull largefiles that are referenced from local changesets but missing
548 549 locally, pulling from a remote repository to the local cache.
549 550
550 551 If SOURCE is omitted, the 'default' path will be used.
551 552 See :hg:`help urls` for more information.
552 553
553 554 .. container:: verbose
554 555
555 556 Some examples:
556 557
557 558 - pull largefiles for all branch heads::
558 559
559 560 hg lfpull -r "head() and not closed()"
560 561
561 562 - pull largefiles on the default branch::
562 563
563 564 hg lfpull -r "branch(default)"
564 565 """
565 566 repo.lfpullsource = source
566 567
567 568 revs = opts.get('rev', [])
568 569 if not revs:
569 570 raise util.Abort(_('no revisions specified'))
570 571 revs = scmutil.revrange(repo, revs)
571 572
572 573 numcached = 0
573 574 for rev in revs:
574 575 ui.note(_('pulling largefiles for revision %s\n') % rev)
575 576 (cached, missing) = cachelfiles(ui, repo, rev)
576 577 numcached += len(cached)
577 578 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,1222 +1,1228 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 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 10
11 11 import os
12 12 import copy
13 13
14 14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 15 archival, merge, pathutil, revset
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from hgext import rebase
19 19
20 20 import lfutil
21 21 import lfcommands
22 22 import basestore
23 23
24 24 # -- Utility functions: commonly/repeatedly needed functionality ---------------
25 25
26 26 def installnormalfilesmatchfn(manifest):
27 27 '''installmatchfn with a matchfn that ignores all largefiles'''
28 28 def overridematch(ctx, pats=[], opts={}, globbed=False,
29 29 default='relpath'):
30 30 match = oldmatch(ctx, pats, opts, globbed, default)
31 31 m = copy.copy(match)
32 32 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
33 33 manifest)
34 34 m._files = filter(notlfile, m._files)
35 35 m._fmap = set(m._files)
36 36 m._always = False
37 37 origmatchfn = m.matchfn
38 38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 39 return m
40 40 oldmatch = installmatchfn(overridematch)
41 41
42 42 def installmatchfn(f):
43 43 '''monkey patch the scmutil module with a custom match function.
44 44 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
45 45 oldmatch = scmutil.match
46 46 setattr(f, 'oldmatch', oldmatch)
47 47 scmutil.match = f
48 48 return oldmatch
49 49
50 50 def restorematchfn():
51 51 '''restores scmutil.match to what it was before installmatchfn
52 52 was called. no-op if scmutil.match is its original function.
53 53
54 54 Note that n calls to installmatchfn will require n calls to
55 55 restore matchfn to reverse'''
56 56 scmutil.match = getattr(scmutil.match, 'oldmatch')
57 57
58 58 def installmatchandpatsfn(f):
59 59 oldmatchandpats = scmutil.matchandpats
60 60 setattr(f, 'oldmatchandpats', oldmatchandpats)
61 61 scmutil.matchandpats = f
62 62 return oldmatchandpats
63 63
64 64 def restorematchandpatsfn():
65 65 '''restores scmutil.matchandpats to what it was before
66 66 installnormalfilesmatchandpatsfn was called. no-op if scmutil.matchandpats
67 67 is its original function.
68 68
69 69 Note that n calls to installnormalfilesmatchandpatsfn will require n calls
70 70 to restore matchfn to reverse'''
71 71 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
72 72 scmutil.matchandpats)
73 73
74 74 def addlargefiles(ui, repo, *pats, **opts):
75 75 large = opts.pop('large', None)
76 76 lfsize = lfutil.getminsize(
77 77 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
78 78
79 79 lfmatcher = None
80 80 if lfutil.islfilesrepo(repo):
81 81 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
82 82 if lfpats:
83 83 lfmatcher = match_.match(repo.root, '', list(lfpats))
84 84
85 85 lfnames = []
86 86 m = scmutil.match(repo[None], pats, opts)
87 87 m.bad = lambda x, y: None
88 88 wctx = repo[None]
89 89 for f in repo.walk(m):
90 90 exact = m.exact(f)
91 91 lfile = lfutil.standin(f) in wctx
92 92 nfile = f in wctx
93 93 exists = lfile or nfile
94 94
95 95 # Don't warn the user when they attempt to add a normal tracked file.
96 96 # The normal add code will do that for us.
97 97 if exact and exists:
98 98 if lfile:
99 99 ui.warn(_('%s already a largefile\n') % f)
100 100 continue
101 101
102 102 if (exact or not exists) and not lfutil.isstandin(f):
103 103 wfile = repo.wjoin(f)
104 104
105 105 # In case the file was removed previously, but not committed
106 106 # (issue3507)
107 107 if not os.path.exists(wfile):
108 108 continue
109 109
110 110 abovemin = (lfsize and
111 111 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
112 112 if large or abovemin or (lfmatcher and lfmatcher(f)):
113 113 lfnames.append(f)
114 114 if ui.verbose or not exact:
115 115 ui.status(_('adding %s as a largefile\n') % m.rel(f))
116 116
117 117 bad = []
118 118 standins = []
119 119
120 120 # Need to lock, otherwise there could be a race condition between
121 121 # when standins are created and added to the repo.
122 122 wlock = repo.wlock()
123 123 try:
124 124 if not opts.get('dry_run'):
125 125 lfdirstate = lfutil.openlfdirstate(ui, repo)
126 126 for f in lfnames:
127 127 standinname = lfutil.standin(f)
128 128 lfutil.writestandin(repo, standinname, hash='',
129 129 executable=lfutil.getexecutable(repo.wjoin(f)))
130 130 standins.append(standinname)
131 131 if lfdirstate[f] == 'r':
132 132 lfdirstate.normallookup(f)
133 133 else:
134 134 lfdirstate.add(f)
135 135 lfdirstate.write()
136 136 bad += [lfutil.splitstandin(f)
137 137 for f in repo[None].add(standins)
138 138 if f in m.files()]
139 139 finally:
140 140 wlock.release()
141 141 return bad
142 142
143 143 def removelargefiles(ui, repo, *pats, **opts):
144 144 after = opts.get('after')
145 145 if not pats and not after:
146 146 raise util.Abort(_('no files specified'))
147 147 m = scmutil.match(repo[None], pats, opts)
148 148 try:
149 149 repo.lfstatus = True
150 150 s = repo.status(match=m, clean=True)
151 151 finally:
152 152 repo.lfstatus = False
153 153 manifest = repo[None].manifest()
154 154 modified, added, deleted, clean = [[f for f in list
155 155 if lfutil.standin(f) in manifest]
156 156 for list in [s[0], s[1], s[3], s[6]]]
157 157
158 158 def warn(files, msg):
159 159 for f in files:
160 160 ui.warn(msg % m.rel(f))
161 161 return int(len(files) > 0)
162 162
163 163 result = 0
164 164
165 165 if after:
166 166 remove, forget = deleted, []
167 167 result = warn(modified + added + clean,
168 168 _('not removing %s: file still exists\n'))
169 169 else:
170 170 remove, forget = deleted + clean, []
171 171 result = warn(modified, _('not removing %s: file is modified (use -f'
172 172 ' to force removal)\n'))
173 173 result = warn(added, _('not removing %s: file has been marked for add'
174 174 ' (use forget to undo)\n')) or result
175 175
176 176 for f in sorted(remove + forget):
177 177 if ui.verbose or not m.exact(f):
178 178 ui.status(_('removing %s\n') % m.rel(f))
179 179
180 180 # Need to lock because standin files are deleted then removed from the
181 181 # repository and we could race in-between.
182 182 wlock = repo.wlock()
183 183 try:
184 184 lfdirstate = lfutil.openlfdirstate(ui, repo)
185 185 for f in remove:
186 186 if not after:
187 187 # If this is being called by addremove, notify the user that we
188 188 # are removing the file.
189 189 if getattr(repo, "_isaddremove", False):
190 190 ui.status(_('removing %s\n') % f)
191 191 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
192 192 lfdirstate.remove(f)
193 193 lfdirstate.write()
194 194 forget = [lfutil.standin(f) for f in forget]
195 195 remove = [lfutil.standin(f) for f in remove]
196 196 repo[None].forget(forget)
197 197 # If this is being called by addremove, let the original addremove
198 198 # function handle this.
199 199 if not getattr(repo, "_isaddremove", False):
200 200 for f in remove:
201 201 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
202 202 repo[None].forget(remove)
203 203 finally:
204 204 wlock.release()
205 205
206 206 return result
207 207
208 208 # For overriding mercurial.hgweb.webcommands so that largefiles will
209 209 # appear at their right place in the manifests.
210 210 def decodepath(orig, path):
211 211 return lfutil.splitstandin(path) or path
212 212
213 213 # -- Wrappers: modify existing commands --------------------------------
214 214
215 215 # Add works by going through the files that the user wanted to add and
216 216 # checking if they should be added as largefiles. Then it makes a new
217 217 # matcher which matches only the normal files and runs the original
218 218 # version of add.
219 219 def overrideadd(orig, ui, repo, *pats, **opts):
220 220 normal = opts.pop('normal')
221 221 if normal:
222 222 if opts.get('large'):
223 223 raise util.Abort(_('--normal cannot be used with --large'))
224 224 return orig(ui, repo, *pats, **opts)
225 225 bad = addlargefiles(ui, repo, *pats, **opts)
226 226 installnormalfilesmatchfn(repo[None].manifest())
227 227 result = orig(ui, repo, *pats, **opts)
228 228 restorematchfn()
229 229
230 230 return (result == 1 or bad) and 1 or 0
231 231
232 232 def overrideremove(orig, ui, repo, *pats, **opts):
233 233 installnormalfilesmatchfn(repo[None].manifest())
234 234 result = orig(ui, repo, *pats, **opts)
235 235 restorematchfn()
236 236 return removelargefiles(ui, repo, *pats, **opts) or result
237 237
238 238 def overridestatusfn(orig, repo, rev2, **opts):
239 239 try:
240 240 repo._repo.lfstatus = True
241 241 return orig(repo, rev2, **opts)
242 242 finally:
243 243 repo._repo.lfstatus = False
244 244
245 245 def overridestatus(orig, ui, repo, *pats, **opts):
246 246 try:
247 247 repo.lfstatus = True
248 248 return orig(ui, repo, *pats, **opts)
249 249 finally:
250 250 repo.lfstatus = False
251 251
252 252 def overridedirty(orig, repo, ignoreupdate=False):
253 253 try:
254 254 repo._repo.lfstatus = True
255 255 return orig(repo, ignoreupdate)
256 256 finally:
257 257 repo._repo.lfstatus = False
258 258
259 259 def overridelog(orig, ui, repo, *pats, **opts):
260 260 def overridematchandpats(ctx, pats=[], opts={}, globbed=False,
261 261 default='relpath'):
262 262 """Matcher that merges root directory with .hglf, suitable for log.
263 263 It is still possible to match .hglf directly.
264 264 For any listed files run log on the standin too.
265 265 matchfn tries both the given filename and with .hglf stripped.
266 266 """
267 267 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default)
268 268 m, p = copy.copy(matchandpats)
269 269
270 270 pats = set(p)
271 271 # TODO: handling of patterns in both cases below
272 272 if m._cwd:
273 273 if os.path.isabs(m._cwd):
274 274 # TODO: handle largefile magic when invoked from other cwd
275 275 return matchandpats
276 276 back = (m._cwd.count('/') + 1) * '../'
277 277 pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p)
278 278 else:
279 279 pats.update(lfutil.standin(f) for f in p)
280 280
281 281 for i in range(0, len(m._files)):
282 282 standin = lfutil.standin(m._files[i])
283 283 if standin in repo[ctx.node()]:
284 284 m._files[i] = standin
285 285 elif m._files[i] not in repo[ctx.node()]:
286 286 m._files.append(standin)
287 287 pats.add(standin)
288 288
289 289 m._fmap = set(m._files)
290 290 m._always = False
291 291 origmatchfn = m.matchfn
292 292 def lfmatchfn(f):
293 293 lf = lfutil.splitstandin(f)
294 294 if lf is not None and origmatchfn(lf):
295 295 return True
296 296 r = origmatchfn(f)
297 297 return r
298 298 m.matchfn = lfmatchfn
299 299
300 300 return m, pats
301 301
302 302 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
303 303 try:
304 304 repo.lfstatus = True
305 305 return orig(ui, repo, *pats, **opts)
306 306 finally:
307 307 repo.lfstatus = False
308 308 restorematchandpatsfn()
309 309
310 310 def overrideverify(orig, ui, repo, *pats, **opts):
311 311 large = opts.pop('large', False)
312 312 all = opts.pop('lfa', False)
313 313 contents = opts.pop('lfc', False)
314 314
315 315 result = orig(ui, repo, *pats, **opts)
316 316 if large or all or contents:
317 317 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
318 318 return result
319 319
320 320 def overridedebugstate(orig, ui, repo, *pats, **opts):
321 321 large = opts.pop('large', False)
322 322 if large:
323 323 class fakerepo(object):
324 324 dirstate = lfutil.openlfdirstate(ui, repo)
325 325 orig(ui, fakerepo, *pats, **opts)
326 326 else:
327 327 orig(ui, repo, *pats, **opts)
328 328
329 329 # Override needs to refresh standins so that update's normal merge
330 330 # will go through properly. Then the other update hook (overriding repo.update)
331 331 # will get the new files. Filemerge is also overridden so that the merge
332 332 # will merge standins correctly.
333 333 def overrideupdate(orig, ui, repo, *pats, **opts):
334 334 # Need to lock between the standins getting updated and their
335 335 # largefiles getting updated
336 336 wlock = repo.wlock()
337 337 try:
338 338 lfdirstate = lfutil.openlfdirstate(ui, repo)
339 339 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
340 340 [], False, False, False)
341 341 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
342 342
343 343 if opts['check']:
344 344 mod = len(modified) > 0
345 345 for lfile in unsure:
346 346 standin = lfutil.standin(lfile)
347 347 if repo['.'][standin].data().strip() != \
348 348 lfutil.hashfile(repo.wjoin(lfile)):
349 349 mod = True
350 350 else:
351 351 lfdirstate.normal(lfile)
352 352 lfdirstate.write()
353 353 if mod:
354 354 raise util.Abort(_('uncommitted changes'))
355 355 # XXX handle removed differently
356 356 if not opts['clean']:
357 357 for lfile in unsure + modified + added:
358 358 lfutil.updatestandin(repo, lfutil.standin(lfile))
359 359 return orig(ui, repo, *pats, **opts)
360 360 finally:
361 361 wlock.release()
362 362
363 363 # Before starting the manifest merge, merge.updates will call
364 364 # _checkunknown to check if there are any files in the merged-in
365 365 # changeset that collide with unknown files in the working copy.
366 366 #
367 367 # The largefiles are seen as unknown, so this prevents us from merging
368 368 # in a file 'foo' if we already have a largefile with the same name.
369 369 #
370 370 # The overridden function filters the unknown files by removing any
371 371 # largefiles. This makes the merge proceed and we can then handle this
372 372 # case further in the overridden manifestmerge function below.
373 373 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
374 374 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
375 375 return False
376 376 return origfn(repo, wctx, mctx, f)
377 377
378 378 # The manifest merge handles conflicts on the manifest level. We want
379 379 # to handle changes in largefile-ness of files at this level too.
380 380 #
381 381 # The strategy is to run the original manifestmerge and then process
382 382 # the action list it outputs. There are two cases we need to deal with:
383 383 #
384 384 # 1. Normal file in p1, largefile in p2. Here the largefile is
385 385 # detected via its standin file, which will enter the working copy
386 386 # with a "get" action. It is not "merge" since the standin is all
387 387 # Mercurial is concerned with at this level -- the link to the
388 388 # existing normal file is not relevant here.
389 389 #
390 390 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
391 391 # since the largefile will be present in the working copy and
392 392 # different from the normal file in p2. Mercurial therefore
393 393 # triggers a merge action.
394 394 #
395 395 # In both cases, we prompt the user and emit new actions to either
396 396 # remove the standin (if the normal file was kept) or to remove the
397 397 # normal file and get the standin (if the largefile was kept). The
398 398 # default prompt answer is to use the largefile version since it was
399 399 # presumably changed on purpose.
400 400 #
401 401 # Finally, the merge.applyupdates function will then take care of
402 402 # writing the files into the working copy and lfcommands.updatelfiles
403 403 # will update the largefiles.
404 404 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
405 405 partial, acceptremote, followcopies):
406 406 overwrite = force and not branchmerge
407 407 actions = origfn(repo, p1, p2, pas, branchmerge, force, partial,
408 408 acceptremote, followcopies)
409 409
410 410 if overwrite:
411 411 return actions
412 412
413 413 removes = set(a[0] for a in actions['r'])
414 414
415 415 newglist = []
416 416 for action in actions['g']:
417 417 f, args, msg = action
418 418 splitstandin = f and lfutil.splitstandin(f)
419 419 if (splitstandin is not None and
420 420 splitstandin in p1 and splitstandin not in removes):
421 421 # Case 1: normal file in the working copy, largefile in
422 422 # the second parent
423 423 lfile = splitstandin
424 424 standin = f
425 425 msg = _('remote turned local normal file %s into a largefile\n'
426 426 'use (l)argefile or keep (n)ormal file?'
427 427 '$$ &Largefile $$ &Normal file') % lfile
428 428 if repo.ui.promptchoice(msg, 0) == 0:
429 429 actions['r'].append((lfile, None, msg))
430 430 newglist.append((standin, (p2.flags(standin),), msg))
431 431 else:
432 432 actions['r'].append((standin, None, msg))
433 433 elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes:
434 434 # Case 2: largefile in the working copy, normal file in
435 435 # the second parent
436 436 standin = lfutil.standin(f)
437 437 lfile = f
438 438 msg = _('remote turned local largefile %s into a normal file\n'
439 439 'keep (l)argefile or use (n)ormal file?'
440 440 '$$ &Largefile $$ &Normal file') % lfile
441 441 if repo.ui.promptchoice(msg, 0) == 0:
442 442 actions['r'].append((lfile, None, msg))
443 443 else:
444 444 actions['r'].append((standin, None, msg))
445 445 newglist.append((lfile, (p2.flags(lfile),), msg))
446 446 else:
447 447 newglist.append(action)
448 448
449 449 newglist.sort()
450 450 actions['g'] = newglist
451 451
452 452 return actions
453 453
454 454 # Override filemerge to prompt the user about how they wish to merge
455 455 # largefiles. This will handle identical edits without prompting the user.
456 456 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
457 457 if not lfutil.isstandin(orig):
458 458 return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels)
459 459
460 460 ahash = fca.data().strip().lower()
461 461 dhash = fcd.data().strip().lower()
462 462 ohash = fco.data().strip().lower()
463 463 if (ohash != ahash and
464 464 ohash != dhash and
465 465 (dhash == ahash or
466 466 repo.ui.promptchoice(
467 467 _('largefile %s has a merge conflict\nancestor was %s\n'
468 468 'keep (l)ocal %s or\ntake (o)ther %s?'
469 469 '$$ &Local $$ &Other') %
470 470 (lfutil.splitstandin(orig), ahash, dhash, ohash),
471 471 0) == 1)):
472 472 repo.wwrite(fcd.path(), fco.data(), fco.flags())
473 473 return 0
474 474
475 475 # Copy first changes the matchers to match standins instead of
476 476 # largefiles. Then it overrides util.copyfile in that function it
477 477 # checks if the destination largefile already exists. It also keeps a
478 478 # list of copied files so that the largefiles can be copied and the
479 479 # dirstate updated.
480 480 def overridecopy(orig, ui, repo, pats, opts, rename=False):
481 481 # doesn't remove largefile on rename
482 482 if len(pats) < 2:
483 483 # this isn't legal, let the original function deal with it
484 484 return orig(ui, repo, pats, opts, rename)
485 485
486 486 def makestandin(relpath):
487 487 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
488 488 return os.path.join(repo.wjoin(lfutil.standin(path)))
489 489
490 490 fullpats = scmutil.expandpats(pats)
491 491 dest = fullpats[-1]
492 492
493 493 if os.path.isdir(dest):
494 494 if not os.path.isdir(makestandin(dest)):
495 495 os.makedirs(makestandin(dest))
496 496 # This could copy both lfiles and normal files in one command,
497 497 # but we don't want to do that. First replace their matcher to
498 498 # only match normal files and run it, then replace it to just
499 499 # match largefiles and run it again.
500 500 nonormalfiles = False
501 501 nolfiles = False
502 502 installnormalfilesmatchfn(repo[None].manifest())
503 503 try:
504 504 try:
505 505 result = orig(ui, repo, pats, opts, rename)
506 506 except util.Abort, e:
507 507 if str(e) != _('no files to copy'):
508 508 raise e
509 509 else:
510 510 nonormalfiles = True
511 511 result = 0
512 512 finally:
513 513 restorematchfn()
514 514
515 515 # The first rename can cause our current working directory to be removed.
516 516 # In that case there is nothing left to copy/rename so just quit.
517 517 try:
518 518 repo.getcwd()
519 519 except OSError:
520 520 return result
521 521
522 522 try:
523 523 try:
524 524 # When we call orig below it creates the standins but we don't add
525 525 # them to the dir state until later so lock during that time.
526 526 wlock = repo.wlock()
527 527
528 528 manifest = repo[None].manifest()
529 529 def overridematch(ctx, pats=[], opts={}, globbed=False,
530 530 default='relpath'):
531 531 newpats = []
532 532 # The patterns were previously mangled to add the standin
533 533 # directory; we need to remove that now
534 534 for pat in pats:
535 535 if match_.patkind(pat) is None and lfutil.shortname in pat:
536 536 newpats.append(pat.replace(lfutil.shortname, ''))
537 537 else:
538 538 newpats.append(pat)
539 539 match = oldmatch(ctx, newpats, opts, globbed, default)
540 540 m = copy.copy(match)
541 541 lfile = lambda f: lfutil.standin(f) in manifest
542 542 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
543 543 m._fmap = set(m._files)
544 544 m._always = False
545 545 origmatchfn = m.matchfn
546 546 m.matchfn = lambda f: (lfutil.isstandin(f) and
547 547 (f in manifest) and
548 548 origmatchfn(lfutil.splitstandin(f)) or
549 549 None)
550 550 return m
551 551 oldmatch = installmatchfn(overridematch)
552 552 listpats = []
553 553 for pat in pats:
554 554 if match_.patkind(pat) is not None:
555 555 listpats.append(pat)
556 556 else:
557 557 listpats.append(makestandin(pat))
558 558
559 559 try:
560 560 origcopyfile = util.copyfile
561 561 copiedfiles = []
562 562 def overridecopyfile(src, dest):
563 563 if (lfutil.shortname in src and
564 564 dest.startswith(repo.wjoin(lfutil.shortname))):
565 565 destlfile = dest.replace(lfutil.shortname, '')
566 566 if not opts['force'] and os.path.exists(destlfile):
567 567 raise IOError('',
568 568 _('destination largefile already exists'))
569 569 copiedfiles.append((src, dest))
570 570 origcopyfile(src, dest)
571 571
572 572 util.copyfile = overridecopyfile
573 573 result += orig(ui, repo, listpats, opts, rename)
574 574 finally:
575 575 util.copyfile = origcopyfile
576 576
577 577 lfdirstate = lfutil.openlfdirstate(ui, repo)
578 578 for (src, dest) in copiedfiles:
579 579 if (lfutil.shortname in src and
580 580 dest.startswith(repo.wjoin(lfutil.shortname))):
581 581 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
582 582 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
583 583 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
584 584 if not os.path.isdir(destlfiledir):
585 585 os.makedirs(destlfiledir)
586 586 if rename:
587 587 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
588 588
589 589 # The file is gone, but this deletes any empty parent
590 590 # directories as a side-effect.
591 591 util.unlinkpath(repo.wjoin(srclfile), True)
592 592 lfdirstate.remove(srclfile)
593 593 else:
594 594 util.copyfile(repo.wjoin(srclfile),
595 595 repo.wjoin(destlfile))
596 596
597 597 lfdirstate.add(destlfile)
598 598 lfdirstate.write()
599 599 except util.Abort, e:
600 600 if str(e) != _('no files to copy'):
601 601 raise e
602 602 else:
603 603 nolfiles = True
604 604 finally:
605 605 restorematchfn()
606 606 wlock.release()
607 607
608 608 if nolfiles and nonormalfiles:
609 609 raise util.Abort(_('no files to copy'))
610 610
611 611 return result
612 612
613 613 # When the user calls revert, we have to be careful to not revert any
614 614 # changes to other largefiles accidentally. This means we have to keep
615 615 # track of the largefiles that are being reverted so we only pull down
616 616 # the necessary largefiles.
617 617 #
618 618 # Standins are only updated (to match the hash of largefiles) before
619 619 # commits. Update the standins then run the original revert, changing
620 620 # the matcher to hit standins instead of largefiles. Based on the
621 621 # resulting standins update the largefiles.
622 622 def overriderevert(orig, ui, repo, *pats, **opts):
623 623 # Because we put the standins in a bad state (by updating them)
624 624 # and then return them to a correct state we need to lock to
625 625 # prevent others from changing them in their incorrect state.
626 626 wlock = repo.wlock()
627 627 try:
628 628 lfdirstate = lfutil.openlfdirstate(ui, repo)
629 629 (modified, added, removed, missing, unknown, ignored, clean) = \
630 630 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
631 631 lfdirstate.write()
632 632 for lfile in modified:
633 633 lfutil.updatestandin(repo, lfutil.standin(lfile))
634 634 for lfile in missing:
635 635 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
636 636 os.unlink(repo.wjoin(lfutil.standin(lfile)))
637 637
638 638 oldstandins = lfutil.getstandinsstate(repo)
639 639
640 640 def overridematch(ctx, pats=[], opts={}, globbed=False,
641 641 default='relpath'):
642 642 match = oldmatch(ctx, pats, opts, globbed, default)
643 643 m = copy.copy(match)
644 644 def tostandin(f):
645 645 if lfutil.standin(f) in ctx:
646 646 return lfutil.standin(f)
647 647 elif lfutil.standin(f) in repo[None]:
648 648 return None
649 649 return f
650 650 m._files = [tostandin(f) for f in m._files]
651 651 m._files = [f for f in m._files if f is not None]
652 652 m._fmap = set(m._files)
653 653 m._always = False
654 654 origmatchfn = m.matchfn
655 655 def matchfn(f):
656 656 if lfutil.isstandin(f):
657 657 return (origmatchfn(lfutil.splitstandin(f)) and
658 658 (f in repo[None] or f in ctx))
659 659 return origmatchfn(f)
660 660 m.matchfn = matchfn
661 661 return m
662 662 oldmatch = installmatchfn(overridematch)
663 663 try:
664 664 orig(ui, repo, *pats, **opts)
665 665 finally:
666 666 restorematchfn()
667 667
668 668 newstandins = lfutil.getstandinsstate(repo)
669 669 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
670 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False)
670 # lfdirstate should be 'normallookup'-ed for updated files,
671 # because reverting doesn't touch dirstate for 'normal' files
672 # when target revision is explicitly specified: in such case,
673 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
674 # of target (standin) file.
675 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
676 normallookup=True)
671 677
672 678 finally:
673 679 wlock.release()
674 680
675 681 def hgupdaterepo(orig, repo, node, overwrite):
676 682 if not overwrite:
677 683 # Only call updatelfiles on the standins that have changed to save time
678 684 oldstandins = lfutil.getstandinsstate(repo)
679 685
680 686 result = orig(repo, node, overwrite)
681 687
682 688 filelist = None
683 689 if not overwrite:
684 690 newstandins = lfutil.getstandinsstate(repo)
685 691 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
686 692 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist)
687 693 return result
688 694
689 695 def hgmerge(orig, repo, node, force=None, remind=True):
690 696 result = orig(repo, node, force, remind)
691 697 lfcommands.updatelfiles(repo.ui, repo)
692 698 return result
693 699
694 700 # When we rebase a repository with remotely changed largefiles, we need to
695 701 # take some extra care so that the largefiles are correctly updated in the
696 702 # working copy
697 703 def overridepull(orig, ui, repo, source=None, **opts):
698 704 revsprepull = len(repo)
699 705 if not source:
700 706 source = 'default'
701 707 repo.lfpullsource = source
702 708 if opts.get('rebase', False):
703 709 repo._isrebasing = True
704 710 try:
705 711 if opts.get('update'):
706 712 del opts['update']
707 713 ui.debug('--update and --rebase are not compatible, ignoring '
708 714 'the update flag\n')
709 715 del opts['rebase']
710 716 origpostincoming = commands.postincoming
711 717 def _dummy(*args, **kwargs):
712 718 pass
713 719 commands.postincoming = _dummy
714 720 try:
715 721 result = commands.pull(ui, repo, source, **opts)
716 722 finally:
717 723 commands.postincoming = origpostincoming
718 724 revspostpull = len(repo)
719 725 if revspostpull > revsprepull:
720 726 result = result or rebase.rebase(ui, repo)
721 727 finally:
722 728 repo._isrebasing = False
723 729 else:
724 730 result = orig(ui, repo, source, **opts)
725 731 revspostpull = len(repo)
726 732 lfrevs = opts.get('lfrev', [])
727 733 if opts.get('all_largefiles'):
728 734 lfrevs.append('pulled()')
729 735 if lfrevs and revspostpull > revsprepull:
730 736 numcached = 0
731 737 repo.firstpulled = revsprepull # for pulled() revset expression
732 738 try:
733 739 for rev in scmutil.revrange(repo, lfrevs):
734 740 ui.note(_('pulling largefiles for revision %s\n') % rev)
735 741 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
736 742 numcached += len(cached)
737 743 finally:
738 744 del repo.firstpulled
739 745 ui.status(_("%d largefiles cached\n") % numcached)
740 746 return result
741 747
742 748 def pulledrevsetsymbol(repo, subset, x):
743 749 """``pulled()``
744 750 Changesets that just has been pulled.
745 751
746 752 Only available with largefiles from pull --lfrev expressions.
747 753
748 754 .. container:: verbose
749 755
750 756 Some examples:
751 757
752 758 - pull largefiles for all new changesets::
753 759
754 760 hg pull -lfrev "pulled()"
755 761
756 762 - pull largefiles for all new branch heads::
757 763
758 764 hg pull -lfrev "head(pulled()) and not closed()"
759 765
760 766 """
761 767
762 768 try:
763 769 firstpulled = repo.firstpulled
764 770 except AttributeError:
765 771 raise util.Abort(_("pulled() only available in --lfrev"))
766 772 return revset.baseset([r for r in subset if r >= firstpulled])
767 773
768 774 def overrideclone(orig, ui, source, dest=None, **opts):
769 775 d = dest
770 776 if d is None:
771 777 d = hg.defaultdest(source)
772 778 if opts.get('all_largefiles') and not hg.islocal(d):
773 779 raise util.Abort(_(
774 780 '--all-largefiles is incompatible with non-local destination %s') %
775 781 d)
776 782
777 783 return orig(ui, source, dest, **opts)
778 784
779 785 def hgclone(orig, ui, opts, *args, **kwargs):
780 786 result = orig(ui, opts, *args, **kwargs)
781 787
782 788 if result is not None:
783 789 sourcerepo, destrepo = result
784 790 repo = destrepo.local()
785 791
786 792 # Caching is implicitly limited to 'rev' option, since the dest repo was
787 793 # truncated at that point. The user may expect a download count with
788 794 # this option, so attempt whether or not this is a largefile repo.
789 795 if opts.get('all_largefiles'):
790 796 success, missing = lfcommands.downloadlfiles(ui, repo, None)
791 797
792 798 if missing != 0:
793 799 return None
794 800
795 801 return result
796 802
797 803 def overriderebase(orig, ui, repo, **opts):
798 804 repo._isrebasing = True
799 805 try:
800 806 return orig(ui, repo, **opts)
801 807 finally:
802 808 repo._isrebasing = False
803 809
804 810 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
805 811 prefix=None, mtime=None, subrepos=None):
806 812 # No need to lock because we are only reading history and
807 813 # largefile caches, neither of which are modified.
808 814 lfcommands.cachelfiles(repo.ui, repo, node)
809 815
810 816 if kind not in archival.archivers:
811 817 raise util.Abort(_("unknown archive type '%s'") % kind)
812 818
813 819 ctx = repo[node]
814 820
815 821 if kind == 'files':
816 822 if prefix:
817 823 raise util.Abort(
818 824 _('cannot give prefix when archiving to files'))
819 825 else:
820 826 prefix = archival.tidyprefix(dest, kind, prefix)
821 827
822 828 def write(name, mode, islink, getdata):
823 829 if matchfn and not matchfn(name):
824 830 return
825 831 data = getdata()
826 832 if decode:
827 833 data = repo.wwritedata(name, data)
828 834 archiver.addfile(prefix + name, mode, islink, data)
829 835
830 836 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
831 837
832 838 if repo.ui.configbool("ui", "archivemeta", True):
833 839 def metadata():
834 840 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
835 841 hex(repo.changelog.node(0)), hex(node), ctx.branch())
836 842
837 843 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
838 844 if repo.tagtype(t) == 'global')
839 845 if not tags:
840 846 repo.ui.pushbuffer()
841 847 opts = {'template': '{latesttag}\n{latesttagdistance}',
842 848 'style': '', 'patch': None, 'git': None}
843 849 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
844 850 ltags, dist = repo.ui.popbuffer().split('\n')
845 851 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
846 852 tags += 'latesttagdistance: %s\n' % dist
847 853
848 854 return base + tags
849 855
850 856 write('.hg_archival.txt', 0644, False, metadata)
851 857
852 858 for f in ctx:
853 859 ff = ctx.flags(f)
854 860 getdata = ctx[f].data
855 861 if lfutil.isstandin(f):
856 862 path = lfutil.findfile(repo, getdata().strip())
857 863 if path is None:
858 864 raise util.Abort(
859 865 _('largefile %s not found in repo store or system cache')
860 866 % lfutil.splitstandin(f))
861 867 f = lfutil.splitstandin(f)
862 868
863 869 def getdatafn():
864 870 fd = None
865 871 try:
866 872 fd = open(path, 'rb')
867 873 return fd.read()
868 874 finally:
869 875 if fd:
870 876 fd.close()
871 877
872 878 getdata = getdatafn
873 879 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
874 880
875 881 if subrepos:
876 882 for subpath in sorted(ctx.substate):
877 883 sub = ctx.sub(subpath)
878 884 submatch = match_.narrowmatcher(subpath, matchfn)
879 885 sub.archive(repo.ui, archiver, prefix, submatch)
880 886
881 887 archiver.done()
882 888
883 889 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
884 890 repo._get(repo._state + ('hg',))
885 891 rev = repo._state[1]
886 892 ctx = repo._repo[rev]
887 893
888 894 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
889 895
890 896 def write(name, mode, islink, getdata):
891 897 # At this point, the standin has been replaced with the largefile name,
892 898 # so the normal matcher works here without the lfutil variants.
893 899 if match and not match(f):
894 900 return
895 901 data = getdata()
896 902
897 903 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
898 904
899 905 for f in ctx:
900 906 ff = ctx.flags(f)
901 907 getdata = ctx[f].data
902 908 if lfutil.isstandin(f):
903 909 path = lfutil.findfile(repo._repo, getdata().strip())
904 910 if path is None:
905 911 raise util.Abort(
906 912 _('largefile %s not found in repo store or system cache')
907 913 % lfutil.splitstandin(f))
908 914 f = lfutil.splitstandin(f)
909 915
910 916 def getdatafn():
911 917 fd = None
912 918 try:
913 919 fd = open(os.path.join(prefix, path), 'rb')
914 920 return fd.read()
915 921 finally:
916 922 if fd:
917 923 fd.close()
918 924
919 925 getdata = getdatafn
920 926
921 927 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
922 928
923 929 for subpath in sorted(ctx.substate):
924 930 sub = ctx.sub(subpath)
925 931 submatch = match_.narrowmatcher(subpath, match)
926 932 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
927 933 submatch)
928 934
929 935 # If a largefile is modified, the change is not reflected in its
930 936 # standin until a commit. cmdutil.bailifchanged() raises an exception
931 937 # if the repo has uncommitted changes. Wrap it to also check if
932 938 # largefiles were changed. This is used by bisect and backout.
933 939 def overridebailifchanged(orig, repo):
934 940 orig(repo)
935 941 repo.lfstatus = True
936 942 modified, added, removed, deleted = repo.status()[:4]
937 943 repo.lfstatus = False
938 944 if modified or added or removed or deleted:
939 945 raise util.Abort(_('uncommitted changes'))
940 946
941 947 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
942 948 def overridefetch(orig, ui, repo, *pats, **opts):
943 949 repo.lfstatus = True
944 950 modified, added, removed, deleted = repo.status()[:4]
945 951 repo.lfstatus = False
946 952 if modified or added or removed or deleted:
947 953 raise util.Abort(_('uncommitted changes'))
948 954 return orig(ui, repo, *pats, **opts)
949 955
950 956 def overrideforget(orig, ui, repo, *pats, **opts):
951 957 installnormalfilesmatchfn(repo[None].manifest())
952 958 result = orig(ui, repo, *pats, **opts)
953 959 restorematchfn()
954 960 m = scmutil.match(repo[None], pats, opts)
955 961
956 962 try:
957 963 repo.lfstatus = True
958 964 s = repo.status(match=m, clean=True)
959 965 finally:
960 966 repo.lfstatus = False
961 967 forget = sorted(s[0] + s[1] + s[3] + s[6])
962 968 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
963 969
964 970 for f in forget:
965 971 if lfutil.standin(f) not in repo.dirstate and not \
966 972 os.path.isdir(m.rel(lfutil.standin(f))):
967 973 ui.warn(_('not removing %s: file is already untracked\n')
968 974 % m.rel(f))
969 975 result = 1
970 976
971 977 for f in forget:
972 978 if ui.verbose or not m.exact(f):
973 979 ui.status(_('removing %s\n') % m.rel(f))
974 980
975 981 # Need to lock because standin files are deleted then removed from the
976 982 # repository and we could race in-between.
977 983 wlock = repo.wlock()
978 984 try:
979 985 lfdirstate = lfutil.openlfdirstate(ui, repo)
980 986 for f in forget:
981 987 if lfdirstate[f] == 'a':
982 988 lfdirstate.drop(f)
983 989 else:
984 990 lfdirstate.remove(f)
985 991 lfdirstate.write()
986 992 standins = [lfutil.standin(f) for f in forget]
987 993 for f in standins:
988 994 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
989 995 repo[None].forget(standins)
990 996 finally:
991 997 wlock.release()
992 998
993 999 return result
994 1000
995 1001 def _getoutgoings(repo, other, missing, addfunc):
996 1002 """get pairs of filename and largefile hash in outgoing revisions
997 1003 in 'missing'.
998 1004
999 1005 largefiles already existing on 'other' repository are ignored.
1000 1006
1001 1007 'addfunc' is invoked with each unique pairs of filename and
1002 1008 largefile hash value.
1003 1009 """
1004 1010 knowns = set()
1005 1011 lfhashes = set()
1006 1012 def dedup(fn, lfhash):
1007 1013 k = (fn, lfhash)
1008 1014 if k not in knowns:
1009 1015 knowns.add(k)
1010 1016 lfhashes.add(lfhash)
1011 1017 lfutil.getlfilestoupload(repo, missing, dedup)
1012 1018 if lfhashes:
1013 1019 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1014 1020 for fn, lfhash in knowns:
1015 1021 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1016 1022 addfunc(fn, lfhash)
1017 1023
1018 1024 def outgoinghook(ui, repo, other, opts, missing):
1019 1025 if opts.pop('large', None):
1020 1026 lfhashes = set()
1021 1027 if ui.debugflag:
1022 1028 toupload = {}
1023 1029 def addfunc(fn, lfhash):
1024 1030 if fn not in toupload:
1025 1031 toupload[fn] = []
1026 1032 toupload[fn].append(lfhash)
1027 1033 lfhashes.add(lfhash)
1028 1034 def showhashes(fn):
1029 1035 for lfhash in sorted(toupload[fn]):
1030 1036 ui.debug(' %s\n' % (lfhash))
1031 1037 else:
1032 1038 toupload = set()
1033 1039 def addfunc(fn, lfhash):
1034 1040 toupload.add(fn)
1035 1041 lfhashes.add(lfhash)
1036 1042 def showhashes(fn):
1037 1043 pass
1038 1044 _getoutgoings(repo, other, missing, addfunc)
1039 1045
1040 1046 if not toupload:
1041 1047 ui.status(_('largefiles: no files to upload\n'))
1042 1048 else:
1043 1049 ui.status(_('largefiles to upload (%d entities):\n')
1044 1050 % (len(lfhashes)))
1045 1051 for file in sorted(toupload):
1046 1052 ui.status(lfutil.splitstandin(file) + '\n')
1047 1053 showhashes(file)
1048 1054 ui.status('\n')
1049 1055
1050 1056 def summaryremotehook(ui, repo, opts, changes):
1051 1057 largeopt = opts.get('large', False)
1052 1058 if changes is None:
1053 1059 if largeopt:
1054 1060 return (False, True) # only outgoing check is needed
1055 1061 else:
1056 1062 return (False, False)
1057 1063 elif largeopt:
1058 1064 url, branch, peer, outgoing = changes[1]
1059 1065 if peer is None:
1060 1066 # i18n: column positioning for "hg summary"
1061 1067 ui.status(_('largefiles: (no remote repo)\n'))
1062 1068 return
1063 1069
1064 1070 toupload = set()
1065 1071 lfhashes = set()
1066 1072 def addfunc(fn, lfhash):
1067 1073 toupload.add(fn)
1068 1074 lfhashes.add(lfhash)
1069 1075 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1070 1076
1071 1077 if not toupload:
1072 1078 # i18n: column positioning for "hg summary"
1073 1079 ui.status(_('largefiles: (no files to upload)\n'))
1074 1080 else:
1075 1081 # i18n: column positioning for "hg summary"
1076 1082 ui.status(_('largefiles: %d entities for %d files to upload\n')
1077 1083 % (len(lfhashes), len(toupload)))
1078 1084
1079 1085 def overridesummary(orig, ui, repo, *pats, **opts):
1080 1086 try:
1081 1087 repo.lfstatus = True
1082 1088 orig(ui, repo, *pats, **opts)
1083 1089 finally:
1084 1090 repo.lfstatus = False
1085 1091
1086 1092 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1087 1093 similarity=None):
1088 1094 if not lfutil.islfilesrepo(repo):
1089 1095 return orig(repo, pats, opts, dry_run, similarity)
1090 1096 # Get the list of missing largefiles so we can remove them
1091 1097 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1092 1098 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1093 1099 False, False)
1094 1100 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1095 1101
1096 1102 # Call into the normal remove code, but the removing of the standin, we want
1097 1103 # to have handled by original addremove. Monkey patching here makes sure
1098 1104 # we don't remove the standin in the largefiles code, preventing a very
1099 1105 # confused state later.
1100 1106 if missing:
1101 1107 m = [repo.wjoin(f) for f in missing]
1102 1108 repo._isaddremove = True
1103 1109 removelargefiles(repo.ui, repo, *m, **opts)
1104 1110 repo._isaddremove = False
1105 1111 # Call into the normal add code, and any files that *should* be added as
1106 1112 # largefiles will be
1107 1113 addlargefiles(repo.ui, repo, *pats, **opts)
1108 1114 # Now that we've handled largefiles, hand off to the original addremove
1109 1115 # function to take care of the rest. Make sure it doesn't do anything with
1110 1116 # largefiles by installing a matcher that will ignore them.
1111 1117 installnormalfilesmatchfn(repo[None].manifest())
1112 1118 result = orig(repo, pats, opts, dry_run, similarity)
1113 1119 restorematchfn()
1114 1120 return result
1115 1121
1116 1122 # Calling purge with --all will cause the largefiles to be deleted.
1117 1123 # Override repo.status to prevent this from happening.
1118 1124 def overridepurge(orig, ui, repo, *dirs, **opts):
1119 1125 # XXX large file status is buggy when used on repo proxy.
1120 1126 # XXX this needs to be investigate.
1121 1127 repo = repo.unfiltered()
1122 1128 oldstatus = repo.status
1123 1129 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1124 1130 clean=False, unknown=False, listsubrepos=False):
1125 1131 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1126 1132 listsubrepos)
1127 1133 lfdirstate = lfutil.openlfdirstate(ui, repo)
1128 1134 modified, added, removed, deleted, unknown, ignored, clean = r
1129 1135 unknown = [f for f in unknown if lfdirstate[f] == '?']
1130 1136 ignored = [f for f in ignored if lfdirstate[f] == '?']
1131 1137 return modified, added, removed, deleted, unknown, ignored, clean
1132 1138 repo.status = overridestatus
1133 1139 orig(ui, repo, *dirs, **opts)
1134 1140 repo.status = oldstatus
1135 1141
1136 1142 def overriderollback(orig, ui, repo, **opts):
1137 1143 result = orig(ui, repo, **opts)
1138 1144 merge.update(repo, node=None, branchmerge=False, force=True,
1139 1145 partial=lfutil.isstandin)
1140 1146 wlock = repo.wlock()
1141 1147 try:
1142 1148 lfdirstate = lfutil.openlfdirstate(ui, repo)
1143 1149 lfiles = lfutil.listlfiles(repo)
1144 1150 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1145 1151 for file in lfiles:
1146 1152 if file in oldlfiles:
1147 1153 lfdirstate.normallookup(file)
1148 1154 else:
1149 1155 lfdirstate.add(file)
1150 1156 lfdirstate.write()
1151 1157 finally:
1152 1158 wlock.release()
1153 1159 return result
1154 1160
1155 1161 def overridetransplant(orig, ui, repo, *revs, **opts):
1156 1162 try:
1157 1163 oldstandins = lfutil.getstandinsstate(repo)
1158 1164 repo._istransplanting = True
1159 1165 result = orig(ui, repo, *revs, **opts)
1160 1166 newstandins = lfutil.getstandinsstate(repo)
1161 1167 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1162 1168 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1163 1169 printmessage=True)
1164 1170 finally:
1165 1171 repo._istransplanting = False
1166 1172 return result
1167 1173
1168 1174 def overridecat(orig, ui, repo, file1, *pats, **opts):
1169 1175 ctx = scmutil.revsingle(repo, opts.get('rev'))
1170 1176 err = 1
1171 1177 notbad = set()
1172 1178 m = scmutil.match(ctx, (file1,) + pats, opts)
1173 1179 origmatchfn = m.matchfn
1174 1180 def lfmatchfn(f):
1175 1181 if origmatchfn(f):
1176 1182 return True
1177 1183 lf = lfutil.splitstandin(f)
1178 1184 if lf is None:
1179 1185 return False
1180 1186 notbad.add(lf)
1181 1187 return origmatchfn(lf)
1182 1188 m.matchfn = lfmatchfn
1183 1189 origbadfn = m.bad
1184 1190 def lfbadfn(f, msg):
1185 1191 if not f in notbad:
1186 1192 origbadfn(f, msg)
1187 1193 m.bad = lfbadfn
1188 1194 for f in ctx.walk(m):
1189 1195 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1190 1196 pathname=f)
1191 1197 lf = lfutil.splitstandin(f)
1192 1198 if lf is None or origmatchfn(f):
1193 1199 # duplicating unreachable code from commands.cat
1194 1200 data = ctx[f].data()
1195 1201 if opts.get('decode'):
1196 1202 data = repo.wwritedata(f, data)
1197 1203 fp.write(data)
1198 1204 else:
1199 1205 hash = lfutil.readstandin(repo, lf, ctx.rev())
1200 1206 if not lfutil.inusercache(repo.ui, hash):
1201 1207 store = basestore._openstore(repo)
1202 1208 success, missing = store.get([(lf, hash)])
1203 1209 if len(success) != 1:
1204 1210 raise util.Abort(
1205 1211 _('largefile %s is not in cache and could not be '
1206 1212 'downloaded') % lf)
1207 1213 path = lfutil.usercachepath(repo.ui, hash)
1208 1214 fpin = open(path, "rb")
1209 1215 for chunk in util.filechunkiter(fpin, 128 * 1024):
1210 1216 fp.write(chunk)
1211 1217 fpin.close()
1212 1218 fp.close()
1213 1219 err = 0
1214 1220 return err
1215 1221
1216 1222 def mercurialsinkbefore(orig, sink):
1217 1223 sink.repo._isconverting = True
1218 1224 orig(sink)
1219 1225
1220 1226 def mercurialsinkafter(orig, sink):
1221 1227 sink.repo._isconverting = False
1222 1228 orig(sink)
@@ -1,82 +1,102 b''
1 1 This file focuses mainly on updating largefiles in the working
2 2 directory (and ".hg/largefiles/dirstate")
3 3
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [ui]
6 6 > merge = internal:fail
7 7 > [extensions]
8 8 > largefiles =
9 9 > EOF
10 10
11 11 $ hg init repo
12 12 $ cd repo
13 13
14 14 $ echo large1 > large1
15 15 $ echo large2 > large2
16 16 $ hg add --large large1 large2
17 17 $ echo normal1 > normal1
18 18 $ hg add normal1
19 19 $ hg commit -m '#0'
20 20 $ echo 'large1 in #1' > large1
21 21 $ echo 'normal1 in #1' > normal1
22 22 $ hg commit -m '#1'
23 23 $ hg update -q -C 0
24 24 $ echo 'large2 in #2' > large2
25 25 $ hg commit -m '#2'
26 26 created new head
27 27
28 28 Test that "hg merge" updates largefiles from "other" correctly
29 29
30 30 (getting largefiles from "other" normally)
31 31
32 32 $ hg status -A large1
33 33 C large1
34 34 $ cat large1
35 35 large1
36 36 $ cat .hglf/large1
37 37 4669e532d5b2c093a78eca010077e708a071bb64
38 38 $ hg merge --config debug.dirstate.delaywrite=2
39 39 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 40 (branch merge, don't forget to commit)
41 41 getting changed largefiles
42 42 1 largefiles updated, 0 removed
43 43 $ hg status -A large1
44 44 M large1
45 45 $ cat large1
46 46 large1 in #1
47 47 $ cat .hglf/large1
48 48 58e24f733a964da346e2407a2bee99d9001184f5
49 49 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
50 50 -4669e532d5b2c093a78eca010077e708a071bb64
51 51 +58e24f733a964da346e2407a2bee99d9001184f5
52 52
53 53 (getting largefiles from "other" via conflict prompt)
54 54
55 55 $ hg update -q -C 2
56 56 $ echo 'large1 in #3' > large1
57 57 $ echo 'normal1 in #3' > normal1
58 58 $ hg commit -m '#3'
59 59 $ cat .hglf/large1
60 60 e5bb990443d6a92aaf7223813720f7566c9dd05b
61 61 $ hg merge --config debug.dirstate.delaywrite=2 --config ui.interactive=True <<EOF
62 62 > o
63 63 > EOF
64 64 largefile large1 has a merge conflict
65 65 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
66 66 keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
67 67 take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? merging normal1
68 68 warning: conflicts during merge.
69 69 merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
70 70 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
71 71 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
72 72 getting changed largefiles
73 73 1 largefiles updated, 0 removed
74 74 [1]
75 75 $ hg status -A large1
76 76 M large1
77 77 $ cat large1
78 78 large1 in #1
79 79 $ cat .hglf/large1
80 80 58e24f733a964da346e2407a2bee99d9001184f5
81 81
82 Test that "hg revert -r REV" updates largefiles from "REV" correctly
83
84 $ hg update -q -C 3
85 $ hg status -A large1
86 C large1
87 $ cat large1
88 large1 in #3
89 $ cat .hglf/large1
90 e5bb990443d6a92aaf7223813720f7566c9dd05b
91 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
92 -4669e532d5b2c093a78eca010077e708a071bb64
93 +58e24f733a964da346e2407a2bee99d9001184f5
94 $ hg revert --no-backup -r 1 --config debug.dirstate.delaywrite=2 large1
95 $ hg status -A large1
96 M large1
97 $ cat large1
98 large1 in #1
99 $ cat .hglf/large1
100 58e24f733a964da346e2407a2bee99d9001184f5
101
82 102 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now