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