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