##// END OF EJS Templates
largefiles: use wrappedfunction() for util.copyfile() override...
Martin von Zweigbergk -
r41720:028bb170 default
parent child Browse files
Show More
@@ -1,1544 +1,1539 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 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 655 def overridematch(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 667 match = oldmatch(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 681 oldmatch = installmatchfn(overridematch)
682 682 listpats = []
683 683 for pat in pats:
684 684 if matchmod.patkind(pat) is not None:
685 685 listpats.append(pat)
686 686 else:
687 687 listpats.append(makestandin(pat))
688 688
689 try:
690 origcopyfile = util.copyfile
691 copiedfiles = []
692 def overridecopyfile(src, dest, *args, **kwargs):
693 if (lfutil.shortname in src and
694 dest.startswith(repo.wjoin(lfutil.shortname))):
695 destlfile = dest.replace(lfutil.shortname, '')
696 if not opts['force'] and os.path.exists(destlfile):
697 raise IOError('',
698 _('destination largefile already exists'))
699 copiedfiles.append((src, dest))
700 origcopyfile(src, dest, *args, **kwargs)
701
702 util.copyfile = overridecopyfile
689 copiedfiles = []
690 def overridecopyfile(orig, src, dest, *args, **kwargs):
691 if (lfutil.shortname in src and
692 dest.startswith(repo.wjoin(lfutil.shortname))):
693 destlfile = dest.replace(lfutil.shortname, '')
694 if not opts['force'] and os.path.exists(destlfile):
695 raise IOError('',
696 _('destination largefile already exists'))
697 copiedfiles.append((src, dest))
698 orig(src, dest, *args, **kwargs)
699 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile):
703 700 result += orig(ui, repo, listpats, opts, rename)
704 finally:
705 util.copyfile = origcopyfile
706 701
707 702 lfdirstate = lfutil.openlfdirstate(ui, repo)
708 703 for (src, dest) in copiedfiles:
709 704 if (lfutil.shortname in src and
710 705 dest.startswith(repo.wjoin(lfutil.shortname))):
711 706 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
712 707 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
713 708 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
714 709 if not os.path.isdir(destlfiledir):
715 710 os.makedirs(destlfiledir)
716 711 if rename:
717 712 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
718 713
719 714 # The file is gone, but this deletes any empty parent
720 715 # directories as a side-effect.
721 716 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
722 717 lfdirstate.remove(srclfile)
723 718 else:
724 719 util.copyfile(repo.wjoin(srclfile),
725 720 repo.wjoin(destlfile))
726 721
727 722 lfdirstate.add(destlfile)
728 723 lfdirstate.write()
729 724 except error.Abort as e:
730 725 if pycompat.bytestr(e) != _('no files to copy'):
731 726 raise e
732 727 else:
733 728 nolfiles = True
734 729 finally:
735 730 restorematchfn()
736 731 wlock.release()
737 732
738 733 if nolfiles and nonormalfiles:
739 734 raise error.Abort(_('no files to copy'))
740 735
741 736 return result
742 737
743 738 # When the user calls revert, we have to be careful to not revert any
744 739 # changes to other largefiles accidentally. This means we have to keep
745 740 # track of the largefiles that are being reverted so we only pull down
746 741 # the necessary largefiles.
747 742 #
748 743 # Standins are only updated (to match the hash of largefiles) before
749 744 # commits. Update the standins then run the original revert, changing
750 745 # the matcher to hit standins instead of largefiles. Based on the
751 746 # resulting standins update the largefiles.
752 747 @eh.wrapfunction(cmdutil, 'revert')
753 748 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
754 749 # Because we put the standins in a bad state (by updating them)
755 750 # and then return them to a correct state we need to lock to
756 751 # prevent others from changing them in their incorrect state.
757 752 with repo.wlock():
758 753 lfdirstate = lfutil.openlfdirstate(ui, repo)
759 754 s = lfutil.lfdirstatestatus(lfdirstate, repo)
760 755 lfdirstate.write()
761 756 for lfile in s.modified:
762 757 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
763 758 for lfile in s.deleted:
764 759 fstandin = lfutil.standin(lfile)
765 760 if (repo.wvfs.exists(fstandin)):
766 761 repo.wvfs.unlink(fstandin)
767 762
768 763 oldstandins = lfutil.getstandinsstate(repo)
769 764
770 765 def overridematch(mctx, pats=(), opts=None, globbed=False,
771 766 default='relpath', badfn=None):
772 767 if opts is None:
773 768 opts = {}
774 769 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
775 770 m = copy.copy(match)
776 771
777 772 # revert supports recursing into subrepos, and though largefiles
778 773 # currently doesn't work correctly in that case, this match is
779 774 # called, so the lfdirstate above may not be the correct one for
780 775 # this invocation of match.
781 776 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
782 777 False)
783 778
784 779 wctx = repo[None]
785 780 matchfiles = []
786 781 for f in m._files:
787 782 standin = lfutil.standin(f)
788 783 if standin in ctx or standin in mctx:
789 784 matchfiles.append(standin)
790 785 elif standin in wctx or lfdirstate[f] == 'r':
791 786 continue
792 787 else:
793 788 matchfiles.append(f)
794 789 m._files = matchfiles
795 790 m._fileset = set(m._files)
796 791 origmatchfn = m.matchfn
797 792 def matchfn(f):
798 793 lfile = lfutil.splitstandin(f)
799 794 if lfile is not None:
800 795 return (origmatchfn(lfile) and
801 796 (f in ctx or f in mctx))
802 797 return origmatchfn(f)
803 798 m.matchfn = matchfn
804 799 return m
805 800 oldmatch = installmatchfn(overridematch)
806 801 try:
807 802 orig(ui, repo, ctx, parents, *pats, **opts)
808 803 finally:
809 804 restorematchfn()
810 805
811 806 newstandins = lfutil.getstandinsstate(repo)
812 807 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
813 808 # lfdirstate should be 'normallookup'-ed for updated files,
814 809 # because reverting doesn't touch dirstate for 'normal' files
815 810 # when target revision is explicitly specified: in such case,
816 811 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
817 812 # of target (standin) file.
818 813 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
819 814 normallookup=True)
820 815
821 816 # after pulling changesets, we need to take some extra care to get
822 817 # largefiles updated remotely
823 818 @eh.wrapcommand('pull',
824 819 opts=[('', 'all-largefiles', None,
825 820 _('download all pulled versions of largefiles (DEPRECATED)')),
826 821 ('', 'lfrev', [],
827 822 _('download largefiles for these revisions'), _('REV'))])
828 823 def overridepull(orig, ui, repo, source=None, **opts):
829 824 revsprepull = len(repo)
830 825 if not source:
831 826 source = 'default'
832 827 repo.lfpullsource = source
833 828 result = orig(ui, repo, source, **opts)
834 829 revspostpull = len(repo)
835 830 lfrevs = opts.get(r'lfrev', [])
836 831 if opts.get(r'all_largefiles'):
837 832 lfrevs.append('pulled()')
838 833 if lfrevs and revspostpull > revsprepull:
839 834 numcached = 0
840 835 repo.firstpulled = revsprepull # for pulled() revset expression
841 836 try:
842 837 for rev in scmutil.revrange(repo, lfrevs):
843 838 ui.note(_('pulling largefiles for revision %d\n') % rev)
844 839 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
845 840 numcached += len(cached)
846 841 finally:
847 842 del repo.firstpulled
848 843 ui.status(_("%d largefiles cached\n") % numcached)
849 844 return result
850 845
851 846 @eh.wrapcommand('push',
852 847 opts=[('', 'lfrev', [],
853 848 _('upload largefiles for these revisions'), _('REV'))])
854 849 def overridepush(orig, ui, repo, *args, **kwargs):
855 850 """Override push command and store --lfrev parameters in opargs"""
856 851 lfrevs = kwargs.pop(r'lfrev', None)
857 852 if lfrevs:
858 853 opargs = kwargs.setdefault(r'opargs', {})
859 854 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
860 855 return orig(ui, repo, *args, **kwargs)
861 856
862 857 @eh.wrapfunction(exchange, 'pushoperation')
863 858 def exchangepushoperation(orig, *args, **kwargs):
864 859 """Override pushoperation constructor and store lfrevs parameter"""
865 860 lfrevs = kwargs.pop(r'lfrevs', None)
866 861 pushop = orig(*args, **kwargs)
867 862 pushop.lfrevs = lfrevs
868 863 return pushop
869 864
870 865 @eh.revsetpredicate('pulled()')
871 866 def pulledrevsetsymbol(repo, subset, x):
872 867 """Changesets that just has been pulled.
873 868
874 869 Only available with largefiles from pull --lfrev expressions.
875 870
876 871 .. container:: verbose
877 872
878 873 Some examples:
879 874
880 875 - pull largefiles for all new changesets::
881 876
882 877 hg pull -lfrev "pulled()"
883 878
884 879 - pull largefiles for all new branch heads::
885 880
886 881 hg pull -lfrev "head(pulled()) and not closed()"
887 882
888 883 """
889 884
890 885 try:
891 886 firstpulled = repo.firstpulled
892 887 except AttributeError:
893 888 raise error.Abort(_("pulled() only available in --lfrev"))
894 889 return smartset.baseset([r for r in subset if r >= firstpulled])
895 890
896 891 @eh.wrapcommand('clone',
897 892 opts=[('', 'all-largefiles', None,
898 893 _('download all versions of all largefiles'))])
899 894 def overrideclone(orig, ui, source, dest=None, **opts):
900 895 d = dest
901 896 if d is None:
902 897 d = hg.defaultdest(source)
903 898 if opts.get(r'all_largefiles') and not hg.islocal(d):
904 899 raise error.Abort(_(
905 900 '--all-largefiles is incompatible with non-local destination %s') %
906 901 d)
907 902
908 903 return orig(ui, source, dest, **opts)
909 904
910 905 @eh.wrapfunction(hg, 'clone')
911 906 def hgclone(orig, ui, opts, *args, **kwargs):
912 907 result = orig(ui, opts, *args, **kwargs)
913 908
914 909 if result is not None:
915 910 sourcerepo, destrepo = result
916 911 repo = destrepo.local()
917 912
918 913 # When cloning to a remote repo (like through SSH), no repo is available
919 914 # from the peer. Therefore the largefiles can't be downloaded and the
920 915 # hgrc can't be updated.
921 916 if not repo:
922 917 return result
923 918
924 919 # Caching is implicitly limited to 'rev' option, since the dest repo was
925 920 # truncated at that point. The user may expect a download count with
926 921 # this option, so attempt whether or not this is a largefile repo.
927 922 if opts.get('all_largefiles'):
928 923 success, missing = lfcommands.downloadlfiles(ui, repo, None)
929 924
930 925 if missing != 0:
931 926 return None
932 927
933 928 return result
934 929
935 930 @eh.wrapcommand('rebase', extension='rebase')
936 931 def overriderebase(orig, ui, repo, **opts):
937 932 if not util.safehasattr(repo, '_largefilesenabled'):
938 933 return orig(ui, repo, **opts)
939 934
940 935 resuming = opts.get(r'continue')
941 936 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
942 937 repo._lfstatuswriters.append(lambda *msg, **opts: None)
943 938 try:
944 939 return orig(ui, repo, **opts)
945 940 finally:
946 941 repo._lfstatuswriters.pop()
947 942 repo._lfcommithooks.pop()
948 943
949 944 @eh.wrapcommand('archive')
950 945 def overridearchivecmd(orig, ui, repo, dest, **opts):
951 946 repo.unfiltered().lfstatus = True
952 947
953 948 try:
954 949 return orig(ui, repo.unfiltered(), dest, **opts)
955 950 finally:
956 951 repo.unfiltered().lfstatus = False
957 952
958 953 @eh.wrapfunction(webcommands, 'archive')
959 954 def hgwebarchive(orig, web):
960 955 web.repo.lfstatus = True
961 956
962 957 try:
963 958 return orig(web)
964 959 finally:
965 960 web.repo.lfstatus = False
966 961
967 962 @eh.wrapfunction(archival, 'archive')
968 963 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
969 964 prefix='', mtime=None, subrepos=None):
970 965 # For some reason setting repo.lfstatus in hgwebarchive only changes the
971 966 # unfiltered repo's attr, so check that as well.
972 967 if not repo.lfstatus and not repo.unfiltered().lfstatus:
973 968 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
974 969 subrepos)
975 970
976 971 # No need to lock because we are only reading history and
977 972 # largefile caches, neither of which are modified.
978 973 if node is not None:
979 974 lfcommands.cachelfiles(repo.ui, repo, node)
980 975
981 976 if kind not in archival.archivers:
982 977 raise error.Abort(_("unknown archive type '%s'") % kind)
983 978
984 979 ctx = repo[node]
985 980
986 981 if kind == 'files':
987 982 if prefix:
988 983 raise error.Abort(
989 984 _('cannot give prefix when archiving to files'))
990 985 else:
991 986 prefix = archival.tidyprefix(dest, kind, prefix)
992 987
993 988 def write(name, mode, islink, getdata):
994 989 if match and not match(name):
995 990 return
996 991 data = getdata()
997 992 if decode:
998 993 data = repo.wwritedata(name, data)
999 994 archiver.addfile(prefix + name, mode, islink, data)
1000 995
1001 996 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
1002 997
1003 998 if repo.ui.configbool("ui", "archivemeta"):
1004 999 write('.hg_archival.txt', 0o644, False,
1005 1000 lambda: archival.buildmetadata(ctx))
1006 1001
1007 1002 for f in ctx:
1008 1003 ff = ctx.flags(f)
1009 1004 getdata = ctx[f].data
1010 1005 lfile = lfutil.splitstandin(f)
1011 1006 if lfile is not None:
1012 1007 if node is not None:
1013 1008 path = lfutil.findfile(repo, getdata().strip())
1014 1009
1015 1010 if path is None:
1016 1011 raise error.Abort(
1017 1012 _('largefile %s not found in repo store or system cache')
1018 1013 % lfile)
1019 1014 else:
1020 1015 path = lfile
1021 1016
1022 1017 f = lfile
1023 1018
1024 1019 getdata = lambda: util.readfile(path)
1025 1020 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1026 1021
1027 1022 if subrepos:
1028 1023 for subpath in sorted(ctx.substate):
1029 1024 sub = ctx.workingsub(subpath)
1030 1025 submatch = matchmod.subdirmatcher(subpath, match)
1031 1026 sub._repo.lfstatus = True
1032 1027 sub.archive(archiver, prefix, submatch)
1033 1028
1034 1029 archiver.done()
1035 1030
1036 1031 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1037 1032 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1038 1033 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1039 1034 if not lfenabled or not repo._repo.lfstatus:
1040 1035 return orig(repo, archiver, prefix, match, decode)
1041 1036
1042 1037 repo._get(repo._state + ('hg',))
1043 1038 rev = repo._state[1]
1044 1039 ctx = repo._repo[rev]
1045 1040
1046 1041 if ctx.node() is not None:
1047 1042 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1048 1043
1049 1044 def write(name, mode, islink, getdata):
1050 1045 # At this point, the standin has been replaced with the largefile name,
1051 1046 # so the normal matcher works here without the lfutil variants.
1052 1047 if match and not match(f):
1053 1048 return
1054 1049 data = getdata()
1055 1050 if decode:
1056 1051 data = repo._repo.wwritedata(name, data)
1057 1052
1058 1053 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1059 1054
1060 1055 for f in ctx:
1061 1056 ff = ctx.flags(f)
1062 1057 getdata = ctx[f].data
1063 1058 lfile = lfutil.splitstandin(f)
1064 1059 if lfile is not None:
1065 1060 if ctx.node() is not None:
1066 1061 path = lfutil.findfile(repo._repo, getdata().strip())
1067 1062
1068 1063 if path is None:
1069 1064 raise error.Abort(
1070 1065 _('largefile %s not found in repo store or system cache')
1071 1066 % lfile)
1072 1067 else:
1073 1068 path = lfile
1074 1069
1075 1070 f = lfile
1076 1071
1077 1072 getdata = lambda: util.readfile(os.path.join(prefix, path))
1078 1073
1079 1074 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1080 1075
1081 1076 for subpath in sorted(ctx.substate):
1082 1077 sub = ctx.workingsub(subpath)
1083 1078 submatch = matchmod.subdirmatcher(subpath, match)
1084 1079 sub._repo.lfstatus = True
1085 1080 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1086 1081
1087 1082 # If a largefile is modified, the change is not reflected in its
1088 1083 # standin until a commit. cmdutil.bailifchanged() raises an exception
1089 1084 # if the repo has uncommitted changes. Wrap it to also check if
1090 1085 # largefiles were changed. This is used by bisect, backout and fetch.
1091 1086 @eh.wrapfunction(cmdutil, 'bailifchanged')
1092 1087 def overridebailifchanged(orig, repo, *args, **kwargs):
1093 1088 orig(repo, *args, **kwargs)
1094 1089 repo.lfstatus = True
1095 1090 s = repo.status()
1096 1091 repo.lfstatus = False
1097 1092 if s.modified or s.added or s.removed or s.deleted:
1098 1093 raise error.Abort(_('uncommitted changes'))
1099 1094
1100 1095 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1101 1096 def postcommitstatus(orig, repo, *args, **kwargs):
1102 1097 repo.lfstatus = True
1103 1098 try:
1104 1099 return orig(repo, *args, **kwargs)
1105 1100 finally:
1106 1101 repo.lfstatus = False
1107 1102
1108 1103 @eh.wrapfunction(cmdutil, 'forget')
1109 1104 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1110 1105 interactive):
1111 1106 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1112 1107 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1113 1108 interactive)
1114 1109 m = composelargefilematcher(match, repo[None].manifest())
1115 1110
1116 1111 try:
1117 1112 repo.lfstatus = True
1118 1113 s = repo.status(match=m, clean=True)
1119 1114 finally:
1120 1115 repo.lfstatus = False
1121 1116 manifest = repo[None].manifest()
1122 1117 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1123 1118 forget = [f for f in forget if lfutil.standin(f) in manifest]
1124 1119
1125 1120 for f in forget:
1126 1121 fstandin = lfutil.standin(f)
1127 1122 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1128 1123 ui.warn(_('not removing %s: file is already untracked\n')
1129 1124 % m.rel(f))
1130 1125 bad.append(f)
1131 1126
1132 1127 for f in forget:
1133 1128 if ui.verbose or not m.exact(f):
1134 1129 ui.status(_('removing %s\n') % m.rel(f))
1135 1130
1136 1131 # Need to lock because standin files are deleted then removed from the
1137 1132 # repository and we could race in-between.
1138 1133 with repo.wlock():
1139 1134 lfdirstate = lfutil.openlfdirstate(ui, repo)
1140 1135 for f in forget:
1141 1136 if lfdirstate[f] == 'a':
1142 1137 lfdirstate.drop(f)
1143 1138 else:
1144 1139 lfdirstate.remove(f)
1145 1140 lfdirstate.write()
1146 1141 standins = [lfutil.standin(f) for f in forget]
1147 1142 for f in standins:
1148 1143 repo.wvfs.unlinkpath(f, ignoremissing=True)
1149 1144 rejected = repo[None].forget(standins)
1150 1145
1151 1146 bad.extend(f for f in rejected if f in m.files())
1152 1147 forgot.extend(f for f in forget if f not in rejected)
1153 1148 return bad, forgot
1154 1149
1155 1150 def _getoutgoings(repo, other, missing, addfunc):
1156 1151 """get pairs of filename and largefile hash in outgoing revisions
1157 1152 in 'missing'.
1158 1153
1159 1154 largefiles already existing on 'other' repository are ignored.
1160 1155
1161 1156 'addfunc' is invoked with each unique pairs of filename and
1162 1157 largefile hash value.
1163 1158 """
1164 1159 knowns = set()
1165 1160 lfhashes = set()
1166 1161 def dedup(fn, lfhash):
1167 1162 k = (fn, lfhash)
1168 1163 if k not in knowns:
1169 1164 knowns.add(k)
1170 1165 lfhashes.add(lfhash)
1171 1166 lfutil.getlfilestoupload(repo, missing, dedup)
1172 1167 if lfhashes:
1173 1168 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1174 1169 for fn, lfhash in knowns:
1175 1170 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1176 1171 addfunc(fn, lfhash)
1177 1172
1178 1173 def outgoinghook(ui, repo, other, opts, missing):
1179 1174 if opts.pop('large', None):
1180 1175 lfhashes = set()
1181 1176 if ui.debugflag:
1182 1177 toupload = {}
1183 1178 def addfunc(fn, lfhash):
1184 1179 if fn not in toupload:
1185 1180 toupload[fn] = []
1186 1181 toupload[fn].append(lfhash)
1187 1182 lfhashes.add(lfhash)
1188 1183 def showhashes(fn):
1189 1184 for lfhash in sorted(toupload[fn]):
1190 1185 ui.debug(' %s\n' % (lfhash))
1191 1186 else:
1192 1187 toupload = set()
1193 1188 def addfunc(fn, lfhash):
1194 1189 toupload.add(fn)
1195 1190 lfhashes.add(lfhash)
1196 1191 def showhashes(fn):
1197 1192 pass
1198 1193 _getoutgoings(repo, other, missing, addfunc)
1199 1194
1200 1195 if not toupload:
1201 1196 ui.status(_('largefiles: no files to upload\n'))
1202 1197 else:
1203 1198 ui.status(_('largefiles to upload (%d entities):\n')
1204 1199 % (len(lfhashes)))
1205 1200 for file in sorted(toupload):
1206 1201 ui.status(lfutil.splitstandin(file) + '\n')
1207 1202 showhashes(file)
1208 1203 ui.status('\n')
1209 1204
1210 1205 @eh.wrapcommand('outgoing',
1211 1206 opts=[('', 'large', None, _('display outgoing largefiles'))])
1212 1207 def _outgoingcmd(orig, *args, **kwargs):
1213 1208 # Nothing to do here other than add the extra help option- the hook above
1214 1209 # processes it.
1215 1210 return orig(*args, **kwargs)
1216 1211
1217 1212 def summaryremotehook(ui, repo, opts, changes):
1218 1213 largeopt = opts.get('large', False)
1219 1214 if changes is None:
1220 1215 if largeopt:
1221 1216 return (False, True) # only outgoing check is needed
1222 1217 else:
1223 1218 return (False, False)
1224 1219 elif largeopt:
1225 1220 url, branch, peer, outgoing = changes[1]
1226 1221 if peer is None:
1227 1222 # i18n: column positioning for "hg summary"
1228 1223 ui.status(_('largefiles: (no remote repo)\n'))
1229 1224 return
1230 1225
1231 1226 toupload = set()
1232 1227 lfhashes = set()
1233 1228 def addfunc(fn, lfhash):
1234 1229 toupload.add(fn)
1235 1230 lfhashes.add(lfhash)
1236 1231 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1237 1232
1238 1233 if not toupload:
1239 1234 # i18n: column positioning for "hg summary"
1240 1235 ui.status(_('largefiles: (no files to upload)\n'))
1241 1236 else:
1242 1237 # i18n: column positioning for "hg summary"
1243 1238 ui.status(_('largefiles: %d entities for %d files to upload\n')
1244 1239 % (len(lfhashes), len(toupload)))
1245 1240
1246 1241 @eh.wrapcommand('summary',
1247 1242 opts=[('', 'large', None, _('display outgoing largefiles'))])
1248 1243 def overridesummary(orig, ui, repo, *pats, **opts):
1249 1244 try:
1250 1245 repo.lfstatus = True
1251 1246 orig(ui, repo, *pats, **opts)
1252 1247 finally:
1253 1248 repo.lfstatus = False
1254 1249
1255 1250 @eh.wrapfunction(scmutil, 'addremove')
1256 1251 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1257 1252 if opts is None:
1258 1253 opts = {}
1259 1254 if not lfutil.islfilesrepo(repo):
1260 1255 return orig(repo, matcher, prefix, opts)
1261 1256 # Get the list of missing largefiles so we can remove them
1262 1257 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1263 1258 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1264 1259 subrepos=[], ignored=False, clean=False,
1265 1260 unknown=False)
1266 1261
1267 1262 # Call into the normal remove code, but the removing of the standin, we want
1268 1263 # to have handled by original addremove. Monkey patching here makes sure
1269 1264 # we don't remove the standin in the largefiles code, preventing a very
1270 1265 # confused state later.
1271 1266 if s.deleted:
1272 1267 m = copy.copy(matcher)
1273 1268
1274 1269 # The m._files and m._map attributes are not changed to the deleted list
1275 1270 # because that affects the m.exact() test, which in turn governs whether
1276 1271 # or not the file name is printed, and how. Simply limit the original
1277 1272 # matches to those in the deleted status list.
1278 1273 matchfn = m.matchfn
1279 1274 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1280 1275
1281 1276 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1282 1277 **pycompat.strkwargs(opts))
1283 1278 # Call into the normal add code, and any files that *should* be added as
1284 1279 # largefiles will be
1285 1280 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1286 1281 **pycompat.strkwargs(opts))
1287 1282 # Now that we've handled largefiles, hand off to the original addremove
1288 1283 # function to take care of the rest. Make sure it doesn't do anything with
1289 1284 # largefiles by passing a matcher that will ignore them.
1290 1285 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1291 1286 return orig(repo, matcher, prefix, opts)
1292 1287
1293 1288 # Calling purge with --all will cause the largefiles to be deleted.
1294 1289 # Override repo.status to prevent this from happening.
1295 1290 @eh.wrapcommand('purge', extension='purge')
1296 1291 def overridepurge(orig, ui, repo, *dirs, **opts):
1297 1292 # XXX Monkey patching a repoview will not work. The assigned attribute will
1298 1293 # be set on the unfiltered repo, but we will only lookup attributes in the
1299 1294 # unfiltered repo if the lookup in the repoview object itself fails. As the
1300 1295 # monkey patched method exists on the repoview class the lookup will not
1301 1296 # fail. As a result, the original version will shadow the monkey patched
1302 1297 # one, defeating the monkey patch.
1303 1298 #
1304 1299 # As a work around we use an unfiltered repo here. We should do something
1305 1300 # cleaner instead.
1306 1301 repo = repo.unfiltered()
1307 1302 oldstatus = repo.status
1308 1303 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1309 1304 clean=False, unknown=False, listsubrepos=False):
1310 1305 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1311 1306 listsubrepos)
1312 1307 lfdirstate = lfutil.openlfdirstate(ui, repo)
1313 1308 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1314 1309 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1315 1310 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1316 1311 unknown, ignored, r.clean)
1317 1312 repo.status = overridestatus
1318 1313 orig(ui, repo, *dirs, **opts)
1319 1314 repo.status = oldstatus
1320 1315
1321 1316 @eh.wrapcommand('rollback')
1322 1317 def overriderollback(orig, ui, repo, **opts):
1323 1318 with repo.wlock():
1324 1319 before = repo.dirstate.parents()
1325 1320 orphans = set(f for f in repo.dirstate
1326 1321 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1327 1322 result = orig(ui, repo, **opts)
1328 1323 after = repo.dirstate.parents()
1329 1324 if before == after:
1330 1325 return result # no need to restore standins
1331 1326
1332 1327 pctx = repo['.']
1333 1328 for f in repo.dirstate:
1334 1329 if lfutil.isstandin(f):
1335 1330 orphans.discard(f)
1336 1331 if repo.dirstate[f] == 'r':
1337 1332 repo.wvfs.unlinkpath(f, ignoremissing=True)
1338 1333 elif f in pctx:
1339 1334 fctx = pctx[f]
1340 1335 repo.wwrite(f, fctx.data(), fctx.flags())
1341 1336 else:
1342 1337 # content of standin is not so important in 'a',
1343 1338 # 'm' or 'n' (coming from the 2nd parent) cases
1344 1339 lfutil.writestandin(repo, f, '', False)
1345 1340 for standin in orphans:
1346 1341 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1347 1342
1348 1343 lfdirstate = lfutil.openlfdirstate(ui, repo)
1349 1344 orphans = set(lfdirstate)
1350 1345 lfiles = lfutil.listlfiles(repo)
1351 1346 for file in lfiles:
1352 1347 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1353 1348 orphans.discard(file)
1354 1349 for lfile in orphans:
1355 1350 lfdirstate.drop(lfile)
1356 1351 lfdirstate.write()
1357 1352 return result
1358 1353
1359 1354 @eh.wrapcommand('transplant', extension='transplant')
1360 1355 def overridetransplant(orig, ui, repo, *revs, **opts):
1361 1356 resuming = opts.get(r'continue')
1362 1357 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1363 1358 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1364 1359 try:
1365 1360 result = orig(ui, repo, *revs, **opts)
1366 1361 finally:
1367 1362 repo._lfstatuswriters.pop()
1368 1363 repo._lfcommithooks.pop()
1369 1364 return result
1370 1365
1371 1366 @eh.wrapcommand('cat')
1372 1367 def overridecat(orig, ui, repo, file1, *pats, **opts):
1373 1368 opts = pycompat.byteskwargs(opts)
1374 1369 ctx = scmutil.revsingle(repo, opts.get('rev'))
1375 1370 err = 1
1376 1371 notbad = set()
1377 1372 m = scmutil.match(ctx, (file1,) + pats, opts)
1378 1373 origmatchfn = m.matchfn
1379 1374 def lfmatchfn(f):
1380 1375 if origmatchfn(f):
1381 1376 return True
1382 1377 lf = lfutil.splitstandin(f)
1383 1378 if lf is None:
1384 1379 return False
1385 1380 notbad.add(lf)
1386 1381 return origmatchfn(lf)
1387 1382 m.matchfn = lfmatchfn
1388 1383 origbadfn = m.bad
1389 1384 def lfbadfn(f, msg):
1390 1385 if not f in notbad:
1391 1386 origbadfn(f, msg)
1392 1387 m.bad = lfbadfn
1393 1388
1394 1389 origvisitdirfn = m.visitdir
1395 1390 def lfvisitdirfn(dir):
1396 1391 if dir == lfutil.shortname:
1397 1392 return True
1398 1393 ret = origvisitdirfn(dir)
1399 1394 if ret:
1400 1395 return ret
1401 1396 lf = lfutil.splitstandin(dir)
1402 1397 if lf is None:
1403 1398 return False
1404 1399 return origvisitdirfn(lf)
1405 1400 m.visitdir = lfvisitdirfn
1406 1401
1407 1402 for f in ctx.walk(m):
1408 1403 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1409 1404 lf = lfutil.splitstandin(f)
1410 1405 if lf is None or origmatchfn(f):
1411 1406 # duplicating unreachable code from commands.cat
1412 1407 data = ctx[f].data()
1413 1408 if opts.get('decode'):
1414 1409 data = repo.wwritedata(f, data)
1415 1410 fp.write(data)
1416 1411 else:
1417 1412 hash = lfutil.readasstandin(ctx[f])
1418 1413 if not lfutil.inusercache(repo.ui, hash):
1419 1414 store = storefactory.openstore(repo)
1420 1415 success, missing = store.get([(lf, hash)])
1421 1416 if len(success) != 1:
1422 1417 raise error.Abort(
1423 1418 _('largefile %s is not in cache and could not be '
1424 1419 'downloaded') % lf)
1425 1420 path = lfutil.usercachepath(repo.ui, hash)
1426 1421 with open(path, "rb") as fpin:
1427 1422 for chunk in util.filechunkiter(fpin):
1428 1423 fp.write(chunk)
1429 1424 err = 0
1430 1425 return err
1431 1426
1432 1427 @eh.wrapfunction(merge, 'update')
1433 1428 def mergeupdate(orig, repo, node, branchmerge, force,
1434 1429 *args, **kwargs):
1435 1430 matcher = kwargs.get(r'matcher', None)
1436 1431 # note if this is a partial update
1437 1432 partial = matcher and not matcher.always()
1438 1433 with repo.wlock():
1439 1434 # branch | | |
1440 1435 # merge | force | partial | action
1441 1436 # -------+-------+---------+--------------
1442 1437 # x | x | x | linear-merge
1443 1438 # o | x | x | branch-merge
1444 1439 # x | o | x | overwrite (as clean update)
1445 1440 # o | o | x | force-branch-merge (*1)
1446 1441 # x | x | o | (*)
1447 1442 # o | x | o | (*)
1448 1443 # x | o | o | overwrite (as revert)
1449 1444 # o | o | o | (*)
1450 1445 #
1451 1446 # (*) don't care
1452 1447 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1453 1448
1454 1449 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1455 1450 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1456 1451 repo.getcwd()),
1457 1452 subrepos=[], ignored=False,
1458 1453 clean=True, unknown=False)
1459 1454 oldclean = set(s.clean)
1460 1455 pctx = repo['.']
1461 1456 dctx = repo[node]
1462 1457 for lfile in unsure + s.modified:
1463 1458 lfileabs = repo.wvfs.join(lfile)
1464 1459 if not repo.wvfs.exists(lfileabs):
1465 1460 continue
1466 1461 lfhash = lfutil.hashfile(lfileabs)
1467 1462 standin = lfutil.standin(lfile)
1468 1463 lfutil.writestandin(repo, standin, lfhash,
1469 1464 lfutil.getexecutable(lfileabs))
1470 1465 if (standin in pctx and
1471 1466 lfhash == lfutil.readasstandin(pctx[standin])):
1472 1467 oldclean.add(lfile)
1473 1468 for lfile in s.added:
1474 1469 fstandin = lfutil.standin(lfile)
1475 1470 if fstandin not in dctx:
1476 1471 # in this case, content of standin file is meaningless
1477 1472 # (in dctx, lfile is unknown, or normal file)
1478 1473 continue
1479 1474 lfutil.updatestandin(repo, lfile, fstandin)
1480 1475 # mark all clean largefiles as dirty, just in case the update gets
1481 1476 # interrupted before largefiles and lfdirstate are synchronized
1482 1477 for lfile in oldclean:
1483 1478 lfdirstate.normallookup(lfile)
1484 1479 lfdirstate.write()
1485 1480
1486 1481 oldstandins = lfutil.getstandinsstate(repo)
1487 1482 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1488 1483 # good candidate for in-memory merge (large files, custom dirstate,
1489 1484 # matcher usage).
1490 1485 kwargs[r'wc'] = repo[None]
1491 1486 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1492 1487
1493 1488 newstandins = lfutil.getstandinsstate(repo)
1494 1489 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1495 1490
1496 1491 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1497 1492 # all the ones that didn't change as clean
1498 1493 for lfile in oldclean.difference(filelist):
1499 1494 lfdirstate.normal(lfile)
1500 1495 lfdirstate.write()
1501 1496
1502 1497 if branchmerge or force or partial:
1503 1498 filelist.extend(s.deleted + s.removed)
1504 1499
1505 1500 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1506 1501 normallookup=partial)
1507 1502
1508 1503 return result
1509 1504
1510 1505 @eh.wrapfunction(scmutil, 'marktouched')
1511 1506 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1512 1507 result = orig(repo, files, *args, **kwargs)
1513 1508
1514 1509 filelist = []
1515 1510 for f in files:
1516 1511 lf = lfutil.splitstandin(f)
1517 1512 if lf is not None:
1518 1513 filelist.append(lf)
1519 1514 if filelist:
1520 1515 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1521 1516 printmessage=False, normallookup=True)
1522 1517
1523 1518 return result
1524 1519
1525 1520 @eh.wrapfunction(upgrade, 'preservedrequirements')
1526 1521 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1527 1522 def upgraderequirements(orig, repo):
1528 1523 reqs = orig(repo)
1529 1524 if 'largefiles' in repo.requirements:
1530 1525 reqs.add('largefiles')
1531 1526 return reqs
1532 1527
1533 1528 _lfscheme = 'largefile://'
1534 1529
1535 1530 @eh.wrapfunction(urlmod, 'open')
1536 1531 def openlargefile(orig, ui, url_, data=None):
1537 1532 if url_.startswith(_lfscheme):
1538 1533 if data:
1539 1534 msg = "cannot use data on a 'largefile://' url"
1540 1535 raise error.ProgrammingError(msg)
1541 1536 lfid = url_[len(_lfscheme):]
1542 1537 return storefactory.getlfile(ui, lfid)
1543 1538 else:
1544 1539 return orig(ui, url_, data=data)
General Comments 0
You need to be logged in to leave comments. Login now