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