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