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