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