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