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