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