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