##// END OF EJS Templates
largefiles: omit restoring standins if working parent is not rollbacked...
FUJIWARA Katsunori -
r22283:cb556ea7 default
parent child Browse files
Show More
@@ -1,1283 +1,1288
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, merge, 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 before = repo.dirstate.parents()
1199 1200 result = orig(ui, repo, **opts)
1201 after = repo.dirstate.parents()
1202 if before == after:
1203 return result # no need to restore standins
1204
1200 1205 merge.update(repo, node=None, branchmerge=False, force=True,
1201 1206 partial=lfutil.isstandin)
1202 1207
1203 1208 lfdirstate = lfutil.openlfdirstate(ui, repo)
1204 1209 orphans = set(lfdirstate)
1205 1210 lfiles = lfutil.listlfiles(repo)
1206 1211 for file in lfiles:
1207 1212 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1208 1213 orphans.discard(file)
1209 1214 for lfile in orphans:
1210 1215 lfdirstate.drop(lfile)
1211 1216 lfdirstate.write()
1212 1217 finally:
1213 1218 wlock.release()
1214 1219 return result
1215 1220
1216 1221 def overridetransplant(orig, ui, repo, *revs, **opts):
1217 1222 try:
1218 1223 oldstandins = lfutil.getstandinsstate(repo)
1219 1224 repo._istransplanting = True
1220 1225 result = orig(ui, repo, *revs, **opts)
1221 1226 newstandins = lfutil.getstandinsstate(repo)
1222 1227 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1223 1228 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1224 1229 printmessage=True)
1225 1230 finally:
1226 1231 repo._istransplanting = False
1227 1232 return result
1228 1233
1229 1234 def overridecat(orig, ui, repo, file1, *pats, **opts):
1230 1235 ctx = scmutil.revsingle(repo, opts.get('rev'))
1231 1236 err = 1
1232 1237 notbad = set()
1233 1238 m = scmutil.match(ctx, (file1,) + pats, opts)
1234 1239 origmatchfn = m.matchfn
1235 1240 def lfmatchfn(f):
1236 1241 if origmatchfn(f):
1237 1242 return True
1238 1243 lf = lfutil.splitstandin(f)
1239 1244 if lf is None:
1240 1245 return False
1241 1246 notbad.add(lf)
1242 1247 return origmatchfn(lf)
1243 1248 m.matchfn = lfmatchfn
1244 1249 origbadfn = m.bad
1245 1250 def lfbadfn(f, msg):
1246 1251 if not f in notbad:
1247 1252 origbadfn(f, msg)
1248 1253 m.bad = lfbadfn
1249 1254 for f in ctx.walk(m):
1250 1255 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1251 1256 pathname=f)
1252 1257 lf = lfutil.splitstandin(f)
1253 1258 if lf is None or origmatchfn(f):
1254 1259 # duplicating unreachable code from commands.cat
1255 1260 data = ctx[f].data()
1256 1261 if opts.get('decode'):
1257 1262 data = repo.wwritedata(f, data)
1258 1263 fp.write(data)
1259 1264 else:
1260 1265 hash = lfutil.readstandin(repo, lf, ctx.rev())
1261 1266 if not lfutil.inusercache(repo.ui, hash):
1262 1267 store = basestore._openstore(repo)
1263 1268 success, missing = store.get([(lf, hash)])
1264 1269 if len(success) != 1:
1265 1270 raise util.Abort(
1266 1271 _('largefile %s is not in cache and could not be '
1267 1272 'downloaded') % lf)
1268 1273 path = lfutil.usercachepath(repo.ui, hash)
1269 1274 fpin = open(path, "rb")
1270 1275 for chunk in util.filechunkiter(fpin, 128 * 1024):
1271 1276 fp.write(chunk)
1272 1277 fpin.close()
1273 1278 fp.close()
1274 1279 err = 0
1275 1280 return err
1276 1281
1277 1282 def mercurialsinkbefore(orig, sink):
1278 1283 sink.repo._isconverting = True
1279 1284 orig(sink)
1280 1285
1281 1286 def mercurialsinkafter(orig, sink):
1282 1287 sink.repo._isconverting = False
1283 1288 orig(sink)
@@ -1,271 +1,294
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 $ hg forget large2
107 107 $ echo largeX > largeX
108 108 $ hg add --large largeX
109 109 $ hg commit -m 'will be rollback-ed soon'
110 110 $ echo largeY > largeY
111 111 $ hg add --large largeY
112 112 $ hg status -A large1
113 113 large1: No such file or directory
114 114 $ hg status -A large2
115 115 ? large2
116 116 $ hg status -A largeX
117 117 C largeX
118 118 $ hg status -A largeY
119 119 A largeY
120 120 $ hg rollback
121 121 repository tip rolled back to revision 3 (undo commit)
122 122 working directory now based on revision 3
123 123 $ hg status -A large1
124 124 R large1
125 125 $ hg status -A large2
126 126 R large2
127 127 $ hg status -A largeX
128 128 A largeX
129 129 $ hg status -A largeY
130 130 ? largeY
131 131
132 Test that "hg rollback" restores standins correctly
133
134 $ hg commit -m 'will be rollback-ed soon'
135 $ hg update -q -C 2
136 $ cat large1
137 large1
138 $ cat .hglf/large1
139 4669e532d5b2c093a78eca010077e708a071bb64
140 $ cat large2
141 large2 in #2
142 $ cat .hglf/large2
143 3cfce6277e7668985707b6887ce56f9f62f6ccd9
144
145 $ hg rollback -q -f
146 $ cat large1
147 large1
148 $ cat .hglf/large1
149 4669e532d5b2c093a78eca010077e708a071bb64
150 $ cat large2
151 large2 in #2
152 $ cat .hglf/large2
153 3cfce6277e7668985707b6887ce56f9f62f6ccd9
154
132 155 Test that "hg status" shows status of largefiles correctly just after
133 156 automated commit like rebase/transplant
134 157
135 158 $ cat >> .hg/hgrc <<EOF
136 159 > [extensions]
137 160 > rebase =
138 161 > strip =
139 162 > transplant =
140 163 > EOF
141 164 $ hg update -q -C 1
142 165 $ hg remove large1
143 166 $ echo largeX > largeX
144 167 $ hg add --large largeX
145 168 $ hg commit -m '#4'
146 169
147 170 $ hg rebase -s 1 -d 2 --keep
148 171 $ hg status -A large1
149 172 large1: No such file or directory
150 173 $ hg status -A largeX
151 174 C largeX
152 175 $ hg strip -q 5
153 176
154 177 $ hg update -q -C 2
155 178 $ hg transplant -q 1 4
156 179 $ hg status -A large1
157 180 large1: No such file or directory
158 181 $ hg status -A largeX
159 182 C largeX
160 183 $ hg strip -q 5
161 184
162 185 $ hg update -q -C 2
163 186 $ hg transplant -q --merge 1 --merge 4
164 187 $ hg status -A large1
165 188 large1: No such file or directory
166 189 $ hg status -A largeX
167 190 C largeX
168 191 $ hg strip -q 5
169 192
170 193 Test that linear merge can detect modification (and conflict) correctly
171 194
172 195 (linear merge without conflict)
173 196
174 197 $ echo 'large2 for linear merge (no conflict)' > large2
175 198 $ hg update 3 --config debug.dirstate.delaywrite=2
176 199 getting changed largefiles
177 200 1 largefiles updated, 0 removed
178 201 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 202 $ hg status -A large2
180 203 M large2
181 204 $ cat large2
182 205 large2 for linear merge (no conflict)
183 206 $ cat .hglf/large2
184 207 9c4bf8f1b33536d6e5f89447e10620cfe52ea710
185 208
186 209 (linear merge with conflict, choosing "other")
187 210
188 211 $ hg update -q -C 2
189 212 $ echo 'large1 for linear merge (conflict)' > large1
190 213 $ hg update 3 --config ui.interactive=True <<EOF
191 214 > o
192 215 > EOF
193 216 largefile large1 has a merge conflict
194 217 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
195 218 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
196 219 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? getting changed largefiles
197 220 1 largefiles updated, 0 removed
198 221 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
199 222 $ hg status -A large1
200 223 C large1
201 224 $ cat large1
202 225 large1 in #3
203 226 $ cat .hglf/large1
204 227 e5bb990443d6a92aaf7223813720f7566c9dd05b
205 228
206 229 (linear merge with conflict, choosing "local")
207 230
208 231 $ hg update -q -C 2
209 232 $ echo 'large1 for linear merge (conflict)' > large1
210 233 $ hg update 3 --config debug.dirstate.delaywrite=2
211 234 largefile large1 has a merge conflict
212 235 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
213 236 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
214 237 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
215 238 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
216 239 $ hg status -A large1
217 240 M large1
218 241 $ cat large1
219 242 large1 for linear merge (conflict)
220 243 $ cat .hglf/large1
221 244 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
222 245
223 246 Test a linear merge to a revision containing same-name normal file
224 247
225 248 $ hg update -q -C 3
226 249 $ hg remove large2
227 250 $ echo 'large2 as normal file' > large2
228 251 $ hg add large2
229 252 $ echo 'large3 as normal file' > large3
230 253 $ hg add large3
231 254 $ hg commit -m '#5'
232 255 $ hg manifest
233 256 .hglf/large1
234 257 large2
235 258 large3
236 259 normal1
237 260
238 261 (modified largefile is already switched to normal)
239 262
240 263 $ hg update -q -C 2
241 264 $ echo 'modified large2 for linear merge' > large2
242 265 $ hg update -q 5
243 266 local changed .hglf/large2 which remote deleted
244 267 use (c)hanged version or (d)elete? c
245 268 remote turned local largefile large2 into a normal file
246 269 keep (l)argefile or use (n)ormal file? l
247 270 $ hg debugdirstate --nodates | grep large2
248 271 a 0 -1 .hglf/large2
249 272 r 0 0 large2
250 273 $ hg status -A large2
251 274 A large2
252 275 $ cat large2
253 276 modified large2 for linear merge
254 277
255 278 (added largefile is already committed as normal)
256 279
257 280 $ hg update -q -C 2
258 281 $ echo 'large3 as large file for linear merge' > large3
259 282 $ hg add --large large3
260 283 $ hg update -q 5
261 284 remote turned local largefile large3 into a normal file
262 285 keep (l)argefile or use (n)ormal file? l
263 286 $ hg debugdirstate --nodates | grep large3
264 287 a 0 -1 .hglf/large3
265 288 r 0 0 large3
266 289 $ hg status -A large3
267 290 A large3
268 291 $ cat large3
269 292 large3 as large file for linear merge
270 293
271 294 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now