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