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