##// END OF EJS Templates
largefiles: introduce a constant for 'lfmr' action...
Pulkit Goyal -
r45855:a3cd63d6 default
parent child Browse files
Show More
@@ -1,1831 +1,1836 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 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.pycompat import open
18 18
19 19 from mercurial.hgweb import webcommands
20 20
21 21 from mercurial import (
22 22 archival,
23 23 cmdutil,
24 24 copies as copiesmod,
25 25 error,
26 26 exchange,
27 27 extensions,
28 28 exthelper,
29 29 filemerge,
30 30 hg,
31 31 logcmdutil,
32 32 match as matchmod,
33 33 merge,
34 34 mergestate as mergestatemod,
35 35 pathutil,
36 36 pycompat,
37 37 scmutil,
38 38 smartset,
39 39 subrepo,
40 40 upgrade,
41 41 url as urlmod,
42 42 util,
43 43 )
44 44
45 45 from . import (
46 46 lfcommands,
47 47 lfutil,
48 48 storefactory,
49 49 )
50 50
51 51 eh = exthelper.exthelper()
52 52
53 53 lfstatus = lfutil.lfstatus
54 54
55 MERGE_ACTION_LARGEFILE_MARK_REMOVED = b'lfmr'
56
55 57 # -- Utility functions: commonly/repeatedly needed functionality ---------------
56 58
57 59
58 60 def composelargefilematcher(match, manifest):
59 61 '''create a matcher that matches only the largefiles in the original
60 62 matcher'''
61 63 m = copy.copy(match)
62 64 lfile = lambda f: lfutil.standin(f) in manifest
63 65 m._files = [lf for lf in m._files if lfile(lf)]
64 66 m._fileset = set(m._files)
65 67 m.always = lambda: False
66 68 origmatchfn = m.matchfn
67 69 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
68 70 return m
69 71
70 72
71 73 def composenormalfilematcher(match, manifest, exclude=None):
72 74 excluded = set()
73 75 if exclude is not None:
74 76 excluded.update(exclude)
75 77
76 78 m = copy.copy(match)
77 79 notlfile = lambda f: not (
78 80 lfutil.isstandin(f) or lfutil.standin(f) in manifest or f in excluded
79 81 )
80 82 m._files = [lf for lf in m._files if notlfile(lf)]
81 83 m._fileset = set(m._files)
82 84 m.always = lambda: False
83 85 origmatchfn = m.matchfn
84 86 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
85 87 return m
86 88
87 89
88 90 def addlargefiles(ui, repo, isaddremove, matcher, uipathfn, **opts):
89 91 large = opts.get('large')
90 92 lfsize = lfutil.getminsize(
91 93 ui, lfutil.islfilesrepo(repo), opts.get('lfsize')
92 94 )
93 95
94 96 lfmatcher = None
95 97 if lfutil.islfilesrepo(repo):
96 98 lfpats = ui.configlist(lfutil.longname, b'patterns')
97 99 if lfpats:
98 100 lfmatcher = matchmod.match(repo.root, b'', list(lfpats))
99 101
100 102 lfnames = []
101 103 m = matcher
102 104
103 105 wctx = repo[None]
104 106 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
105 107 exact = m.exact(f)
106 108 lfile = lfutil.standin(f) in wctx
107 109 nfile = f in wctx
108 110 exists = lfile or nfile
109 111
110 112 # Don't warn the user when they attempt to add a normal tracked file.
111 113 # The normal add code will do that for us.
112 114 if exact and exists:
113 115 if lfile:
114 116 ui.warn(_(b'%s already a largefile\n') % uipathfn(f))
115 117 continue
116 118
117 119 if (exact or not exists) and not lfutil.isstandin(f):
118 120 # In case the file was removed previously, but not committed
119 121 # (issue3507)
120 122 if not repo.wvfs.exists(f):
121 123 continue
122 124
123 125 abovemin = (
124 126 lfsize and repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024
125 127 )
126 128 if large or abovemin or (lfmatcher and lfmatcher(f)):
127 129 lfnames.append(f)
128 130 if ui.verbose or not exact:
129 131 ui.status(_(b'adding %s as a largefile\n') % uipathfn(f))
130 132
131 133 bad = []
132 134
133 135 # Need to lock, otherwise there could be a race condition between
134 136 # when standins are created and added to the repo.
135 137 with repo.wlock():
136 138 if not opts.get('dry_run'):
137 139 standins = []
138 140 lfdirstate = lfutil.openlfdirstate(ui, repo)
139 141 for f in lfnames:
140 142 standinname = lfutil.standin(f)
141 143 lfutil.writestandin(
142 144 repo,
143 145 standinname,
144 146 hash=b'',
145 147 executable=lfutil.getexecutable(repo.wjoin(f)),
146 148 )
147 149 standins.append(standinname)
148 150 if lfdirstate[f] == b'r':
149 151 lfdirstate.normallookup(f)
150 152 else:
151 153 lfdirstate.add(f)
152 154 lfdirstate.write()
153 155 bad += [
154 156 lfutil.splitstandin(f)
155 157 for f in repo[None].add(standins)
156 158 if f in m.files()
157 159 ]
158 160
159 161 added = [f for f in lfnames if f not in bad]
160 162 return added, bad
161 163
162 164
163 165 def removelargefiles(ui, repo, isaddremove, matcher, uipathfn, dryrun, **opts):
164 166 after = opts.get('after')
165 167 m = composelargefilematcher(matcher, repo[None].manifest())
166 168 with lfstatus(repo):
167 169 s = repo.status(match=m, clean=not isaddremove)
168 170 manifest = repo[None].manifest()
169 171 modified, added, deleted, clean = [
170 172 [f for f in list if lfutil.standin(f) in manifest]
171 173 for list in (s.modified, s.added, s.deleted, s.clean)
172 174 ]
173 175
174 176 def warn(files, msg):
175 177 for f in files:
176 178 ui.warn(msg % uipathfn(f))
177 179 return int(len(files) > 0)
178 180
179 181 if after:
180 182 remove = deleted
181 183 result = warn(
182 184 modified + added + clean, _(b'not removing %s: file still exists\n')
183 185 )
184 186 else:
185 187 remove = deleted + clean
186 188 result = warn(
187 189 modified,
188 190 _(
189 191 b'not removing %s: file is modified (use -f'
190 192 b' to force removal)\n'
191 193 ),
192 194 )
193 195 result = (
194 196 warn(
195 197 added,
196 198 _(
197 199 b'not removing %s: file has been marked for add'
198 200 b' (use forget to undo)\n'
199 201 ),
200 202 )
201 203 or result
202 204 )
203 205
204 206 # Need to lock because standin files are deleted then removed from the
205 207 # repository and we could race in-between.
206 208 with repo.wlock():
207 209 lfdirstate = lfutil.openlfdirstate(ui, repo)
208 210 for f in sorted(remove):
209 211 if ui.verbose or not m.exact(f):
210 212 ui.status(_(b'removing %s\n') % uipathfn(f))
211 213
212 214 if not dryrun:
213 215 if not after:
214 216 repo.wvfs.unlinkpath(f, ignoremissing=True)
215 217
216 218 if dryrun:
217 219 return result
218 220
219 221 remove = [lfutil.standin(f) for f in remove]
220 222 # If this is being called by addremove, let the original addremove
221 223 # function handle this.
222 224 if not isaddremove:
223 225 for f in remove:
224 226 repo.wvfs.unlinkpath(f, ignoremissing=True)
225 227 repo[None].forget(remove)
226 228
227 229 for f in remove:
228 230 lfutil.synclfdirstate(
229 231 repo, lfdirstate, lfutil.splitstandin(f), False
230 232 )
231 233
232 234 lfdirstate.write()
233 235
234 236 return result
235 237
236 238
237 239 # For overriding mercurial.hgweb.webcommands so that largefiles will
238 240 # appear at their right place in the manifests.
239 241 @eh.wrapfunction(webcommands, b'decodepath')
240 242 def decodepath(orig, path):
241 243 return lfutil.splitstandin(path) or path
242 244
243 245
244 246 # -- Wrappers: modify existing commands --------------------------------
245 247
246 248
247 249 @eh.wrapcommand(
248 250 b'add',
249 251 opts=[
250 252 (b'', b'large', None, _(b'add as largefile')),
251 253 (b'', b'normal', None, _(b'add as normal file')),
252 254 (
253 255 b'',
254 256 b'lfsize',
255 257 b'',
256 258 _(
257 259 b'add all files above this size (in megabytes) '
258 260 b'as largefiles (default: 10)'
259 261 ),
260 262 ),
261 263 ],
262 264 )
263 265 def overrideadd(orig, ui, repo, *pats, **opts):
264 266 if opts.get('normal') and opts.get('large'):
265 267 raise error.Abort(_(b'--normal cannot be used with --large'))
266 268 return orig(ui, repo, *pats, **opts)
267 269
268 270
269 271 @eh.wrapfunction(cmdutil, b'add')
270 272 def cmdutiladd(orig, ui, repo, matcher, prefix, uipathfn, explicitonly, **opts):
271 273 # The --normal flag short circuits this override
272 274 if opts.get('normal'):
273 275 return orig(ui, repo, matcher, prefix, uipathfn, explicitonly, **opts)
274 276
275 277 ladded, lbad = addlargefiles(ui, repo, False, matcher, uipathfn, **opts)
276 278 normalmatcher = composenormalfilematcher(
277 279 matcher, repo[None].manifest(), ladded
278 280 )
279 281 bad = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly, **opts)
280 282
281 283 bad.extend(f for f in lbad)
282 284 return bad
283 285
284 286
285 287 @eh.wrapfunction(cmdutil, b'remove')
286 288 def cmdutilremove(
287 289 orig, ui, repo, matcher, prefix, uipathfn, after, force, subrepos, dryrun
288 290 ):
289 291 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
290 292 result = orig(
291 293 ui,
292 294 repo,
293 295 normalmatcher,
294 296 prefix,
295 297 uipathfn,
296 298 after,
297 299 force,
298 300 subrepos,
299 301 dryrun,
300 302 )
301 303 return (
302 304 removelargefiles(
303 305 ui, repo, False, matcher, uipathfn, dryrun, after=after, force=force
304 306 )
305 307 or result
306 308 )
307 309
308 310
309 311 @eh.wrapfunction(subrepo.hgsubrepo, b'status')
310 312 def overridestatusfn(orig, repo, rev2, **opts):
311 313 with lfstatus(repo._repo):
312 314 return orig(repo, rev2, **opts)
313 315
314 316
315 317 @eh.wrapcommand(b'status')
316 318 def overridestatus(orig, ui, repo, *pats, **opts):
317 319 with lfstatus(repo):
318 320 return orig(ui, repo, *pats, **opts)
319 321
320 322
321 323 @eh.wrapfunction(subrepo.hgsubrepo, b'dirty')
322 324 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
323 325 with lfstatus(repo._repo):
324 326 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
325 327
326 328
327 329 @eh.wrapcommand(b'log')
328 330 def overridelog(orig, ui, repo, *pats, **opts):
329 331 def overridematchandpats(
330 332 orig,
331 333 ctx,
332 334 pats=(),
333 335 opts=None,
334 336 globbed=False,
335 337 default=b'relpath',
336 338 badfn=None,
337 339 ):
338 340 """Matcher that merges root directory with .hglf, suitable for log.
339 341 It is still possible to match .hglf directly.
340 342 For any listed files run log on the standin too.
341 343 matchfn tries both the given filename and with .hglf stripped.
342 344 """
343 345 if opts is None:
344 346 opts = {}
345 347 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
346 348 m, p = copy.copy(matchandpats)
347 349
348 350 if m.always():
349 351 # We want to match everything anyway, so there's no benefit trying
350 352 # to add standins.
351 353 return matchandpats
352 354
353 355 pats = set(p)
354 356
355 357 def fixpats(pat, tostandin=lfutil.standin):
356 358 if pat.startswith(b'set:'):
357 359 return pat
358 360
359 361 kindpat = matchmod._patsplit(pat, None)
360 362
361 363 if kindpat[0] is not None:
362 364 return kindpat[0] + b':' + tostandin(kindpat[1])
363 365 return tostandin(kindpat[1])
364 366
365 367 cwd = repo.getcwd()
366 368 if cwd:
367 369 hglf = lfutil.shortname
368 370 back = util.pconvert(repo.pathto(hglf)[: -len(hglf)])
369 371
370 372 def tostandin(f):
371 373 # The file may already be a standin, so truncate the back
372 374 # prefix and test before mangling it. This avoids turning
373 375 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
374 376 if f.startswith(back) and lfutil.splitstandin(f[len(back) :]):
375 377 return f
376 378
377 379 # An absolute path is from outside the repo, so truncate the
378 380 # path to the root before building the standin. Otherwise cwd
379 381 # is somewhere in the repo, relative to root, and needs to be
380 382 # prepended before building the standin.
381 383 if os.path.isabs(cwd):
382 384 f = f[len(back) :]
383 385 else:
384 386 f = cwd + b'/' + f
385 387 return back + lfutil.standin(f)
386 388
387 389 else:
388 390
389 391 def tostandin(f):
390 392 if lfutil.isstandin(f):
391 393 return f
392 394 return lfutil.standin(f)
393 395
394 396 pats.update(fixpats(f, tostandin) for f in p)
395 397
396 398 for i in range(0, len(m._files)):
397 399 # Don't add '.hglf' to m.files, since that is already covered by '.'
398 400 if m._files[i] == b'.':
399 401 continue
400 402 standin = lfutil.standin(m._files[i])
401 403 # If the "standin" is a directory, append instead of replace to
402 404 # support naming a directory on the command line with only
403 405 # largefiles. The original directory is kept to support normal
404 406 # files.
405 407 if standin in ctx:
406 408 m._files[i] = standin
407 409 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
408 410 m._files.append(standin)
409 411
410 412 m._fileset = set(m._files)
411 413 m.always = lambda: False
412 414 origmatchfn = m.matchfn
413 415
414 416 def lfmatchfn(f):
415 417 lf = lfutil.splitstandin(f)
416 418 if lf is not None and origmatchfn(lf):
417 419 return True
418 420 r = origmatchfn(f)
419 421 return r
420 422
421 423 m.matchfn = lfmatchfn
422 424
423 425 ui.debug(b'updated patterns: %s\n' % b', '.join(sorted(pats)))
424 426 return m, pats
425 427
426 428 # For hg log --patch, the match object is used in two different senses:
427 429 # (1) to determine what revisions should be printed out, and
428 430 # (2) to determine what files to print out diffs for.
429 431 # The magic matchandpats override should be used for case (1) but not for
430 432 # case (2).
431 433 oldmatchandpats = scmutil.matchandpats
432 434
433 435 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
434 436 wctx = repo[None]
435 437 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
436 438 return lambda ctx: match
437 439
438 440 wrappedmatchandpats = extensions.wrappedfunction(
439 441 scmutil, b'matchandpats', overridematchandpats
440 442 )
441 443 wrappedmakefilematcher = extensions.wrappedfunction(
442 444 logcmdutil, b'_makenofollowfilematcher', overridemakefilematcher
443 445 )
444 446 with wrappedmatchandpats, wrappedmakefilematcher:
445 447 return orig(ui, repo, *pats, **opts)
446 448
447 449
448 450 @eh.wrapcommand(
449 451 b'verify',
450 452 opts=[
451 453 (
452 454 b'',
453 455 b'large',
454 456 None,
455 457 _(b'verify that all largefiles in current revision exists'),
456 458 ),
457 459 (
458 460 b'',
459 461 b'lfa',
460 462 None,
461 463 _(b'verify largefiles in all revisions, not just current'),
462 464 ),
463 465 (
464 466 b'',
465 467 b'lfc',
466 468 None,
467 469 _(b'verify local largefile contents, not just existence'),
468 470 ),
469 471 ],
470 472 )
471 473 def overrideverify(orig, ui, repo, *pats, **opts):
472 474 large = opts.pop('large', False)
473 475 all = opts.pop('lfa', False)
474 476 contents = opts.pop('lfc', False)
475 477
476 478 result = orig(ui, repo, *pats, **opts)
477 479 if large or all or contents:
478 480 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
479 481 return result
480 482
481 483
482 484 @eh.wrapcommand(
483 485 b'debugstate',
484 486 opts=[(b'', b'large', None, _(b'display largefiles dirstate'))],
485 487 )
486 488 def overridedebugstate(orig, ui, repo, *pats, **opts):
487 489 large = opts.pop('large', False)
488 490 if large:
489 491
490 492 class fakerepo(object):
491 493 dirstate = lfutil.openlfdirstate(ui, repo)
492 494
493 495 orig(ui, fakerepo, *pats, **opts)
494 496 else:
495 497 orig(ui, repo, *pats, **opts)
496 498
497 499
498 # Register the `lfmr` merge action in emptyactions() return type
500 # Register the MERGE_ACTION_LARGEFILE_MARK_REMOVED in emptyactions() return type
499 501 @eh.wrapfunction(merge, b'emptyactions')
500 502 def overrideemptyactions(origfn):
501 503 ret = origfn()
502 ret[b'lfmr'] = []
504 ret[MERGE_ACTION_LARGEFILE_MARK_REMOVED] = []
503 505 return ret
504 506
505 507
506 508 # Before starting the manifest merge, merge.updates will call
507 509 # _checkunknownfile to check if there are any files in the merged-in
508 510 # changeset that collide with unknown files in the working copy.
509 511 #
510 512 # The largefiles are seen as unknown, so this prevents us from merging
511 513 # in a file 'foo' if we already have a largefile with the same name.
512 514 #
513 515 # The overridden function filters the unknown files by removing any
514 516 # largefiles. This makes the merge proceed and we can then handle this
515 517 # case further in the overridden calculateupdates function below.
516 518 @eh.wrapfunction(merge, b'_checkunknownfile')
517 519 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
518 520 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
519 521 return False
520 522 return origfn(repo, wctx, mctx, f, f2)
521 523
522 524
523 525 # The manifest merge handles conflicts on the manifest level. We want
524 526 # to handle changes in largefile-ness of files at this level too.
525 527 #
526 528 # The strategy is to run the original calculateupdates and then process
527 529 # the action list it outputs. There are two cases we need to deal with:
528 530 #
529 531 # 1. Normal file in p1, largefile in p2. Here the largefile is
530 532 # detected via its standin file, which will enter the working copy
531 533 # with a "get" action. It is not "merge" since the standin is all
532 534 # Mercurial is concerned with at this level -- the link to the
533 535 # existing normal file is not relevant here.
534 536 #
535 537 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
536 538 # since the largefile will be present in the working copy and
537 539 # different from the normal file in p2. Mercurial therefore
538 540 # triggers a merge action.
539 541 #
540 542 # In both cases, we prompt the user and emit new actions to either
541 543 # remove the standin (if the normal file was kept) or to remove the
542 544 # normal file and get the standin (if the largefile was kept). The
543 545 # default prompt answer is to use the largefile version since it was
544 546 # presumably changed on purpose.
545 547 #
546 548 # Finally, the merge.applyupdates function will then take care of
547 549 # writing the files into the working copy and lfcommands.updatelfiles
548 550 # will update the largefiles.
549 551 @eh.wrapfunction(merge, b'calculateupdates')
550 552 def overridecalculateupdates(
551 553 origfn, repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs
552 554 ):
553 555 overwrite = force and not branchmerge
554 556 mresult = origfn(
555 557 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs
556 558 )
557 559
558 560 if overwrite:
559 561 return mresult
560 562
561 563 # Convert to dictionary with filename as key and action as value.
562 564 lfiles = set()
563 565 for f in mresult.actions:
564 566 splitstandin = lfutil.splitstandin(f)
565 567 if splitstandin is not None and splitstandin in p1:
566 568 lfiles.add(splitstandin)
567 569 elif lfutil.standin(f) in p1:
568 570 lfiles.add(f)
569 571
570 572 for lfile in sorted(lfiles):
571 573 standin = lfutil.standin(lfile)
572 574 (lm, largs, lmsg) = mresult.actions.get(lfile, (None, None, None))
573 575 (sm, sargs, smsg) = mresult.actions.get(standin, (None, None, None))
574 576 if sm in (b'g', b'dc') and lm != b'r':
575 577 if sm == b'dc':
576 578 f1, f2, fa, move, anc = sargs
577 579 sargs = (p2[f2].flags(), False)
578 580 # Case 1: normal file in the working copy, largefile in
579 581 # the second parent
580 582 usermsg = (
581 583 _(
582 584 b'remote turned local normal file %s into a largefile\n'
583 585 b'use (l)argefile or keep (n)ormal file?'
584 586 b'$$ &Largefile $$ &Normal file'
585 587 )
586 588 % lfile
587 589 )
588 590 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
589 591 mresult.addfile(lfile, b'r', None, b'replaced by standin')
590 592 mresult.addfile(standin, b'g', sargs, b'replaces standin')
591 593 else: # keep local normal file
592 594 mresult.addfile(lfile, b'k', None, b'replaces standin')
593 595 if branchmerge:
594 596 mresult.addfile(
595 597 standin, b'k', None, b'replaced by non-standin',
596 598 )
597 599 else:
598 600 mresult.addfile(
599 601 standin, b'r', None, b'replaced by non-standin',
600 602 )
601 603 elif lm in (b'g', b'dc') and sm != b'r':
602 604 if lm == b'dc':
603 605 f1, f2, fa, move, anc = largs
604 606 largs = (p2[f2].flags(), False)
605 607 # Case 2: largefile in the working copy, normal file in
606 608 # the second parent
607 609 usermsg = (
608 610 _(
609 611 b'remote turned local largefile %s into a normal file\n'
610 612 b'keep (l)argefile or use (n)ormal file?'
611 613 b'$$ &Largefile $$ &Normal file'
612 614 )
613 615 % lfile
614 616 )
615 617 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
616 618 if branchmerge:
617 619 # largefile can be restored from standin safely
618 620 mresult.addfile(
619 621 lfile, b'k', None, b'replaced by standin',
620 622 )
621 623 mresult.addfile(standin, b'k', None, b'replaces standin')
622 624 else:
623 625 # "lfile" should be marked as "removed" without
624 626 # removal of itself
625 627 mresult.addfile(
626 lfile, b'lfmr', None, b'forget non-standin largefile',
628 lfile,
629 MERGE_ACTION_LARGEFILE_MARK_REMOVED,
630 None,
631 b'forget non-standin largefile',
627 632 )
628 633
629 634 # linear-merge should treat this largefile as 're-added'
630 635 mresult.addfile(standin, b'a', None, b'keep standin')
631 636 else: # pick remote normal file
632 637 mresult.addfile(lfile, b'g', largs, b'replaces standin')
633 638 mresult.addfile(
634 639 standin, b'r', None, b'replaced by non-standin',
635 640 )
636 641
637 642 return mresult
638 643
639 644
640 645 @eh.wrapfunction(mergestatemod, b'recordupdates')
641 646 def mergerecordupdates(orig, repo, actions, branchmerge, getfiledata):
642 if b'lfmr' in actions:
647 if MERGE_ACTION_LARGEFILE_MARK_REMOVED in actions:
643 648 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
644 for lfile, args, msg in actions[b'lfmr']:
649 for lfile, args, msg in actions[MERGE_ACTION_LARGEFILE_MARK_REMOVED]:
645 650 # this should be executed before 'orig', to execute 'remove'
646 651 # before all other actions
647 652 repo.dirstate.remove(lfile)
648 653 # make sure lfile doesn't get synclfdirstate'd as normal
649 654 lfdirstate.add(lfile)
650 655 lfdirstate.write()
651 656
652 657 return orig(repo, actions, branchmerge, getfiledata)
653 658
654 659
655 660 # Override filemerge to prompt the user about how they wish to merge
656 661 # largefiles. This will handle identical edits without prompting the user.
657 662 @eh.wrapfunction(filemerge, b'_filemerge')
658 663 def overridefilemerge(
659 664 origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None
660 665 ):
661 666 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
662 667 return origfn(
663 668 premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels
664 669 )
665 670
666 671 ahash = lfutil.readasstandin(fca).lower()
667 672 dhash = lfutil.readasstandin(fcd).lower()
668 673 ohash = lfutil.readasstandin(fco).lower()
669 674 if (
670 675 ohash != ahash
671 676 and ohash != dhash
672 677 and (
673 678 dhash == ahash
674 679 or repo.ui.promptchoice(
675 680 _(
676 681 b'largefile %s has a merge conflict\nancestor was %s\n'
677 682 b'you can keep (l)ocal %s or take (o)ther %s.\n'
678 683 b'what do you want to do?'
679 684 b'$$ &Local $$ &Other'
680 685 )
681 686 % (lfutil.splitstandin(orig), ahash, dhash, ohash),
682 687 0,
683 688 )
684 689 == 1
685 690 )
686 691 ):
687 692 repo.wwrite(fcd.path(), fco.data(), fco.flags())
688 693 return True, 0, False
689 694
690 695
691 696 @eh.wrapfunction(copiesmod, b'pathcopies')
692 697 def copiespathcopies(orig, ctx1, ctx2, match=None):
693 698 copies = orig(ctx1, ctx2, match=match)
694 699 updated = {}
695 700
696 701 for k, v in pycompat.iteritems(copies):
697 702 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
698 703
699 704 return updated
700 705
701 706
702 707 # Copy first changes the matchers to match standins instead of
703 708 # largefiles. Then it overrides util.copyfile in that function it
704 709 # checks if the destination largefile already exists. It also keeps a
705 710 # list of copied files so that the largefiles can be copied and the
706 711 # dirstate updated.
707 712 @eh.wrapfunction(cmdutil, b'copy')
708 713 def overridecopy(orig, ui, repo, pats, opts, rename=False):
709 714 # doesn't remove largefile on rename
710 715 if len(pats) < 2:
711 716 # this isn't legal, let the original function deal with it
712 717 return orig(ui, repo, pats, opts, rename)
713 718
714 719 # This could copy both lfiles and normal files in one command,
715 720 # but we don't want to do that. First replace their matcher to
716 721 # only match normal files and run it, then replace it to just
717 722 # match largefiles and run it again.
718 723 nonormalfiles = False
719 724 nolfiles = False
720 725 manifest = repo[None].manifest()
721 726
722 727 def normalfilesmatchfn(
723 728 orig,
724 729 ctx,
725 730 pats=(),
726 731 opts=None,
727 732 globbed=False,
728 733 default=b'relpath',
729 734 badfn=None,
730 735 ):
731 736 if opts is None:
732 737 opts = {}
733 738 match = orig(ctx, pats, opts, globbed, default, badfn=badfn)
734 739 return composenormalfilematcher(match, manifest)
735 740
736 741 with extensions.wrappedfunction(scmutil, b'match', normalfilesmatchfn):
737 742 try:
738 743 result = orig(ui, repo, pats, opts, rename)
739 744 except error.Abort as e:
740 745 if pycompat.bytestr(e) != _(b'no files to copy'):
741 746 raise e
742 747 else:
743 748 nonormalfiles = True
744 749 result = 0
745 750
746 751 # The first rename can cause our current working directory to be removed.
747 752 # In that case there is nothing left to copy/rename so just quit.
748 753 try:
749 754 repo.getcwd()
750 755 except OSError:
751 756 return result
752 757
753 758 def makestandin(relpath):
754 759 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
755 760 return repo.wvfs.join(lfutil.standin(path))
756 761
757 762 fullpats = scmutil.expandpats(pats)
758 763 dest = fullpats[-1]
759 764
760 765 if os.path.isdir(dest):
761 766 if not os.path.isdir(makestandin(dest)):
762 767 os.makedirs(makestandin(dest))
763 768
764 769 try:
765 770 # When we call orig below it creates the standins but we don't add
766 771 # them to the dir state until later so lock during that time.
767 772 wlock = repo.wlock()
768 773
769 774 manifest = repo[None].manifest()
770 775
771 776 def overridematch(
772 777 orig,
773 778 ctx,
774 779 pats=(),
775 780 opts=None,
776 781 globbed=False,
777 782 default=b'relpath',
778 783 badfn=None,
779 784 ):
780 785 if opts is None:
781 786 opts = {}
782 787 newpats = []
783 788 # The patterns were previously mangled to add the standin
784 789 # directory; we need to remove that now
785 790 for pat in pats:
786 791 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
787 792 newpats.append(pat.replace(lfutil.shortname, b''))
788 793 else:
789 794 newpats.append(pat)
790 795 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
791 796 m = copy.copy(match)
792 797 lfile = lambda f: lfutil.standin(f) in manifest
793 798 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
794 799 m._fileset = set(m._files)
795 800 origmatchfn = m.matchfn
796 801
797 802 def matchfn(f):
798 803 lfile = lfutil.splitstandin(f)
799 804 return (
800 805 lfile is not None
801 806 and (f in manifest)
802 807 and origmatchfn(lfile)
803 808 or None
804 809 )
805 810
806 811 m.matchfn = matchfn
807 812 return m
808 813
809 814 listpats = []
810 815 for pat in pats:
811 816 if matchmod.patkind(pat) is not None:
812 817 listpats.append(pat)
813 818 else:
814 819 listpats.append(makestandin(pat))
815 820
816 821 copiedfiles = []
817 822
818 823 def overridecopyfile(orig, src, dest, *args, **kwargs):
819 824 if lfutil.shortname in src and dest.startswith(
820 825 repo.wjoin(lfutil.shortname)
821 826 ):
822 827 destlfile = dest.replace(lfutil.shortname, b'')
823 828 if not opts[b'force'] and os.path.exists(destlfile):
824 829 raise IOError(
825 830 b'', _(b'destination largefile already exists')
826 831 )
827 832 copiedfiles.append((src, dest))
828 833 orig(src, dest, *args, **kwargs)
829 834
830 835 with extensions.wrappedfunction(util, b'copyfile', overridecopyfile):
831 836 with extensions.wrappedfunction(scmutil, b'match', overridematch):
832 837 result += orig(ui, repo, listpats, opts, rename)
833 838
834 839 lfdirstate = lfutil.openlfdirstate(ui, repo)
835 840 for (src, dest) in copiedfiles:
836 841 if lfutil.shortname in src and dest.startswith(
837 842 repo.wjoin(lfutil.shortname)
838 843 ):
839 844 srclfile = src.replace(repo.wjoin(lfutil.standin(b'')), b'')
840 845 destlfile = dest.replace(repo.wjoin(lfutil.standin(b'')), b'')
841 846 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or b'.'
842 847 if not os.path.isdir(destlfiledir):
843 848 os.makedirs(destlfiledir)
844 849 if rename:
845 850 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
846 851
847 852 # The file is gone, but this deletes any empty parent
848 853 # directories as a side-effect.
849 854 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
850 855 lfdirstate.remove(srclfile)
851 856 else:
852 857 util.copyfile(repo.wjoin(srclfile), repo.wjoin(destlfile))
853 858
854 859 lfdirstate.add(destlfile)
855 860 lfdirstate.write()
856 861 except error.Abort as e:
857 862 if pycompat.bytestr(e) != _(b'no files to copy'):
858 863 raise e
859 864 else:
860 865 nolfiles = True
861 866 finally:
862 867 wlock.release()
863 868
864 869 if nolfiles and nonormalfiles:
865 870 raise error.Abort(_(b'no files to copy'))
866 871
867 872 return result
868 873
869 874
870 875 # When the user calls revert, we have to be careful to not revert any
871 876 # changes to other largefiles accidentally. This means we have to keep
872 877 # track of the largefiles that are being reverted so we only pull down
873 878 # the necessary largefiles.
874 879 #
875 880 # Standins are only updated (to match the hash of largefiles) before
876 881 # commits. Update the standins then run the original revert, changing
877 882 # the matcher to hit standins instead of largefiles. Based on the
878 883 # resulting standins update the largefiles.
879 884 @eh.wrapfunction(cmdutil, b'revert')
880 885 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
881 886 # Because we put the standins in a bad state (by updating them)
882 887 # and then return them to a correct state we need to lock to
883 888 # prevent others from changing them in their incorrect state.
884 889 with repo.wlock():
885 890 lfdirstate = lfutil.openlfdirstate(ui, repo)
886 891 s = lfutil.lfdirstatestatus(lfdirstate, repo)
887 892 lfdirstate.write()
888 893 for lfile in s.modified:
889 894 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
890 895 for lfile in s.deleted:
891 896 fstandin = lfutil.standin(lfile)
892 897 if repo.wvfs.exists(fstandin):
893 898 repo.wvfs.unlink(fstandin)
894 899
895 900 oldstandins = lfutil.getstandinsstate(repo)
896 901
897 902 def overridematch(
898 903 orig,
899 904 mctx,
900 905 pats=(),
901 906 opts=None,
902 907 globbed=False,
903 908 default=b'relpath',
904 909 badfn=None,
905 910 ):
906 911 if opts is None:
907 912 opts = {}
908 913 match = orig(mctx, pats, opts, globbed, default, badfn=badfn)
909 914 m = copy.copy(match)
910 915
911 916 # revert supports recursing into subrepos, and though largefiles
912 917 # currently doesn't work correctly in that case, this match is
913 918 # called, so the lfdirstate above may not be the correct one for
914 919 # this invocation of match.
915 920 lfdirstate = lfutil.openlfdirstate(
916 921 mctx.repo().ui, mctx.repo(), False
917 922 )
918 923
919 924 wctx = repo[None]
920 925 matchfiles = []
921 926 for f in m._files:
922 927 standin = lfutil.standin(f)
923 928 if standin in ctx or standin in mctx:
924 929 matchfiles.append(standin)
925 930 elif standin in wctx or lfdirstate[f] == b'r':
926 931 continue
927 932 else:
928 933 matchfiles.append(f)
929 934 m._files = matchfiles
930 935 m._fileset = set(m._files)
931 936 origmatchfn = m.matchfn
932 937
933 938 def matchfn(f):
934 939 lfile = lfutil.splitstandin(f)
935 940 if lfile is not None:
936 941 return origmatchfn(lfile) and (f in ctx or f in mctx)
937 942 return origmatchfn(f)
938 943
939 944 m.matchfn = matchfn
940 945 return m
941 946
942 947 with extensions.wrappedfunction(scmutil, b'match', overridematch):
943 948 orig(ui, repo, ctx, parents, *pats, **opts)
944 949
945 950 newstandins = lfutil.getstandinsstate(repo)
946 951 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
947 952 # lfdirstate should be 'normallookup'-ed for updated files,
948 953 # because reverting doesn't touch dirstate for 'normal' files
949 954 # when target revision is explicitly specified: in such case,
950 955 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
951 956 # of target (standin) file.
952 957 lfcommands.updatelfiles(
953 958 ui, repo, filelist, printmessage=False, normallookup=True
954 959 )
955 960
956 961
957 962 # after pulling changesets, we need to take some extra care to get
958 963 # largefiles updated remotely
959 964 @eh.wrapcommand(
960 965 b'pull',
961 966 opts=[
962 967 (
963 968 b'',
964 969 b'all-largefiles',
965 970 None,
966 971 _(b'download all pulled versions of largefiles (DEPRECATED)'),
967 972 ),
968 973 (
969 974 b'',
970 975 b'lfrev',
971 976 [],
972 977 _(b'download largefiles for these revisions'),
973 978 _(b'REV'),
974 979 ),
975 980 ],
976 981 )
977 982 def overridepull(orig, ui, repo, source=None, **opts):
978 983 revsprepull = len(repo)
979 984 if not source:
980 985 source = b'default'
981 986 repo.lfpullsource = source
982 987 result = orig(ui, repo, source, **opts)
983 988 revspostpull = len(repo)
984 989 lfrevs = opts.get('lfrev', [])
985 990 if opts.get('all_largefiles'):
986 991 lfrevs.append(b'pulled()')
987 992 if lfrevs and revspostpull > revsprepull:
988 993 numcached = 0
989 994 repo.firstpulled = revsprepull # for pulled() revset expression
990 995 try:
991 996 for rev in scmutil.revrange(repo, lfrevs):
992 997 ui.note(_(b'pulling largefiles for revision %d\n') % rev)
993 998 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
994 999 numcached += len(cached)
995 1000 finally:
996 1001 del repo.firstpulled
997 1002 ui.status(_(b"%d largefiles cached\n") % numcached)
998 1003 return result
999 1004
1000 1005
1001 1006 @eh.wrapcommand(
1002 1007 b'push',
1003 1008 opts=[
1004 1009 (
1005 1010 b'',
1006 1011 b'lfrev',
1007 1012 [],
1008 1013 _(b'upload largefiles for these revisions'),
1009 1014 _(b'REV'),
1010 1015 )
1011 1016 ],
1012 1017 )
1013 1018 def overridepush(orig, ui, repo, *args, **kwargs):
1014 1019 """Override push command and store --lfrev parameters in opargs"""
1015 1020 lfrevs = kwargs.pop('lfrev', None)
1016 1021 if lfrevs:
1017 1022 opargs = kwargs.setdefault('opargs', {})
1018 1023 opargs[b'lfrevs'] = scmutil.revrange(repo, lfrevs)
1019 1024 return orig(ui, repo, *args, **kwargs)
1020 1025
1021 1026
1022 1027 @eh.wrapfunction(exchange, b'pushoperation')
1023 1028 def exchangepushoperation(orig, *args, **kwargs):
1024 1029 """Override pushoperation constructor and store lfrevs parameter"""
1025 1030 lfrevs = kwargs.pop('lfrevs', None)
1026 1031 pushop = orig(*args, **kwargs)
1027 1032 pushop.lfrevs = lfrevs
1028 1033 return pushop
1029 1034
1030 1035
1031 1036 @eh.revsetpredicate(b'pulled()')
1032 1037 def pulledrevsetsymbol(repo, subset, x):
1033 1038 """Changesets that just has been pulled.
1034 1039
1035 1040 Only available with largefiles from pull --lfrev expressions.
1036 1041
1037 1042 .. container:: verbose
1038 1043
1039 1044 Some examples:
1040 1045
1041 1046 - pull largefiles for all new changesets::
1042 1047
1043 1048 hg pull -lfrev "pulled()"
1044 1049
1045 1050 - pull largefiles for all new branch heads::
1046 1051
1047 1052 hg pull -lfrev "head(pulled()) and not closed()"
1048 1053
1049 1054 """
1050 1055
1051 1056 try:
1052 1057 firstpulled = repo.firstpulled
1053 1058 except AttributeError:
1054 1059 raise error.Abort(_(b"pulled() only available in --lfrev"))
1055 1060 return smartset.baseset([r for r in subset if r >= firstpulled])
1056 1061
1057 1062
1058 1063 @eh.wrapcommand(
1059 1064 b'clone',
1060 1065 opts=[
1061 1066 (
1062 1067 b'',
1063 1068 b'all-largefiles',
1064 1069 None,
1065 1070 _(b'download all versions of all largefiles'),
1066 1071 )
1067 1072 ],
1068 1073 )
1069 1074 def overrideclone(orig, ui, source, dest=None, **opts):
1070 1075 d = dest
1071 1076 if d is None:
1072 1077 d = hg.defaultdest(source)
1073 1078 if opts.get('all_largefiles') and not hg.islocal(d):
1074 1079 raise error.Abort(
1075 1080 _(b'--all-largefiles is incompatible with non-local destination %s')
1076 1081 % d
1077 1082 )
1078 1083
1079 1084 return orig(ui, source, dest, **opts)
1080 1085
1081 1086
1082 1087 @eh.wrapfunction(hg, b'clone')
1083 1088 def hgclone(orig, ui, opts, *args, **kwargs):
1084 1089 result = orig(ui, opts, *args, **kwargs)
1085 1090
1086 1091 if result is not None:
1087 1092 sourcerepo, destrepo = result
1088 1093 repo = destrepo.local()
1089 1094
1090 1095 # When cloning to a remote repo (like through SSH), no repo is available
1091 1096 # from the peer. Therefore the largefiles can't be downloaded and the
1092 1097 # hgrc can't be updated.
1093 1098 if not repo:
1094 1099 return result
1095 1100
1096 1101 # Caching is implicitly limited to 'rev' option, since the dest repo was
1097 1102 # truncated at that point. The user may expect a download count with
1098 1103 # this option, so attempt whether or not this is a largefile repo.
1099 1104 if opts.get(b'all_largefiles'):
1100 1105 success, missing = lfcommands.downloadlfiles(ui, repo, None)
1101 1106
1102 1107 if missing != 0:
1103 1108 return None
1104 1109
1105 1110 return result
1106 1111
1107 1112
1108 1113 @eh.wrapcommand(b'rebase', extension=b'rebase')
1109 1114 def overriderebase(orig, ui, repo, **opts):
1110 1115 if not util.safehasattr(repo, b'_largefilesenabled'):
1111 1116 return orig(ui, repo, **opts)
1112 1117
1113 1118 resuming = opts.get('continue')
1114 1119 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1115 1120 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1116 1121 try:
1117 1122 return orig(ui, repo, **opts)
1118 1123 finally:
1119 1124 repo._lfstatuswriters.pop()
1120 1125 repo._lfcommithooks.pop()
1121 1126
1122 1127
1123 1128 @eh.wrapcommand(b'archive')
1124 1129 def overridearchivecmd(orig, ui, repo, dest, **opts):
1125 1130 with lfstatus(repo.unfiltered()):
1126 1131 return orig(ui, repo.unfiltered(), dest, **opts)
1127 1132
1128 1133
1129 1134 @eh.wrapfunction(webcommands, b'archive')
1130 1135 def hgwebarchive(orig, web):
1131 1136 with lfstatus(web.repo):
1132 1137 return orig(web)
1133 1138
1134 1139
1135 1140 @eh.wrapfunction(archival, b'archive')
1136 1141 def overridearchive(
1137 1142 orig,
1138 1143 repo,
1139 1144 dest,
1140 1145 node,
1141 1146 kind,
1142 1147 decode=True,
1143 1148 match=None,
1144 1149 prefix=b'',
1145 1150 mtime=None,
1146 1151 subrepos=None,
1147 1152 ):
1148 1153 # For some reason setting repo.lfstatus in hgwebarchive only changes the
1149 1154 # unfiltered repo's attr, so check that as well.
1150 1155 if not repo.lfstatus and not repo.unfiltered().lfstatus:
1151 1156 return orig(
1152 1157 repo, dest, node, kind, decode, match, prefix, mtime, subrepos
1153 1158 )
1154 1159
1155 1160 # No need to lock because we are only reading history and
1156 1161 # largefile caches, neither of which are modified.
1157 1162 if node is not None:
1158 1163 lfcommands.cachelfiles(repo.ui, repo, node)
1159 1164
1160 1165 if kind not in archival.archivers:
1161 1166 raise error.Abort(_(b"unknown archive type '%s'") % kind)
1162 1167
1163 1168 ctx = repo[node]
1164 1169
1165 1170 if kind == b'files':
1166 1171 if prefix:
1167 1172 raise error.Abort(_(b'cannot give prefix when archiving to files'))
1168 1173 else:
1169 1174 prefix = archival.tidyprefix(dest, kind, prefix)
1170 1175
1171 1176 def write(name, mode, islink, getdata):
1172 1177 if match and not match(name):
1173 1178 return
1174 1179 data = getdata()
1175 1180 if decode:
1176 1181 data = repo.wwritedata(name, data)
1177 1182 archiver.addfile(prefix + name, mode, islink, data)
1178 1183
1179 1184 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
1180 1185
1181 1186 if repo.ui.configbool(b"ui", b"archivemeta"):
1182 1187 write(
1183 1188 b'.hg_archival.txt',
1184 1189 0o644,
1185 1190 False,
1186 1191 lambda: archival.buildmetadata(ctx),
1187 1192 )
1188 1193
1189 1194 for f in ctx:
1190 1195 ff = ctx.flags(f)
1191 1196 getdata = ctx[f].data
1192 1197 lfile = lfutil.splitstandin(f)
1193 1198 if lfile is not None:
1194 1199 if node is not None:
1195 1200 path = lfutil.findfile(repo, getdata().strip())
1196 1201
1197 1202 if path is None:
1198 1203 raise error.Abort(
1199 1204 _(
1200 1205 b'largefile %s not found in repo store or system cache'
1201 1206 )
1202 1207 % lfile
1203 1208 )
1204 1209 else:
1205 1210 path = lfile
1206 1211
1207 1212 f = lfile
1208 1213
1209 1214 getdata = lambda: util.readfile(path)
1210 1215 write(f, b'x' in ff and 0o755 or 0o644, b'l' in ff, getdata)
1211 1216
1212 1217 if subrepos:
1213 1218 for subpath in sorted(ctx.substate):
1214 1219 sub = ctx.workingsub(subpath)
1215 1220 submatch = matchmod.subdirmatcher(subpath, match)
1216 1221 subprefix = prefix + subpath + b'/'
1217 1222
1218 1223 # TODO: Only hgsubrepo instances have `_repo`, so figure out how to
1219 1224 # infer and possibly set lfstatus in hgsubrepoarchive. That would
1220 1225 # allow only hgsubrepos to set this, instead of the current scheme
1221 1226 # where the parent sets this for the child.
1222 1227 with (
1223 1228 util.safehasattr(sub, '_repo')
1224 1229 and lfstatus(sub._repo)
1225 1230 or util.nullcontextmanager()
1226 1231 ):
1227 1232 sub.archive(archiver, subprefix, submatch)
1228 1233
1229 1234 archiver.done()
1230 1235
1231 1236
1232 1237 @eh.wrapfunction(subrepo.hgsubrepo, b'archive')
1233 1238 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1234 1239 lfenabled = util.safehasattr(repo._repo, b'_largefilesenabled')
1235 1240 if not lfenabled or not repo._repo.lfstatus:
1236 1241 return orig(repo, archiver, prefix, match, decode)
1237 1242
1238 1243 repo._get(repo._state + (b'hg',))
1239 1244 rev = repo._state[1]
1240 1245 ctx = repo._repo[rev]
1241 1246
1242 1247 if ctx.node() is not None:
1243 1248 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1244 1249
1245 1250 def write(name, mode, islink, getdata):
1246 1251 # At this point, the standin has been replaced with the largefile name,
1247 1252 # so the normal matcher works here without the lfutil variants.
1248 1253 if match and not match(f):
1249 1254 return
1250 1255 data = getdata()
1251 1256 if decode:
1252 1257 data = repo._repo.wwritedata(name, data)
1253 1258
1254 1259 archiver.addfile(prefix + name, mode, islink, data)
1255 1260
1256 1261 for f in ctx:
1257 1262 ff = ctx.flags(f)
1258 1263 getdata = ctx[f].data
1259 1264 lfile = lfutil.splitstandin(f)
1260 1265 if lfile is not None:
1261 1266 if ctx.node() is not None:
1262 1267 path = lfutil.findfile(repo._repo, getdata().strip())
1263 1268
1264 1269 if path is None:
1265 1270 raise error.Abort(
1266 1271 _(
1267 1272 b'largefile %s not found in repo store or system cache'
1268 1273 )
1269 1274 % lfile
1270 1275 )
1271 1276 else:
1272 1277 path = lfile
1273 1278
1274 1279 f = lfile
1275 1280
1276 1281 getdata = lambda: util.readfile(os.path.join(prefix, path))
1277 1282
1278 1283 write(f, b'x' in ff and 0o755 or 0o644, b'l' in ff, getdata)
1279 1284
1280 1285 for subpath in sorted(ctx.substate):
1281 1286 sub = ctx.workingsub(subpath)
1282 1287 submatch = matchmod.subdirmatcher(subpath, match)
1283 1288 subprefix = prefix + subpath + b'/'
1284 1289 # TODO: Only hgsubrepo instances have `_repo`, so figure out how to
1285 1290 # infer and possibly set lfstatus at the top of this function. That
1286 1291 # would allow only hgsubrepos to set this, instead of the current scheme
1287 1292 # where the parent sets this for the child.
1288 1293 with (
1289 1294 util.safehasattr(sub, '_repo')
1290 1295 and lfstatus(sub._repo)
1291 1296 or util.nullcontextmanager()
1292 1297 ):
1293 1298 sub.archive(archiver, subprefix, submatch, decode)
1294 1299
1295 1300
1296 1301 # If a largefile is modified, the change is not reflected in its
1297 1302 # standin until a commit. cmdutil.bailifchanged() raises an exception
1298 1303 # if the repo has uncommitted changes. Wrap it to also check if
1299 1304 # largefiles were changed. This is used by bisect, backout and fetch.
1300 1305 @eh.wrapfunction(cmdutil, b'bailifchanged')
1301 1306 def overridebailifchanged(orig, repo, *args, **kwargs):
1302 1307 orig(repo, *args, **kwargs)
1303 1308 with lfstatus(repo):
1304 1309 s = repo.status()
1305 1310 if s.modified or s.added or s.removed or s.deleted:
1306 1311 raise error.Abort(_(b'uncommitted changes'))
1307 1312
1308 1313
1309 1314 @eh.wrapfunction(cmdutil, b'postcommitstatus')
1310 1315 def postcommitstatus(orig, repo, *args, **kwargs):
1311 1316 with lfstatus(repo):
1312 1317 return orig(repo, *args, **kwargs)
1313 1318
1314 1319
1315 1320 @eh.wrapfunction(cmdutil, b'forget')
1316 1321 def cmdutilforget(
1317 1322 orig, ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
1318 1323 ):
1319 1324 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1320 1325 bad, forgot = orig(
1321 1326 ui,
1322 1327 repo,
1323 1328 normalmatcher,
1324 1329 prefix,
1325 1330 uipathfn,
1326 1331 explicitonly,
1327 1332 dryrun,
1328 1333 interactive,
1329 1334 )
1330 1335 m = composelargefilematcher(match, repo[None].manifest())
1331 1336
1332 1337 with lfstatus(repo):
1333 1338 s = repo.status(match=m, clean=True)
1334 1339 manifest = repo[None].manifest()
1335 1340 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1336 1341 forget = [f for f in forget if lfutil.standin(f) in manifest]
1337 1342
1338 1343 for f in forget:
1339 1344 fstandin = lfutil.standin(f)
1340 1345 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1341 1346 ui.warn(
1342 1347 _(b'not removing %s: file is already untracked\n') % uipathfn(f)
1343 1348 )
1344 1349 bad.append(f)
1345 1350
1346 1351 for f in forget:
1347 1352 if ui.verbose or not m.exact(f):
1348 1353 ui.status(_(b'removing %s\n') % uipathfn(f))
1349 1354
1350 1355 # Need to lock because standin files are deleted then removed from the
1351 1356 # repository and we could race in-between.
1352 1357 with repo.wlock():
1353 1358 lfdirstate = lfutil.openlfdirstate(ui, repo)
1354 1359 for f in forget:
1355 1360 if lfdirstate[f] == b'a':
1356 1361 lfdirstate.drop(f)
1357 1362 else:
1358 1363 lfdirstate.remove(f)
1359 1364 lfdirstate.write()
1360 1365 standins = [lfutil.standin(f) for f in forget]
1361 1366 for f in standins:
1362 1367 repo.wvfs.unlinkpath(f, ignoremissing=True)
1363 1368 rejected = repo[None].forget(standins)
1364 1369
1365 1370 bad.extend(f for f in rejected if f in m.files())
1366 1371 forgot.extend(f for f in forget if f not in rejected)
1367 1372 return bad, forgot
1368 1373
1369 1374
1370 1375 def _getoutgoings(repo, other, missing, addfunc):
1371 1376 """get pairs of filename and largefile hash in outgoing revisions
1372 1377 in 'missing'.
1373 1378
1374 1379 largefiles already existing on 'other' repository are ignored.
1375 1380
1376 1381 'addfunc' is invoked with each unique pairs of filename and
1377 1382 largefile hash value.
1378 1383 """
1379 1384 knowns = set()
1380 1385 lfhashes = set()
1381 1386
1382 1387 def dedup(fn, lfhash):
1383 1388 k = (fn, lfhash)
1384 1389 if k not in knowns:
1385 1390 knowns.add(k)
1386 1391 lfhashes.add(lfhash)
1387 1392
1388 1393 lfutil.getlfilestoupload(repo, missing, dedup)
1389 1394 if lfhashes:
1390 1395 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1391 1396 for fn, lfhash in knowns:
1392 1397 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1393 1398 addfunc(fn, lfhash)
1394 1399
1395 1400
1396 1401 def outgoinghook(ui, repo, other, opts, missing):
1397 1402 if opts.pop(b'large', None):
1398 1403 lfhashes = set()
1399 1404 if ui.debugflag:
1400 1405 toupload = {}
1401 1406
1402 1407 def addfunc(fn, lfhash):
1403 1408 if fn not in toupload:
1404 1409 toupload[fn] = []
1405 1410 toupload[fn].append(lfhash)
1406 1411 lfhashes.add(lfhash)
1407 1412
1408 1413 def showhashes(fn):
1409 1414 for lfhash in sorted(toupload[fn]):
1410 1415 ui.debug(b' %s\n' % lfhash)
1411 1416
1412 1417 else:
1413 1418 toupload = set()
1414 1419
1415 1420 def addfunc(fn, lfhash):
1416 1421 toupload.add(fn)
1417 1422 lfhashes.add(lfhash)
1418 1423
1419 1424 def showhashes(fn):
1420 1425 pass
1421 1426
1422 1427 _getoutgoings(repo, other, missing, addfunc)
1423 1428
1424 1429 if not toupload:
1425 1430 ui.status(_(b'largefiles: no files to upload\n'))
1426 1431 else:
1427 1432 ui.status(
1428 1433 _(b'largefiles to upload (%d entities):\n') % (len(lfhashes))
1429 1434 )
1430 1435 for file in sorted(toupload):
1431 1436 ui.status(lfutil.splitstandin(file) + b'\n')
1432 1437 showhashes(file)
1433 1438 ui.status(b'\n')
1434 1439
1435 1440
1436 1441 @eh.wrapcommand(
1437 1442 b'outgoing', opts=[(b'', b'large', None, _(b'display outgoing largefiles'))]
1438 1443 )
1439 1444 def _outgoingcmd(orig, *args, **kwargs):
1440 1445 # Nothing to do here other than add the extra help option- the hook above
1441 1446 # processes it.
1442 1447 return orig(*args, **kwargs)
1443 1448
1444 1449
1445 1450 def summaryremotehook(ui, repo, opts, changes):
1446 1451 largeopt = opts.get(b'large', False)
1447 1452 if changes is None:
1448 1453 if largeopt:
1449 1454 return (False, True) # only outgoing check is needed
1450 1455 else:
1451 1456 return (False, False)
1452 1457 elif largeopt:
1453 1458 url, branch, peer, outgoing = changes[1]
1454 1459 if peer is None:
1455 1460 # i18n: column positioning for "hg summary"
1456 1461 ui.status(_(b'largefiles: (no remote repo)\n'))
1457 1462 return
1458 1463
1459 1464 toupload = set()
1460 1465 lfhashes = set()
1461 1466
1462 1467 def addfunc(fn, lfhash):
1463 1468 toupload.add(fn)
1464 1469 lfhashes.add(lfhash)
1465 1470
1466 1471 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1467 1472
1468 1473 if not toupload:
1469 1474 # i18n: column positioning for "hg summary"
1470 1475 ui.status(_(b'largefiles: (no files to upload)\n'))
1471 1476 else:
1472 1477 # i18n: column positioning for "hg summary"
1473 1478 ui.status(
1474 1479 _(b'largefiles: %d entities for %d files to upload\n')
1475 1480 % (len(lfhashes), len(toupload))
1476 1481 )
1477 1482
1478 1483
1479 1484 @eh.wrapcommand(
1480 1485 b'summary', opts=[(b'', b'large', None, _(b'display outgoing largefiles'))]
1481 1486 )
1482 1487 def overridesummary(orig, ui, repo, *pats, **opts):
1483 1488 with lfstatus(repo):
1484 1489 orig(ui, repo, *pats, **opts)
1485 1490
1486 1491
1487 1492 @eh.wrapfunction(scmutil, b'addremove')
1488 1493 def scmutiladdremove(orig, repo, matcher, prefix, uipathfn, opts=None):
1489 1494 if opts is None:
1490 1495 opts = {}
1491 1496 if not lfutil.islfilesrepo(repo):
1492 1497 return orig(repo, matcher, prefix, uipathfn, opts)
1493 1498 # Get the list of missing largefiles so we can remove them
1494 1499 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1495 1500 unsure, s = lfdirstate.status(
1496 1501 matchmod.always(),
1497 1502 subrepos=[],
1498 1503 ignored=False,
1499 1504 clean=False,
1500 1505 unknown=False,
1501 1506 )
1502 1507
1503 1508 # Call into the normal remove code, but the removing of the standin, we want
1504 1509 # to have handled by original addremove. Monkey patching here makes sure
1505 1510 # we don't remove the standin in the largefiles code, preventing a very
1506 1511 # confused state later.
1507 1512 if s.deleted:
1508 1513 m = copy.copy(matcher)
1509 1514
1510 1515 # The m._files and m._map attributes are not changed to the deleted list
1511 1516 # because that affects the m.exact() test, which in turn governs whether
1512 1517 # or not the file name is printed, and how. Simply limit the original
1513 1518 # matches to those in the deleted status list.
1514 1519 matchfn = m.matchfn
1515 1520 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1516 1521
1517 1522 removelargefiles(
1518 1523 repo.ui,
1519 1524 repo,
1520 1525 True,
1521 1526 m,
1522 1527 uipathfn,
1523 1528 opts.get(b'dry_run'),
1524 1529 **pycompat.strkwargs(opts)
1525 1530 )
1526 1531 # Call into the normal add code, and any files that *should* be added as
1527 1532 # largefiles will be
1528 1533 added, bad = addlargefiles(
1529 1534 repo.ui, repo, True, matcher, uipathfn, **pycompat.strkwargs(opts)
1530 1535 )
1531 1536 # Now that we've handled largefiles, hand off to the original addremove
1532 1537 # function to take care of the rest. Make sure it doesn't do anything with
1533 1538 # largefiles by passing a matcher that will ignore them.
1534 1539 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1535 1540 return orig(repo, matcher, prefix, uipathfn, opts)
1536 1541
1537 1542
1538 1543 # Calling purge with --all will cause the largefiles to be deleted.
1539 1544 # Override repo.status to prevent this from happening.
1540 1545 @eh.wrapcommand(b'purge', extension=b'purge')
1541 1546 def overridepurge(orig, ui, repo, *dirs, **opts):
1542 1547 # XXX Monkey patching a repoview will not work. The assigned attribute will
1543 1548 # be set on the unfiltered repo, but we will only lookup attributes in the
1544 1549 # unfiltered repo if the lookup in the repoview object itself fails. As the
1545 1550 # monkey patched method exists on the repoview class the lookup will not
1546 1551 # fail. As a result, the original version will shadow the monkey patched
1547 1552 # one, defeating the monkey patch.
1548 1553 #
1549 1554 # As a work around we use an unfiltered repo here. We should do something
1550 1555 # cleaner instead.
1551 1556 repo = repo.unfiltered()
1552 1557 oldstatus = repo.status
1553 1558
1554 1559 def overridestatus(
1555 1560 node1=b'.',
1556 1561 node2=None,
1557 1562 match=None,
1558 1563 ignored=False,
1559 1564 clean=False,
1560 1565 unknown=False,
1561 1566 listsubrepos=False,
1562 1567 ):
1563 1568 r = oldstatus(
1564 1569 node1, node2, match, ignored, clean, unknown, listsubrepos
1565 1570 )
1566 1571 lfdirstate = lfutil.openlfdirstate(ui, repo)
1567 1572 unknown = [f for f in r.unknown if lfdirstate[f] == b'?']
1568 1573 ignored = [f for f in r.ignored if lfdirstate[f] == b'?']
1569 1574 return scmutil.status(
1570 1575 r.modified, r.added, r.removed, r.deleted, unknown, ignored, r.clean
1571 1576 )
1572 1577
1573 1578 repo.status = overridestatus
1574 1579 orig(ui, repo, *dirs, **opts)
1575 1580 repo.status = oldstatus
1576 1581
1577 1582
1578 1583 @eh.wrapcommand(b'rollback')
1579 1584 def overriderollback(orig, ui, repo, **opts):
1580 1585 with repo.wlock():
1581 1586 before = repo.dirstate.parents()
1582 1587 orphans = {
1583 1588 f
1584 1589 for f in repo.dirstate
1585 1590 if lfutil.isstandin(f) and repo.dirstate[f] != b'r'
1586 1591 }
1587 1592 result = orig(ui, repo, **opts)
1588 1593 after = repo.dirstate.parents()
1589 1594 if before == after:
1590 1595 return result # no need to restore standins
1591 1596
1592 1597 pctx = repo[b'.']
1593 1598 for f in repo.dirstate:
1594 1599 if lfutil.isstandin(f):
1595 1600 orphans.discard(f)
1596 1601 if repo.dirstate[f] == b'r':
1597 1602 repo.wvfs.unlinkpath(f, ignoremissing=True)
1598 1603 elif f in pctx:
1599 1604 fctx = pctx[f]
1600 1605 repo.wwrite(f, fctx.data(), fctx.flags())
1601 1606 else:
1602 1607 # content of standin is not so important in 'a',
1603 1608 # 'm' or 'n' (coming from the 2nd parent) cases
1604 1609 lfutil.writestandin(repo, f, b'', False)
1605 1610 for standin in orphans:
1606 1611 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1607 1612
1608 1613 lfdirstate = lfutil.openlfdirstate(ui, repo)
1609 1614 orphans = set(lfdirstate)
1610 1615 lfiles = lfutil.listlfiles(repo)
1611 1616 for file in lfiles:
1612 1617 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1613 1618 orphans.discard(file)
1614 1619 for lfile in orphans:
1615 1620 lfdirstate.drop(lfile)
1616 1621 lfdirstate.write()
1617 1622 return result
1618 1623
1619 1624
1620 1625 @eh.wrapcommand(b'transplant', extension=b'transplant')
1621 1626 def overridetransplant(orig, ui, repo, *revs, **opts):
1622 1627 resuming = opts.get('continue')
1623 1628 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1624 1629 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1625 1630 try:
1626 1631 result = orig(ui, repo, *revs, **opts)
1627 1632 finally:
1628 1633 repo._lfstatuswriters.pop()
1629 1634 repo._lfcommithooks.pop()
1630 1635 return result
1631 1636
1632 1637
1633 1638 @eh.wrapcommand(b'cat')
1634 1639 def overridecat(orig, ui, repo, file1, *pats, **opts):
1635 1640 opts = pycompat.byteskwargs(opts)
1636 1641 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
1637 1642 err = 1
1638 1643 notbad = set()
1639 1644 m = scmutil.match(ctx, (file1,) + pats, opts)
1640 1645 origmatchfn = m.matchfn
1641 1646
1642 1647 def lfmatchfn(f):
1643 1648 if origmatchfn(f):
1644 1649 return True
1645 1650 lf = lfutil.splitstandin(f)
1646 1651 if lf is None:
1647 1652 return False
1648 1653 notbad.add(lf)
1649 1654 return origmatchfn(lf)
1650 1655
1651 1656 m.matchfn = lfmatchfn
1652 1657 origbadfn = m.bad
1653 1658
1654 1659 def lfbadfn(f, msg):
1655 1660 if not f in notbad:
1656 1661 origbadfn(f, msg)
1657 1662
1658 1663 m.bad = lfbadfn
1659 1664
1660 1665 origvisitdirfn = m.visitdir
1661 1666
1662 1667 def lfvisitdirfn(dir):
1663 1668 if dir == lfutil.shortname:
1664 1669 return True
1665 1670 ret = origvisitdirfn(dir)
1666 1671 if ret:
1667 1672 return ret
1668 1673 lf = lfutil.splitstandin(dir)
1669 1674 if lf is None:
1670 1675 return False
1671 1676 return origvisitdirfn(lf)
1672 1677
1673 1678 m.visitdir = lfvisitdirfn
1674 1679
1675 1680 for f in ctx.walk(m):
1676 1681 with cmdutil.makefileobj(ctx, opts.get(b'output'), pathname=f) as fp:
1677 1682 lf = lfutil.splitstandin(f)
1678 1683 if lf is None or origmatchfn(f):
1679 1684 # duplicating unreachable code from commands.cat
1680 1685 data = ctx[f].data()
1681 1686 if opts.get(b'decode'):
1682 1687 data = repo.wwritedata(f, data)
1683 1688 fp.write(data)
1684 1689 else:
1685 1690 hash = lfutil.readasstandin(ctx[f])
1686 1691 if not lfutil.inusercache(repo.ui, hash):
1687 1692 store = storefactory.openstore(repo)
1688 1693 success, missing = store.get([(lf, hash)])
1689 1694 if len(success) != 1:
1690 1695 raise error.Abort(
1691 1696 _(
1692 1697 b'largefile %s is not in cache and could not be '
1693 1698 b'downloaded'
1694 1699 )
1695 1700 % lf
1696 1701 )
1697 1702 path = lfutil.usercachepath(repo.ui, hash)
1698 1703 with open(path, b"rb") as fpin:
1699 1704 for chunk in util.filechunkiter(fpin):
1700 1705 fp.write(chunk)
1701 1706 err = 0
1702 1707 return err
1703 1708
1704 1709
1705 1710 @eh.wrapfunction(merge, b'update')
1706 1711 def mergeupdate(orig, repo, node, branchmerge, force, *args, **kwargs):
1707 1712 matcher = kwargs.get('matcher', None)
1708 1713 # note if this is a partial update
1709 1714 partial = matcher and not matcher.always()
1710 1715 with repo.wlock():
1711 1716 # branch | | |
1712 1717 # merge | force | partial | action
1713 1718 # -------+-------+---------+--------------
1714 1719 # x | x | x | linear-merge
1715 1720 # o | x | x | branch-merge
1716 1721 # x | o | x | overwrite (as clean update)
1717 1722 # o | o | x | force-branch-merge (*1)
1718 1723 # x | x | o | (*)
1719 1724 # o | x | o | (*)
1720 1725 # x | o | o | overwrite (as revert)
1721 1726 # o | o | o | (*)
1722 1727 #
1723 1728 # (*) don't care
1724 1729 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1725 1730
1726 1731 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1727 1732 unsure, s = lfdirstate.status(
1728 1733 matchmod.always(),
1729 1734 subrepos=[],
1730 1735 ignored=False,
1731 1736 clean=True,
1732 1737 unknown=False,
1733 1738 )
1734 1739 oldclean = set(s.clean)
1735 1740 pctx = repo[b'.']
1736 1741 dctx = repo[node]
1737 1742 for lfile in unsure + s.modified:
1738 1743 lfileabs = repo.wvfs.join(lfile)
1739 1744 if not repo.wvfs.exists(lfileabs):
1740 1745 continue
1741 1746 lfhash = lfutil.hashfile(lfileabs)
1742 1747 standin = lfutil.standin(lfile)
1743 1748 lfutil.writestandin(
1744 1749 repo, standin, lfhash, lfutil.getexecutable(lfileabs)
1745 1750 )
1746 1751 if standin in pctx and lfhash == lfutil.readasstandin(
1747 1752 pctx[standin]
1748 1753 ):
1749 1754 oldclean.add(lfile)
1750 1755 for lfile in s.added:
1751 1756 fstandin = lfutil.standin(lfile)
1752 1757 if fstandin not in dctx:
1753 1758 # in this case, content of standin file is meaningless
1754 1759 # (in dctx, lfile is unknown, or normal file)
1755 1760 continue
1756 1761 lfutil.updatestandin(repo, lfile, fstandin)
1757 1762 # mark all clean largefiles as dirty, just in case the update gets
1758 1763 # interrupted before largefiles and lfdirstate are synchronized
1759 1764 for lfile in oldclean:
1760 1765 lfdirstate.normallookup(lfile)
1761 1766 lfdirstate.write()
1762 1767
1763 1768 oldstandins = lfutil.getstandinsstate(repo)
1764 1769 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1765 1770 # good candidate for in-memory merge (large files, custom dirstate,
1766 1771 # matcher usage).
1767 1772 kwargs['wc'] = repo[None]
1768 1773 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1769 1774
1770 1775 newstandins = lfutil.getstandinsstate(repo)
1771 1776 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1772 1777
1773 1778 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1774 1779 # all the ones that didn't change as clean
1775 1780 for lfile in oldclean.difference(filelist):
1776 1781 lfdirstate.normal(lfile)
1777 1782 lfdirstate.write()
1778 1783
1779 1784 if branchmerge or force or partial:
1780 1785 filelist.extend(s.deleted + s.removed)
1781 1786
1782 1787 lfcommands.updatelfiles(
1783 1788 repo.ui, repo, filelist=filelist, normallookup=partial
1784 1789 )
1785 1790
1786 1791 return result
1787 1792
1788 1793
1789 1794 @eh.wrapfunction(scmutil, b'marktouched')
1790 1795 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1791 1796 result = orig(repo, files, *args, **kwargs)
1792 1797
1793 1798 filelist = []
1794 1799 for f in files:
1795 1800 lf = lfutil.splitstandin(f)
1796 1801 if lf is not None:
1797 1802 filelist.append(lf)
1798 1803 if filelist:
1799 1804 lfcommands.updatelfiles(
1800 1805 repo.ui,
1801 1806 repo,
1802 1807 filelist=filelist,
1803 1808 printmessage=False,
1804 1809 normallookup=True,
1805 1810 )
1806 1811
1807 1812 return result
1808 1813
1809 1814
1810 1815 @eh.wrapfunction(upgrade, b'preservedrequirements')
1811 1816 @eh.wrapfunction(upgrade, b'supporteddestrequirements')
1812 1817 def upgraderequirements(orig, repo):
1813 1818 reqs = orig(repo)
1814 1819 if b'largefiles' in repo.requirements:
1815 1820 reqs.add(b'largefiles')
1816 1821 return reqs
1817 1822
1818 1823
1819 1824 _lfscheme = b'largefile://'
1820 1825
1821 1826
1822 1827 @eh.wrapfunction(urlmod, b'open')
1823 1828 def openlargefile(orig, ui, url_, data=None):
1824 1829 if url_.startswith(_lfscheme):
1825 1830 if data:
1826 1831 msg = b"cannot use data on a 'largefile://' url"
1827 1832 raise error.ProgrammingError(msg)
1828 1833 lfid = url_[len(_lfscheme) :]
1829 1834 return storefactory.getlfile(ui, lfid)
1830 1835 else:
1831 1836 return orig(ui, url_, data=data)
General Comments 0
You need to be logged in to leave comments. Login now