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