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