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