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