##// END OF EJS Templates
largefiles: avoid redundant changectx looking up at each repetitions...
FUJIWARA Katsunori -
r31654:1af4a164 default
parent child Browse files
Show More
@@ -1,577 +1,579
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 from __future__ import absolute_import
11 11
12 12 import errno
13 13 import hashlib
14 14 import os
15 15 import shutil
16 16
17 17 from mercurial.i18n import _
18 18
19 19 from mercurial import (
20 20 cmdutil,
21 21 commands,
22 22 context,
23 23 error,
24 24 hg,
25 25 lock,
26 26 match as matchmod,
27 27 node,
28 28 scmutil,
29 29 util,
30 30 )
31 31
32 32 from ..convert import (
33 33 convcmd,
34 34 filemap,
35 35 )
36 36
37 37 from . import (
38 38 lfutil,
39 39 storefactory
40 40 )
41 41
42 42 release = lock.release
43 43
44 44 # -- Commands ----------------------------------------------------------
45 45
46 46 cmdtable = {}
47 47 command = cmdutil.command(cmdtable)
48 48
49 49 @command('lfconvert',
50 50 [('s', 'size', '',
51 51 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
52 52 ('', 'to-normal', False,
53 53 _('convert from a largefiles repo to a normal repo')),
54 54 ],
55 55 _('hg lfconvert SOURCE DEST [FILE ...]'),
56 56 norepo=True,
57 57 inferrepo=True)
58 58 def lfconvert(ui, src, dest, *pats, **opts):
59 59 '''convert a normal repository to a largefiles repository
60 60
61 61 Convert repository SOURCE to a new repository DEST, identical to
62 62 SOURCE except that certain files will be converted as largefiles:
63 63 specifically, any file that matches any PATTERN *or* whose size is
64 64 above the minimum size threshold is converted as a largefile. The
65 65 size used to determine whether or not to track a file as a
66 66 largefile is the size of the first version of the file. The
67 67 minimum size can be specified either with --size or in
68 68 configuration as ``largefiles.size``.
69 69
70 70 After running this command you will need to make sure that
71 71 largefiles is enabled anywhere you intend to push the new
72 72 repository.
73 73
74 74 Use --to-normal to convert largefiles back to normal files; after
75 75 this, the DEST repository can be used without largefiles at all.'''
76 76
77 77 if opts['to_normal']:
78 78 tolfile = False
79 79 else:
80 80 tolfile = True
81 81 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
82 82
83 83 if not hg.islocal(src):
84 84 raise error.Abort(_('%s is not a local Mercurial repo') % src)
85 85 if not hg.islocal(dest):
86 86 raise error.Abort(_('%s is not a local Mercurial repo') % dest)
87 87
88 88 rsrc = hg.repository(ui, src)
89 89 ui.status(_('initializing destination %s\n') % dest)
90 90 rdst = hg.repository(ui, dest, create=True)
91 91
92 92 success = False
93 93 dstwlock = dstlock = None
94 94 try:
95 95 # Get a list of all changesets in the source. The easy way to do this
96 96 # is to simply walk the changelog, using changelog.nodesbetween().
97 97 # Take a look at mercurial/revlog.py:639 for more details.
98 98 # Use a generator instead of a list to decrease memory usage
99 99 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
100 100 rsrc.heads())[0])
101 101 revmap = {node.nullid: node.nullid}
102 102 if tolfile:
103 103 # Lock destination to prevent modification while it is converted to.
104 104 # Don't need to lock src because we are just reading from its
105 105 # history which can't change.
106 106 dstwlock = rdst.wlock()
107 107 dstlock = rdst.lock()
108 108
109 109 lfiles = set()
110 110 normalfiles = set()
111 111 if not pats:
112 112 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
113 113 if pats:
114 114 matcher = matchmod.match(rsrc.root, '', list(pats))
115 115 else:
116 116 matcher = None
117 117
118 118 lfiletohash = {}
119 119 for ctx in ctxs:
120 120 ui.progress(_('converting revisions'), ctx.rev(),
121 121 unit=_('revisions'), total=rsrc['tip'].rev())
122 122 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
123 123 lfiles, normalfiles, matcher, size, lfiletohash)
124 124 ui.progress(_('converting revisions'), None)
125 125
126 126 if rdst.wvfs.exists(lfutil.shortname):
127 127 rdst.wvfs.rmtree(lfutil.shortname)
128 128
129 129 for f in lfiletohash.keys():
130 130 if rdst.wvfs.isfile(f):
131 131 rdst.wvfs.unlink(f)
132 132 try:
133 133 rdst.wvfs.removedirs(rdst.wvfs.dirname(f))
134 134 except OSError:
135 135 pass
136 136
137 137 # If there were any files converted to largefiles, add largefiles
138 138 # to the destination repository's requirements.
139 139 if lfiles:
140 140 rdst.requirements.add('largefiles')
141 141 rdst._writerequirements()
142 142 else:
143 143 class lfsource(filemap.filemap_source):
144 144 def __init__(self, ui, source):
145 145 super(lfsource, self).__init__(ui, source, None)
146 146 self.filemapper.rename[lfutil.shortname] = '.'
147 147
148 148 def getfile(self, name, rev):
149 149 realname, realrev = rev
150 150 f = super(lfsource, self).getfile(name, rev)
151 151
152 152 if (not realname.startswith(lfutil.shortnameslash)
153 153 or f[0] is None):
154 154 return f
155 155
156 156 # Substitute in the largefile data for the hash
157 157 hash = f[0].strip()
158 158 path = lfutil.findfile(rsrc, hash)
159 159
160 160 if path is None:
161 161 raise error.Abort(_("missing largefile for '%s' in %s")
162 162 % (realname, realrev))
163 163 return util.readfile(path), f[1]
164 164
165 165 class converter(convcmd.converter):
166 166 def __init__(self, ui, source, dest, revmapfile, opts):
167 167 src = lfsource(ui, source)
168 168
169 169 super(converter, self).__init__(ui, src, dest, revmapfile,
170 170 opts)
171 171
172 172 found, missing = downloadlfiles(ui, rsrc)
173 173 if missing != 0:
174 174 raise error.Abort(_("all largefiles must be present locally"))
175 175
176 176 orig = convcmd.converter
177 177 convcmd.converter = converter
178 178
179 179 try:
180 180 convcmd.convert(ui, src, dest)
181 181 finally:
182 182 convcmd.converter = orig
183 183 success = True
184 184 finally:
185 185 if tolfile:
186 186 rdst.dirstate.clear()
187 187 release(dstlock, dstwlock)
188 188 if not success:
189 189 # we failed, remove the new directory
190 190 shutil.rmtree(rdst.root)
191 191
192 192 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
193 193 matcher, size, lfiletohash):
194 194 # Convert src parents to dst parents
195 195 parents = _convertparents(ctx, revmap)
196 196
197 197 # Generate list of changed files
198 198 files = _getchangedfiles(ctx, parents)
199 199
200 200 dstfiles = []
201 201 for f in files:
202 202 if f not in lfiles and f not in normalfiles:
203 203 islfile = _islfile(f, ctx, matcher, size)
204 204 # If this file was renamed or copied then copy
205 205 # the largefile-ness of its predecessor
206 206 if f in ctx.manifest():
207 207 fctx = ctx.filectx(f)
208 208 renamed = fctx.renamed()
209 209 renamedlfile = renamed and renamed[0] in lfiles
210 210 islfile |= renamedlfile
211 211 if 'l' in fctx.flags():
212 212 if renamedlfile:
213 213 raise error.Abort(
214 214 _('renamed/copied largefile %s becomes symlink')
215 215 % f)
216 216 islfile = False
217 217 if islfile:
218 218 lfiles.add(f)
219 219 else:
220 220 normalfiles.add(f)
221 221
222 222 if f in lfiles:
223 223 fstandin = lfutil.standin(f)
224 224 dstfiles.append(fstandin)
225 225 # largefile in manifest if it has not been removed/renamed
226 226 if f in ctx.manifest():
227 227 fctx = ctx.filectx(f)
228 228 if 'l' in fctx.flags():
229 229 renamed = fctx.renamed()
230 230 if renamed and renamed[0] in lfiles:
231 231 raise error.Abort(_('largefile %s becomes symlink') % f)
232 232
233 233 # largefile was modified, update standins
234 234 m = hashlib.sha1('')
235 235 m.update(ctx[f].data())
236 236 hash = m.hexdigest()
237 237 if f not in lfiletohash or lfiletohash[f] != hash:
238 238 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
239 239 executable = 'x' in ctx[f].flags()
240 240 lfutil.writestandin(rdst, fstandin, hash,
241 241 executable)
242 242 lfiletohash[f] = hash
243 243 else:
244 244 # normal file
245 245 dstfiles.append(f)
246 246
247 247 def getfilectx(repo, memctx, f):
248 248 srcfname = lfutil.splitstandin(f)
249 249 if srcfname is not None:
250 250 # if the file isn't in the manifest then it was removed
251 251 # or renamed, return None to indicate this
252 252 try:
253 253 fctx = ctx.filectx(srcfname)
254 254 except error.LookupError:
255 255 return None
256 256 renamed = fctx.renamed()
257 257 if renamed:
258 258 # standin is always a largefile because largefile-ness
259 259 # doesn't change after rename or copy
260 260 renamed = lfutil.standin(renamed[0])
261 261
262 262 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
263 263 'l' in fctx.flags(), 'x' in fctx.flags(),
264 264 renamed)
265 265 else:
266 266 return _getnormalcontext(repo, ctx, f, revmap)
267 267
268 268 # Commit
269 269 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
270 270
271 271 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
272 272 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
273 273 getfilectx, ctx.user(), ctx.date(), ctx.extra())
274 274 ret = rdst.commitctx(mctx)
275 275 lfutil.copyalltostore(rdst, ret)
276 276 rdst.setparents(ret)
277 277 revmap[ctx.node()] = rdst.changelog.tip()
278 278
279 279 # Generate list of changed files
280 280 def _getchangedfiles(ctx, parents):
281 281 files = set(ctx.files())
282 282 if node.nullid not in parents:
283 283 mc = ctx.manifest()
284 284 mp1 = ctx.parents()[0].manifest()
285 285 mp2 = ctx.parents()[1].manifest()
286 286 files |= (set(mp1) | set(mp2)) - set(mc)
287 287 for f in mc:
288 288 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
289 289 files.add(f)
290 290 return files
291 291
292 292 # Convert src parents to dst parents
293 293 def _convertparents(ctx, revmap):
294 294 parents = []
295 295 for p in ctx.parents():
296 296 parents.append(revmap[p.node()])
297 297 while len(parents) < 2:
298 298 parents.append(node.nullid)
299 299 return parents
300 300
301 301 # Get memfilectx for a normal file
302 302 def _getnormalcontext(repo, ctx, f, revmap):
303 303 try:
304 304 fctx = ctx.filectx(f)
305 305 except error.LookupError:
306 306 return None
307 307 renamed = fctx.renamed()
308 308 if renamed:
309 309 renamed = renamed[0]
310 310
311 311 data = fctx.data()
312 312 if f == '.hgtags':
313 313 data = _converttags (repo.ui, revmap, data)
314 314 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
315 315 'x' in fctx.flags(), renamed)
316 316
317 317 # Remap tag data using a revision map
318 318 def _converttags(ui, revmap, data):
319 319 newdata = []
320 320 for line in data.splitlines():
321 321 try:
322 322 id, name = line.split(' ', 1)
323 323 except ValueError:
324 324 ui.warn(_('skipping incorrectly formatted tag %s\n')
325 325 % line)
326 326 continue
327 327 try:
328 328 newid = node.bin(id)
329 329 except TypeError:
330 330 ui.warn(_('skipping incorrectly formatted id %s\n')
331 331 % id)
332 332 continue
333 333 try:
334 334 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
335 335 name))
336 336 except KeyError:
337 337 ui.warn(_('no mapping for id %s\n') % id)
338 338 continue
339 339 return ''.join(newdata)
340 340
341 341 def _islfile(file, ctx, matcher, size):
342 342 '''Return true if file should be considered a largefile, i.e.
343 343 matcher matches it or it is larger than size.'''
344 344 # never store special .hg* files as largefiles
345 345 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
346 346 return False
347 347 if matcher and matcher(file):
348 348 return True
349 349 try:
350 350 return ctx.filectx(file).size() >= size * 1024 * 1024
351 351 except error.LookupError:
352 352 return False
353 353
354 354 def uploadlfiles(ui, rsrc, rdst, files):
355 355 '''upload largefiles to the central store'''
356 356
357 357 if not files:
358 358 return
359 359
360 360 store = storefactory.openstore(rsrc, rdst, put=True)
361 361
362 362 at = 0
363 363 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
364 364 retval = store.exists(files)
365 365 files = filter(lambda h: not retval[h], files)
366 366 ui.debug("%d largefiles need to be uploaded\n" % len(files))
367 367
368 368 for hash in files:
369 369 ui.progress(_('uploading largefiles'), at, unit=_('files'),
370 370 total=len(files))
371 371 source = lfutil.findfile(rsrc, hash)
372 372 if not source:
373 373 raise error.Abort(_('largefile %s missing from store'
374 374 ' (needs to be uploaded)') % hash)
375 375 # XXX check for errors here
376 376 store.put(source, hash)
377 377 at += 1
378 378 ui.progress(_('uploading largefiles'), None)
379 379
380 380 def verifylfiles(ui, repo, all=False, contents=False):
381 381 '''Verify that every largefile revision in the current changeset
382 382 exists in the central store. With --contents, also verify that
383 383 the contents of each local largefile file revision are correct (SHA-1 hash
384 384 matches the revision ID). With --all, check every changeset in
385 385 this repository.'''
386 386 if all:
387 387 revs = repo.revs('all()')
388 388 else:
389 389 revs = ['.']
390 390
391 391 store = storefactory.openstore(repo)
392 392 return store.verify(revs, contents=contents)
393 393
394 394 def cachelfiles(ui, repo, node, filelist=None):
395 395 '''cachelfiles ensures that all largefiles needed by the specified revision
396 396 are present in the repository's largefile cache.
397 397
398 398 returns a tuple (cached, missing). cached is the list of files downloaded
399 399 by this operation; missing is the list of files that were needed but could
400 400 not be found.'''
401 401 lfiles = lfutil.listlfiles(repo, node)
402 402 if filelist:
403 403 lfiles = set(lfiles) & set(filelist)
404 404 toget = []
405 405
406 ctx = repo[node]
406 407 for lfile in lfiles:
407 408 try:
408 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
409 expectedhash = ctx[lfutil.standin(lfile)].data().strip()
409 410 except IOError as err:
410 411 if err.errno == errno.ENOENT:
411 412 continue # node must be None and standin wasn't found in wctx
412 413 raise
413 414 if not lfutil.findfile(repo, expectedhash):
414 415 toget.append((lfile, expectedhash))
415 416
416 417 if toget:
417 418 store = storefactory.openstore(repo)
418 419 ret = store.get(toget)
419 420 return ret
420 421
421 422 return ([], [])
422 423
423 424 def downloadlfiles(ui, repo, rev=None):
424 425 matchfn = scmutil.match(repo[None],
425 426 [repo.wjoin(lfutil.shortname)], {})
426 427 def prepare(ctx, fns):
427 428 pass
428 429 totalsuccess = 0
429 430 totalmissing = 0
430 431 if rev != []: # walkchangerevs on empty list would return all revs
431 432 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
432 433 prepare):
433 434 success, missing = cachelfiles(ui, repo, ctx.node())
434 435 totalsuccess += len(success)
435 436 totalmissing += len(missing)
436 437 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
437 438 if totalmissing > 0:
438 439 ui.status(_("%d largefiles failed to download\n") % totalmissing)
439 440 return totalsuccess, totalmissing
440 441
441 442 def updatelfiles(ui, repo, filelist=None, printmessage=None,
442 443 normallookup=False):
443 444 '''Update largefiles according to standins in the working directory
444 445
445 446 If ``printmessage`` is other than ``None``, it means "print (or
446 447 ignore, for false) message forcibly".
447 448 '''
448 449 statuswriter = lfutil.getstatuswriter(ui, repo, printmessage)
449 450 with repo.wlock():
450 451 lfdirstate = lfutil.openlfdirstate(ui, repo)
451 452 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
452 453
453 454 if filelist is not None:
454 455 filelist = set(filelist)
455 456 lfiles = [f for f in lfiles if f in filelist]
456 457
457 458 update = {}
458 459 updated, removed = 0, 0
459 460 wvfs = repo.wvfs
461 wctx = repo[None]
460 462 for lfile in lfiles:
461 463 rellfile = lfile
462 464 rellfileorig = os.path.relpath(
463 465 scmutil.origpath(ui, repo, wvfs.join(rellfile)),
464 466 start=repo.root)
465 467 relstandin = lfutil.standin(lfile)
466 468 relstandinorig = os.path.relpath(
467 469 scmutil.origpath(ui, repo, wvfs.join(relstandin)),
468 470 start=repo.root)
469 471 if wvfs.exists(relstandin):
470 472 if (wvfs.exists(relstandinorig) and
471 473 wvfs.exists(rellfile)):
472 474 shutil.copyfile(wvfs.join(rellfile),
473 475 wvfs.join(rellfileorig))
474 476 wvfs.unlinkpath(relstandinorig)
475 477 expecthash = lfutil.readstandin(repo, lfile)
476 478 if expecthash != '':
477 if lfile not in repo[None]: # not switched to normal file
479 if lfile not in wctx: # not switched to normal file
478 480 wvfs.unlinkpath(rellfile, ignoremissing=True)
479 481 # use normallookup() to allocate an entry in largefiles
480 482 # dirstate to prevent lfilesrepo.status() from reporting
481 483 # missing files as removed.
482 484 lfdirstate.normallookup(lfile)
483 485 update[lfile] = expecthash
484 486 else:
485 487 # Remove lfiles for which the standin is deleted, unless the
486 488 # lfile is added to the repository again. This happens when a
487 489 # largefile is converted back to a normal file: the standin
488 490 # disappears, but a new (normal) file appears as the lfile.
489 491 if (wvfs.exists(rellfile) and
490 repo.dirstate.normalize(lfile) not in repo[None]):
492 repo.dirstate.normalize(lfile) not in wctx):
491 493 wvfs.unlinkpath(rellfile)
492 494 removed += 1
493 495
494 496 # largefile processing might be slow and be interrupted - be prepared
495 497 lfdirstate.write()
496 498
497 499 if lfiles:
498 500 statuswriter(_('getting changed largefiles\n'))
499 501 cachelfiles(ui, repo, None, lfiles)
500 502
501 503 for lfile in lfiles:
502 504 update1 = 0
503 505
504 506 expecthash = update.get(lfile)
505 507 if expecthash:
506 508 if not lfutil.copyfromcache(repo, expecthash, lfile):
507 509 # failed ... but already removed and set to normallookup
508 510 continue
509 511 # Synchronize largefile dirstate to the last modified
510 512 # time of the file
511 513 lfdirstate.normal(lfile)
512 514 update1 = 1
513 515
514 516 # copy the exec mode of largefile standin from the repository's
515 517 # dirstate to its state in the lfdirstate.
516 518 rellfile = lfile
517 519 relstandin = lfutil.standin(lfile)
518 520 if wvfs.exists(relstandin):
519 521 # exec is decided by the users permissions using mask 0o100
520 522 standinexec = wvfs.stat(relstandin).st_mode & 0o100
521 523 st = wvfs.stat(rellfile)
522 524 mode = st.st_mode
523 525 if standinexec != mode & 0o100:
524 526 # first remove all X bits, then shift all R bits to X
525 527 mode &= ~0o111
526 528 if standinexec:
527 529 mode |= (mode >> 2) & 0o111 & ~util.umask
528 530 wvfs.chmod(rellfile, mode)
529 531 update1 = 1
530 532
531 533 updated += update1
532 534
533 535 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
534 536
535 537 lfdirstate.write()
536 538 if lfiles:
537 539 statuswriter(_('%d largefiles updated, %d removed\n') % (updated,
538 540 removed))
539 541
540 542 @command('lfpull',
541 543 [('r', 'rev', [], _('pull largefiles for these revisions'))
542 544 ] + commands.remoteopts,
543 545 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
544 546 def lfpull(ui, repo, source="default", **opts):
545 547 """pull largefiles for the specified revisions from the specified source
546 548
547 549 Pull largefiles that are referenced from local changesets but missing
548 550 locally, pulling from a remote repository to the local cache.
549 551
550 552 If SOURCE is omitted, the 'default' path will be used.
551 553 See :hg:`help urls` for more information.
552 554
553 555 .. container:: verbose
554 556
555 557 Some examples:
556 558
557 559 - pull largefiles for all branch heads::
558 560
559 561 hg lfpull -r "head() and not closed()"
560 562
561 563 - pull largefiles on the default branch::
562 564
563 565 hg lfpull -r "branch(default)"
564 566 """
565 567 repo.lfpullsource = source
566 568
567 569 revs = opts.get('rev', [])
568 570 if not revs:
569 571 raise error.Abort(_('no revisions specified'))
570 572 revs = scmutil.revrange(repo, revs)
571 573
572 574 numcached = 0
573 575 for rev in revs:
574 576 ui.note(_('pulling largefiles for revision %s\n') % rev)
575 577 (cached, missing) = cachelfiles(ui, repo, rev)
576 578 numcached += len(cached)
577 579 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,1458 +1,1460
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 from __future__ import absolute_import
11 11
12 12 import copy
13 13 import os
14 14
15 15 from mercurial.i18n import _
16 16
17 17 from mercurial import (
18 18 archival,
19 19 cmdutil,
20 20 error,
21 21 hg,
22 22 match as matchmod,
23 23 pathutil,
24 24 registrar,
25 25 scmutil,
26 26 smartset,
27 27 util,
28 28 )
29 29
30 30 from . import (
31 31 lfcommands,
32 32 lfutil,
33 33 storefactory,
34 34 )
35 35
36 36 # -- Utility functions: commonly/repeatedly needed functionality ---------------
37 37
38 38 def composelargefilematcher(match, manifest):
39 39 '''create a matcher that matches only the largefiles in the original
40 40 matcher'''
41 41 m = copy.copy(match)
42 42 lfile = lambda f: lfutil.standin(f) in manifest
43 43 m._files = filter(lfile, m._files)
44 44 m._fileroots = set(m._files)
45 45 m._always = False
46 46 origmatchfn = m.matchfn
47 47 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
48 48 return m
49 49
50 50 def composenormalfilematcher(match, manifest, exclude=None):
51 51 excluded = set()
52 52 if exclude is not None:
53 53 excluded.update(exclude)
54 54
55 55 m = copy.copy(match)
56 56 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
57 57 manifest or f in excluded)
58 58 m._files = filter(notlfile, m._files)
59 59 m._fileroots = set(m._files)
60 60 m._always = False
61 61 origmatchfn = m.matchfn
62 62 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
63 63 return m
64 64
65 65 def installnormalfilesmatchfn(manifest):
66 66 '''installmatchfn with a matchfn that ignores all largefiles'''
67 67 def overridematch(ctx, pats=(), opts=None, globbed=False,
68 68 default='relpath', badfn=None):
69 69 if opts is None:
70 70 opts = {}
71 71 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
72 72 return composenormalfilematcher(match, manifest)
73 73 oldmatch = installmatchfn(overridematch)
74 74
75 75 def installmatchfn(f):
76 76 '''monkey patch the scmutil module with a custom match function.
77 77 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
78 78 oldmatch = scmutil.match
79 79 setattr(f, 'oldmatch', oldmatch)
80 80 scmutil.match = f
81 81 return oldmatch
82 82
83 83 def restorematchfn():
84 84 '''restores scmutil.match to what it was before installmatchfn
85 85 was called. no-op if scmutil.match is its original function.
86 86
87 87 Note that n calls to installmatchfn will require n calls to
88 88 restore the original matchfn.'''
89 89 scmutil.match = getattr(scmutil.match, 'oldmatch')
90 90
91 91 def installmatchandpatsfn(f):
92 92 oldmatchandpats = scmutil.matchandpats
93 93 setattr(f, 'oldmatchandpats', oldmatchandpats)
94 94 scmutil.matchandpats = f
95 95 return oldmatchandpats
96 96
97 97 def restorematchandpatsfn():
98 98 '''restores scmutil.matchandpats to what it was before
99 99 installmatchandpatsfn was called. No-op if scmutil.matchandpats
100 100 is its original function.
101 101
102 102 Note that n calls to installmatchandpatsfn will require n calls
103 103 to restore the original matchfn.'''
104 104 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
105 105 scmutil.matchandpats)
106 106
107 107 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
108 108 large = opts.get('large')
109 109 lfsize = lfutil.getminsize(
110 110 ui, lfutil.islfilesrepo(repo), opts.get('lfsize'))
111 111
112 112 lfmatcher = None
113 113 if lfutil.islfilesrepo(repo):
114 114 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
115 115 if lfpats:
116 116 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
117 117
118 118 lfnames = []
119 119 m = matcher
120 120
121 121 wctx = repo[None]
122 122 for f in repo.walk(matchmod.badmatch(m, lambda x, y: None)):
123 123 exact = m.exact(f)
124 124 lfile = lfutil.standin(f) in wctx
125 125 nfile = f in wctx
126 126 exists = lfile or nfile
127 127
128 128 # addremove in core gets fancy with the name, add doesn't
129 129 if isaddremove:
130 130 name = m.uipath(f)
131 131 else:
132 132 name = m.rel(f)
133 133
134 134 # Don't warn the user when they attempt to add a normal tracked file.
135 135 # The normal add code will do that for us.
136 136 if exact and exists:
137 137 if lfile:
138 138 ui.warn(_('%s already a largefile\n') % name)
139 139 continue
140 140
141 141 if (exact or not exists) and not lfutil.isstandin(f):
142 142 # In case the file was removed previously, but not committed
143 143 # (issue3507)
144 144 if not repo.wvfs.exists(f):
145 145 continue
146 146
147 147 abovemin = (lfsize and
148 148 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
149 149 if large or abovemin or (lfmatcher and lfmatcher(f)):
150 150 lfnames.append(f)
151 151 if ui.verbose or not exact:
152 152 ui.status(_('adding %s as a largefile\n') % name)
153 153
154 154 bad = []
155 155
156 156 # Need to lock, otherwise there could be a race condition between
157 157 # when standins are created and added to the repo.
158 158 with repo.wlock():
159 159 if not opts.get('dry_run'):
160 160 standins = []
161 161 lfdirstate = lfutil.openlfdirstate(ui, repo)
162 162 for f in lfnames:
163 163 standinname = lfutil.standin(f)
164 164 lfutil.writestandin(repo, standinname, hash='',
165 165 executable=lfutil.getexecutable(repo.wjoin(f)))
166 166 standins.append(standinname)
167 167 if lfdirstate[f] == 'r':
168 168 lfdirstate.normallookup(f)
169 169 else:
170 170 lfdirstate.add(f)
171 171 lfdirstate.write()
172 172 bad += [lfutil.splitstandin(f)
173 173 for f in repo[None].add(standins)
174 174 if f in m.files()]
175 175
176 176 added = [f for f in lfnames if f not in bad]
177 177 return added, bad
178 178
179 179 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
180 180 after = opts.get('after')
181 181 m = composelargefilematcher(matcher, repo[None].manifest())
182 182 try:
183 183 repo.lfstatus = True
184 184 s = repo.status(match=m, clean=not isaddremove)
185 185 finally:
186 186 repo.lfstatus = False
187 187 manifest = repo[None].manifest()
188 188 modified, added, deleted, clean = [[f for f in list
189 189 if lfutil.standin(f) in manifest]
190 190 for list in (s.modified, s.added,
191 191 s.deleted, s.clean)]
192 192
193 193 def warn(files, msg):
194 194 for f in files:
195 195 ui.warn(msg % m.rel(f))
196 196 return int(len(files) > 0)
197 197
198 198 result = 0
199 199
200 200 if after:
201 201 remove = deleted
202 202 result = warn(modified + added + clean,
203 203 _('not removing %s: file still exists\n'))
204 204 else:
205 205 remove = deleted + clean
206 206 result = warn(modified, _('not removing %s: file is modified (use -f'
207 207 ' to force removal)\n'))
208 208 result = warn(added, _('not removing %s: file has been marked for add'
209 209 ' (use forget to undo)\n')) or result
210 210
211 211 # Need to lock because standin files are deleted then removed from the
212 212 # repository and we could race in-between.
213 213 with repo.wlock():
214 214 lfdirstate = lfutil.openlfdirstate(ui, repo)
215 215 for f in sorted(remove):
216 216 if ui.verbose or not m.exact(f):
217 217 # addremove in core gets fancy with the name, remove doesn't
218 218 if isaddremove:
219 219 name = m.uipath(f)
220 220 else:
221 221 name = m.rel(f)
222 222 ui.status(_('removing %s\n') % name)
223 223
224 224 if not opts.get('dry_run'):
225 225 if not after:
226 226 repo.wvfs.unlinkpath(f, ignoremissing=True)
227 227
228 228 if opts.get('dry_run'):
229 229 return result
230 230
231 231 remove = [lfutil.standin(f) for f in remove]
232 232 # If this is being called by addremove, let the original addremove
233 233 # function handle this.
234 234 if not isaddremove:
235 235 for f in remove:
236 236 repo.wvfs.unlinkpath(f, ignoremissing=True)
237 237 repo[None].forget(remove)
238 238
239 239 for f in remove:
240 240 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
241 241 False)
242 242
243 243 lfdirstate.write()
244 244
245 245 return result
246 246
247 247 # For overriding mercurial.hgweb.webcommands so that largefiles will
248 248 # appear at their right place in the manifests.
249 249 def decodepath(orig, path):
250 250 return lfutil.splitstandin(path) or path
251 251
252 252 # -- Wrappers: modify existing commands --------------------------------
253 253
254 254 def overrideadd(orig, ui, repo, *pats, **opts):
255 255 if opts.get('normal') and opts.get('large'):
256 256 raise error.Abort(_('--normal cannot be used with --large'))
257 257 return orig(ui, repo, *pats, **opts)
258 258
259 259 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
260 260 # The --normal flag short circuits this override
261 261 if opts.get('normal'):
262 262 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
263 263
264 264 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
265 265 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
266 266 ladded)
267 267 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
268 268
269 269 bad.extend(f for f in lbad)
270 270 return bad
271 271
272 272 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
273 273 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
274 274 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
275 275 return removelargefiles(ui, repo, False, matcher, after=after,
276 276 force=force) or result
277 277
278 278 def overridestatusfn(orig, repo, rev2, **opts):
279 279 try:
280 280 repo._repo.lfstatus = True
281 281 return orig(repo, rev2, **opts)
282 282 finally:
283 283 repo._repo.lfstatus = False
284 284
285 285 def overridestatus(orig, ui, repo, *pats, **opts):
286 286 try:
287 287 repo.lfstatus = True
288 288 return orig(ui, repo, *pats, **opts)
289 289 finally:
290 290 repo.lfstatus = False
291 291
292 292 def overridedirty(orig, repo, ignoreupdate=False):
293 293 try:
294 294 repo._repo.lfstatus = True
295 295 return orig(repo, ignoreupdate)
296 296 finally:
297 297 repo._repo.lfstatus = False
298 298
299 299 def overridelog(orig, ui, repo, *pats, **opts):
300 300 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
301 301 default='relpath', badfn=None):
302 302 """Matcher that merges root directory with .hglf, suitable for log.
303 303 It is still possible to match .hglf directly.
304 304 For any listed files run log on the standin too.
305 305 matchfn tries both the given filename and with .hglf stripped.
306 306 """
307 307 if opts is None:
308 308 opts = {}
309 309 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
310 310 badfn=badfn)
311 311 m, p = copy.copy(matchandpats)
312 312
313 313 if m.always():
314 314 # We want to match everything anyway, so there's no benefit trying
315 315 # to add standins.
316 316 return matchandpats
317 317
318 318 pats = set(p)
319 319
320 320 def fixpats(pat, tostandin=lfutil.standin):
321 321 if pat.startswith('set:'):
322 322 return pat
323 323
324 324 kindpat = matchmod._patsplit(pat, None)
325 325
326 326 if kindpat[0] is not None:
327 327 return kindpat[0] + ':' + tostandin(kindpat[1])
328 328 return tostandin(kindpat[1])
329 329
330 330 if m._cwd:
331 331 hglf = lfutil.shortname
332 332 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
333 333
334 334 def tostandin(f):
335 335 # The file may already be a standin, so truncate the back
336 336 # prefix and test before mangling it. This avoids turning
337 337 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
338 338 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
339 339 return f
340 340
341 341 # An absolute path is from outside the repo, so truncate the
342 342 # path to the root before building the standin. Otherwise cwd
343 343 # is somewhere in the repo, relative to root, and needs to be
344 344 # prepended before building the standin.
345 345 if os.path.isabs(m._cwd):
346 346 f = f[len(back):]
347 347 else:
348 348 f = m._cwd + '/' + f
349 349 return back + lfutil.standin(f)
350 350
351 351 pats.update(fixpats(f, tostandin) for f in p)
352 352 else:
353 353 def tostandin(f):
354 354 if lfutil.isstandin(f):
355 355 return f
356 356 return lfutil.standin(f)
357 357 pats.update(fixpats(f, tostandin) for f in p)
358 358
359 359 for i in range(0, len(m._files)):
360 360 # Don't add '.hglf' to m.files, since that is already covered by '.'
361 361 if m._files[i] == '.':
362 362 continue
363 363 standin = lfutil.standin(m._files[i])
364 364 # If the "standin" is a directory, append instead of replace to
365 365 # support naming a directory on the command line with only
366 366 # largefiles. The original directory is kept to support normal
367 367 # files.
368 368 if standin in repo[ctx.node()]:
369 369 m._files[i] = standin
370 370 elif m._files[i] not in repo[ctx.node()] \
371 371 and repo.wvfs.isdir(standin):
372 372 m._files.append(standin)
373 373
374 374 m._fileroots = set(m._files)
375 375 m._always = False
376 376 origmatchfn = m.matchfn
377 377 def lfmatchfn(f):
378 378 lf = lfutil.splitstandin(f)
379 379 if lf is not None and origmatchfn(lf):
380 380 return True
381 381 r = origmatchfn(f)
382 382 return r
383 383 m.matchfn = lfmatchfn
384 384
385 385 ui.debug('updated patterns: %s\n' % sorted(pats))
386 386 return m, pats
387 387
388 388 # For hg log --patch, the match object is used in two different senses:
389 389 # (1) to determine what revisions should be printed out, and
390 390 # (2) to determine what files to print out diffs for.
391 391 # The magic matchandpats override should be used for case (1) but not for
392 392 # case (2).
393 393 def overridemakelogfilematcher(repo, pats, opts, badfn=None):
394 394 wctx = repo[None]
395 395 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
396 396 return lambda rev: match
397 397
398 398 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
399 399 oldmakelogfilematcher = cmdutil._makenofollowlogfilematcher
400 400 setattr(cmdutil, '_makenofollowlogfilematcher', overridemakelogfilematcher)
401 401
402 402 try:
403 403 return orig(ui, repo, *pats, **opts)
404 404 finally:
405 405 restorematchandpatsfn()
406 406 setattr(cmdutil, '_makenofollowlogfilematcher', oldmakelogfilematcher)
407 407
408 408 def overrideverify(orig, ui, repo, *pats, **opts):
409 409 large = opts.pop('large', False)
410 410 all = opts.pop('lfa', False)
411 411 contents = opts.pop('lfc', False)
412 412
413 413 result = orig(ui, repo, *pats, **opts)
414 414 if large or all or contents:
415 415 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
416 416 return result
417 417
418 418 def overridedebugstate(orig, ui, repo, *pats, **opts):
419 419 large = opts.pop('large', False)
420 420 if large:
421 421 class fakerepo(object):
422 422 dirstate = lfutil.openlfdirstate(ui, repo)
423 423 orig(ui, fakerepo, *pats, **opts)
424 424 else:
425 425 orig(ui, repo, *pats, **opts)
426 426
427 427 # Before starting the manifest merge, merge.updates will call
428 428 # _checkunknownfile to check if there are any files in the merged-in
429 429 # changeset that collide with unknown files in the working copy.
430 430 #
431 431 # The largefiles are seen as unknown, so this prevents us from merging
432 432 # in a file 'foo' if we already have a largefile with the same name.
433 433 #
434 434 # The overridden function filters the unknown files by removing any
435 435 # largefiles. This makes the merge proceed and we can then handle this
436 436 # case further in the overridden calculateupdates function below.
437 437 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
438 438 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
439 439 return False
440 440 return origfn(repo, wctx, mctx, f, f2)
441 441
442 442 # The manifest merge handles conflicts on the manifest level. We want
443 443 # to handle changes in largefile-ness of files at this level too.
444 444 #
445 445 # The strategy is to run the original calculateupdates and then process
446 446 # the action list it outputs. There are two cases we need to deal with:
447 447 #
448 448 # 1. Normal file in p1, largefile in p2. Here the largefile is
449 449 # detected via its standin file, which will enter the working copy
450 450 # with a "get" action. It is not "merge" since the standin is all
451 451 # Mercurial is concerned with at this level -- the link to the
452 452 # existing normal file is not relevant here.
453 453 #
454 454 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
455 455 # since the largefile will be present in the working copy and
456 456 # different from the normal file in p2. Mercurial therefore
457 457 # triggers a merge action.
458 458 #
459 459 # In both cases, we prompt the user and emit new actions to either
460 460 # remove the standin (if the normal file was kept) or to remove the
461 461 # normal file and get the standin (if the largefile was kept). The
462 462 # default prompt answer is to use the largefile version since it was
463 463 # presumably changed on purpose.
464 464 #
465 465 # Finally, the merge.applyupdates function will then take care of
466 466 # writing the files into the working copy and lfcommands.updatelfiles
467 467 # will update the largefiles.
468 468 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
469 469 acceptremote, *args, **kwargs):
470 470 overwrite = force and not branchmerge
471 471 actions, diverge, renamedelete = origfn(
472 472 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
473 473
474 474 if overwrite:
475 475 return actions, diverge, renamedelete
476 476
477 477 # Convert to dictionary with filename as key and action as value.
478 478 lfiles = set()
479 479 for f in actions:
480 480 splitstandin = lfutil.splitstandin(f)
481 481 if splitstandin in p1:
482 482 lfiles.add(splitstandin)
483 483 elif lfutil.standin(f) in p1:
484 484 lfiles.add(f)
485 485
486 486 for lfile in sorted(lfiles):
487 487 standin = lfutil.standin(lfile)
488 488 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
489 489 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
490 490 if sm in ('g', 'dc') and lm != 'r':
491 491 if sm == 'dc':
492 492 f1, f2, fa, move, anc = sargs
493 493 sargs = (p2[f2].flags(), False)
494 494 # Case 1: normal file in the working copy, largefile in
495 495 # the second parent
496 496 usermsg = _('remote turned local normal file %s into a largefile\n'
497 497 'use (l)argefile or keep (n)ormal file?'
498 498 '$$ &Largefile $$ &Normal file') % lfile
499 499 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
500 500 actions[lfile] = ('r', None, 'replaced by standin')
501 501 actions[standin] = ('g', sargs, 'replaces standin')
502 502 else: # keep local normal file
503 503 actions[lfile] = ('k', None, 'replaces standin')
504 504 if branchmerge:
505 505 actions[standin] = ('k', None, 'replaced by non-standin')
506 506 else:
507 507 actions[standin] = ('r', None, 'replaced by non-standin')
508 508 elif lm in ('g', 'dc') and sm != 'r':
509 509 if lm == 'dc':
510 510 f1, f2, fa, move, anc = largs
511 511 largs = (p2[f2].flags(), False)
512 512 # Case 2: largefile in the working copy, normal file in
513 513 # the second parent
514 514 usermsg = _('remote turned local largefile %s into a normal file\n'
515 515 'keep (l)argefile or use (n)ormal file?'
516 516 '$$ &Largefile $$ &Normal file') % lfile
517 517 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
518 518 if branchmerge:
519 519 # largefile can be restored from standin safely
520 520 actions[lfile] = ('k', None, 'replaced by standin')
521 521 actions[standin] = ('k', None, 'replaces standin')
522 522 else:
523 523 # "lfile" should be marked as "removed" without
524 524 # removal of itself
525 525 actions[lfile] = ('lfmr', None,
526 526 'forget non-standin largefile')
527 527
528 528 # linear-merge should treat this largefile as 're-added'
529 529 actions[standin] = ('a', None, 'keep standin')
530 530 else: # pick remote normal file
531 531 actions[lfile] = ('g', largs, 'replaces standin')
532 532 actions[standin] = ('r', None, 'replaced by non-standin')
533 533
534 534 return actions, diverge, renamedelete
535 535
536 536 def mergerecordupdates(orig, repo, actions, branchmerge):
537 537 if 'lfmr' in actions:
538 538 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
539 539 for lfile, args, msg in actions['lfmr']:
540 540 # this should be executed before 'orig', to execute 'remove'
541 541 # before all other actions
542 542 repo.dirstate.remove(lfile)
543 543 # make sure lfile doesn't get synclfdirstate'd as normal
544 544 lfdirstate.add(lfile)
545 545 lfdirstate.write()
546 546
547 547 return orig(repo, actions, branchmerge)
548 548
549 549 # Override filemerge to prompt the user about how they wish to merge
550 550 # largefiles. This will handle identical edits without prompting the user.
551 551 def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
552 552 labels=None):
553 553 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
554 554 return origfn(premerge, repo, mynode, orig, fcd, fco, fca,
555 555 labels=labels)
556 556
557 557 ahash = fca.data().strip().lower()
558 558 dhash = fcd.data().strip().lower()
559 559 ohash = fco.data().strip().lower()
560 560 if (ohash != ahash and
561 561 ohash != dhash and
562 562 (dhash == ahash or
563 563 repo.ui.promptchoice(
564 564 _('largefile %s has a merge conflict\nancestor was %s\n'
565 565 'keep (l)ocal %s or\ntake (o)ther %s?'
566 566 '$$ &Local $$ &Other') %
567 567 (lfutil.splitstandin(orig), ahash, dhash, ohash),
568 568 0) == 1)):
569 569 repo.wwrite(fcd.path(), fco.data(), fco.flags())
570 570 return True, 0, False
571 571
572 572 def copiespathcopies(orig, ctx1, ctx2, match=None):
573 573 copies = orig(ctx1, ctx2, match=match)
574 574 updated = {}
575 575
576 576 for k, v in copies.iteritems():
577 577 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
578 578
579 579 return updated
580 580
581 581 # Copy first changes the matchers to match standins instead of
582 582 # largefiles. Then it overrides util.copyfile in that function it
583 583 # checks if the destination largefile already exists. It also keeps a
584 584 # list of copied files so that the largefiles can be copied and the
585 585 # dirstate updated.
586 586 def overridecopy(orig, ui, repo, pats, opts, rename=False):
587 587 # doesn't remove largefile on rename
588 588 if len(pats) < 2:
589 589 # this isn't legal, let the original function deal with it
590 590 return orig(ui, repo, pats, opts, rename)
591 591
592 592 # This could copy both lfiles and normal files in one command,
593 593 # but we don't want to do that. First replace their matcher to
594 594 # only match normal files and run it, then replace it to just
595 595 # match largefiles and run it again.
596 596 nonormalfiles = False
597 597 nolfiles = False
598 598 installnormalfilesmatchfn(repo[None].manifest())
599 599 try:
600 600 result = orig(ui, repo, pats, opts, rename)
601 601 except error.Abort as e:
602 602 if str(e) != _('no files to copy'):
603 603 raise e
604 604 else:
605 605 nonormalfiles = True
606 606 result = 0
607 607 finally:
608 608 restorematchfn()
609 609
610 610 # The first rename can cause our current working directory to be removed.
611 611 # In that case there is nothing left to copy/rename so just quit.
612 612 try:
613 613 repo.getcwd()
614 614 except OSError:
615 615 return result
616 616
617 617 def makestandin(relpath):
618 618 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
619 619 return repo.wvfs.join(lfutil.standin(path))
620 620
621 621 fullpats = scmutil.expandpats(pats)
622 622 dest = fullpats[-1]
623 623
624 624 if os.path.isdir(dest):
625 625 if not os.path.isdir(makestandin(dest)):
626 626 os.makedirs(makestandin(dest))
627 627
628 628 try:
629 629 # When we call orig below it creates the standins but we don't add
630 630 # them to the dir state until later so lock during that time.
631 631 wlock = repo.wlock()
632 632
633 633 manifest = repo[None].manifest()
634 634 def overridematch(ctx, pats=(), opts=None, globbed=False,
635 635 default='relpath', badfn=None):
636 636 if opts is None:
637 637 opts = {}
638 638 newpats = []
639 639 # The patterns were previously mangled to add the standin
640 640 # directory; we need to remove that now
641 641 for pat in pats:
642 642 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
643 643 newpats.append(pat.replace(lfutil.shortname, ''))
644 644 else:
645 645 newpats.append(pat)
646 646 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
647 647 m = copy.copy(match)
648 648 lfile = lambda f: lfutil.standin(f) in manifest
649 649 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
650 650 m._fileroots = set(m._files)
651 651 origmatchfn = m.matchfn
652 652 def matchfn(f):
653 653 lfile = lfutil.splitstandin(f)
654 654 return (lfile is not None and
655 655 (f in manifest) and
656 656 origmatchfn(lfile) or
657 657 None)
658 658 m.matchfn = matchfn
659 659 return m
660 660 oldmatch = installmatchfn(overridematch)
661 661 listpats = []
662 662 for pat in pats:
663 663 if matchmod.patkind(pat) is not None:
664 664 listpats.append(pat)
665 665 else:
666 666 listpats.append(makestandin(pat))
667 667
668 668 try:
669 669 origcopyfile = util.copyfile
670 670 copiedfiles = []
671 671 def overridecopyfile(src, dest):
672 672 if (lfutil.shortname in src and
673 673 dest.startswith(repo.wjoin(lfutil.shortname))):
674 674 destlfile = dest.replace(lfutil.shortname, '')
675 675 if not opts['force'] and os.path.exists(destlfile):
676 676 raise IOError('',
677 677 _('destination largefile already exists'))
678 678 copiedfiles.append((src, dest))
679 679 origcopyfile(src, dest)
680 680
681 681 util.copyfile = overridecopyfile
682 682 result += orig(ui, repo, listpats, opts, rename)
683 683 finally:
684 684 util.copyfile = origcopyfile
685 685
686 686 lfdirstate = lfutil.openlfdirstate(ui, repo)
687 687 for (src, dest) in copiedfiles:
688 688 if (lfutil.shortname in src and
689 689 dest.startswith(repo.wjoin(lfutil.shortname))):
690 690 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
691 691 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
692 692 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
693 693 if not os.path.isdir(destlfiledir):
694 694 os.makedirs(destlfiledir)
695 695 if rename:
696 696 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
697 697
698 698 # The file is gone, but this deletes any empty parent
699 699 # directories as a side-effect.
700 700 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
701 701 lfdirstate.remove(srclfile)
702 702 else:
703 703 util.copyfile(repo.wjoin(srclfile),
704 704 repo.wjoin(destlfile))
705 705
706 706 lfdirstate.add(destlfile)
707 707 lfdirstate.write()
708 708 except error.Abort as e:
709 709 if str(e) != _('no files to copy'):
710 710 raise e
711 711 else:
712 712 nolfiles = True
713 713 finally:
714 714 restorematchfn()
715 715 wlock.release()
716 716
717 717 if nolfiles and nonormalfiles:
718 718 raise error.Abort(_('no files to copy'))
719 719
720 720 return result
721 721
722 722 # When the user calls revert, we have to be careful to not revert any
723 723 # changes to other largefiles accidentally. This means we have to keep
724 724 # track of the largefiles that are being reverted so we only pull down
725 725 # the necessary largefiles.
726 726 #
727 727 # Standins are only updated (to match the hash of largefiles) before
728 728 # commits. Update the standins then run the original revert, changing
729 729 # the matcher to hit standins instead of largefiles. Based on the
730 730 # resulting standins update the largefiles.
731 731 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
732 732 # Because we put the standins in a bad state (by updating them)
733 733 # and then return them to a correct state we need to lock to
734 734 # prevent others from changing them in their incorrect state.
735 735 with repo.wlock():
736 736 lfdirstate = lfutil.openlfdirstate(ui, repo)
737 737 s = lfutil.lfdirstatestatus(lfdirstate, repo)
738 738 lfdirstate.write()
739 739 for lfile in s.modified:
740 740 lfutil.updatestandin(repo, lfutil.standin(lfile))
741 741 for lfile in s.deleted:
742 742 fstandin = lfutil.standin(lfile)
743 743 if (repo.wvfs.exists(fstandin)):
744 744 repo.wvfs.unlink(fstandin)
745 745
746 746 oldstandins = lfutil.getstandinsstate(repo)
747 747
748 748 def overridematch(mctx, pats=(), opts=None, globbed=False,
749 749 default='relpath', badfn=None):
750 750 if opts is None:
751 751 opts = {}
752 752 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
753 753 m = copy.copy(match)
754 754
755 755 # revert supports recursing into subrepos, and though largefiles
756 756 # currently doesn't work correctly in that case, this match is
757 757 # called, so the lfdirstate above may not be the correct one for
758 758 # this invocation of match.
759 759 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
760 760 False)
761 761
762 wctx = repo[None]
762 763 def tostandin(f):
763 764 standin = lfutil.standin(f)
764 765 if standin in ctx or standin in mctx:
765 766 return standin
766 elif standin in repo[None] or lfdirstate[f] == 'r':
767 elif standin in wctx or lfdirstate[f] == 'r':
767 768 return None
768 769 return f
769 770 m._files = [tostandin(f) for f in m._files]
770 771 m._files = [f for f in m._files if f is not None]
771 772 m._fileroots = set(m._files)
772 773 origmatchfn = m.matchfn
773 774 def matchfn(f):
774 775 lfile = lfutil.splitstandin(f)
775 776 if lfile is not None:
776 777 return (origmatchfn(lfile) and
777 778 (f in ctx or f in mctx))
778 779 return origmatchfn(f)
779 780 m.matchfn = matchfn
780 781 return m
781 782 oldmatch = installmatchfn(overridematch)
782 783 try:
783 784 orig(ui, repo, ctx, parents, *pats, **opts)
784 785 finally:
785 786 restorematchfn()
786 787
787 788 newstandins = lfutil.getstandinsstate(repo)
788 789 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
789 790 # lfdirstate should be 'normallookup'-ed for updated files,
790 791 # because reverting doesn't touch dirstate for 'normal' files
791 792 # when target revision is explicitly specified: in such case,
792 793 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
793 794 # of target (standin) file.
794 795 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
795 796 normallookup=True)
796 797
797 798 # after pulling changesets, we need to take some extra care to get
798 799 # largefiles updated remotely
799 800 def overridepull(orig, ui, repo, source=None, **opts):
800 801 revsprepull = len(repo)
801 802 if not source:
802 803 source = 'default'
803 804 repo.lfpullsource = source
804 805 result = orig(ui, repo, source, **opts)
805 806 revspostpull = len(repo)
806 807 lfrevs = opts.get('lfrev', [])
807 808 if opts.get('all_largefiles'):
808 809 lfrevs.append('pulled()')
809 810 if lfrevs and revspostpull > revsprepull:
810 811 numcached = 0
811 812 repo.firstpulled = revsprepull # for pulled() revset expression
812 813 try:
813 814 for rev in scmutil.revrange(repo, lfrevs):
814 815 ui.note(_('pulling largefiles for revision %s\n') % rev)
815 816 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
816 817 numcached += len(cached)
817 818 finally:
818 819 del repo.firstpulled
819 820 ui.status(_("%d largefiles cached\n") % numcached)
820 821 return result
821 822
822 823 def overridepush(orig, ui, repo, *args, **kwargs):
823 824 """Override push command and store --lfrev parameters in opargs"""
824 825 lfrevs = kwargs.pop('lfrev', None)
825 826 if lfrevs:
826 827 opargs = kwargs.setdefault('opargs', {})
827 828 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
828 829 return orig(ui, repo, *args, **kwargs)
829 830
830 831 def exchangepushoperation(orig, *args, **kwargs):
831 832 """Override pushoperation constructor and store lfrevs parameter"""
832 833 lfrevs = kwargs.pop('lfrevs', None)
833 834 pushop = orig(*args, **kwargs)
834 835 pushop.lfrevs = lfrevs
835 836 return pushop
836 837
837 838 revsetpredicate = registrar.revsetpredicate()
838 839
839 840 @revsetpredicate('pulled()')
840 841 def pulledrevsetsymbol(repo, subset, x):
841 842 """Changesets that just has been pulled.
842 843
843 844 Only available with largefiles from pull --lfrev expressions.
844 845
845 846 .. container:: verbose
846 847
847 848 Some examples:
848 849
849 850 - pull largefiles for all new changesets::
850 851
851 852 hg pull -lfrev "pulled()"
852 853
853 854 - pull largefiles for all new branch heads::
854 855
855 856 hg pull -lfrev "head(pulled()) and not closed()"
856 857
857 858 """
858 859
859 860 try:
860 861 firstpulled = repo.firstpulled
861 862 except AttributeError:
862 863 raise error.Abort(_("pulled() only available in --lfrev"))
863 864 return smartset.baseset([r for r in subset if r >= firstpulled])
864 865
865 866 def overrideclone(orig, ui, source, dest=None, **opts):
866 867 d = dest
867 868 if d is None:
868 869 d = hg.defaultdest(source)
869 870 if opts.get('all_largefiles') and not hg.islocal(d):
870 871 raise error.Abort(_(
871 872 '--all-largefiles is incompatible with non-local destination %s') %
872 873 d)
873 874
874 875 return orig(ui, source, dest, **opts)
875 876
876 877 def hgclone(orig, ui, opts, *args, **kwargs):
877 878 result = orig(ui, opts, *args, **kwargs)
878 879
879 880 if result is not None:
880 881 sourcerepo, destrepo = result
881 882 repo = destrepo.local()
882 883
883 884 # When cloning to a remote repo (like through SSH), no repo is available
884 885 # from the peer. Therefore the largefiles can't be downloaded and the
885 886 # hgrc can't be updated.
886 887 if not repo:
887 888 return result
888 889
889 890 # If largefiles is required for this repo, permanently enable it locally
890 891 if 'largefiles' in repo.requirements:
891 892 with repo.vfs('hgrc', 'a', text=True) as fp:
892 893 fp.write('\n[extensions]\nlargefiles=\n')
893 894
894 895 # Caching is implicitly limited to 'rev' option, since the dest repo was
895 896 # truncated at that point. The user may expect a download count with
896 897 # this option, so attempt whether or not this is a largefile repo.
897 898 if opts.get('all_largefiles'):
898 899 success, missing = lfcommands.downloadlfiles(ui, repo, None)
899 900
900 901 if missing != 0:
901 902 return None
902 903
903 904 return result
904 905
905 906 def overriderebase(orig, ui, repo, **opts):
906 907 if not util.safehasattr(repo, '_largefilesenabled'):
907 908 return orig(ui, repo, **opts)
908 909
909 910 resuming = opts.get('continue')
910 911 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
911 912 repo._lfstatuswriters.append(lambda *msg, **opts: None)
912 913 try:
913 914 return orig(ui, repo, **opts)
914 915 finally:
915 916 repo._lfstatuswriters.pop()
916 917 repo._lfcommithooks.pop()
917 918
918 919 def overridearchivecmd(orig, ui, repo, dest, **opts):
919 920 repo.unfiltered().lfstatus = True
920 921
921 922 try:
922 923 return orig(ui, repo.unfiltered(), dest, **opts)
923 924 finally:
924 925 repo.unfiltered().lfstatus = False
925 926
926 927 def hgwebarchive(orig, web, req, tmpl):
927 928 web.repo.lfstatus = True
928 929
929 930 try:
930 931 return orig(web, req, tmpl)
931 932 finally:
932 933 web.repo.lfstatus = False
933 934
934 935 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
935 936 prefix='', mtime=None, subrepos=None):
936 937 # For some reason setting repo.lfstatus in hgwebarchive only changes the
937 938 # unfiltered repo's attr, so check that as well.
938 939 if not repo.lfstatus and not repo.unfiltered().lfstatus:
939 940 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
940 941 subrepos)
941 942
942 943 # No need to lock because we are only reading history and
943 944 # largefile caches, neither of which are modified.
944 945 if node is not None:
945 946 lfcommands.cachelfiles(repo.ui, repo, node)
946 947
947 948 if kind not in archival.archivers:
948 949 raise error.Abort(_("unknown archive type '%s'") % kind)
949 950
950 951 ctx = repo[node]
951 952
952 953 if kind == 'files':
953 954 if prefix:
954 955 raise error.Abort(
955 956 _('cannot give prefix when archiving to files'))
956 957 else:
957 958 prefix = archival.tidyprefix(dest, kind, prefix)
958 959
959 960 def write(name, mode, islink, getdata):
960 961 if matchfn and not matchfn(name):
961 962 return
962 963 data = getdata()
963 964 if decode:
964 965 data = repo.wwritedata(name, data)
965 966 archiver.addfile(prefix + name, mode, islink, data)
966 967
967 968 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
968 969
969 970 if repo.ui.configbool("ui", "archivemeta", True):
970 971 write('.hg_archival.txt', 0o644, False,
971 972 lambda: archival.buildmetadata(ctx))
972 973
973 974 for f in ctx:
974 975 ff = ctx.flags(f)
975 976 getdata = ctx[f].data
976 977 lfile = lfutil.splitstandin(f)
977 978 if lfile is not None:
978 979 if node is not None:
979 980 path = lfutil.findfile(repo, getdata().strip())
980 981
981 982 if path is None:
982 983 raise error.Abort(
983 984 _('largefile %s not found in repo store or system cache')
984 985 % lfile)
985 986 else:
986 987 path = lfile
987 988
988 989 f = lfile
989 990
990 991 getdata = lambda: util.readfile(path)
991 992 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
992 993
993 994 if subrepos:
994 995 for subpath in sorted(ctx.substate):
995 996 sub = ctx.workingsub(subpath)
996 997 submatch = matchmod.subdirmatcher(subpath, matchfn)
997 998 sub._repo.lfstatus = True
998 999 sub.archive(archiver, prefix, submatch)
999 1000
1000 1001 archiver.done()
1001 1002
1002 1003 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1003 1004 if not repo._repo.lfstatus:
1004 1005 return orig(repo, archiver, prefix, match, decode)
1005 1006
1006 1007 repo._get(repo._state + ('hg',))
1007 1008 rev = repo._state[1]
1008 1009 ctx = repo._repo[rev]
1009 1010
1010 1011 if ctx.node() is not None:
1011 1012 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1012 1013
1013 1014 def write(name, mode, islink, getdata):
1014 1015 # At this point, the standin has been replaced with the largefile name,
1015 1016 # so the normal matcher works here without the lfutil variants.
1016 1017 if match and not match(f):
1017 1018 return
1018 1019 data = getdata()
1019 1020 if decode:
1020 1021 data = repo._repo.wwritedata(name, data)
1021 1022
1022 1023 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1023 1024
1024 1025 for f in ctx:
1025 1026 ff = ctx.flags(f)
1026 1027 getdata = ctx[f].data
1027 1028 lfile = lfutil.splitstandin(f)
1028 1029 if lfile is not None:
1029 1030 if ctx.node() is not None:
1030 1031 path = lfutil.findfile(repo._repo, getdata().strip())
1031 1032
1032 1033 if path is None:
1033 1034 raise error.Abort(
1034 1035 _('largefile %s not found in repo store or system cache')
1035 1036 % lfile)
1036 1037 else:
1037 1038 path = lfile
1038 1039
1039 1040 f = lfile
1040 1041
1041 1042 getdata = lambda: util.readfile(os.path.join(prefix, path))
1042 1043
1043 1044 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1044 1045
1045 1046 for subpath in sorted(ctx.substate):
1046 1047 sub = ctx.workingsub(subpath)
1047 1048 submatch = matchmod.subdirmatcher(subpath, match)
1048 1049 sub._repo.lfstatus = True
1049 1050 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1050 1051
1051 1052 # If a largefile is modified, the change is not reflected in its
1052 1053 # standin until a commit. cmdutil.bailifchanged() raises an exception
1053 1054 # if the repo has uncommitted changes. Wrap it to also check if
1054 1055 # largefiles were changed. This is used by bisect, backout and fetch.
1055 1056 def overridebailifchanged(orig, repo, *args, **kwargs):
1056 1057 orig(repo, *args, **kwargs)
1057 1058 repo.lfstatus = True
1058 1059 s = repo.status()
1059 1060 repo.lfstatus = False
1060 1061 if s.modified or s.added or s.removed or s.deleted:
1061 1062 raise error.Abort(_('uncommitted changes'))
1062 1063
1063 1064 def postcommitstatus(orig, repo, *args, **kwargs):
1064 1065 repo.lfstatus = True
1065 1066 try:
1066 1067 return orig(repo, *args, **kwargs)
1067 1068 finally:
1068 1069 repo.lfstatus = False
1069 1070
1070 1071 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly):
1071 1072 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1072 1073 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly)
1073 1074 m = composelargefilematcher(match, repo[None].manifest())
1074 1075
1075 1076 try:
1076 1077 repo.lfstatus = True
1077 1078 s = repo.status(match=m, clean=True)
1078 1079 finally:
1079 1080 repo.lfstatus = False
1081 manifest = repo[None].manifest()
1080 1082 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1081 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
1083 forget = [f for f in forget if lfutil.standin(f) in manifest]
1082 1084
1083 1085 for f in forget:
1084 1086 fstandin = lfutil.standin(f)
1085 1087 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1086 1088 ui.warn(_('not removing %s: file is already untracked\n')
1087 1089 % m.rel(f))
1088 1090 bad.append(f)
1089 1091
1090 1092 for f in forget:
1091 1093 if ui.verbose or not m.exact(f):
1092 1094 ui.status(_('removing %s\n') % m.rel(f))
1093 1095
1094 1096 # Need to lock because standin files are deleted then removed from the
1095 1097 # repository and we could race in-between.
1096 1098 with repo.wlock():
1097 1099 lfdirstate = lfutil.openlfdirstate(ui, repo)
1098 1100 for f in forget:
1099 1101 if lfdirstate[f] == 'a':
1100 1102 lfdirstate.drop(f)
1101 1103 else:
1102 1104 lfdirstate.remove(f)
1103 1105 lfdirstate.write()
1104 1106 standins = [lfutil.standin(f) for f in forget]
1105 1107 for f in standins:
1106 1108 repo.wvfs.unlinkpath(f, ignoremissing=True)
1107 1109 rejected = repo[None].forget(standins)
1108 1110
1109 1111 bad.extend(f for f in rejected if f in m.files())
1110 1112 forgot.extend(f for f in forget if f not in rejected)
1111 1113 return bad, forgot
1112 1114
1113 1115 def _getoutgoings(repo, other, missing, addfunc):
1114 1116 """get pairs of filename and largefile hash in outgoing revisions
1115 1117 in 'missing'.
1116 1118
1117 1119 largefiles already existing on 'other' repository are ignored.
1118 1120
1119 1121 'addfunc' is invoked with each unique pairs of filename and
1120 1122 largefile hash value.
1121 1123 """
1122 1124 knowns = set()
1123 1125 lfhashes = set()
1124 1126 def dedup(fn, lfhash):
1125 1127 k = (fn, lfhash)
1126 1128 if k not in knowns:
1127 1129 knowns.add(k)
1128 1130 lfhashes.add(lfhash)
1129 1131 lfutil.getlfilestoupload(repo, missing, dedup)
1130 1132 if lfhashes:
1131 1133 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1132 1134 for fn, lfhash in knowns:
1133 1135 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1134 1136 addfunc(fn, lfhash)
1135 1137
1136 1138 def outgoinghook(ui, repo, other, opts, missing):
1137 1139 if opts.pop('large', None):
1138 1140 lfhashes = set()
1139 1141 if ui.debugflag:
1140 1142 toupload = {}
1141 1143 def addfunc(fn, lfhash):
1142 1144 if fn not in toupload:
1143 1145 toupload[fn] = []
1144 1146 toupload[fn].append(lfhash)
1145 1147 lfhashes.add(lfhash)
1146 1148 def showhashes(fn):
1147 1149 for lfhash in sorted(toupload[fn]):
1148 1150 ui.debug(' %s\n' % (lfhash))
1149 1151 else:
1150 1152 toupload = set()
1151 1153 def addfunc(fn, lfhash):
1152 1154 toupload.add(fn)
1153 1155 lfhashes.add(lfhash)
1154 1156 def showhashes(fn):
1155 1157 pass
1156 1158 _getoutgoings(repo, other, missing, addfunc)
1157 1159
1158 1160 if not toupload:
1159 1161 ui.status(_('largefiles: no files to upload\n'))
1160 1162 else:
1161 1163 ui.status(_('largefiles to upload (%d entities):\n')
1162 1164 % (len(lfhashes)))
1163 1165 for file in sorted(toupload):
1164 1166 ui.status(lfutil.splitstandin(file) + '\n')
1165 1167 showhashes(file)
1166 1168 ui.status('\n')
1167 1169
1168 1170 def summaryremotehook(ui, repo, opts, changes):
1169 1171 largeopt = opts.get('large', False)
1170 1172 if changes is None:
1171 1173 if largeopt:
1172 1174 return (False, True) # only outgoing check is needed
1173 1175 else:
1174 1176 return (False, False)
1175 1177 elif largeopt:
1176 1178 url, branch, peer, outgoing = changes[1]
1177 1179 if peer is None:
1178 1180 # i18n: column positioning for "hg summary"
1179 1181 ui.status(_('largefiles: (no remote repo)\n'))
1180 1182 return
1181 1183
1182 1184 toupload = set()
1183 1185 lfhashes = set()
1184 1186 def addfunc(fn, lfhash):
1185 1187 toupload.add(fn)
1186 1188 lfhashes.add(lfhash)
1187 1189 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1188 1190
1189 1191 if not toupload:
1190 1192 # i18n: column positioning for "hg summary"
1191 1193 ui.status(_('largefiles: (no files to upload)\n'))
1192 1194 else:
1193 1195 # i18n: column positioning for "hg summary"
1194 1196 ui.status(_('largefiles: %d entities for %d files to upload\n')
1195 1197 % (len(lfhashes), len(toupload)))
1196 1198
1197 1199 def overridesummary(orig, ui, repo, *pats, **opts):
1198 1200 try:
1199 1201 repo.lfstatus = True
1200 1202 orig(ui, repo, *pats, **opts)
1201 1203 finally:
1202 1204 repo.lfstatus = False
1203 1205
1204 1206 def scmutiladdremove(orig, repo, matcher, prefix, opts=None, dry_run=None,
1205 1207 similarity=None):
1206 1208 if opts is None:
1207 1209 opts = {}
1208 1210 if not lfutil.islfilesrepo(repo):
1209 1211 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1210 1212 # Get the list of missing largefiles so we can remove them
1211 1213 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1212 1214 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()), [],
1213 1215 False, False, False)
1214 1216
1215 1217 # Call into the normal remove code, but the removing of the standin, we want
1216 1218 # to have handled by original addremove. Monkey patching here makes sure
1217 1219 # we don't remove the standin in the largefiles code, preventing a very
1218 1220 # confused state later.
1219 1221 if s.deleted:
1220 1222 m = copy.copy(matcher)
1221 1223
1222 1224 # The m._files and m._map attributes are not changed to the deleted list
1223 1225 # because that affects the m.exact() test, which in turn governs whether
1224 1226 # or not the file name is printed, and how. Simply limit the original
1225 1227 # matches to those in the deleted status list.
1226 1228 matchfn = m.matchfn
1227 1229 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1228 1230
1229 1231 removelargefiles(repo.ui, repo, True, m, **opts)
1230 1232 # Call into the normal add code, and any files that *should* be added as
1231 1233 # largefiles will be
1232 1234 added, bad = addlargefiles(repo.ui, repo, True, matcher, **opts)
1233 1235 # Now that we've handled largefiles, hand off to the original addremove
1234 1236 # function to take care of the rest. Make sure it doesn't do anything with
1235 1237 # largefiles by passing a matcher that will ignore them.
1236 1238 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1237 1239 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1238 1240
1239 1241 # Calling purge with --all will cause the largefiles to be deleted.
1240 1242 # Override repo.status to prevent this from happening.
1241 1243 def overridepurge(orig, ui, repo, *dirs, **opts):
1242 1244 # XXX Monkey patching a repoview will not work. The assigned attribute will
1243 1245 # be set on the unfiltered repo, but we will only lookup attributes in the
1244 1246 # unfiltered repo if the lookup in the repoview object itself fails. As the
1245 1247 # monkey patched method exists on the repoview class the lookup will not
1246 1248 # fail. As a result, the original version will shadow the monkey patched
1247 1249 # one, defeating the monkey patch.
1248 1250 #
1249 1251 # As a work around we use an unfiltered repo here. We should do something
1250 1252 # cleaner instead.
1251 1253 repo = repo.unfiltered()
1252 1254 oldstatus = repo.status
1253 1255 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1254 1256 clean=False, unknown=False, listsubrepos=False):
1255 1257 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1256 1258 listsubrepos)
1257 1259 lfdirstate = lfutil.openlfdirstate(ui, repo)
1258 1260 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1259 1261 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1260 1262 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1261 1263 unknown, ignored, r.clean)
1262 1264 repo.status = overridestatus
1263 1265 orig(ui, repo, *dirs, **opts)
1264 1266 repo.status = oldstatus
1265 1267 def overriderollback(orig, ui, repo, **opts):
1266 1268 with repo.wlock():
1267 1269 before = repo.dirstate.parents()
1268 1270 orphans = set(f for f in repo.dirstate
1269 1271 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1270 1272 result = orig(ui, repo, **opts)
1271 1273 after = repo.dirstate.parents()
1272 1274 if before == after:
1273 1275 return result # no need to restore standins
1274 1276
1275 1277 pctx = repo['.']
1276 1278 for f in repo.dirstate:
1277 1279 if lfutil.isstandin(f):
1278 1280 orphans.discard(f)
1279 1281 if repo.dirstate[f] == 'r':
1280 1282 repo.wvfs.unlinkpath(f, ignoremissing=True)
1281 1283 elif f in pctx:
1282 1284 fctx = pctx[f]
1283 1285 repo.wwrite(f, fctx.data(), fctx.flags())
1284 1286 else:
1285 1287 # content of standin is not so important in 'a',
1286 1288 # 'm' or 'n' (coming from the 2nd parent) cases
1287 1289 lfutil.writestandin(repo, f, '', False)
1288 1290 for standin in orphans:
1289 1291 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1290 1292
1291 1293 lfdirstate = lfutil.openlfdirstate(ui, repo)
1292 1294 orphans = set(lfdirstate)
1293 1295 lfiles = lfutil.listlfiles(repo)
1294 1296 for file in lfiles:
1295 1297 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1296 1298 orphans.discard(file)
1297 1299 for lfile in orphans:
1298 1300 lfdirstate.drop(lfile)
1299 1301 lfdirstate.write()
1300 1302 return result
1301 1303
1302 1304 def overridetransplant(orig, ui, repo, *revs, **opts):
1303 1305 resuming = opts.get('continue')
1304 1306 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1305 1307 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1306 1308 try:
1307 1309 result = orig(ui, repo, *revs, **opts)
1308 1310 finally:
1309 1311 repo._lfstatuswriters.pop()
1310 1312 repo._lfcommithooks.pop()
1311 1313 return result
1312 1314
1313 1315 def overridecat(orig, ui, repo, file1, *pats, **opts):
1314 1316 ctx = scmutil.revsingle(repo, opts.get('rev'))
1315 1317 err = 1
1316 1318 notbad = set()
1317 1319 m = scmutil.match(ctx, (file1,) + pats, opts)
1318 1320 origmatchfn = m.matchfn
1319 1321 def lfmatchfn(f):
1320 1322 if origmatchfn(f):
1321 1323 return True
1322 1324 lf = lfutil.splitstandin(f)
1323 1325 if lf is None:
1324 1326 return False
1325 1327 notbad.add(lf)
1326 1328 return origmatchfn(lf)
1327 1329 m.matchfn = lfmatchfn
1328 1330 origbadfn = m.bad
1329 1331 def lfbadfn(f, msg):
1330 1332 if not f in notbad:
1331 1333 origbadfn(f, msg)
1332 1334 m.bad = lfbadfn
1333 1335
1334 1336 origvisitdirfn = m.visitdir
1335 1337 def lfvisitdirfn(dir):
1336 1338 if dir == lfutil.shortname:
1337 1339 return True
1338 1340 ret = origvisitdirfn(dir)
1339 1341 if ret:
1340 1342 return ret
1341 1343 lf = lfutil.splitstandin(dir)
1342 1344 if lf is None:
1343 1345 return False
1344 1346 return origvisitdirfn(lf)
1345 1347 m.visitdir = lfvisitdirfn
1346 1348
1347 1349 for f in ctx.walk(m):
1348 1350 with cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1349 1351 pathname=f) as fp:
1350 1352 lf = lfutil.splitstandin(f)
1351 1353 if lf is None or origmatchfn(f):
1352 1354 # duplicating unreachable code from commands.cat
1353 1355 data = ctx[f].data()
1354 1356 if opts.get('decode'):
1355 1357 data = repo.wwritedata(f, data)
1356 1358 fp.write(data)
1357 1359 else:
1358 1360 hash = lfutil.readstandin(repo, lf, ctx)
1359 1361 if not lfutil.inusercache(repo.ui, hash):
1360 1362 store = storefactory.openstore(repo)
1361 1363 success, missing = store.get([(lf, hash)])
1362 1364 if len(success) != 1:
1363 1365 raise error.Abort(
1364 1366 _('largefile %s is not in cache and could not be '
1365 1367 'downloaded') % lf)
1366 1368 path = lfutil.usercachepath(repo.ui, hash)
1367 1369 with open(path, "rb") as fpin:
1368 1370 for chunk in util.filechunkiter(fpin):
1369 1371 fp.write(chunk)
1370 1372 err = 0
1371 1373 return err
1372 1374
1373 1375 def mergeupdate(orig, repo, node, branchmerge, force,
1374 1376 *args, **kwargs):
1375 1377 matcher = kwargs.get('matcher', None)
1376 1378 # note if this is a partial update
1377 1379 partial = matcher and not matcher.always()
1378 1380 with repo.wlock():
1379 1381 # branch | | |
1380 1382 # merge | force | partial | action
1381 1383 # -------+-------+---------+--------------
1382 1384 # x | x | x | linear-merge
1383 1385 # o | x | x | branch-merge
1384 1386 # x | o | x | overwrite (as clean update)
1385 1387 # o | o | x | force-branch-merge (*1)
1386 1388 # x | x | o | (*)
1387 1389 # o | x | o | (*)
1388 1390 # x | o | o | overwrite (as revert)
1389 1391 # o | o | o | (*)
1390 1392 #
1391 1393 # (*) don't care
1392 1394 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1393 1395
1394 1396 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1395 1397 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1396 1398 repo.getcwd()),
1397 1399 [], False, True, False)
1398 1400 oldclean = set(s.clean)
1399 1401 pctx = repo['.']
1400 1402 dctx = repo[node]
1401 1403 for lfile in unsure + s.modified:
1402 1404 lfileabs = repo.wvfs.join(lfile)
1403 1405 if not repo.wvfs.exists(lfileabs):
1404 1406 continue
1405 1407 lfhash = lfutil.hashfile(lfileabs)
1406 1408 standin = lfutil.standin(lfile)
1407 1409 lfutil.writestandin(repo, standin, lfhash,
1408 1410 lfutil.getexecutable(lfileabs))
1409 1411 if (standin in pctx and
1410 1412 lfhash == lfutil.readstandin(repo, lfile, pctx)):
1411 1413 oldclean.add(lfile)
1412 1414 for lfile in s.added:
1413 1415 fstandin = lfutil.standin(lfile)
1414 1416 if fstandin not in dctx:
1415 1417 # in this case, content of standin file is meaningless
1416 1418 # (in dctx, lfile is unknown, or normal file)
1417 1419 continue
1418 1420 lfutil.updatestandin(repo, fstandin)
1419 1421 # mark all clean largefiles as dirty, just in case the update gets
1420 1422 # interrupted before largefiles and lfdirstate are synchronized
1421 1423 for lfile in oldclean:
1422 1424 lfdirstate.normallookup(lfile)
1423 1425 lfdirstate.write()
1424 1426
1425 1427 oldstandins = lfutil.getstandinsstate(repo)
1426 1428
1427 1429 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1428 1430
1429 1431 newstandins = lfutil.getstandinsstate(repo)
1430 1432 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1431 1433
1432 1434 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1433 1435 # all the ones that didn't change as clean
1434 1436 for lfile in oldclean.difference(filelist):
1435 1437 lfdirstate.normal(lfile)
1436 1438 lfdirstate.write()
1437 1439
1438 1440 if branchmerge or force or partial:
1439 1441 filelist.extend(s.deleted + s.removed)
1440 1442
1441 1443 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1442 1444 normallookup=partial)
1443 1445
1444 1446 return result
1445 1447
1446 1448 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1447 1449 result = orig(repo, files, *args, **kwargs)
1448 1450
1449 1451 filelist = []
1450 1452 for f in files:
1451 1453 lf = lfutil.splitstandin(f)
1452 1454 if lf is not None:
1453 1455 filelist.append(lf)
1454 1456 if filelist:
1455 1457 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1456 1458 printmessage=False, normallookup=True)
1457 1459
1458 1460 return result
General Comments 0
You need to be logged in to leave comments. Login now