##// END OF EJS Templates
merge with stable
Matt Mackall -
r22173:d3702a82 merge default
parent child Browse files
Show More
@@ -1,1229 +1,1246 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 10
11 11 import os
12 12 import copy
13 13
14 14 from mercurial import hg, 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 if m.always():
271 # We want to match everything anyway, so there's no benefit trying
272 # to add standins.
273 return matchandpats
274
270 275 pats = set(p)
271 276 # TODO: handling of patterns in both cases below
272 277 if m._cwd:
273 278 if os.path.isabs(m._cwd):
274 279 # TODO: handle largefile magic when invoked from other cwd
275 280 return matchandpats
276 281 back = (m._cwd.count('/') + 1) * '../'
277 282 pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p)
278 283 else:
279 284 pats.update(lfutil.standin(f) for f in p)
280 285
281 286 for i in range(0, len(m._files)):
282 287 standin = lfutil.standin(m._files[i])
283 288 if standin in repo[ctx.node()]:
284 289 m._files[i] = standin
285 290 elif m._files[i] not in repo[ctx.node()]:
286 291 m._files.append(standin)
287 292 pats.add(standin)
288 293
289 294 m._fmap = set(m._files)
290 295 m._always = False
291 296 origmatchfn = m.matchfn
292 297 def lfmatchfn(f):
293 298 lf = lfutil.splitstandin(f)
294 299 if lf is not None and origmatchfn(lf):
295 300 return True
296 301 r = origmatchfn(f)
297 302 return r
298 303 m.matchfn = lfmatchfn
299 304
300 305 return m, pats
301 306
307 # For hg log --patch, the match object is used in two different senses:
308 # (1) to determine what revisions should be printed out, and
309 # (2) to determine what files to print out diffs for.
310 # The magic matchandpats override should be used for case (1) but not for
311 # case (2).
312 def overridemakelogfilematcher(repo, pats, opts):
313 pctx = repo[None]
314 match, pats = oldmatchandpats(pctx, pats, opts)
315 return lambda rev: match
316
302 317 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
318 oldmakelogfilematcher = cmdutil._makenofollowlogfilematcher
319 setattr(cmdutil, '_makenofollowlogfilematcher', overridemakelogfilematcher)
320
303 321 try:
304 repo.lfstatus = True
305 322 return orig(ui, repo, *pats, **opts)
306 323 finally:
307 repo.lfstatus = False
308 324 restorematchandpatsfn()
325 setattr(cmdutil, '_makenofollowlogfilematcher', oldmakelogfilematcher)
309 326
310 327 def overrideverify(orig, ui, repo, *pats, **opts):
311 328 large = opts.pop('large', False)
312 329 all = opts.pop('lfa', False)
313 330 contents = opts.pop('lfc', False)
314 331
315 332 result = orig(ui, repo, *pats, **opts)
316 333 if large or all or contents:
317 334 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
318 335 return result
319 336
320 337 def overridedebugstate(orig, ui, repo, *pats, **opts):
321 338 large = opts.pop('large', False)
322 339 if large:
323 340 class fakerepo(object):
324 341 dirstate = lfutil.openlfdirstate(ui, repo)
325 342 orig(ui, fakerepo, *pats, **opts)
326 343 else:
327 344 orig(ui, repo, *pats, **opts)
328 345
329 346 # Override needs to refresh standins so that update's normal merge
330 347 # will go through properly. Then the other update hook (overriding repo.update)
331 348 # will get the new files. Filemerge is also overridden so that the merge
332 349 # will merge standins correctly.
333 350 def overrideupdate(orig, ui, repo, *pats, **opts):
334 351 # Need to lock between the standins getting updated and their
335 352 # largefiles getting updated
336 353 wlock = repo.wlock()
337 354 try:
338 355 lfdirstate = lfutil.openlfdirstate(ui, repo)
339 356 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
340 357 [], False, False, False)
341 358 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
342 359
343 360 if opts['check']:
344 361 mod = len(modified) > 0
345 362 for lfile in unsure:
346 363 standin = lfutil.standin(lfile)
347 364 if repo['.'][standin].data().strip() != \
348 365 lfutil.hashfile(repo.wjoin(lfile)):
349 366 mod = True
350 367 else:
351 368 lfdirstate.normal(lfile)
352 369 lfdirstate.write()
353 370 if mod:
354 371 raise util.Abort(_('uncommitted changes'))
355 372 # XXX handle removed differently
356 373 if not opts['clean']:
357 374 for lfile in unsure + modified + added:
358 375 lfutil.updatestandin(repo, lfutil.standin(lfile))
359 376 return orig(ui, repo, *pats, **opts)
360 377 finally:
361 378 wlock.release()
362 379
363 380 # Before starting the manifest merge, merge.updates will call
364 381 # _checkunknown to check if there are any files in the merged-in
365 382 # changeset that collide with unknown files in the working copy.
366 383 #
367 384 # The largefiles are seen as unknown, so this prevents us from merging
368 385 # in a file 'foo' if we already have a largefile with the same name.
369 386 #
370 387 # The overridden function filters the unknown files by removing any
371 388 # largefiles. This makes the merge proceed and we can then handle this
372 389 # case further in the overridden manifestmerge function below.
373 390 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
374 391 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
375 392 return False
376 393 return origfn(repo, wctx, mctx, f)
377 394
378 395 # The manifest merge handles conflicts on the manifest level. We want
379 396 # to handle changes in largefile-ness of files at this level too.
380 397 #
381 398 # The strategy is to run the original manifestmerge and then process
382 399 # the action list it outputs. There are two cases we need to deal with:
383 400 #
384 401 # 1. Normal file in p1, largefile in p2. Here the largefile is
385 402 # detected via its standin file, which will enter the working copy
386 403 # with a "get" action. It is not "merge" since the standin is all
387 404 # Mercurial is concerned with at this level -- the link to the
388 405 # existing normal file is not relevant here.
389 406 #
390 407 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
391 408 # since the largefile will be present in the working copy and
392 409 # different from the normal file in p2. Mercurial therefore
393 410 # triggers a merge action.
394 411 #
395 412 # In both cases, we prompt the user and emit new actions to either
396 413 # remove the standin (if the normal file was kept) or to remove the
397 414 # normal file and get the standin (if the largefile was kept). The
398 415 # default prompt answer is to use the largefile version since it was
399 416 # presumably changed on purpose.
400 417 #
401 418 # Finally, the merge.applyupdates function will then take care of
402 419 # writing the files into the working copy and lfcommands.updatelfiles
403 420 # will update the largefiles.
404 421 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
405 422 partial, acceptremote, followcopies):
406 423 overwrite = force and not branchmerge
407 424 actions = origfn(repo, p1, p2, pas, branchmerge, force, partial,
408 425 acceptremote, followcopies)
409 426
410 427 if overwrite:
411 428 return actions
412 429
413 430 removes = set(a[0] for a in actions['r'])
414 431
415 432 newglist = []
416 433 for action in actions['g']:
417 434 f, args, msg = action
418 435 splitstandin = f and lfutil.splitstandin(f)
419 436 if (splitstandin is not None and
420 437 splitstandin in p1 and splitstandin not in removes):
421 438 # Case 1: normal file in the working copy, largefile in
422 439 # the second parent
423 440 lfile = splitstandin
424 441 standin = f
425 442 msg = _('remote turned local normal file %s into a largefile\n'
426 443 'use (l)argefile or keep (n)ormal file?'
427 444 '$$ &Largefile $$ &Normal file') % lfile
428 445 if repo.ui.promptchoice(msg, 0) == 0:
429 446 actions['r'].append((lfile, None, msg))
430 447 newglist.append((standin, (p2.flags(standin),), msg))
431 448 else:
432 449 actions['r'].append((standin, None, msg))
433 450 elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes:
434 451 # Case 2: largefile in the working copy, normal file in
435 452 # the second parent
436 453 standin = lfutil.standin(f)
437 454 lfile = f
438 455 msg = _('remote turned local largefile %s into a normal file\n'
439 456 'keep (l)argefile or use (n)ormal file?'
440 457 '$$ &Largefile $$ &Normal file') % lfile
441 458 if repo.ui.promptchoice(msg, 0) == 0:
442 459 actions['r'].append((lfile, None, msg))
443 460 else:
444 461 actions['r'].append((standin, None, msg))
445 462 newglist.append((lfile, (p2.flags(lfile),), msg))
446 463 else:
447 464 newglist.append(action)
448 465
449 466 newglist.sort()
450 467 actions['g'] = newglist
451 468
452 469 return actions
453 470
454 471 # Override filemerge to prompt the user about how they wish to merge
455 472 # largefiles. This will handle identical edits without prompting the user.
456 473 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
457 474 if not lfutil.isstandin(orig):
458 475 return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels)
459 476
460 477 ahash = fca.data().strip().lower()
461 478 dhash = fcd.data().strip().lower()
462 479 ohash = fco.data().strip().lower()
463 480 if (ohash != ahash and
464 481 ohash != dhash and
465 482 (dhash == ahash or
466 483 repo.ui.promptchoice(
467 484 _('largefile %s has a merge conflict\nancestor was %s\n'
468 485 'keep (l)ocal %s or\ntake (o)ther %s?'
469 486 '$$ &Local $$ &Other') %
470 487 (lfutil.splitstandin(orig), ahash, dhash, ohash),
471 488 0) == 1)):
472 489 repo.wwrite(fcd.path(), fco.data(), fco.flags())
473 490 return 0
474 491
475 492 # Copy first changes the matchers to match standins instead of
476 493 # largefiles. Then it overrides util.copyfile in that function it
477 494 # checks if the destination largefile already exists. It also keeps a
478 495 # list of copied files so that the largefiles can be copied and the
479 496 # dirstate updated.
480 497 def overridecopy(orig, ui, repo, pats, opts, rename=False):
481 498 # doesn't remove largefile on rename
482 499 if len(pats) < 2:
483 500 # this isn't legal, let the original function deal with it
484 501 return orig(ui, repo, pats, opts, rename)
485 502
486 503 def makestandin(relpath):
487 504 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
488 505 return os.path.join(repo.wjoin(lfutil.standin(path)))
489 506
490 507 fullpats = scmutil.expandpats(pats)
491 508 dest = fullpats[-1]
492 509
493 510 if os.path.isdir(dest):
494 511 if not os.path.isdir(makestandin(dest)):
495 512 os.makedirs(makestandin(dest))
496 513 # This could copy both lfiles and normal files in one command,
497 514 # but we don't want to do that. First replace their matcher to
498 515 # only match normal files and run it, then replace it to just
499 516 # match largefiles and run it again.
500 517 nonormalfiles = False
501 518 nolfiles = False
502 519 installnormalfilesmatchfn(repo[None].manifest())
503 520 try:
504 521 try:
505 522 result = orig(ui, repo, pats, opts, rename)
506 523 except util.Abort, e:
507 524 if str(e) != _('no files to copy'):
508 525 raise e
509 526 else:
510 527 nonormalfiles = True
511 528 result = 0
512 529 finally:
513 530 restorematchfn()
514 531
515 532 # The first rename can cause our current working directory to be removed.
516 533 # In that case there is nothing left to copy/rename so just quit.
517 534 try:
518 535 repo.getcwd()
519 536 except OSError:
520 537 return result
521 538
522 539 try:
523 540 try:
524 541 # When we call orig below it creates the standins but we don't add
525 542 # them to the dir state until later so lock during that time.
526 543 wlock = repo.wlock()
527 544
528 545 manifest = repo[None].manifest()
529 546 def overridematch(ctx, pats=[], opts={}, globbed=False,
530 547 default='relpath'):
531 548 newpats = []
532 549 # The patterns were previously mangled to add the standin
533 550 # directory; we need to remove that now
534 551 for pat in pats:
535 552 if match_.patkind(pat) is None and lfutil.shortname in pat:
536 553 newpats.append(pat.replace(lfutil.shortname, ''))
537 554 else:
538 555 newpats.append(pat)
539 556 match = oldmatch(ctx, newpats, opts, globbed, default)
540 557 m = copy.copy(match)
541 558 lfile = lambda f: lfutil.standin(f) in manifest
542 559 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
543 560 m._fmap = set(m._files)
544 561 m._always = False
545 562 origmatchfn = m.matchfn
546 563 m.matchfn = lambda f: (lfutil.isstandin(f) and
547 564 (f in manifest) and
548 565 origmatchfn(lfutil.splitstandin(f)) or
549 566 None)
550 567 return m
551 568 oldmatch = installmatchfn(overridematch)
552 569 listpats = []
553 570 for pat in pats:
554 571 if match_.patkind(pat) is not None:
555 572 listpats.append(pat)
556 573 else:
557 574 listpats.append(makestandin(pat))
558 575
559 576 try:
560 577 origcopyfile = util.copyfile
561 578 copiedfiles = []
562 579 def overridecopyfile(src, dest):
563 580 if (lfutil.shortname in src and
564 581 dest.startswith(repo.wjoin(lfutil.shortname))):
565 582 destlfile = dest.replace(lfutil.shortname, '')
566 583 if not opts['force'] and os.path.exists(destlfile):
567 584 raise IOError('',
568 585 _('destination largefile already exists'))
569 586 copiedfiles.append((src, dest))
570 587 origcopyfile(src, dest)
571 588
572 589 util.copyfile = overridecopyfile
573 590 result += orig(ui, repo, listpats, opts, rename)
574 591 finally:
575 592 util.copyfile = origcopyfile
576 593
577 594 lfdirstate = lfutil.openlfdirstate(ui, repo)
578 595 for (src, dest) in copiedfiles:
579 596 if (lfutil.shortname in src and
580 597 dest.startswith(repo.wjoin(lfutil.shortname))):
581 598 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
582 599 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
583 600 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
584 601 if not os.path.isdir(destlfiledir):
585 602 os.makedirs(destlfiledir)
586 603 if rename:
587 604 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
588 605
589 606 # The file is gone, but this deletes any empty parent
590 607 # directories as a side-effect.
591 608 util.unlinkpath(repo.wjoin(srclfile), True)
592 609 lfdirstate.remove(srclfile)
593 610 else:
594 611 util.copyfile(repo.wjoin(srclfile),
595 612 repo.wjoin(destlfile))
596 613
597 614 lfdirstate.add(destlfile)
598 615 lfdirstate.write()
599 616 except util.Abort, e:
600 617 if str(e) != _('no files to copy'):
601 618 raise e
602 619 else:
603 620 nolfiles = True
604 621 finally:
605 622 restorematchfn()
606 623 wlock.release()
607 624
608 625 if nolfiles and nonormalfiles:
609 626 raise util.Abort(_('no files to copy'))
610 627
611 628 return result
612 629
613 630 # When the user calls revert, we have to be careful to not revert any
614 631 # changes to other largefiles accidentally. This means we have to keep
615 632 # track of the largefiles that are being reverted so we only pull down
616 633 # the necessary largefiles.
617 634 #
618 635 # Standins are only updated (to match the hash of largefiles) before
619 636 # commits. Update the standins then run the original revert, changing
620 637 # the matcher to hit standins instead of largefiles. Based on the
621 638 # resulting standins update the largefiles.
622 639 def overriderevert(orig, ui, repo, *pats, **opts):
623 640 # Because we put the standins in a bad state (by updating them)
624 641 # and then return them to a correct state we need to lock to
625 642 # prevent others from changing them in their incorrect state.
626 643 wlock = repo.wlock()
627 644 try:
628 645 lfdirstate = lfutil.openlfdirstate(ui, repo)
629 646 (modified, added, removed, missing, unknown, ignored, clean) = \
630 647 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
631 648 lfdirstate.write()
632 649 for lfile in modified:
633 650 lfutil.updatestandin(repo, lfutil.standin(lfile))
634 651 for lfile in missing:
635 652 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
636 653 os.unlink(repo.wjoin(lfutil.standin(lfile)))
637 654
638 655 oldstandins = lfutil.getstandinsstate(repo)
639 656
640 657 def overridematch(ctx, pats=[], opts={}, globbed=False,
641 658 default='relpath'):
642 659 match = oldmatch(ctx, pats, opts, globbed, default)
643 660 m = copy.copy(match)
644 661 def tostandin(f):
645 662 if lfutil.standin(f) in ctx:
646 663 return lfutil.standin(f)
647 664 elif lfutil.standin(f) in repo[None]:
648 665 return None
649 666 return f
650 667 m._files = [tostandin(f) for f in m._files]
651 668 m._files = [f for f in m._files if f is not None]
652 669 m._fmap = set(m._files)
653 670 m._always = False
654 671 origmatchfn = m.matchfn
655 672 def matchfn(f):
656 673 if lfutil.isstandin(f):
657 674 return (origmatchfn(lfutil.splitstandin(f)) and
658 675 (f in repo[None] or f in ctx))
659 676 return origmatchfn(f)
660 677 m.matchfn = matchfn
661 678 return m
662 679 oldmatch = installmatchfn(overridematch)
663 680 try:
664 681 orig(ui, repo, *pats, **opts)
665 682 finally:
666 683 restorematchfn()
667 684
668 685 newstandins = lfutil.getstandinsstate(repo)
669 686 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
670 687 # lfdirstate should be 'normallookup'-ed for updated files,
671 688 # because reverting doesn't touch dirstate for 'normal' files
672 689 # when target revision is explicitly specified: in such case,
673 690 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
674 691 # of target (standin) file.
675 692 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
676 693 normallookup=True)
677 694
678 695 finally:
679 696 wlock.release()
680 697
681 698 def hgupdaterepo(orig, repo, node, overwrite):
682 699 if not overwrite:
683 700 # Only call updatelfiles on the standins that have changed to save time
684 701 oldstandins = lfutil.getstandinsstate(repo)
685 702
686 703 result = orig(repo, node, overwrite)
687 704
688 705 filelist = None
689 706 if not overwrite:
690 707 newstandins = lfutil.getstandinsstate(repo)
691 708 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
692 709 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist)
693 710 return result
694 711
695 712 def hgmerge(orig, repo, node, force=None, remind=True):
696 713 result = orig(repo, node, force, remind)
697 714 lfcommands.updatelfiles(repo.ui, repo)
698 715 return result
699 716
700 717 # When we rebase a repository with remotely changed largefiles, we need to
701 718 # take some extra care so that the largefiles are correctly updated in the
702 719 # working copy
703 720 def overridepull(orig, ui, repo, source=None, **opts):
704 721 revsprepull = len(repo)
705 722 if not source:
706 723 source = 'default'
707 724 repo.lfpullsource = source
708 725 if opts.get('rebase', False):
709 726 repo._isrebasing = True
710 727 try:
711 728 if opts.get('update'):
712 729 del opts['update']
713 730 ui.debug('--update and --rebase are not compatible, ignoring '
714 731 'the update flag\n')
715 732 del opts['rebase']
716 733 origpostincoming = commands.postincoming
717 734 def _dummy(*args, **kwargs):
718 735 pass
719 736 commands.postincoming = _dummy
720 737 try:
721 738 result = commands.pull(ui, repo, source, **opts)
722 739 finally:
723 740 commands.postincoming = origpostincoming
724 741 revspostpull = len(repo)
725 742 if revspostpull > revsprepull:
726 743 result = result or rebase.rebase(ui, repo)
727 744 finally:
728 745 repo._isrebasing = False
729 746 else:
730 747 result = orig(ui, repo, source, **opts)
731 748 revspostpull = len(repo)
732 749 lfrevs = opts.get('lfrev', [])
733 750 if opts.get('all_largefiles'):
734 751 lfrevs.append('pulled()')
735 752 if lfrevs and revspostpull > revsprepull:
736 753 numcached = 0
737 754 repo.firstpulled = revsprepull # for pulled() revset expression
738 755 try:
739 756 for rev in scmutil.revrange(repo, lfrevs):
740 757 ui.note(_('pulling largefiles for revision %s\n') % rev)
741 758 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
742 759 numcached += len(cached)
743 760 finally:
744 761 del repo.firstpulled
745 762 ui.status(_("%d largefiles cached\n") % numcached)
746 763 return result
747 764
748 765 def pulledrevsetsymbol(repo, subset, x):
749 766 """``pulled()``
750 767 Changesets that just has been pulled.
751 768
752 769 Only available with largefiles from pull --lfrev expressions.
753 770
754 771 .. container:: verbose
755 772
756 773 Some examples:
757 774
758 775 - pull largefiles for all new changesets::
759 776
760 777 hg pull -lfrev "pulled()"
761 778
762 779 - pull largefiles for all new branch heads::
763 780
764 781 hg pull -lfrev "head(pulled()) and not closed()"
765 782
766 783 """
767 784
768 785 try:
769 786 firstpulled = repo.firstpulled
770 787 except AttributeError:
771 788 raise util.Abort(_("pulled() only available in --lfrev"))
772 789 return revset.baseset([r for r in subset if r >= firstpulled])
773 790
774 791 def overrideclone(orig, ui, source, dest=None, **opts):
775 792 d = dest
776 793 if d is None:
777 794 d = hg.defaultdest(source)
778 795 if opts.get('all_largefiles') and not hg.islocal(d):
779 796 raise util.Abort(_(
780 797 '--all-largefiles is incompatible with non-local destination %s') %
781 798 d)
782 799
783 800 return orig(ui, source, dest, **opts)
784 801
785 802 def hgclone(orig, ui, opts, *args, **kwargs):
786 803 result = orig(ui, opts, *args, **kwargs)
787 804
788 805 if result is not None:
789 806 sourcerepo, destrepo = result
790 807 repo = destrepo.local()
791 808
792 809 # Caching is implicitly limited to 'rev' option, since the dest repo was
793 810 # truncated at that point. The user may expect a download count with
794 811 # this option, so attempt whether or not this is a largefile repo.
795 812 if opts.get('all_largefiles'):
796 813 success, missing = lfcommands.downloadlfiles(ui, repo, None)
797 814
798 815 if missing != 0:
799 816 return None
800 817
801 818 return result
802 819
803 820 def overriderebase(orig, ui, repo, **opts):
804 821 repo._isrebasing = True
805 822 try:
806 823 return orig(ui, repo, **opts)
807 824 finally:
808 825 repo._isrebasing = False
809 826
810 827 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
811 828 prefix=None, mtime=None, subrepos=None):
812 829 # No need to lock because we are only reading history and
813 830 # largefile caches, neither of which are modified.
814 831 lfcommands.cachelfiles(repo.ui, repo, node)
815 832
816 833 if kind not in archival.archivers:
817 834 raise util.Abort(_("unknown archive type '%s'") % kind)
818 835
819 836 ctx = repo[node]
820 837
821 838 if kind == 'files':
822 839 if prefix:
823 840 raise util.Abort(
824 841 _('cannot give prefix when archiving to files'))
825 842 else:
826 843 prefix = archival.tidyprefix(dest, kind, prefix)
827 844
828 845 def write(name, mode, islink, getdata):
829 846 if matchfn and not matchfn(name):
830 847 return
831 848 data = getdata()
832 849 if decode:
833 850 data = repo.wwritedata(name, data)
834 851 archiver.addfile(prefix + name, mode, islink, data)
835 852
836 853 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
837 854
838 855 if repo.ui.configbool("ui", "archivemeta", True):
839 856 def metadata():
840 857 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
841 858 hex(repo.changelog.node(0)), hex(node), ctx.branch())
842 859
843 860 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
844 861 if repo.tagtype(t) == 'global')
845 862 if not tags:
846 863 repo.ui.pushbuffer()
847 864 opts = {'template': '{latesttag}\n{latesttagdistance}',
848 865 'style': '', 'patch': None, 'git': None}
849 866 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
850 867 ltags, dist = repo.ui.popbuffer().split('\n')
851 868 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
852 869 tags += 'latesttagdistance: %s\n' % dist
853 870
854 871 return base + tags
855 872
856 873 write('.hg_archival.txt', 0644, False, metadata)
857 874
858 875 for f in ctx:
859 876 ff = ctx.flags(f)
860 877 getdata = ctx[f].data
861 878 if lfutil.isstandin(f):
862 879 path = lfutil.findfile(repo, getdata().strip())
863 880 if path is None:
864 881 raise util.Abort(
865 882 _('largefile %s not found in repo store or system cache')
866 883 % lfutil.splitstandin(f))
867 884 f = lfutil.splitstandin(f)
868 885
869 886 def getdatafn():
870 887 fd = None
871 888 try:
872 889 fd = open(path, 'rb')
873 890 return fd.read()
874 891 finally:
875 892 if fd:
876 893 fd.close()
877 894
878 895 getdata = getdatafn
879 896 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
880 897
881 898 if subrepos:
882 899 for subpath in sorted(ctx.substate):
883 900 sub = ctx.sub(subpath)
884 901 submatch = match_.narrowmatcher(subpath, matchfn)
885 902 sub.archive(repo.ui, archiver, prefix, submatch)
886 903
887 904 archiver.done()
888 905
889 906 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
890 907 repo._get(repo._state + ('hg',))
891 908 rev = repo._state[1]
892 909 ctx = repo._repo[rev]
893 910
894 911 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
895 912
896 913 def write(name, mode, islink, getdata):
897 914 # At this point, the standin has been replaced with the largefile name,
898 915 # so the normal matcher works here without the lfutil variants.
899 916 if match and not match(f):
900 917 return
901 918 data = getdata()
902 919
903 920 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
904 921
905 922 for f in ctx:
906 923 ff = ctx.flags(f)
907 924 getdata = ctx[f].data
908 925 if lfutil.isstandin(f):
909 926 path = lfutil.findfile(repo._repo, getdata().strip())
910 927 if path is None:
911 928 raise util.Abort(
912 929 _('largefile %s not found in repo store or system cache')
913 930 % lfutil.splitstandin(f))
914 931 f = lfutil.splitstandin(f)
915 932
916 933 def getdatafn():
917 934 fd = None
918 935 try:
919 936 fd = open(os.path.join(prefix, path), 'rb')
920 937 return fd.read()
921 938 finally:
922 939 if fd:
923 940 fd.close()
924 941
925 942 getdata = getdatafn
926 943
927 944 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
928 945
929 946 for subpath in sorted(ctx.substate):
930 947 sub = ctx.sub(subpath)
931 948 submatch = match_.narrowmatcher(subpath, match)
932 949 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
933 950 submatch)
934 951
935 952 # If a largefile is modified, the change is not reflected in its
936 953 # standin until a commit. cmdutil.bailifchanged() raises an exception
937 954 # if the repo has uncommitted changes. Wrap it to also check if
938 955 # largefiles were changed. This is used by bisect and backout.
939 956 def overridebailifchanged(orig, repo):
940 957 orig(repo)
941 958 repo.lfstatus = True
942 959 modified, added, removed, deleted = repo.status()[:4]
943 960 repo.lfstatus = False
944 961 if modified or added or removed or deleted:
945 962 raise util.Abort(_('uncommitted changes'))
946 963
947 964 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
948 965 def overridefetch(orig, ui, repo, *pats, **opts):
949 966 repo.lfstatus = True
950 967 modified, added, removed, deleted = repo.status()[:4]
951 968 repo.lfstatus = False
952 969 if modified or added or removed or deleted:
953 970 raise util.Abort(_('uncommitted changes'))
954 971 return orig(ui, repo, *pats, **opts)
955 972
956 973 def overrideforget(orig, ui, repo, *pats, **opts):
957 974 installnormalfilesmatchfn(repo[None].manifest())
958 975 result = orig(ui, repo, *pats, **opts)
959 976 restorematchfn()
960 977 m = scmutil.match(repo[None], pats, opts)
961 978
962 979 try:
963 980 repo.lfstatus = True
964 981 s = repo.status(match=m, clean=True)
965 982 finally:
966 983 repo.lfstatus = False
967 984 forget = sorted(s[0] + s[1] + s[3] + s[6])
968 985 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
969 986
970 987 for f in forget:
971 988 if lfutil.standin(f) not in repo.dirstate and not \
972 989 os.path.isdir(m.rel(lfutil.standin(f))):
973 990 ui.warn(_('not removing %s: file is already untracked\n')
974 991 % m.rel(f))
975 992 result = 1
976 993
977 994 for f in forget:
978 995 if ui.verbose or not m.exact(f):
979 996 ui.status(_('removing %s\n') % m.rel(f))
980 997
981 998 # Need to lock because standin files are deleted then removed from the
982 999 # repository and we could race in-between.
983 1000 wlock = repo.wlock()
984 1001 try:
985 1002 lfdirstate = lfutil.openlfdirstate(ui, repo)
986 1003 for f in forget:
987 1004 if lfdirstate[f] == 'a':
988 1005 lfdirstate.drop(f)
989 1006 else:
990 1007 lfdirstate.remove(f)
991 1008 lfdirstate.write()
992 1009 standins = [lfutil.standin(f) for f in forget]
993 1010 for f in standins:
994 1011 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
995 1012 repo[None].forget(standins)
996 1013 finally:
997 1014 wlock.release()
998 1015
999 1016 return result
1000 1017
1001 1018 def _getoutgoings(repo, other, missing, addfunc):
1002 1019 """get pairs of filename and largefile hash in outgoing revisions
1003 1020 in 'missing'.
1004 1021
1005 1022 largefiles already existing on 'other' repository are ignored.
1006 1023
1007 1024 'addfunc' is invoked with each unique pairs of filename and
1008 1025 largefile hash value.
1009 1026 """
1010 1027 knowns = set()
1011 1028 lfhashes = set()
1012 1029 def dedup(fn, lfhash):
1013 1030 k = (fn, lfhash)
1014 1031 if k not in knowns:
1015 1032 knowns.add(k)
1016 1033 lfhashes.add(lfhash)
1017 1034 lfutil.getlfilestoupload(repo, missing, dedup)
1018 1035 if lfhashes:
1019 1036 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1020 1037 for fn, lfhash in knowns:
1021 1038 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1022 1039 addfunc(fn, lfhash)
1023 1040
1024 1041 def outgoinghook(ui, repo, other, opts, missing):
1025 1042 if opts.pop('large', None):
1026 1043 lfhashes = set()
1027 1044 if ui.debugflag:
1028 1045 toupload = {}
1029 1046 def addfunc(fn, lfhash):
1030 1047 if fn not in toupload:
1031 1048 toupload[fn] = []
1032 1049 toupload[fn].append(lfhash)
1033 1050 lfhashes.add(lfhash)
1034 1051 def showhashes(fn):
1035 1052 for lfhash in sorted(toupload[fn]):
1036 1053 ui.debug(' %s\n' % (lfhash))
1037 1054 else:
1038 1055 toupload = set()
1039 1056 def addfunc(fn, lfhash):
1040 1057 toupload.add(fn)
1041 1058 lfhashes.add(lfhash)
1042 1059 def showhashes(fn):
1043 1060 pass
1044 1061 _getoutgoings(repo, other, missing, addfunc)
1045 1062
1046 1063 if not toupload:
1047 1064 ui.status(_('largefiles: no files to upload\n'))
1048 1065 else:
1049 1066 ui.status(_('largefiles to upload (%d entities):\n')
1050 1067 % (len(lfhashes)))
1051 1068 for file in sorted(toupload):
1052 1069 ui.status(lfutil.splitstandin(file) + '\n')
1053 1070 showhashes(file)
1054 1071 ui.status('\n')
1055 1072
1056 1073 def summaryremotehook(ui, repo, opts, changes):
1057 1074 largeopt = opts.get('large', False)
1058 1075 if changes is None:
1059 1076 if largeopt:
1060 1077 return (False, True) # only outgoing check is needed
1061 1078 else:
1062 1079 return (False, False)
1063 1080 elif largeopt:
1064 1081 url, branch, peer, outgoing = changes[1]
1065 1082 if peer is None:
1066 1083 # i18n: column positioning for "hg summary"
1067 1084 ui.status(_('largefiles: (no remote repo)\n'))
1068 1085 return
1069 1086
1070 1087 toupload = set()
1071 1088 lfhashes = set()
1072 1089 def addfunc(fn, lfhash):
1073 1090 toupload.add(fn)
1074 1091 lfhashes.add(lfhash)
1075 1092 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1076 1093
1077 1094 if not toupload:
1078 1095 # i18n: column positioning for "hg summary"
1079 1096 ui.status(_('largefiles: (no files to upload)\n'))
1080 1097 else:
1081 1098 # i18n: column positioning for "hg summary"
1082 1099 ui.status(_('largefiles: %d entities for %d files to upload\n')
1083 1100 % (len(lfhashes), len(toupload)))
1084 1101
1085 1102 def overridesummary(orig, ui, repo, *pats, **opts):
1086 1103 try:
1087 1104 repo.lfstatus = True
1088 1105 orig(ui, repo, *pats, **opts)
1089 1106 finally:
1090 1107 repo.lfstatus = False
1091 1108
1092 1109 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1093 1110 similarity=None):
1094 1111 if not lfutil.islfilesrepo(repo):
1095 1112 return orig(repo, pats, opts, dry_run, similarity)
1096 1113 # Get the list of missing largefiles so we can remove them
1097 1114 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1098 1115 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1099 1116 False, False)
1100 1117 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1101 1118
1102 1119 # Call into the normal remove code, but the removing of the standin, we want
1103 1120 # to have handled by original addremove. Monkey patching here makes sure
1104 1121 # we don't remove the standin in the largefiles code, preventing a very
1105 1122 # confused state later.
1106 1123 if missing:
1107 1124 m = [repo.wjoin(f) for f in missing]
1108 1125 repo._isaddremove = True
1109 1126 removelargefiles(repo.ui, repo, *m, **opts)
1110 1127 repo._isaddremove = False
1111 1128 # Call into the normal add code, and any files that *should* be added as
1112 1129 # largefiles will be
1113 1130 addlargefiles(repo.ui, repo, *pats, **opts)
1114 1131 # Now that we've handled largefiles, hand off to the original addremove
1115 1132 # function to take care of the rest. Make sure it doesn't do anything with
1116 1133 # largefiles by installing a matcher that will ignore them.
1117 1134 installnormalfilesmatchfn(repo[None].manifest())
1118 1135 result = orig(repo, pats, opts, dry_run, similarity)
1119 1136 restorematchfn()
1120 1137 return result
1121 1138
1122 1139 # Calling purge with --all will cause the largefiles to be deleted.
1123 1140 # Override repo.status to prevent this from happening.
1124 1141 def overridepurge(orig, ui, repo, *dirs, **opts):
1125 1142 # XXX large file status is buggy when used on repo proxy.
1126 1143 # XXX this needs to be investigate.
1127 1144 repo = repo.unfiltered()
1128 1145 oldstatus = repo.status
1129 1146 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1130 1147 clean=False, unknown=False, listsubrepos=False):
1131 1148 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1132 1149 listsubrepos)
1133 1150 lfdirstate = lfutil.openlfdirstate(ui, repo)
1134 1151 modified, added, removed, deleted, unknown, ignored, clean = r
1135 1152 unknown = [f for f in unknown if lfdirstate[f] == '?']
1136 1153 ignored = [f for f in ignored if lfdirstate[f] == '?']
1137 1154 return modified, added, removed, deleted, unknown, ignored, clean
1138 1155 repo.status = overridestatus
1139 1156 orig(ui, repo, *dirs, **opts)
1140 1157 repo.status = oldstatus
1141 1158
1142 1159 def overriderollback(orig, ui, repo, **opts):
1143 1160 wlock = repo.wlock()
1144 1161 try:
1145 1162 result = orig(ui, repo, **opts)
1146 1163 merge.update(repo, node=None, branchmerge=False, force=True,
1147 1164 partial=lfutil.isstandin)
1148 1165
1149 1166 lfdirstate = lfutil.openlfdirstate(ui, repo)
1150 1167 orphans = set(lfdirstate)
1151 1168 lfiles = lfutil.listlfiles(repo)
1152 1169 for file in lfiles:
1153 1170 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1154 1171 orphans.discard(file)
1155 1172 for lfile in orphans:
1156 1173 lfdirstate.drop(lfile)
1157 1174 lfdirstate.write()
1158 1175 finally:
1159 1176 wlock.release()
1160 1177 return result
1161 1178
1162 1179 def overridetransplant(orig, ui, repo, *revs, **opts):
1163 1180 try:
1164 1181 oldstandins = lfutil.getstandinsstate(repo)
1165 1182 repo._istransplanting = True
1166 1183 result = orig(ui, repo, *revs, **opts)
1167 1184 newstandins = lfutil.getstandinsstate(repo)
1168 1185 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1169 1186 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1170 1187 printmessage=True)
1171 1188 finally:
1172 1189 repo._istransplanting = False
1173 1190 return result
1174 1191
1175 1192 def overridecat(orig, ui, repo, file1, *pats, **opts):
1176 1193 ctx = scmutil.revsingle(repo, opts.get('rev'))
1177 1194 err = 1
1178 1195 notbad = set()
1179 1196 m = scmutil.match(ctx, (file1,) + pats, opts)
1180 1197 origmatchfn = m.matchfn
1181 1198 def lfmatchfn(f):
1182 1199 if origmatchfn(f):
1183 1200 return True
1184 1201 lf = lfutil.splitstandin(f)
1185 1202 if lf is None:
1186 1203 return False
1187 1204 notbad.add(lf)
1188 1205 return origmatchfn(lf)
1189 1206 m.matchfn = lfmatchfn
1190 1207 origbadfn = m.bad
1191 1208 def lfbadfn(f, msg):
1192 1209 if not f in notbad:
1193 1210 origbadfn(f, msg)
1194 1211 m.bad = lfbadfn
1195 1212 for f in ctx.walk(m):
1196 1213 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1197 1214 pathname=f)
1198 1215 lf = lfutil.splitstandin(f)
1199 1216 if lf is None or origmatchfn(f):
1200 1217 # duplicating unreachable code from commands.cat
1201 1218 data = ctx[f].data()
1202 1219 if opts.get('decode'):
1203 1220 data = repo.wwritedata(f, data)
1204 1221 fp.write(data)
1205 1222 else:
1206 1223 hash = lfutil.readstandin(repo, lf, ctx.rev())
1207 1224 if not lfutil.inusercache(repo.ui, hash):
1208 1225 store = basestore._openstore(repo)
1209 1226 success, missing = store.get([(lf, hash)])
1210 1227 if len(success) != 1:
1211 1228 raise util.Abort(
1212 1229 _('largefile %s is not in cache and could not be '
1213 1230 'downloaded') % lf)
1214 1231 path = lfutil.usercachepath(repo.ui, hash)
1215 1232 fpin = open(path, "rb")
1216 1233 for chunk in util.filechunkiter(fpin, 128 * 1024):
1217 1234 fp.write(chunk)
1218 1235 fpin.close()
1219 1236 fp.close()
1220 1237 err = 0
1221 1238 return err
1222 1239
1223 1240 def mercurialsinkbefore(orig, sink):
1224 1241 sink.repo._isconverting = True
1225 1242 orig(sink)
1226 1243
1227 1244 def mercurialsinkafter(orig, sink):
1228 1245 sink.repo._isconverting = False
1229 1246 orig(sink)
@@ -1,2678 +1,2685 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_('uncommitted changes'))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
113 113 editform='', **opts):
114 114 """get appropriate commit message editor according to '--edit' option
115 115
116 116 'finishdesc' is a function to be called with edited commit message
117 117 (= 'description' of the new changeset) just after editing, but
118 118 before checking empty-ness. It should return actual text to be
119 119 stored into history. This allows to change description before
120 120 storing.
121 121
122 122 'extramsg' is a extra message to be shown in the editor instead of
123 123 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
124 124 is automatically added.
125 125
126 126 'editform' is a dot-separated list of names, to distinguish
127 127 the purpose of commit text editing.
128 128
129 129 'getcommiteditor' returns 'commitforceeditor' regardless of
130 130 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
131 131 they are specific for usage in MQ.
132 132 """
133 133 if edit or finishdesc or extramsg:
134 134 return lambda r, c, s: commitforceeditor(r, c, s,
135 135 finishdesc=finishdesc,
136 136 extramsg=extramsg,
137 137 editform=editform)
138 138 elif editform:
139 139 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
140 140 else:
141 141 return commiteditor
142 142
143 143 def loglimit(opts):
144 144 """get the log limit according to option -l/--limit"""
145 145 limit = opts.get('limit')
146 146 if limit:
147 147 try:
148 148 limit = int(limit)
149 149 except ValueError:
150 150 raise util.Abort(_('limit must be a positive integer'))
151 151 if limit <= 0:
152 152 raise util.Abort(_('limit must be positive'))
153 153 else:
154 154 limit = None
155 155 return limit
156 156
157 157 def makefilename(repo, pat, node, desc=None,
158 158 total=None, seqno=None, revwidth=None, pathname=None):
159 159 node_expander = {
160 160 'H': lambda: hex(node),
161 161 'R': lambda: str(repo.changelog.rev(node)),
162 162 'h': lambda: short(node),
163 163 'm': lambda: re.sub('[^\w]', '_', str(desc))
164 164 }
165 165 expander = {
166 166 '%': lambda: '%',
167 167 'b': lambda: os.path.basename(repo.root),
168 168 }
169 169
170 170 try:
171 171 if node:
172 172 expander.update(node_expander)
173 173 if node:
174 174 expander['r'] = (lambda:
175 175 str(repo.changelog.rev(node)).zfill(revwidth or 0))
176 176 if total is not None:
177 177 expander['N'] = lambda: str(total)
178 178 if seqno is not None:
179 179 expander['n'] = lambda: str(seqno)
180 180 if total is not None and seqno is not None:
181 181 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
182 182 if pathname is not None:
183 183 expander['s'] = lambda: os.path.basename(pathname)
184 184 expander['d'] = lambda: os.path.dirname(pathname) or '.'
185 185 expander['p'] = lambda: pathname
186 186
187 187 newname = []
188 188 patlen = len(pat)
189 189 i = 0
190 190 while i < patlen:
191 191 c = pat[i]
192 192 if c == '%':
193 193 i += 1
194 194 c = pat[i]
195 195 c = expander[c]()
196 196 newname.append(c)
197 197 i += 1
198 198 return ''.join(newname)
199 199 except KeyError, inst:
200 200 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
201 201 inst.args[0])
202 202
203 203 def makefileobj(repo, pat, node=None, desc=None, total=None,
204 204 seqno=None, revwidth=None, mode='wb', modemap=None,
205 205 pathname=None):
206 206
207 207 writable = mode not in ('r', 'rb')
208 208
209 209 if not pat or pat == '-':
210 210 fp = writable and repo.ui.fout or repo.ui.fin
211 211 if util.safehasattr(fp, 'fileno'):
212 212 return os.fdopen(os.dup(fp.fileno()), mode)
213 213 else:
214 214 # if this fp can't be duped properly, return
215 215 # a dummy object that can be closed
216 216 class wrappedfileobj(object):
217 217 noop = lambda x: None
218 218 def __init__(self, f):
219 219 self.f = f
220 220 def __getattr__(self, attr):
221 221 if attr == 'close':
222 222 return self.noop
223 223 else:
224 224 return getattr(self.f, attr)
225 225
226 226 return wrappedfileobj(fp)
227 227 if util.safehasattr(pat, 'write') and writable:
228 228 return pat
229 229 if util.safehasattr(pat, 'read') and 'r' in mode:
230 230 return pat
231 231 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
232 232 if modemap is not None:
233 233 mode = modemap.get(fn, mode)
234 234 if mode == 'wb':
235 235 modemap[fn] = 'ab'
236 236 return open(fn, mode)
237 237
238 238 def openrevlog(repo, cmd, file_, opts):
239 239 """opens the changelog, manifest, a filelog or a given revlog"""
240 240 cl = opts['changelog']
241 241 mf = opts['manifest']
242 242 msg = None
243 243 if cl and mf:
244 244 msg = _('cannot specify --changelog and --manifest at the same time')
245 245 elif cl or mf:
246 246 if file_:
247 247 msg = _('cannot specify filename with --changelog or --manifest')
248 248 elif not repo:
249 249 msg = _('cannot specify --changelog or --manifest '
250 250 'without a repository')
251 251 if msg:
252 252 raise util.Abort(msg)
253 253
254 254 r = None
255 255 if repo:
256 256 if cl:
257 257 r = repo.unfiltered().changelog
258 258 elif mf:
259 259 r = repo.manifest
260 260 elif file_:
261 261 filelog = repo.file(file_)
262 262 if len(filelog):
263 263 r = filelog
264 264 if not r:
265 265 if not file_:
266 266 raise error.CommandError(cmd, _('invalid arguments'))
267 267 if not os.path.isfile(file_):
268 268 raise util.Abort(_("revlog '%s' not found") % file_)
269 269 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
270 270 file_[:-2] + ".i")
271 271 return r
272 272
273 273 def copy(ui, repo, pats, opts, rename=False):
274 274 # called with the repo lock held
275 275 #
276 276 # hgsep => pathname that uses "/" to separate directories
277 277 # ossep => pathname that uses os.sep to separate directories
278 278 cwd = repo.getcwd()
279 279 targets = {}
280 280 after = opts.get("after")
281 281 dryrun = opts.get("dry_run")
282 282 wctx = repo[None]
283 283
284 284 def walkpat(pat):
285 285 srcs = []
286 286 badstates = after and '?' or '?r'
287 287 m = scmutil.match(repo[None], [pat], opts, globbed=True)
288 288 for abs in repo.walk(m):
289 289 state = repo.dirstate[abs]
290 290 rel = m.rel(abs)
291 291 exact = m.exact(abs)
292 292 if state in badstates:
293 293 if exact and state == '?':
294 294 ui.warn(_('%s: not copying - file is not managed\n') % rel)
295 295 if exact and state == 'r':
296 296 ui.warn(_('%s: not copying - file has been marked for'
297 297 ' remove\n') % rel)
298 298 continue
299 299 # abs: hgsep
300 300 # rel: ossep
301 301 srcs.append((abs, rel, exact))
302 302 return srcs
303 303
304 304 # abssrc: hgsep
305 305 # relsrc: ossep
306 306 # otarget: ossep
307 307 def copyfile(abssrc, relsrc, otarget, exact):
308 308 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
309 309 if '/' in abstarget:
310 310 # We cannot normalize abstarget itself, this would prevent
311 311 # case only renames, like a => A.
312 312 abspath, absname = abstarget.rsplit('/', 1)
313 313 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
314 314 reltarget = repo.pathto(abstarget, cwd)
315 315 target = repo.wjoin(abstarget)
316 316 src = repo.wjoin(abssrc)
317 317 state = repo.dirstate[abstarget]
318 318
319 319 scmutil.checkportable(ui, abstarget)
320 320
321 321 # check for collisions
322 322 prevsrc = targets.get(abstarget)
323 323 if prevsrc is not None:
324 324 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
325 325 (reltarget, repo.pathto(abssrc, cwd),
326 326 repo.pathto(prevsrc, cwd)))
327 327 return
328 328
329 329 # check for overwrites
330 330 exists = os.path.lexists(target)
331 331 samefile = False
332 332 if exists and abssrc != abstarget:
333 333 if (repo.dirstate.normalize(abssrc) ==
334 334 repo.dirstate.normalize(abstarget)):
335 335 if not rename:
336 336 ui.warn(_("%s: can't copy - same file\n") % reltarget)
337 337 return
338 338 exists = False
339 339 samefile = True
340 340
341 341 if not after and exists or after and state in 'mn':
342 342 if not opts['force']:
343 343 ui.warn(_('%s: not overwriting - file exists\n') %
344 344 reltarget)
345 345 return
346 346
347 347 if after:
348 348 if not exists:
349 349 if rename:
350 350 ui.warn(_('%s: not recording move - %s does not exist\n') %
351 351 (relsrc, reltarget))
352 352 else:
353 353 ui.warn(_('%s: not recording copy - %s does not exist\n') %
354 354 (relsrc, reltarget))
355 355 return
356 356 elif not dryrun:
357 357 try:
358 358 if exists:
359 359 os.unlink(target)
360 360 targetdir = os.path.dirname(target) or '.'
361 361 if not os.path.isdir(targetdir):
362 362 os.makedirs(targetdir)
363 363 if samefile:
364 364 tmp = target + "~hgrename"
365 365 os.rename(src, tmp)
366 366 os.rename(tmp, target)
367 367 else:
368 368 util.copyfile(src, target)
369 369 srcexists = True
370 370 except IOError, inst:
371 371 if inst.errno == errno.ENOENT:
372 372 ui.warn(_('%s: deleted in working copy\n') % relsrc)
373 373 srcexists = False
374 374 else:
375 375 ui.warn(_('%s: cannot copy - %s\n') %
376 376 (relsrc, inst.strerror))
377 377 return True # report a failure
378 378
379 379 if ui.verbose or not exact:
380 380 if rename:
381 381 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
382 382 else:
383 383 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
384 384
385 385 targets[abstarget] = abssrc
386 386
387 387 # fix up dirstate
388 388 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
389 389 dryrun=dryrun, cwd=cwd)
390 390 if rename and not dryrun:
391 391 if not after and srcexists and not samefile:
392 392 util.unlinkpath(repo.wjoin(abssrc))
393 393 wctx.forget([abssrc])
394 394
395 395 # pat: ossep
396 396 # dest ossep
397 397 # srcs: list of (hgsep, hgsep, ossep, bool)
398 398 # return: function that takes hgsep and returns ossep
399 399 def targetpathfn(pat, dest, srcs):
400 400 if os.path.isdir(pat):
401 401 abspfx = pathutil.canonpath(repo.root, cwd, pat)
402 402 abspfx = util.localpath(abspfx)
403 403 if destdirexists:
404 404 striplen = len(os.path.split(abspfx)[0])
405 405 else:
406 406 striplen = len(abspfx)
407 407 if striplen:
408 408 striplen += len(os.sep)
409 409 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
410 410 elif destdirexists:
411 411 res = lambda p: os.path.join(dest,
412 412 os.path.basename(util.localpath(p)))
413 413 else:
414 414 res = lambda p: dest
415 415 return res
416 416
417 417 # pat: ossep
418 418 # dest ossep
419 419 # srcs: list of (hgsep, hgsep, ossep, bool)
420 420 # return: function that takes hgsep and returns ossep
421 421 def targetpathafterfn(pat, dest, srcs):
422 422 if matchmod.patkind(pat):
423 423 # a mercurial pattern
424 424 res = lambda p: os.path.join(dest,
425 425 os.path.basename(util.localpath(p)))
426 426 else:
427 427 abspfx = pathutil.canonpath(repo.root, cwd, pat)
428 428 if len(abspfx) < len(srcs[0][0]):
429 429 # A directory. Either the target path contains the last
430 430 # component of the source path or it does not.
431 431 def evalpath(striplen):
432 432 score = 0
433 433 for s in srcs:
434 434 t = os.path.join(dest, util.localpath(s[0])[striplen:])
435 435 if os.path.lexists(t):
436 436 score += 1
437 437 return score
438 438
439 439 abspfx = util.localpath(abspfx)
440 440 striplen = len(abspfx)
441 441 if striplen:
442 442 striplen += len(os.sep)
443 443 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
444 444 score = evalpath(striplen)
445 445 striplen1 = len(os.path.split(abspfx)[0])
446 446 if striplen1:
447 447 striplen1 += len(os.sep)
448 448 if evalpath(striplen1) > score:
449 449 striplen = striplen1
450 450 res = lambda p: os.path.join(dest,
451 451 util.localpath(p)[striplen:])
452 452 else:
453 453 # a file
454 454 if destdirexists:
455 455 res = lambda p: os.path.join(dest,
456 456 os.path.basename(util.localpath(p)))
457 457 else:
458 458 res = lambda p: dest
459 459 return res
460 460
461 461
462 462 pats = scmutil.expandpats(pats)
463 463 if not pats:
464 464 raise util.Abort(_('no source or destination specified'))
465 465 if len(pats) == 1:
466 466 raise util.Abort(_('no destination specified'))
467 467 dest = pats.pop()
468 468 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
469 469 if not destdirexists:
470 470 if len(pats) > 1 or matchmod.patkind(pats[0]):
471 471 raise util.Abort(_('with multiple sources, destination must be an '
472 472 'existing directory'))
473 473 if util.endswithsep(dest):
474 474 raise util.Abort(_('destination %s is not a directory') % dest)
475 475
476 476 tfn = targetpathfn
477 477 if after:
478 478 tfn = targetpathafterfn
479 479 copylist = []
480 480 for pat in pats:
481 481 srcs = walkpat(pat)
482 482 if not srcs:
483 483 continue
484 484 copylist.append((tfn(pat, dest, srcs), srcs))
485 485 if not copylist:
486 486 raise util.Abort(_('no files to copy'))
487 487
488 488 errors = 0
489 489 for targetpath, srcs in copylist:
490 490 for abssrc, relsrc, exact in srcs:
491 491 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
492 492 errors += 1
493 493
494 494 if errors:
495 495 ui.warn(_('(consider using --after)\n'))
496 496
497 497 return errors != 0
498 498
499 499 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
500 500 runargs=None, appendpid=False):
501 501 '''Run a command as a service.'''
502 502
503 503 def writepid(pid):
504 504 if opts['pid_file']:
505 505 mode = appendpid and 'a' or 'w'
506 506 fp = open(opts['pid_file'], mode)
507 507 fp.write(str(pid) + '\n')
508 508 fp.close()
509 509
510 510 if opts['daemon'] and not opts['daemon_pipefds']:
511 511 # Signal child process startup with file removal
512 512 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
513 513 os.close(lockfd)
514 514 try:
515 515 if not runargs:
516 516 runargs = util.hgcmd() + sys.argv[1:]
517 517 runargs.append('--daemon-pipefds=%s' % lockpath)
518 518 # Don't pass --cwd to the child process, because we've already
519 519 # changed directory.
520 520 for i in xrange(1, len(runargs)):
521 521 if runargs[i].startswith('--cwd='):
522 522 del runargs[i]
523 523 break
524 524 elif runargs[i].startswith('--cwd'):
525 525 del runargs[i:i + 2]
526 526 break
527 527 def condfn():
528 528 return not os.path.exists(lockpath)
529 529 pid = util.rundetached(runargs, condfn)
530 530 if pid < 0:
531 531 raise util.Abort(_('child process failed to start'))
532 532 writepid(pid)
533 533 finally:
534 534 try:
535 535 os.unlink(lockpath)
536 536 except OSError, e:
537 537 if e.errno != errno.ENOENT:
538 538 raise
539 539 if parentfn:
540 540 return parentfn(pid)
541 541 else:
542 542 return
543 543
544 544 if initfn:
545 545 initfn()
546 546
547 547 if not opts['daemon']:
548 548 writepid(os.getpid())
549 549
550 550 if opts['daemon_pipefds']:
551 551 lockpath = opts['daemon_pipefds']
552 552 try:
553 553 os.setsid()
554 554 except AttributeError:
555 555 pass
556 556 os.unlink(lockpath)
557 557 util.hidewindow()
558 558 sys.stdout.flush()
559 559 sys.stderr.flush()
560 560
561 561 nullfd = os.open(os.devnull, os.O_RDWR)
562 562 logfilefd = nullfd
563 563 if logfile:
564 564 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
565 565 os.dup2(nullfd, 0)
566 566 os.dup2(logfilefd, 1)
567 567 os.dup2(logfilefd, 2)
568 568 if nullfd not in (0, 1, 2):
569 569 os.close(nullfd)
570 570 if logfile and logfilefd not in (0, 1, 2):
571 571 os.close(logfilefd)
572 572
573 573 if runfn:
574 574 return runfn()
575 575
576 576 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
577 577 """Utility function used by commands.import to import a single patch
578 578
579 579 This function is explicitly defined here to help the evolve extension to
580 580 wrap this part of the import logic.
581 581
582 582 The API is currently a bit ugly because it a simple code translation from
583 583 the import command. Feel free to make it better.
584 584
585 585 :hunk: a patch (as a binary string)
586 586 :parents: nodes that will be parent of the created commit
587 587 :opts: the full dict of option passed to the import command
588 588 :msgs: list to save commit message to.
589 589 (used in case we need to save it when failing)
590 590 :updatefunc: a function that update a repo to a given node
591 591 updatefunc(<repo>, <node>)
592 592 """
593 593 tmpname, message, user, date, branch, nodeid, p1, p2 = \
594 594 patch.extract(ui, hunk)
595 595
596 596 editor = getcommiteditor(editform='import.normal', **opts)
597 597 update = not opts.get('bypass')
598 598 strip = opts["strip"]
599 599 sim = float(opts.get('similarity') or 0)
600 600 if not tmpname:
601 601 return (None, None, False)
602 602 msg = _('applied to working directory')
603 603
604 604 rejects = False
605 605
606 606 try:
607 607 cmdline_message = logmessage(ui, opts)
608 608 if cmdline_message:
609 609 # pickup the cmdline msg
610 610 message = cmdline_message
611 611 elif message:
612 612 # pickup the patch msg
613 613 message = message.strip()
614 614 else:
615 615 # launch the editor
616 616 message = None
617 617 ui.debug('message:\n%s\n' % message)
618 618
619 619 if len(parents) == 1:
620 620 parents.append(repo[nullid])
621 621 if opts.get('exact'):
622 622 if not nodeid or not p1:
623 623 raise util.Abort(_('not a Mercurial patch'))
624 624 p1 = repo[p1]
625 625 p2 = repo[p2 or nullid]
626 626 elif p2:
627 627 try:
628 628 p1 = repo[p1]
629 629 p2 = repo[p2]
630 630 # Without any options, consider p2 only if the
631 631 # patch is being applied on top of the recorded
632 632 # first parent.
633 633 if p1 != parents[0]:
634 634 p1 = parents[0]
635 635 p2 = repo[nullid]
636 636 except error.RepoError:
637 637 p1, p2 = parents
638 638 else:
639 639 p1, p2 = parents
640 640
641 641 n = None
642 642 if update:
643 643 if p1 != parents[0]:
644 644 updatefunc(repo, p1.node())
645 645 if p2 != parents[1]:
646 646 repo.setparents(p1.node(), p2.node())
647 647
648 648 if opts.get('exact') or opts.get('import_branch'):
649 649 repo.dirstate.setbranch(branch or 'default')
650 650
651 651 partial = opts.get('partial', False)
652 652 files = set()
653 653 try:
654 654 patch.patch(ui, repo, tmpname, strip=strip, files=files,
655 655 eolmode=None, similarity=sim / 100.0)
656 656 except patch.PatchError, e:
657 657 if not partial:
658 658 raise util.Abort(str(e))
659 659 if partial:
660 660 rejects = True
661 661
662 662 files = list(files)
663 663 if opts.get('no_commit'):
664 664 if message:
665 665 msgs.append(message)
666 666 else:
667 667 if opts.get('exact') or p2:
668 668 # If you got here, you either use --force and know what
669 669 # you are doing or used --exact or a merge patch while
670 670 # being updated to its first parent.
671 671 m = None
672 672 else:
673 673 m = scmutil.matchfiles(repo, files or [])
674 674 n = repo.commit(message, opts.get('user') or user,
675 675 opts.get('date') or date, match=m,
676 676 editor=editor, force=partial)
677 677 else:
678 678 if opts.get('exact') or opts.get('import_branch'):
679 679 branch = branch or 'default'
680 680 else:
681 681 branch = p1.branch()
682 682 store = patch.filestore()
683 683 try:
684 684 files = set()
685 685 try:
686 686 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
687 687 files, eolmode=None)
688 688 except patch.PatchError, e:
689 689 raise util.Abort(str(e))
690 690 editor = getcommiteditor(editform='import.bypass')
691 691 memctx = context.makememctx(repo, (p1.node(), p2.node()),
692 692 message,
693 693 opts.get('user') or user,
694 694 opts.get('date') or date,
695 695 branch, files, store,
696 696 editor=editor)
697 697 n = memctx.commit()
698 698 finally:
699 699 store.close()
700 700 if opts.get('exact') and hex(n) != nodeid:
701 701 raise util.Abort(_('patch is damaged or loses information'))
702 702 if n:
703 703 # i18n: refers to a short changeset id
704 704 msg = _('created %s') % short(n)
705 705 return (msg, n, rejects)
706 706 finally:
707 707 os.unlink(tmpname)
708 708
709 709 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
710 710 opts=None):
711 711 '''export changesets as hg patches.'''
712 712
713 713 total = len(revs)
714 714 revwidth = max([len(str(rev)) for rev in revs])
715 715 filemode = {}
716 716
717 717 def single(rev, seqno, fp):
718 718 ctx = repo[rev]
719 719 node = ctx.node()
720 720 parents = [p.node() for p in ctx.parents() if p]
721 721 branch = ctx.branch()
722 722 if switch_parent:
723 723 parents.reverse()
724 724 prev = (parents and parents[0]) or nullid
725 725
726 726 shouldclose = False
727 727 if not fp and len(template) > 0:
728 728 desc_lines = ctx.description().rstrip().split('\n')
729 729 desc = desc_lines[0] #Commit always has a first line.
730 730 fp = makefileobj(repo, template, node, desc=desc, total=total,
731 731 seqno=seqno, revwidth=revwidth, mode='wb',
732 732 modemap=filemode)
733 733 if fp != template:
734 734 shouldclose = True
735 735 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
736 736 repo.ui.note("%s\n" % fp.name)
737 737
738 738 if not fp:
739 739 write = repo.ui.write
740 740 else:
741 741 def write(s, **kw):
742 742 fp.write(s)
743 743
744 744
745 745 write("# HG changeset patch\n")
746 746 write("# User %s\n" % ctx.user())
747 747 write("# Date %d %d\n" % ctx.date())
748 748 write("# %s\n" % util.datestr(ctx.date()))
749 749 if branch and branch != 'default':
750 750 write("# Branch %s\n" % branch)
751 751 write("# Node ID %s\n" % hex(node))
752 752 write("# Parent %s\n" % hex(prev))
753 753 if len(parents) > 1:
754 754 write("# Parent %s\n" % hex(parents[1]))
755 755 write(ctx.description().rstrip())
756 756 write("\n\n")
757 757
758 758 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
759 759 write(chunk, label=label)
760 760
761 761 if shouldclose:
762 762 fp.close()
763 763
764 764 for seqno, rev in enumerate(revs):
765 765 single(rev, seqno + 1, fp)
766 766
767 767 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
768 768 changes=None, stat=False, fp=None, prefix='',
769 769 listsubrepos=False):
770 770 '''show diff or diffstat.'''
771 771 if fp is None:
772 772 write = ui.write
773 773 else:
774 774 def write(s, **kw):
775 775 fp.write(s)
776 776
777 777 if stat:
778 778 diffopts = diffopts.copy(context=0)
779 779 width = 80
780 780 if not ui.plain():
781 781 width = ui.termwidth()
782 782 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
783 783 prefix=prefix)
784 784 for chunk, label in patch.diffstatui(util.iterlines(chunks),
785 785 width=width,
786 786 git=diffopts.git):
787 787 write(chunk, label=label)
788 788 else:
789 789 for chunk, label in patch.diffui(repo, node1, node2, match,
790 790 changes, diffopts, prefix=prefix):
791 791 write(chunk, label=label)
792 792
793 793 if listsubrepos:
794 794 ctx1 = repo[node1]
795 795 ctx2 = repo[node2]
796 796 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
797 797 tempnode2 = node2
798 798 try:
799 799 if node2 is not None:
800 800 tempnode2 = ctx2.substate[subpath][1]
801 801 except KeyError:
802 802 # A subrepo that existed in node1 was deleted between node1 and
803 803 # node2 (inclusive). Thus, ctx2's substate won't contain that
804 804 # subpath. The best we can do is to ignore it.
805 805 tempnode2 = None
806 806 submatch = matchmod.narrowmatcher(subpath, match)
807 807 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
808 808 stat=stat, fp=fp, prefix=prefix)
809 809
810 810 class changeset_printer(object):
811 811 '''show changeset information when templating not requested.'''
812 812
813 813 def __init__(self, ui, repo, patch, diffopts, buffered):
814 814 self.ui = ui
815 815 self.repo = repo
816 816 self.buffered = buffered
817 817 self.patch = patch
818 818 self.diffopts = diffopts
819 819 self.header = {}
820 820 self.hunk = {}
821 821 self.lastheader = None
822 822 self.footer = None
823 823
824 824 def flush(self, rev):
825 825 if rev in self.header:
826 826 h = self.header[rev]
827 827 if h != self.lastheader:
828 828 self.lastheader = h
829 829 self.ui.write(h)
830 830 del self.header[rev]
831 831 if rev in self.hunk:
832 832 self.ui.write(self.hunk[rev])
833 833 del self.hunk[rev]
834 834 return 1
835 835 return 0
836 836
837 837 def close(self):
838 838 if self.footer:
839 839 self.ui.write(self.footer)
840 840
841 841 def show(self, ctx, copies=None, matchfn=None, **props):
842 842 if self.buffered:
843 843 self.ui.pushbuffer()
844 844 self._show(ctx, copies, matchfn, props)
845 845 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
846 846 else:
847 847 self._show(ctx, copies, matchfn, props)
848 848
849 849 def _show(self, ctx, copies, matchfn, props):
850 850 '''show a single changeset or file revision'''
851 851 changenode = ctx.node()
852 852 rev = ctx.rev()
853 853
854 854 if self.ui.quiet:
855 855 self.ui.write("%d:%s\n" % (rev, short(changenode)),
856 856 label='log.node')
857 857 return
858 858
859 859 log = self.repo.changelog
860 860 date = util.datestr(ctx.date())
861 861
862 862 hexfunc = self.ui.debugflag and hex or short
863 863
864 864 parents = [(p, hexfunc(log.node(p)))
865 865 for p in self._meaningful_parentrevs(log, rev)]
866 866
867 867 # i18n: column positioning for "hg log"
868 868 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
869 869 label='log.changeset changeset.%s' % ctx.phasestr())
870 870
871 871 branch = ctx.branch()
872 872 # don't show the default branch name
873 873 if branch != 'default':
874 874 # i18n: column positioning for "hg log"
875 875 self.ui.write(_("branch: %s\n") % branch,
876 876 label='log.branch')
877 877 for bookmark in self.repo.nodebookmarks(changenode):
878 878 # i18n: column positioning for "hg log"
879 879 self.ui.write(_("bookmark: %s\n") % bookmark,
880 880 label='log.bookmark')
881 881 for tag in self.repo.nodetags(changenode):
882 882 # i18n: column positioning for "hg log"
883 883 self.ui.write(_("tag: %s\n") % tag,
884 884 label='log.tag')
885 885 if self.ui.debugflag and ctx.phase():
886 886 # i18n: column positioning for "hg log"
887 887 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
888 888 label='log.phase')
889 889 for parent in parents:
890 890 # i18n: column positioning for "hg log"
891 891 self.ui.write(_("parent: %d:%s\n") % parent,
892 892 label='log.parent changeset.%s' % ctx.phasestr())
893 893
894 894 if self.ui.debugflag:
895 895 mnode = ctx.manifestnode()
896 896 # i18n: column positioning for "hg log"
897 897 self.ui.write(_("manifest: %d:%s\n") %
898 898 (self.repo.manifest.rev(mnode), hex(mnode)),
899 899 label='ui.debug log.manifest')
900 900 # i18n: column positioning for "hg log"
901 901 self.ui.write(_("user: %s\n") % ctx.user(),
902 902 label='log.user')
903 903 # i18n: column positioning for "hg log"
904 904 self.ui.write(_("date: %s\n") % date,
905 905 label='log.date')
906 906
907 907 if self.ui.debugflag:
908 908 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
909 909 for key, value in zip([# i18n: column positioning for "hg log"
910 910 _("files:"),
911 911 # i18n: column positioning for "hg log"
912 912 _("files+:"),
913 913 # i18n: column positioning for "hg log"
914 914 _("files-:")], files):
915 915 if value:
916 916 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
917 917 label='ui.debug log.files')
918 918 elif ctx.files() and self.ui.verbose:
919 919 # i18n: column positioning for "hg log"
920 920 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
921 921 label='ui.note log.files')
922 922 if copies and self.ui.verbose:
923 923 copies = ['%s (%s)' % c for c in copies]
924 924 # i18n: column positioning for "hg log"
925 925 self.ui.write(_("copies: %s\n") % ' '.join(copies),
926 926 label='ui.note log.copies')
927 927
928 928 extra = ctx.extra()
929 929 if extra and self.ui.debugflag:
930 930 for key, value in sorted(extra.items()):
931 931 # i18n: column positioning for "hg log"
932 932 self.ui.write(_("extra: %s=%s\n")
933 933 % (key, value.encode('string_escape')),
934 934 label='ui.debug log.extra')
935 935
936 936 description = ctx.description().strip()
937 937 if description:
938 938 if self.ui.verbose:
939 939 self.ui.write(_("description:\n"),
940 940 label='ui.note log.description')
941 941 self.ui.write(description,
942 942 label='ui.note log.description')
943 943 self.ui.write("\n\n")
944 944 else:
945 945 # i18n: column positioning for "hg log"
946 946 self.ui.write(_("summary: %s\n") %
947 947 description.splitlines()[0],
948 948 label='log.summary')
949 949 self.ui.write("\n")
950 950
951 951 self.showpatch(changenode, matchfn)
952 952
953 953 def showpatch(self, node, matchfn):
954 954 if not matchfn:
955 955 matchfn = self.patch
956 956 if matchfn:
957 957 stat = self.diffopts.get('stat')
958 958 diff = self.diffopts.get('patch')
959 959 diffopts = patch.diffopts(self.ui, self.diffopts)
960 960 prev = self.repo.changelog.parents(node)[0]
961 961 if stat:
962 962 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
963 963 match=matchfn, stat=True)
964 964 if diff:
965 965 if stat:
966 966 self.ui.write("\n")
967 967 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
968 968 match=matchfn, stat=False)
969 969 self.ui.write("\n")
970 970
971 971 def _meaningful_parentrevs(self, log, rev):
972 972 """Return list of meaningful (or all if debug) parentrevs for rev.
973 973
974 974 For merges (two non-nullrev revisions) both parents are meaningful.
975 975 Otherwise the first parent revision is considered meaningful if it
976 976 is not the preceding revision.
977 977 """
978 978 parents = log.parentrevs(rev)
979 979 if not self.ui.debugflag and parents[1] == nullrev:
980 980 if parents[0] >= rev - 1:
981 981 parents = []
982 982 else:
983 983 parents = [parents[0]]
984 984 return parents
985 985
986 986
987 987 class changeset_templater(changeset_printer):
988 988 '''format changeset information.'''
989 989
990 990 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
991 991 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
992 992 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
993 993 defaulttempl = {
994 994 'parent': '{rev}:{node|formatnode} ',
995 995 'manifest': '{rev}:{node|formatnode}',
996 996 'file_copy': '{name} ({source})',
997 997 'extra': '{key}={value|stringescape}'
998 998 }
999 999 # filecopy is preserved for compatibility reasons
1000 1000 defaulttempl['filecopy'] = defaulttempl['file_copy']
1001 1001 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1002 1002 cache=defaulttempl)
1003 1003 if tmpl:
1004 1004 self.t.cache['changeset'] = tmpl
1005 1005
1006 1006 self.cache = {}
1007 1007
1008 1008 def _meaningful_parentrevs(self, ctx):
1009 1009 """Return list of meaningful (or all if debug) parentrevs for rev.
1010 1010 """
1011 1011 parents = ctx.parents()
1012 1012 if len(parents) > 1:
1013 1013 return parents
1014 1014 if self.ui.debugflag:
1015 1015 return [parents[0], self.repo['null']]
1016 1016 if parents[0].rev() >= ctx.rev() - 1:
1017 1017 return []
1018 1018 return parents
1019 1019
1020 1020 def _show(self, ctx, copies, matchfn, props):
1021 1021 '''show a single changeset or file revision'''
1022 1022
1023 1023 showlist = templatekw.showlist
1024 1024
1025 1025 # showparents() behaviour depends on ui trace level which
1026 1026 # causes unexpected behaviours at templating level and makes
1027 1027 # it harder to extract it in a standalone function. Its
1028 1028 # behaviour cannot be changed so leave it here for now.
1029 1029 def showparents(**args):
1030 1030 ctx = args['ctx']
1031 1031 parents = [[('rev', p.rev()), ('node', p.hex())]
1032 1032 for p in self._meaningful_parentrevs(ctx)]
1033 1033 return showlist('parent', parents, **args)
1034 1034
1035 1035 props = props.copy()
1036 1036 props.update(templatekw.keywords)
1037 1037 props['parents'] = showparents
1038 1038 props['templ'] = self.t
1039 1039 props['ctx'] = ctx
1040 1040 props['repo'] = self.repo
1041 1041 props['revcache'] = {'copies': copies}
1042 1042 props['cache'] = self.cache
1043 1043
1044 1044 # find correct templates for current mode
1045 1045
1046 1046 tmplmodes = [
1047 1047 (True, None),
1048 1048 (self.ui.verbose, 'verbose'),
1049 1049 (self.ui.quiet, 'quiet'),
1050 1050 (self.ui.debugflag, 'debug'),
1051 1051 ]
1052 1052
1053 1053 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1054 1054 for mode, postfix in tmplmodes:
1055 1055 for type in types:
1056 1056 cur = postfix and ('%s_%s' % (type, postfix)) or type
1057 1057 if mode and cur in self.t:
1058 1058 types[type] = cur
1059 1059
1060 1060 try:
1061 1061
1062 1062 # write header
1063 1063 if types['header']:
1064 1064 h = templater.stringify(self.t(types['header'], **props))
1065 1065 if self.buffered:
1066 1066 self.header[ctx.rev()] = h
1067 1067 else:
1068 1068 if self.lastheader != h:
1069 1069 self.lastheader = h
1070 1070 self.ui.write(h)
1071 1071
1072 1072 # write changeset metadata, then patch if requested
1073 1073 key = types['changeset']
1074 1074 self.ui.write(templater.stringify(self.t(key, **props)))
1075 1075 self.showpatch(ctx.node(), matchfn)
1076 1076
1077 1077 if types['footer']:
1078 1078 if not self.footer:
1079 1079 self.footer = templater.stringify(self.t(types['footer'],
1080 1080 **props))
1081 1081
1082 1082 except KeyError, inst:
1083 1083 msg = _("%s: no key named '%s'")
1084 1084 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1085 1085 except SyntaxError, inst:
1086 1086 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1087 1087
1088 1088 def gettemplate(ui, tmpl, style):
1089 1089 """
1090 1090 Find the template matching the given template spec or style.
1091 1091 """
1092 1092
1093 1093 # ui settings
1094 1094 if not tmpl and not style:
1095 1095 tmpl = ui.config('ui', 'logtemplate')
1096 1096 if tmpl:
1097 1097 try:
1098 1098 tmpl = templater.parsestring(tmpl)
1099 1099 except SyntaxError:
1100 1100 tmpl = templater.parsestring(tmpl, quoted=False)
1101 1101 return tmpl, None
1102 1102 else:
1103 1103 style = util.expandpath(ui.config('ui', 'style', ''))
1104 1104
1105 1105 if style:
1106 1106 mapfile = style
1107 1107 if not os.path.split(mapfile)[0]:
1108 1108 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1109 1109 or templater.templatepath(mapfile))
1110 1110 if mapname:
1111 1111 mapfile = mapname
1112 1112 return None, mapfile
1113 1113
1114 1114 if not tmpl:
1115 1115 return None, None
1116 1116
1117 1117 # looks like a literal template?
1118 1118 if '{' in tmpl:
1119 1119 return tmpl, None
1120 1120
1121 1121 # perhaps a stock style?
1122 1122 if not os.path.split(tmpl)[0]:
1123 1123 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1124 1124 or templater.templatepath(tmpl))
1125 1125 if mapname and os.path.isfile(mapname):
1126 1126 return None, mapname
1127 1127
1128 1128 # perhaps it's a reference to [templates]
1129 1129 t = ui.config('templates', tmpl)
1130 1130 if t:
1131 1131 try:
1132 1132 tmpl = templater.parsestring(t)
1133 1133 except SyntaxError:
1134 1134 tmpl = templater.parsestring(t, quoted=False)
1135 1135 return tmpl, None
1136 1136
1137 1137 if tmpl == 'list':
1138 1138 ui.write(_("available styles: %s\n") % templater.stylelist())
1139 1139 raise util.Abort(_("specify a template"))
1140 1140
1141 1141 # perhaps it's a path to a map or a template
1142 1142 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1143 1143 # is it a mapfile for a style?
1144 1144 if os.path.basename(tmpl).startswith("map-"):
1145 1145 return None, os.path.realpath(tmpl)
1146 1146 tmpl = open(tmpl).read()
1147 1147 return tmpl, None
1148 1148
1149 1149 # constant string?
1150 1150 return tmpl, None
1151 1151
1152 1152 def show_changeset(ui, repo, opts, buffered=False):
1153 1153 """show one changeset using template or regular display.
1154 1154
1155 1155 Display format will be the first non-empty hit of:
1156 1156 1. option 'template'
1157 1157 2. option 'style'
1158 1158 3. [ui] setting 'logtemplate'
1159 1159 4. [ui] setting 'style'
1160 1160 If all of these values are either the unset or the empty string,
1161 1161 regular display via changeset_printer() is done.
1162 1162 """
1163 1163 # options
1164 1164 patch = None
1165 1165 if opts.get('patch') or opts.get('stat'):
1166 1166 patch = scmutil.matchall(repo)
1167 1167
1168 1168 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1169 1169
1170 1170 if not tmpl and not mapfile:
1171 1171 return changeset_printer(ui, repo, patch, opts, buffered)
1172 1172
1173 1173 try:
1174 1174 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1175 1175 except SyntaxError, inst:
1176 1176 raise util.Abort(inst.args[0])
1177 1177 return t
1178 1178
1179 1179 def showmarker(ui, marker):
1180 1180 """utility function to display obsolescence marker in a readable way
1181 1181
1182 1182 To be used by debug function."""
1183 1183 ui.write(hex(marker.precnode()))
1184 1184 for repl in marker.succnodes():
1185 1185 ui.write(' ')
1186 1186 ui.write(hex(repl))
1187 1187 ui.write(' %X ' % marker._data[2])
1188 1188 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1189 1189 sorted(marker.metadata().items()))))
1190 1190 ui.write('\n')
1191 1191
1192 1192 def finddate(ui, repo, date):
1193 1193 """Find the tipmost changeset that matches the given date spec"""
1194 1194
1195 1195 df = util.matchdate(date)
1196 1196 m = scmutil.matchall(repo)
1197 1197 results = {}
1198 1198
1199 1199 def prep(ctx, fns):
1200 1200 d = ctx.date()
1201 1201 if df(d[0]):
1202 1202 results[ctx.rev()] = d
1203 1203
1204 1204 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1205 1205 rev = ctx.rev()
1206 1206 if rev in results:
1207 1207 ui.status(_("found revision %s from %s\n") %
1208 1208 (rev, util.datestr(results[rev])))
1209 1209 return str(rev)
1210 1210
1211 1211 raise util.Abort(_("revision matching date not found"))
1212 1212
1213 1213 def increasingwindows(windowsize=8, sizelimit=512):
1214 1214 while True:
1215 1215 yield windowsize
1216 1216 if windowsize < sizelimit:
1217 1217 windowsize *= 2
1218 1218
1219 1219 class FileWalkError(Exception):
1220 1220 pass
1221 1221
1222 1222 def walkfilerevs(repo, match, follow, revs, fncache):
1223 1223 '''Walks the file history for the matched files.
1224 1224
1225 1225 Returns the changeset revs that are involved in the file history.
1226 1226
1227 1227 Throws FileWalkError if the file history can't be walked using
1228 1228 filelogs alone.
1229 1229 '''
1230 1230 wanted = set()
1231 1231 copies = []
1232 1232 minrev, maxrev = min(revs), max(revs)
1233 1233 def filerevgen(filelog, last):
1234 1234 """
1235 1235 Only files, no patterns. Check the history of each file.
1236 1236
1237 1237 Examines filelog entries within minrev, maxrev linkrev range
1238 1238 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1239 1239 tuples in backwards order
1240 1240 """
1241 1241 cl_count = len(repo)
1242 1242 revs = []
1243 1243 for j in xrange(0, last + 1):
1244 1244 linkrev = filelog.linkrev(j)
1245 1245 if linkrev < minrev:
1246 1246 continue
1247 1247 # only yield rev for which we have the changelog, it can
1248 1248 # happen while doing "hg log" during a pull or commit
1249 1249 if linkrev >= cl_count:
1250 1250 break
1251 1251
1252 1252 parentlinkrevs = []
1253 1253 for p in filelog.parentrevs(j):
1254 1254 if p != nullrev:
1255 1255 parentlinkrevs.append(filelog.linkrev(p))
1256 1256 n = filelog.node(j)
1257 1257 revs.append((linkrev, parentlinkrevs,
1258 1258 follow and filelog.renamed(n)))
1259 1259
1260 1260 return reversed(revs)
1261 1261 def iterfiles():
1262 1262 pctx = repo['.']
1263 1263 for filename in match.files():
1264 1264 if follow:
1265 1265 if filename not in pctx:
1266 1266 raise util.Abort(_('cannot follow file not in parent '
1267 1267 'revision: "%s"') % filename)
1268 1268 yield filename, pctx[filename].filenode()
1269 1269 else:
1270 1270 yield filename, None
1271 1271 for filename_node in copies:
1272 1272 yield filename_node
1273 1273
1274 1274 for file_, node in iterfiles():
1275 1275 filelog = repo.file(file_)
1276 1276 if not len(filelog):
1277 1277 if node is None:
1278 1278 # A zero count may be a directory or deleted file, so
1279 1279 # try to find matching entries on the slow path.
1280 1280 if follow:
1281 1281 raise util.Abort(
1282 1282 _('cannot follow nonexistent file: "%s"') % file_)
1283 1283 raise FileWalkError("Cannot walk via filelog")
1284 1284 else:
1285 1285 continue
1286 1286
1287 1287 if node is None:
1288 1288 last = len(filelog) - 1
1289 1289 else:
1290 1290 last = filelog.rev(node)
1291 1291
1292 1292
1293 1293 # keep track of all ancestors of the file
1294 1294 ancestors = set([filelog.linkrev(last)])
1295 1295
1296 1296 # iterate from latest to oldest revision
1297 1297 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1298 1298 if not follow:
1299 1299 if rev > maxrev:
1300 1300 continue
1301 1301 else:
1302 1302 # Note that last might not be the first interesting
1303 1303 # rev to us:
1304 1304 # if the file has been changed after maxrev, we'll
1305 1305 # have linkrev(last) > maxrev, and we still need
1306 1306 # to explore the file graph
1307 1307 if rev not in ancestors:
1308 1308 continue
1309 1309 # XXX insert 1327 fix here
1310 1310 if flparentlinkrevs:
1311 1311 ancestors.update(flparentlinkrevs)
1312 1312
1313 1313 fncache.setdefault(rev, []).append(file_)
1314 1314 wanted.add(rev)
1315 1315 if copied:
1316 1316 copies.append(copied)
1317 1317
1318 1318 return wanted
1319 1319
1320 1320 def walkchangerevs(repo, match, opts, prepare):
1321 1321 '''Iterate over files and the revs in which they changed.
1322 1322
1323 1323 Callers most commonly need to iterate backwards over the history
1324 1324 in which they are interested. Doing so has awful (quadratic-looking)
1325 1325 performance, so we use iterators in a "windowed" way.
1326 1326
1327 1327 We walk a window of revisions in the desired order. Within the
1328 1328 window, we first walk forwards to gather data, then in the desired
1329 1329 order (usually backwards) to display it.
1330 1330
1331 1331 This function returns an iterator yielding contexts. Before
1332 1332 yielding each context, the iterator will first call the prepare
1333 1333 function on each context in the window in forward order.'''
1334 1334
1335 1335 follow = opts.get('follow') or opts.get('follow_first')
1336 1336
1337 1337 if opts.get('rev'):
1338 1338 revs = scmutil.revrange(repo, opts.get('rev'))
1339 1339 elif follow:
1340 1340 revs = repo.revs('reverse(:.)')
1341 1341 else:
1342 1342 revs = revset.spanset(repo)
1343 1343 revs.reverse()
1344 1344 if not revs:
1345 1345 return []
1346 1346 wanted = set()
1347 1347 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1348 1348 fncache = {}
1349 1349 change = repo.changectx
1350 1350
1351 1351 # First step is to fill wanted, the set of revisions that we want to yield.
1352 1352 # When it does not induce extra cost, we also fill fncache for revisions in
1353 1353 # wanted: a cache of filenames that were changed (ctx.files()) and that
1354 1354 # match the file filtering conditions.
1355 1355
1356 1356 if not slowpath and not match.files():
1357 1357 # No files, no patterns. Display all revs.
1358 1358 wanted = revs
1359 1359
1360 1360 if not slowpath and match.files():
1361 1361 # We only have to read through the filelog to find wanted revisions
1362 1362
1363 1363 try:
1364 1364 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1365 1365 except FileWalkError:
1366 1366 slowpath = True
1367 1367
1368 1368 # We decided to fall back to the slowpath because at least one
1369 1369 # of the paths was not a file. Check to see if at least one of them
1370 1370 # existed in history, otherwise simply return
1371 1371 for path in match.files():
1372 1372 if path == '.' or path in repo.store:
1373 1373 break
1374 1374 else:
1375 1375 return []
1376 1376
1377 1377 if slowpath:
1378 1378 # We have to read the changelog to match filenames against
1379 1379 # changed files
1380 1380
1381 1381 if follow:
1382 1382 raise util.Abort(_('can only follow copies/renames for explicit '
1383 1383 'filenames'))
1384 1384
1385 1385 # The slow path checks files modified in every changeset.
1386 1386 # This is really slow on large repos, so compute the set lazily.
1387 1387 class lazywantedset(object):
1388 1388 def __init__(self):
1389 1389 self.set = set()
1390 1390 self.revs = set(revs)
1391 1391
1392 1392 # No need to worry about locality here because it will be accessed
1393 1393 # in the same order as the increasing window below.
1394 1394 def __contains__(self, value):
1395 1395 if value in self.set:
1396 1396 return True
1397 1397 elif not value in self.revs:
1398 1398 return False
1399 1399 else:
1400 1400 self.revs.discard(value)
1401 1401 ctx = change(value)
1402 1402 matches = filter(match, ctx.files())
1403 1403 if matches:
1404 1404 fncache[value] = matches
1405 1405 self.set.add(value)
1406 1406 return True
1407 1407 return False
1408 1408
1409 1409 def discard(self, value):
1410 1410 self.revs.discard(value)
1411 1411 self.set.discard(value)
1412 1412
1413 1413 wanted = lazywantedset()
1414 1414
1415 1415 class followfilter(object):
1416 1416 def __init__(self, onlyfirst=False):
1417 1417 self.startrev = nullrev
1418 1418 self.roots = set()
1419 1419 self.onlyfirst = onlyfirst
1420 1420
1421 1421 def match(self, rev):
1422 1422 def realparents(rev):
1423 1423 if self.onlyfirst:
1424 1424 return repo.changelog.parentrevs(rev)[0:1]
1425 1425 else:
1426 1426 return filter(lambda x: x != nullrev,
1427 1427 repo.changelog.parentrevs(rev))
1428 1428
1429 1429 if self.startrev == nullrev:
1430 1430 self.startrev = rev
1431 1431 return True
1432 1432
1433 1433 if rev > self.startrev:
1434 1434 # forward: all descendants
1435 1435 if not self.roots:
1436 1436 self.roots.add(self.startrev)
1437 1437 for parent in realparents(rev):
1438 1438 if parent in self.roots:
1439 1439 self.roots.add(rev)
1440 1440 return True
1441 1441 else:
1442 1442 # backwards: all parents
1443 1443 if not self.roots:
1444 1444 self.roots.update(realparents(self.startrev))
1445 1445 if rev in self.roots:
1446 1446 self.roots.remove(rev)
1447 1447 self.roots.update(realparents(rev))
1448 1448 return True
1449 1449
1450 1450 return False
1451 1451
1452 1452 # it might be worthwhile to do this in the iterator if the rev range
1453 1453 # is descending and the prune args are all within that range
1454 1454 for rev in opts.get('prune', ()):
1455 1455 rev = repo[rev].rev()
1456 1456 ff = followfilter()
1457 1457 stop = min(revs[0], revs[-1])
1458 1458 for x in xrange(rev, stop - 1, -1):
1459 1459 if ff.match(x):
1460 1460 wanted = wanted - [x]
1461 1461
1462 1462 # Now that wanted is correctly initialized, we can iterate over the
1463 1463 # revision range, yielding only revisions in wanted.
1464 1464 def iterate():
1465 1465 if follow and not match.files():
1466 1466 ff = followfilter(onlyfirst=opts.get('follow_first'))
1467 1467 def want(rev):
1468 1468 return ff.match(rev) and rev in wanted
1469 1469 else:
1470 1470 def want(rev):
1471 1471 return rev in wanted
1472 1472
1473 1473 it = iter(revs)
1474 1474 stopiteration = False
1475 1475 for windowsize in increasingwindows():
1476 1476 nrevs = []
1477 1477 for i in xrange(windowsize):
1478 1478 try:
1479 1479 rev = it.next()
1480 1480 if want(rev):
1481 1481 nrevs.append(rev)
1482 1482 except (StopIteration):
1483 1483 stopiteration = True
1484 1484 break
1485 1485 for rev in sorted(nrevs):
1486 1486 fns = fncache.get(rev)
1487 1487 ctx = change(rev)
1488 1488 if not fns:
1489 1489 def fns_generator():
1490 1490 for f in ctx.files():
1491 1491 if match(f):
1492 1492 yield f
1493 1493 fns = fns_generator()
1494 1494 prepare(ctx, fns)
1495 1495 for rev in nrevs:
1496 1496 yield change(rev)
1497 1497
1498 1498 if stopiteration:
1499 1499 break
1500 1500
1501 1501 return iterate()
1502 1502
1503 def _makelogfilematcher(repo, files, followfirst):
1503 def _makefollowlogfilematcher(repo, files, followfirst):
1504 1504 # When displaying a revision with --patch --follow FILE, we have
1505 1505 # to know which file of the revision must be diffed. With
1506 1506 # --follow, we want the names of the ancestors of FILE in the
1507 1507 # revision, stored in "fcache". "fcache" is populated by
1508 1508 # reproducing the graph traversal already done by --follow revset
1509 1509 # and relating linkrevs to file names (which is not "correct" but
1510 1510 # good enough).
1511 1511 fcache = {}
1512 1512 fcacheready = [False]
1513 1513 pctx = repo['.']
1514 1514
1515 1515 def populate():
1516 1516 for fn in files:
1517 1517 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1518 1518 for c in i:
1519 1519 fcache.setdefault(c.linkrev(), set()).add(c.path())
1520 1520
1521 1521 def filematcher(rev):
1522 1522 if not fcacheready[0]:
1523 1523 # Lazy initialization
1524 1524 fcacheready[0] = True
1525 1525 populate()
1526 1526 return scmutil.matchfiles(repo, fcache.get(rev, []))
1527 1527
1528 1528 return filematcher
1529 1529
1530 def _makenofollowlogfilematcher(repo, pats, opts):
1531 '''hook for extensions to override the filematcher for non-follow cases'''
1532 return None
1533
1530 1534 def _makelogrevset(repo, pats, opts, revs):
1531 1535 """Return (expr, filematcher) where expr is a revset string built
1532 1536 from log options and file patterns or None. If --stat or --patch
1533 1537 are not passed filematcher is None. Otherwise it is a callable
1534 1538 taking a revision number and returning a match objects filtering
1535 1539 the files to be detailed when displaying the revision.
1536 1540 """
1537 1541 opt2revset = {
1538 1542 'no_merges': ('not merge()', None),
1539 1543 'only_merges': ('merge()', None),
1540 1544 '_ancestors': ('ancestors(%(val)s)', None),
1541 1545 '_fancestors': ('_firstancestors(%(val)s)', None),
1542 1546 '_descendants': ('descendants(%(val)s)', None),
1543 1547 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1544 1548 '_matchfiles': ('_matchfiles(%(val)s)', None),
1545 1549 'date': ('date(%(val)r)', None),
1546 1550 'branch': ('branch(%(val)r)', ' or '),
1547 1551 '_patslog': ('filelog(%(val)r)', ' or '),
1548 1552 '_patsfollow': ('follow(%(val)r)', ' or '),
1549 1553 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1550 1554 'keyword': ('keyword(%(val)r)', ' or '),
1551 1555 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1552 1556 'user': ('user(%(val)r)', ' or '),
1553 1557 }
1554 1558
1555 1559 opts = dict(opts)
1556 1560 # follow or not follow?
1557 1561 follow = opts.get('follow') or opts.get('follow_first')
1558 1562 followfirst = opts.get('follow_first') and 1 or 0
1559 1563 # --follow with FILE behaviour depends on revs...
1560 1564 it = iter(revs)
1561 1565 startrev = it.next()
1562 1566 try:
1563 1567 followdescendants = startrev < it.next()
1564 1568 except (StopIteration):
1565 1569 followdescendants = False
1566 1570
1567 1571 # branch and only_branch are really aliases and must be handled at
1568 1572 # the same time
1569 1573 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1570 1574 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1571 1575 # pats/include/exclude are passed to match.match() directly in
1572 1576 # _matchfiles() revset but walkchangerevs() builds its matcher with
1573 1577 # scmutil.match(). The difference is input pats are globbed on
1574 1578 # platforms without shell expansion (windows).
1575 1579 pctx = repo[None]
1576 1580 match, pats = scmutil.matchandpats(pctx, pats, opts)
1577 1581 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1578 1582 if not slowpath:
1579 1583 for f in match.files():
1580 1584 if follow and f not in pctx:
1581 1585 # If the file exists, it may be a directory, so let it
1582 1586 # take the slow path.
1583 1587 if os.path.exists(repo.wjoin(f)):
1584 1588 slowpath = True
1585 1589 continue
1586 1590 else:
1587 1591 raise util.Abort(_('cannot follow file not in parent '
1588 1592 'revision: "%s"') % f)
1589 1593 filelog = repo.file(f)
1590 1594 if not filelog:
1591 1595 # A zero count may be a directory or deleted file, so
1592 1596 # try to find matching entries on the slow path.
1593 1597 if follow:
1594 1598 raise util.Abort(
1595 1599 _('cannot follow nonexistent file: "%s"') % f)
1596 1600 slowpath = True
1597 1601
1598 1602 # We decided to fall back to the slowpath because at least one
1599 1603 # of the paths was not a file. Check to see if at least one of them
1600 1604 # existed in history - in that case, we'll continue down the
1601 1605 # slowpath; otherwise, we can turn off the slowpath
1602 1606 if slowpath:
1603 1607 for path in match.files():
1604 1608 if path == '.' or path in repo.store:
1605 1609 break
1606 1610 else:
1607 1611 slowpath = False
1608 1612
1609 1613 if slowpath:
1610 1614 # See walkchangerevs() slow path.
1611 1615 #
1612 1616 # pats/include/exclude cannot be represented as separate
1613 1617 # revset expressions as their filtering logic applies at file
1614 1618 # level. For instance "-I a -X a" matches a revision touching
1615 1619 # "a" and "b" while "file(a) and not file(b)" does
1616 1620 # not. Besides, filesets are evaluated against the working
1617 1621 # directory.
1618 1622 matchargs = ['r:', 'd:relpath']
1619 1623 for p in pats:
1620 1624 matchargs.append('p:' + p)
1621 1625 for p in opts.get('include', []):
1622 1626 matchargs.append('i:' + p)
1623 1627 for p in opts.get('exclude', []):
1624 1628 matchargs.append('x:' + p)
1625 1629 matchargs = ','.join(('%r' % p) for p in matchargs)
1626 1630 opts['_matchfiles'] = matchargs
1627 1631 else:
1628 1632 if follow:
1629 1633 fpats = ('_patsfollow', '_patsfollowfirst')
1630 1634 fnopats = (('_ancestors', '_fancestors'),
1631 1635 ('_descendants', '_fdescendants'))
1632 1636 if pats:
1633 1637 # follow() revset interprets its file argument as a
1634 1638 # manifest entry, so use match.files(), not pats.
1635 1639 opts[fpats[followfirst]] = list(match.files())
1636 1640 else:
1637 1641 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1638 1642 else:
1639 1643 opts['_patslog'] = list(pats)
1640 1644
1641 1645 filematcher = None
1642 1646 if opts.get('patch') or opts.get('stat'):
1643 1647 # When following files, track renames via a special matcher.
1644 1648 # If we're forced to take the slowpath it means we're following
1645 1649 # at least one pattern/directory, so don't bother with rename tracking.
1646 1650 if follow and not match.always() and not slowpath:
1647 1651 # _makelogfilematcher expects its files argument to be relative to
1648 1652 # the repo root, so use match.files(), not pats.
1649 filematcher = _makelogfilematcher(repo, match.files(), followfirst)
1653 filematcher = _makefollowlogfilematcher(repo, match.files(),
1654 followfirst)
1650 1655 else:
1651 filematcher = lambda rev: match
1656 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1657 if filematcher is None:
1658 filematcher = lambda rev: match
1652 1659
1653 1660 expr = []
1654 1661 for op, val in opts.iteritems():
1655 1662 if not val:
1656 1663 continue
1657 1664 if op not in opt2revset:
1658 1665 continue
1659 1666 revop, andor = opt2revset[op]
1660 1667 if '%(val)' not in revop:
1661 1668 expr.append(revop)
1662 1669 else:
1663 1670 if not isinstance(val, list):
1664 1671 e = revop % {'val': val}
1665 1672 else:
1666 1673 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1667 1674 expr.append(e)
1668 1675
1669 1676 if expr:
1670 1677 expr = '(' + ' and '.join(expr) + ')'
1671 1678 else:
1672 1679 expr = None
1673 1680 return expr, filematcher
1674 1681
1675 1682 def getgraphlogrevs(repo, pats, opts):
1676 1683 """Return (revs, expr, filematcher) where revs is an iterable of
1677 1684 revision numbers, expr is a revset string built from log options
1678 1685 and file patterns or None, and used to filter 'revs'. If --stat or
1679 1686 --patch are not passed filematcher is None. Otherwise it is a
1680 1687 callable taking a revision number and returning a match objects
1681 1688 filtering the files to be detailed when displaying the revision.
1682 1689 """
1683 1690 if not len(repo):
1684 1691 return [], None, None
1685 1692 limit = loglimit(opts)
1686 1693 # Default --rev value depends on --follow but --follow behaviour
1687 1694 # depends on revisions resolved from --rev...
1688 1695 follow = opts.get('follow') or opts.get('follow_first')
1689 1696 possiblyunsorted = False # whether revs might need sorting
1690 1697 if opts.get('rev'):
1691 1698 revs = scmutil.revrange(repo, opts['rev'])
1692 1699 # Don't sort here because _makelogrevset might depend on the
1693 1700 # order of revs
1694 1701 possiblyunsorted = True
1695 1702 else:
1696 1703 if follow and len(repo) > 0:
1697 1704 revs = repo.revs('reverse(:.)')
1698 1705 else:
1699 1706 revs = revset.spanset(repo)
1700 1707 revs.reverse()
1701 1708 if not revs:
1702 1709 return revset.baseset(), None, None
1703 1710 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1704 1711 if possiblyunsorted:
1705 1712 revs.sort(reverse=True)
1706 1713 if expr:
1707 1714 # Revset matchers often operate faster on revisions in changelog
1708 1715 # order, because most filters deal with the changelog.
1709 1716 revs.reverse()
1710 1717 matcher = revset.match(repo.ui, expr)
1711 1718 # Revset matches can reorder revisions. "A or B" typically returns
1712 1719 # returns the revision matching A then the revision matching B. Sort
1713 1720 # again to fix that.
1714 1721 revs = matcher(repo, revs)
1715 1722 revs.sort(reverse=True)
1716 1723 if limit is not None:
1717 1724 limitedrevs = revset.baseset()
1718 1725 for idx, rev in enumerate(revs):
1719 1726 if idx >= limit:
1720 1727 break
1721 1728 limitedrevs.append(rev)
1722 1729 revs = limitedrevs
1723 1730
1724 1731 return revs, expr, filematcher
1725 1732
1726 1733 def getlogrevs(repo, pats, opts):
1727 1734 """Return (revs, expr, filematcher) where revs is an iterable of
1728 1735 revision numbers, expr is a revset string built from log options
1729 1736 and file patterns or None, and used to filter 'revs'. If --stat or
1730 1737 --patch are not passed filematcher is None. Otherwise it is a
1731 1738 callable taking a revision number and returning a match objects
1732 1739 filtering the files to be detailed when displaying the revision.
1733 1740 """
1734 1741 limit = loglimit(opts)
1735 1742 # Default --rev value depends on --follow but --follow behaviour
1736 1743 # depends on revisions resolved from --rev...
1737 1744 follow = opts.get('follow') or opts.get('follow_first')
1738 1745 if opts.get('rev'):
1739 1746 revs = scmutil.revrange(repo, opts['rev'])
1740 1747 elif follow:
1741 1748 revs = repo.revs('reverse(:.)')
1742 1749 else:
1743 1750 revs = revset.spanset(repo)
1744 1751 revs.reverse()
1745 1752 if not revs:
1746 1753 return revset.baseset([]), None, None
1747 1754 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1748 1755 if expr:
1749 1756 # Revset matchers often operate faster on revisions in changelog
1750 1757 # order, because most filters deal with the changelog.
1751 1758 if not opts.get('rev'):
1752 1759 revs.reverse()
1753 1760 matcher = revset.match(repo.ui, expr)
1754 1761 # Revset matches can reorder revisions. "A or B" typically returns
1755 1762 # returns the revision matching A then the revision matching B. Sort
1756 1763 # again to fix that.
1757 1764 revs = matcher(repo, revs)
1758 1765 if not opts.get('rev'):
1759 1766 revs.sort(reverse=True)
1760 1767 if limit is not None:
1761 1768 count = 0
1762 1769 limitedrevs = revset.baseset([])
1763 1770 it = iter(revs)
1764 1771 while count < limit:
1765 1772 try:
1766 1773 limitedrevs.append(it.next())
1767 1774 except (StopIteration):
1768 1775 break
1769 1776 count += 1
1770 1777 revs = limitedrevs
1771 1778
1772 1779 return revs, expr, filematcher
1773 1780
1774 1781 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1775 1782 filematcher=None):
1776 1783 seen, state = [], graphmod.asciistate()
1777 1784 for rev, type, ctx, parents in dag:
1778 1785 char = 'o'
1779 1786 if ctx.node() in showparents:
1780 1787 char = '@'
1781 1788 elif ctx.obsolete():
1782 1789 char = 'x'
1783 1790 copies = None
1784 1791 if getrenamed and ctx.rev():
1785 1792 copies = []
1786 1793 for fn in ctx.files():
1787 1794 rename = getrenamed(fn, ctx.rev())
1788 1795 if rename:
1789 1796 copies.append((fn, rename[0]))
1790 1797 revmatchfn = None
1791 1798 if filematcher is not None:
1792 1799 revmatchfn = filematcher(ctx.rev())
1793 1800 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1794 1801 lines = displayer.hunk.pop(rev).split('\n')
1795 1802 if not lines[-1]:
1796 1803 del lines[-1]
1797 1804 displayer.flush(rev)
1798 1805 edges = edgefn(type, char, lines, seen, rev, parents)
1799 1806 for type, char, lines, coldata in edges:
1800 1807 graphmod.ascii(ui, state, type, char, lines, coldata)
1801 1808 displayer.close()
1802 1809
1803 1810 def graphlog(ui, repo, *pats, **opts):
1804 1811 # Parameters are identical to log command ones
1805 1812 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1806 1813 revdag = graphmod.dagwalker(repo, revs)
1807 1814
1808 1815 getrenamed = None
1809 1816 if opts.get('copies'):
1810 1817 endrev = None
1811 1818 if opts.get('rev'):
1812 1819 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1813 1820 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1814 1821 displayer = show_changeset(ui, repo, opts, buffered=True)
1815 1822 showparents = [ctx.node() for ctx in repo[None].parents()]
1816 1823 displaygraph(ui, revdag, displayer, showparents,
1817 1824 graphmod.asciiedges, getrenamed, filematcher)
1818 1825
1819 1826 def checkunsupportedgraphflags(pats, opts):
1820 1827 for op in ["newest_first"]:
1821 1828 if op in opts and opts[op]:
1822 1829 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1823 1830 % op.replace("_", "-"))
1824 1831
1825 1832 def graphrevs(repo, nodes, opts):
1826 1833 limit = loglimit(opts)
1827 1834 nodes.reverse()
1828 1835 if limit is not None:
1829 1836 nodes = nodes[:limit]
1830 1837 return graphmod.nodes(repo, nodes)
1831 1838
1832 1839 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1833 1840 join = lambda f: os.path.join(prefix, f)
1834 1841 bad = []
1835 1842 oldbad = match.bad
1836 1843 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1837 1844 names = []
1838 1845 wctx = repo[None]
1839 1846 cca = None
1840 1847 abort, warn = scmutil.checkportabilityalert(ui)
1841 1848 if abort or warn:
1842 1849 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1843 1850 for f in repo.walk(match):
1844 1851 exact = match.exact(f)
1845 1852 if exact or not explicitonly and f not in repo.dirstate:
1846 1853 if cca:
1847 1854 cca(f)
1848 1855 names.append(f)
1849 1856 if ui.verbose or not exact:
1850 1857 ui.status(_('adding %s\n') % match.rel(join(f)))
1851 1858
1852 1859 for subpath in sorted(wctx.substate):
1853 1860 sub = wctx.sub(subpath)
1854 1861 try:
1855 1862 submatch = matchmod.narrowmatcher(subpath, match)
1856 1863 if listsubrepos:
1857 1864 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1858 1865 False))
1859 1866 else:
1860 1867 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1861 1868 True))
1862 1869 except error.LookupError:
1863 1870 ui.status(_("skipping missing subrepository: %s\n")
1864 1871 % join(subpath))
1865 1872
1866 1873 if not dryrun:
1867 1874 rejected = wctx.add(names, prefix)
1868 1875 bad.extend(f for f in rejected if f in match.files())
1869 1876 return bad
1870 1877
1871 1878 def forget(ui, repo, match, prefix, explicitonly):
1872 1879 join = lambda f: os.path.join(prefix, f)
1873 1880 bad = []
1874 1881 oldbad = match.bad
1875 1882 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1876 1883 wctx = repo[None]
1877 1884 forgot = []
1878 1885 s = repo.status(match=match, clean=True)
1879 1886 forget = sorted(s[0] + s[1] + s[3] + s[6])
1880 1887 if explicitonly:
1881 1888 forget = [f for f in forget if match.exact(f)]
1882 1889
1883 1890 for subpath in sorted(wctx.substate):
1884 1891 sub = wctx.sub(subpath)
1885 1892 try:
1886 1893 submatch = matchmod.narrowmatcher(subpath, match)
1887 1894 subbad, subforgot = sub.forget(ui, submatch, prefix)
1888 1895 bad.extend([subpath + '/' + f for f in subbad])
1889 1896 forgot.extend([subpath + '/' + f for f in subforgot])
1890 1897 except error.LookupError:
1891 1898 ui.status(_("skipping missing subrepository: %s\n")
1892 1899 % join(subpath))
1893 1900
1894 1901 if not explicitonly:
1895 1902 for f in match.files():
1896 1903 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1897 1904 if f not in forgot:
1898 1905 if os.path.exists(match.rel(join(f))):
1899 1906 ui.warn(_('not removing %s: '
1900 1907 'file is already untracked\n')
1901 1908 % match.rel(join(f)))
1902 1909 bad.append(f)
1903 1910
1904 1911 for f in forget:
1905 1912 if ui.verbose or not match.exact(f):
1906 1913 ui.status(_('removing %s\n') % match.rel(join(f)))
1907 1914
1908 1915 rejected = wctx.forget(forget, prefix)
1909 1916 bad.extend(f for f in rejected if f in match.files())
1910 1917 forgot.extend(forget)
1911 1918 return bad, forgot
1912 1919
1913 1920 def cat(ui, repo, ctx, matcher, prefix, **opts):
1914 1921 err = 1
1915 1922
1916 1923 def write(path):
1917 1924 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1918 1925 pathname=os.path.join(prefix, path))
1919 1926 data = ctx[path].data()
1920 1927 if opts.get('decode'):
1921 1928 data = repo.wwritedata(path, data)
1922 1929 fp.write(data)
1923 1930 fp.close()
1924 1931
1925 1932 # Automation often uses hg cat on single files, so special case it
1926 1933 # for performance to avoid the cost of parsing the manifest.
1927 1934 if len(matcher.files()) == 1 and not matcher.anypats():
1928 1935 file = matcher.files()[0]
1929 1936 mf = repo.manifest
1930 1937 mfnode = ctx._changeset[0]
1931 1938 if mf.find(mfnode, file)[0]:
1932 1939 write(file)
1933 1940 return 0
1934 1941
1935 1942 # Don't warn about "missing" files that are really in subrepos
1936 1943 bad = matcher.bad
1937 1944
1938 1945 def badfn(path, msg):
1939 1946 for subpath in ctx.substate:
1940 1947 if path.startswith(subpath):
1941 1948 return
1942 1949 bad(path, msg)
1943 1950
1944 1951 matcher.bad = badfn
1945 1952
1946 1953 for abs in ctx.walk(matcher):
1947 1954 write(abs)
1948 1955 err = 0
1949 1956
1950 1957 matcher.bad = bad
1951 1958
1952 1959 for subpath in sorted(ctx.substate):
1953 1960 sub = ctx.sub(subpath)
1954 1961 try:
1955 1962 submatch = matchmod.narrowmatcher(subpath, matcher)
1956 1963
1957 1964 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1958 1965 **opts):
1959 1966 err = 0
1960 1967 except error.RepoLookupError:
1961 1968 ui.status(_("skipping missing subrepository: %s\n")
1962 1969 % os.path.join(prefix, subpath))
1963 1970
1964 1971 return err
1965 1972
1966 1973 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1967 1974 '''reproduce copies from fromrev to rev in the dirstate
1968 1975
1969 1976 If skiprev is specified, it's a revision that should be used to
1970 1977 filter copy records. Any copies that occur between fromrev and
1971 1978 skiprev will not be duplicated, even if they appear in the set of
1972 1979 copies between fromrev and rev.
1973 1980 '''
1974 1981 exclude = {}
1975 1982 if skiprev is not None:
1976 1983 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1977 1984 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1978 1985 # copies.pathcopies returns backward renames, so dst might not
1979 1986 # actually be in the dirstate
1980 1987 if dst in exclude:
1981 1988 continue
1982 1989 if repo.dirstate[dst] in "nma":
1983 1990 repo.dirstate.copy(src, dst)
1984 1991
1985 1992 def commit(ui, repo, commitfunc, pats, opts):
1986 1993 '''commit the specified files or all outstanding changes'''
1987 1994 date = opts.get('date')
1988 1995 if date:
1989 1996 opts['date'] = util.parsedate(date)
1990 1997 message = logmessage(ui, opts)
1991 1998
1992 1999 # extract addremove carefully -- this function can be called from a command
1993 2000 # that doesn't support addremove
1994 2001 if opts.get('addremove'):
1995 2002 scmutil.addremove(repo, pats, opts)
1996 2003
1997 2004 return commitfunc(ui, repo, message,
1998 2005 scmutil.match(repo[None], pats, opts), opts)
1999 2006
2000 2007 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2001 2008 ui.note(_('amending changeset %s\n') % old)
2002 2009 base = old.p1()
2003 2010
2004 2011 wlock = lock = newid = None
2005 2012 try:
2006 2013 wlock = repo.wlock()
2007 2014 lock = repo.lock()
2008 2015 tr = repo.transaction('amend')
2009 2016 try:
2010 2017 # See if we got a message from -m or -l, if not, open the editor
2011 2018 # with the message of the changeset to amend
2012 2019 message = logmessage(ui, opts)
2013 2020 # ensure logfile does not conflict with later enforcement of the
2014 2021 # message. potential logfile content has been processed by
2015 2022 # `logmessage` anyway.
2016 2023 opts.pop('logfile')
2017 2024 # First, do a regular commit to record all changes in the working
2018 2025 # directory (if there are any)
2019 2026 ui.callhooks = False
2020 2027 currentbookmark = repo._bookmarkcurrent
2021 2028 try:
2022 2029 repo._bookmarkcurrent = None
2023 2030 opts['message'] = 'temporary amend commit for %s' % old
2024 2031 node = commit(ui, repo, commitfunc, pats, opts)
2025 2032 finally:
2026 2033 repo._bookmarkcurrent = currentbookmark
2027 2034 ui.callhooks = True
2028 2035 ctx = repo[node]
2029 2036
2030 2037 # Participating changesets:
2031 2038 #
2032 2039 # node/ctx o - new (intermediate) commit that contains changes
2033 2040 # | from working dir to go into amending commit
2034 2041 # | (or a workingctx if there were no changes)
2035 2042 # |
2036 2043 # old o - changeset to amend
2037 2044 # |
2038 2045 # base o - parent of amending changeset
2039 2046
2040 2047 # Update extra dict from amended commit (e.g. to preserve graft
2041 2048 # source)
2042 2049 extra.update(old.extra())
2043 2050
2044 2051 # Also update it from the intermediate commit or from the wctx
2045 2052 extra.update(ctx.extra())
2046 2053
2047 2054 if len(old.parents()) > 1:
2048 2055 # ctx.files() isn't reliable for merges, so fall back to the
2049 2056 # slower repo.status() method
2050 2057 files = set([fn for st in repo.status(base, old)[:3]
2051 2058 for fn in st])
2052 2059 else:
2053 2060 files = set(old.files())
2054 2061
2055 2062 # Second, we use either the commit we just did, or if there were no
2056 2063 # changes the parent of the working directory as the version of the
2057 2064 # files in the final amend commit
2058 2065 if node:
2059 2066 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2060 2067
2061 2068 user = ctx.user()
2062 2069 date = ctx.date()
2063 2070 # Recompute copies (avoid recording a -> b -> a)
2064 2071 copied = copies.pathcopies(base, ctx)
2065 2072
2066 2073 # Prune files which were reverted by the updates: if old
2067 2074 # introduced file X and our intermediate commit, node,
2068 2075 # renamed that file, then those two files are the same and
2069 2076 # we can discard X from our list of files. Likewise if X
2070 2077 # was deleted, it's no longer relevant
2071 2078 files.update(ctx.files())
2072 2079
2073 2080 def samefile(f):
2074 2081 if f in ctx.manifest():
2075 2082 a = ctx.filectx(f)
2076 2083 if f in base.manifest():
2077 2084 b = base.filectx(f)
2078 2085 return (not a.cmp(b)
2079 2086 and a.flags() == b.flags())
2080 2087 else:
2081 2088 return False
2082 2089 else:
2083 2090 return f not in base.manifest()
2084 2091 files = [f for f in files if not samefile(f)]
2085 2092
2086 2093 def filectxfn(repo, ctx_, path):
2087 2094 try:
2088 2095 fctx = ctx[path]
2089 2096 flags = fctx.flags()
2090 2097 mctx = context.memfilectx(repo,
2091 2098 fctx.path(), fctx.data(),
2092 2099 islink='l' in flags,
2093 2100 isexec='x' in flags,
2094 2101 copied=copied.get(path))
2095 2102 return mctx
2096 2103 except KeyError:
2097 2104 raise IOError
2098 2105 else:
2099 2106 ui.note(_('copying changeset %s to %s\n') % (old, base))
2100 2107
2101 2108 # Use version of files as in the old cset
2102 2109 def filectxfn(repo, ctx_, path):
2103 2110 try:
2104 2111 return old.filectx(path)
2105 2112 except KeyError:
2106 2113 raise IOError
2107 2114
2108 2115 user = opts.get('user') or old.user()
2109 2116 date = opts.get('date') or old.date()
2110 2117 editform = 'commit.amend'
2111 2118 editor = getcommiteditor(editform=editform, **opts)
2112 2119 if not message:
2113 2120 editor = getcommiteditor(edit=True, editform=editform)
2114 2121 message = old.description()
2115 2122
2116 2123 pureextra = extra.copy()
2117 2124 extra['amend_source'] = old.hex()
2118 2125
2119 2126 new = context.memctx(repo,
2120 2127 parents=[base.node(), old.p2().node()],
2121 2128 text=message,
2122 2129 files=files,
2123 2130 filectxfn=filectxfn,
2124 2131 user=user,
2125 2132 date=date,
2126 2133 extra=extra,
2127 2134 editor=editor)
2128 2135
2129 2136 newdesc = changelog.stripdesc(new.description())
2130 2137 if ((not node)
2131 2138 and newdesc == old.description()
2132 2139 and user == old.user()
2133 2140 and date == old.date()
2134 2141 and pureextra == old.extra()):
2135 2142 # nothing changed. continuing here would create a new node
2136 2143 # anyway because of the amend_source noise.
2137 2144 #
2138 2145 # This not what we expect from amend.
2139 2146 return old.node()
2140 2147
2141 2148 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2142 2149 try:
2143 2150 if opts.get('secret'):
2144 2151 commitphase = 'secret'
2145 2152 else:
2146 2153 commitphase = old.phase()
2147 2154 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2148 2155 newid = repo.commitctx(new)
2149 2156 finally:
2150 2157 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2151 2158 if newid != old.node():
2152 2159 # Reroute the working copy parent to the new changeset
2153 2160 repo.setparents(newid, nullid)
2154 2161
2155 2162 # Move bookmarks from old parent to amend commit
2156 2163 bms = repo.nodebookmarks(old.node())
2157 2164 if bms:
2158 2165 marks = repo._bookmarks
2159 2166 for bm in bms:
2160 2167 marks[bm] = newid
2161 2168 marks.write()
2162 2169 #commit the whole amend process
2163 2170 if obsolete._enabled and newid != old.node():
2164 2171 # mark the new changeset as successor of the rewritten one
2165 2172 new = repo[newid]
2166 2173 obs = [(old, (new,))]
2167 2174 if node:
2168 2175 obs.append((ctx, ()))
2169 2176
2170 2177 obsolete.createmarkers(repo, obs)
2171 2178 tr.close()
2172 2179 finally:
2173 2180 tr.release()
2174 2181 if (not obsolete._enabled) and newid != old.node():
2175 2182 # Strip the intermediate commit (if there was one) and the amended
2176 2183 # commit
2177 2184 if node:
2178 2185 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2179 2186 ui.note(_('stripping amended changeset %s\n') % old)
2180 2187 repair.strip(ui, repo, old.node(), topic='amend-backup')
2181 2188 finally:
2182 2189 if newid is None:
2183 2190 repo.dirstate.invalidate()
2184 2191 lockmod.release(lock, wlock)
2185 2192 return newid
2186 2193
2187 2194 def commiteditor(repo, ctx, subs, editform=''):
2188 2195 if ctx.description():
2189 2196 return ctx.description()
2190 2197 return commitforceeditor(repo, ctx, subs, editform=editform)
2191 2198
2192 2199 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2193 2200 editform=''):
2194 2201 if not extramsg:
2195 2202 extramsg = _("Leave message empty to abort commit.")
2196 2203
2197 2204 forms = [e for e in editform.split('.') if e]
2198 2205 forms.insert(0, 'changeset')
2199 2206 while forms:
2200 2207 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2201 2208 if tmpl:
2202 2209 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2203 2210 break
2204 2211 forms.pop()
2205 2212 else:
2206 2213 committext = buildcommittext(repo, ctx, subs, extramsg)
2207 2214
2208 2215 # run editor in the repository root
2209 2216 olddir = os.getcwd()
2210 2217 os.chdir(repo.root)
2211 2218 text = repo.ui.edit(committext, ctx.user(), ctx.extra())
2212 2219 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2213 2220 os.chdir(olddir)
2214 2221
2215 2222 if finishdesc:
2216 2223 text = finishdesc(text)
2217 2224 if not text.strip():
2218 2225 raise util.Abort(_("empty commit message"))
2219 2226
2220 2227 return text
2221 2228
2222 2229 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2223 2230 ui = repo.ui
2224 2231 tmpl, mapfile = gettemplate(ui, tmpl, None)
2225 2232
2226 2233 try:
2227 2234 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2228 2235 except SyntaxError, inst:
2229 2236 raise util.Abort(inst.args[0])
2230 2237
2231 2238 for k, v in repo.ui.configitems('committemplate'):
2232 2239 if k != 'changeset':
2233 2240 t.t.cache[k] = v
2234 2241
2235 2242 if not extramsg:
2236 2243 extramsg = '' # ensure that extramsg is string
2237 2244
2238 2245 ui.pushbuffer()
2239 2246 t.show(ctx, extramsg=extramsg)
2240 2247 return ui.popbuffer()
2241 2248
2242 2249 def buildcommittext(repo, ctx, subs, extramsg):
2243 2250 edittext = []
2244 2251 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2245 2252 if ctx.description():
2246 2253 edittext.append(ctx.description())
2247 2254 edittext.append("")
2248 2255 edittext.append("") # Empty line between message and comments.
2249 2256 edittext.append(_("HG: Enter commit message."
2250 2257 " Lines beginning with 'HG:' are removed."))
2251 2258 edittext.append("HG: %s" % extramsg)
2252 2259 edittext.append("HG: --")
2253 2260 edittext.append(_("HG: user: %s") % ctx.user())
2254 2261 if ctx.p2():
2255 2262 edittext.append(_("HG: branch merge"))
2256 2263 if ctx.branch():
2257 2264 edittext.append(_("HG: branch '%s'") % ctx.branch())
2258 2265 if bookmarks.iscurrent(repo):
2259 2266 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2260 2267 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2261 2268 edittext.extend([_("HG: added %s") % f for f in added])
2262 2269 edittext.extend([_("HG: changed %s") % f for f in modified])
2263 2270 edittext.extend([_("HG: removed %s") % f for f in removed])
2264 2271 if not added and not modified and not removed:
2265 2272 edittext.append(_("HG: no files changed"))
2266 2273 edittext.append("")
2267 2274
2268 2275 return "\n".join(edittext)
2269 2276
2270 2277 def commitstatus(repo, node, branch, bheads=None, opts={}):
2271 2278 ctx = repo[node]
2272 2279 parents = ctx.parents()
2273 2280
2274 2281 if (not opts.get('amend') and bheads and node not in bheads and not
2275 2282 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2276 2283 repo.ui.status(_('created new head\n'))
2277 2284 # The message is not printed for initial roots. For the other
2278 2285 # changesets, it is printed in the following situations:
2279 2286 #
2280 2287 # Par column: for the 2 parents with ...
2281 2288 # N: null or no parent
2282 2289 # B: parent is on another named branch
2283 2290 # C: parent is a regular non head changeset
2284 2291 # H: parent was a branch head of the current branch
2285 2292 # Msg column: whether we print "created new head" message
2286 2293 # In the following, it is assumed that there already exists some
2287 2294 # initial branch heads of the current branch, otherwise nothing is
2288 2295 # printed anyway.
2289 2296 #
2290 2297 # Par Msg Comment
2291 2298 # N N y additional topo root
2292 2299 #
2293 2300 # B N y additional branch root
2294 2301 # C N y additional topo head
2295 2302 # H N n usual case
2296 2303 #
2297 2304 # B B y weird additional branch root
2298 2305 # C B y branch merge
2299 2306 # H B n merge with named branch
2300 2307 #
2301 2308 # C C y additional head from merge
2302 2309 # C H n merge with a head
2303 2310 #
2304 2311 # H H n head merge: head count decreases
2305 2312
2306 2313 if not opts.get('close_branch'):
2307 2314 for r in parents:
2308 2315 if r.closesbranch() and r.branch() == branch:
2309 2316 repo.ui.status(_('reopening closed branch head %d\n') % r)
2310 2317
2311 2318 if repo.ui.debugflag:
2312 2319 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2313 2320 elif repo.ui.verbose:
2314 2321 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2315 2322
2316 2323 def revert(ui, repo, ctx, parents, *pats, **opts):
2317 2324 parent, p2 = parents
2318 2325 node = ctx.node()
2319 2326
2320 2327 mf = ctx.manifest()
2321 2328 if node == p2:
2322 2329 parent = p2
2323 2330 if node == parent:
2324 2331 pmf = mf
2325 2332 else:
2326 2333 pmf = None
2327 2334
2328 2335 # need all matching names in dirstate and manifest of target rev,
2329 2336 # so have to walk both. do not print errors if files exist in one
2330 2337 # but not other.
2331 2338
2332 2339 # `names` is a mapping for all elements in working copy and target revision
2333 2340 # The mapping is in the form:
2334 2341 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2335 2342 names = {}
2336 2343
2337 2344 wlock = repo.wlock()
2338 2345 try:
2339 2346 ## filling of the `names` mapping
2340 2347 # walk dirstate to fill `names`
2341 2348
2342 2349 m = scmutil.match(repo[None], pats, opts)
2343 2350 m.bad = lambda x, y: False
2344 2351 for abs in repo.walk(m):
2345 2352 names[abs] = m.rel(abs), m.exact(abs)
2346 2353
2347 2354 # walk target manifest to fill `names`
2348 2355
2349 2356 def badfn(path, msg):
2350 2357 if path in names:
2351 2358 return
2352 2359 if path in ctx.substate:
2353 2360 return
2354 2361 path_ = path + '/'
2355 2362 for f in names:
2356 2363 if f.startswith(path_):
2357 2364 return
2358 2365 ui.warn("%s: %s\n" % (m.rel(path), msg))
2359 2366
2360 2367 m = scmutil.match(ctx, pats, opts)
2361 2368 m.bad = badfn
2362 2369 for abs in ctx.walk(m):
2363 2370 if abs not in names:
2364 2371 names[abs] = m.rel(abs), m.exact(abs)
2365 2372
2366 2373 # get the list of subrepos that must be reverted
2367 2374 targetsubs = sorted(s for s in ctx.substate if m(s))
2368 2375
2369 2376 # Find status of all file in `names`.
2370 2377 m = scmutil.matchfiles(repo, names)
2371 2378
2372 2379 changes = repo.status(node1=node, match=m, clean=True)
2373 2380 modified = set(changes[0])
2374 2381 added = set(changes[1])
2375 2382 removed = set(changes[2])
2376 2383 deleted = set(changes[3])
2377 2384
2378 2385 # We need to account for the state of file in the dirstate
2379 2386 #
2380 2387 # Even, when we revert agains something else than parent. this will
2381 2388 # slightly alter the behavior of revert (doing back up or not, delete
2382 2389 # or just forget etc)
2383 2390 if parent == node:
2384 2391 dsmodified = modified
2385 2392 dsadded = added
2386 2393 dsremoved = removed
2387 2394 modified, added, removed = set(), set(), set()
2388 2395 else:
2389 2396 changes = repo.status(node1=parent, match=m)
2390 2397 dsmodified = set(changes[0])
2391 2398 dsadded = set(changes[1])
2392 2399 dsremoved = set(changes[2])
2393 2400
2394 2401 # if f is a rename, update `names` to also revert the source
2395 2402 cwd = repo.getcwd()
2396 2403 for f in dsadded:
2397 2404 src = repo.dirstate.copied(f)
2398 2405 if src and src not in names and repo.dirstate[src] == 'r':
2399 2406 dsremoved.add(src)
2400 2407 names[src] = (repo.pathto(src, cwd), True)
2401 2408
2402 2409 ## computation of the action to performs on `names` content.
2403 2410
2404 2411 def removeforget(abs):
2405 2412 if repo.dirstate[abs] == 'a':
2406 2413 return _('forgetting %s\n')
2407 2414 return _('removing %s\n')
2408 2415
2409 2416 # split between files known in target manifest and the others
2410 2417 smf = set(mf)
2411 2418
2412 2419 missingmodified = dsmodified - smf
2413 2420 dsmodified -= missingmodified
2414 2421 missingadded = dsadded - smf
2415 2422 dsadded -= missingadded
2416 2423 missingremoved = dsremoved - smf
2417 2424 dsremoved -= missingremoved
2418 2425 missingdeleted = deleted - smf
2419 2426 deleted -= missingdeleted
2420 2427
2421 2428 # action to be actually performed by revert
2422 2429 # (<list of file>, message>) tuple
2423 2430 actions = {'revert': ([], _('reverting %s\n')),
2424 2431 'add': ([], _('adding %s\n')),
2425 2432 'remove': ([], removeforget),
2426 2433 'undelete': ([], _('undeleting %s\n'))}
2427 2434
2428 2435 disptable = (
2429 2436 # dispatch table:
2430 2437 # file state
2431 2438 # action
2432 2439 # make backup
2433 2440 (dsmodified, (actions['revert'], True)),
2434 2441 (missingmodified, (actions['remove'], True)),
2435 2442 (dsadded, (actions['revert'], True)),
2436 2443 (missingadded, (actions['remove'], False)),
2437 2444 (dsremoved, (actions['undelete'], True)),
2438 2445 (missingremoved, (None, False)),
2439 2446 (deleted, (actions['revert'], False)),
2440 2447 (missingdeleted, (actions['remove'], False)),
2441 2448 )
2442 2449
2443 2450 for abs, (rel, exact) in sorted(names.items()):
2444 2451 # hash on file in target manifest (or None if missing from target)
2445 2452 mfentry = mf.get(abs)
2446 2453 # target file to be touch on disk (relative to cwd)
2447 2454 target = repo.wjoin(abs)
2448 2455 def handle(xlist, dobackup):
2449 2456 xlist[0].append(abs)
2450 2457 if (dobackup and not opts.get('no_backup') and
2451 2458 os.path.lexists(target) and
2452 2459 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2453 2460 bakname = "%s.orig" % rel
2454 2461 ui.note(_('saving current version of %s as %s\n') %
2455 2462 (rel, bakname))
2456 2463 if not opts.get('dry_run'):
2457 2464 util.rename(target, bakname)
2458 2465 if ui.verbose or not exact:
2459 2466 msg = xlist[1]
2460 2467 if not isinstance(msg, basestring):
2461 2468 msg = msg(abs)
2462 2469 ui.status(msg % rel)
2463 2470 # search the entry in the dispatch table.
2464 2471 # if the file is in any of this sets, it was touched in the working
2465 2472 # directory parent and we are sure it needs to be reverted.
2466 2473 for table, (action, backup) in disptable:
2467 2474 if abs not in table:
2468 2475 continue
2469 2476 if action is None:
2470 2477 if exact:
2471 2478 ui.warn(_('no changes needed to %s\n') % rel)
2472 2479
2473 2480 else:
2474 2481 handle(action, backup)
2475 2482 break
2476 2483 else:
2477 2484 # Not touched in current dirstate.
2478 2485
2479 2486 # file is unknown in parent, restore older version or ignore.
2480 2487 if abs not in repo.dirstate:
2481 2488 if mfentry:
2482 2489 handle(actions['add'], True)
2483 2490 elif exact:
2484 2491 ui.warn(_('file not managed: %s\n') % rel)
2485 2492 continue
2486 2493
2487 2494 # parent is target, no changes mean no changes
2488 2495 if node == parent:
2489 2496 if exact:
2490 2497 ui.warn(_('no changes needed to %s\n') % rel)
2491 2498 continue
2492 2499 # no change in dirstate but parent and target may differ
2493 2500 if pmf is None:
2494 2501 # only need parent manifest in this unlikely case,
2495 2502 # so do not read by default
2496 2503 pmf = repo[parent].manifest()
2497 2504 if abs in pmf and mfentry:
2498 2505 # if version of file is same in parent and target
2499 2506 # manifests, do nothing
2500 2507 if (pmf[abs] != mfentry or
2501 2508 pmf.flags(abs) != mf.flags(abs)):
2502 2509 handle(actions['revert'], False)
2503 2510 else:
2504 2511 handle(actions['remove'], False)
2505 2512
2506 2513 if not opts.get('dry_run'):
2507 2514 _performrevert(repo, parents, ctx, actions)
2508 2515
2509 2516 if targetsubs:
2510 2517 # Revert the subrepos on the revert list
2511 2518 for sub in targetsubs:
2512 2519 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2513 2520 finally:
2514 2521 wlock.release()
2515 2522
2516 2523 def _performrevert(repo, parents, ctx, actions):
2517 2524 """function that actually perform all the actions computed for revert
2518 2525
2519 2526 This is an independent function to let extension to plug in and react to
2520 2527 the imminent revert.
2521 2528
2522 2529 Make sure you have the working directory locked when calling this function.
2523 2530 """
2524 2531 parent, p2 = parents
2525 2532 node = ctx.node()
2526 2533 def checkout(f):
2527 2534 fc = ctx[f]
2528 2535 repo.wwrite(f, fc.data(), fc.flags())
2529 2536
2530 2537 audit_path = pathutil.pathauditor(repo.root)
2531 2538 for f in actions['remove'][0]:
2532 2539 if repo.dirstate[f] == 'a':
2533 2540 repo.dirstate.drop(f)
2534 2541 continue
2535 2542 audit_path(f)
2536 2543 try:
2537 2544 util.unlinkpath(repo.wjoin(f))
2538 2545 except OSError:
2539 2546 pass
2540 2547 repo.dirstate.remove(f)
2541 2548
2542 2549 normal = None
2543 2550 if node == parent:
2544 2551 # We're reverting to our parent. If possible, we'd like status
2545 2552 # to report the file as clean. We have to use normallookup for
2546 2553 # merges to avoid losing information about merged/dirty files.
2547 2554 if p2 != nullid:
2548 2555 normal = repo.dirstate.normallookup
2549 2556 else:
2550 2557 normal = repo.dirstate.normal
2551 2558 for f in actions['revert'][0]:
2552 2559 checkout(f)
2553 2560 if normal:
2554 2561 normal(f)
2555 2562
2556 2563 for f in actions['add'][0]:
2557 2564 checkout(f)
2558 2565 repo.dirstate.add(f)
2559 2566
2560 2567 normal = repo.dirstate.normallookup
2561 2568 if node == parent and p2 == nullid:
2562 2569 normal = repo.dirstate.normal
2563 2570 for f in actions['undelete'][0]:
2564 2571 checkout(f)
2565 2572 normal(f)
2566 2573
2567 2574 copied = copies.pathcopies(repo[parent], ctx)
2568 2575
2569 2576 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2570 2577 if f in copied:
2571 2578 repo.dirstate.copy(copied[f], f)
2572 2579
2573 2580 def command(table):
2574 2581 """Returns a function object to be used as a decorator for making commands.
2575 2582
2576 2583 This function receives a command table as its argument. The table should
2577 2584 be a dict.
2578 2585
2579 2586 The returned function can be used as a decorator for adding commands
2580 2587 to that command table. This function accepts multiple arguments to define
2581 2588 a command.
2582 2589
2583 2590 The first argument is the command name.
2584 2591
2585 2592 The options argument is an iterable of tuples defining command arguments.
2586 2593 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2587 2594
2588 2595 The synopsis argument defines a short, one line summary of how to use the
2589 2596 command. This shows up in the help output.
2590 2597
2591 2598 The norepo argument defines whether the command does not require a
2592 2599 local repository. Most commands operate against a repository, thus the
2593 2600 default is False.
2594 2601
2595 2602 The optionalrepo argument defines whether the command optionally requires
2596 2603 a local repository.
2597 2604
2598 2605 The inferrepo argument defines whether to try to find a repository from the
2599 2606 command line arguments. If True, arguments will be examined for potential
2600 2607 repository locations. See ``findrepo()``. If a repository is found, it
2601 2608 will be used.
2602 2609 """
2603 2610 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2604 2611 inferrepo=False):
2605 2612 def decorator(func):
2606 2613 if synopsis:
2607 2614 table[name] = func, list(options), synopsis
2608 2615 else:
2609 2616 table[name] = func, list(options)
2610 2617
2611 2618 if norepo:
2612 2619 # Avoid import cycle.
2613 2620 import commands
2614 2621 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2615 2622
2616 2623 if optionalrepo:
2617 2624 import commands
2618 2625 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2619 2626
2620 2627 if inferrepo:
2621 2628 import commands
2622 2629 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2623 2630
2624 2631 return func
2625 2632 return decorator
2626 2633
2627 2634 return cmd
2628 2635
2629 2636 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2630 2637 # commands.outgoing. "missing" is "missing" of the result of
2631 2638 # "findcommonoutgoing()"
2632 2639 outgoinghooks = util.hooks()
2633 2640
2634 2641 # a list of (ui, repo) functions called by commands.summary
2635 2642 summaryhooks = util.hooks()
2636 2643
2637 2644 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2638 2645 #
2639 2646 # functions should return tuple of booleans below, if 'changes' is None:
2640 2647 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2641 2648 #
2642 2649 # otherwise, 'changes' is a tuple of tuples below:
2643 2650 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2644 2651 # - (desturl, destbranch, destpeer, outgoing)
2645 2652 summaryremotehooks = util.hooks()
2646 2653
2647 2654 # A list of state files kept by multistep operations like graft.
2648 2655 # Since graft cannot be aborted, it is considered 'clearable' by update.
2649 2656 # note: bisect is intentionally excluded
2650 2657 # (state file, clearable, allowcommit, error, hint)
2651 2658 unfinishedstates = [
2652 2659 ('graftstate', True, False, _('graft in progress'),
2653 2660 _("use 'hg graft --continue' or 'hg update' to abort")),
2654 2661 ('updatestate', True, False, _('last update was interrupted'),
2655 2662 _("use 'hg update' to get a consistent checkout"))
2656 2663 ]
2657 2664
2658 2665 def checkunfinished(repo, commit=False):
2659 2666 '''Look for an unfinished multistep operation, like graft, and abort
2660 2667 if found. It's probably good to check this right before
2661 2668 bailifchanged().
2662 2669 '''
2663 2670 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2664 2671 if commit and allowcommit:
2665 2672 continue
2666 2673 if repo.vfs.exists(f):
2667 2674 raise util.Abort(msg, hint=hint)
2668 2675
2669 2676 def clearunfinished(repo):
2670 2677 '''Check for unfinished operations (as above), and clear the ones
2671 2678 that are clearable.
2672 2679 '''
2673 2680 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2674 2681 if not clearable and repo.vfs.exists(f):
2675 2682 raise util.Abort(msg, hint=hint)
2676 2683 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2677 2684 if clearable and repo.vfs.exists(f):
2678 2685 util.unlink(repo.join(f))
@@ -1,1634 +1,1825 b''
1 1 This file used to contains all largefile tests.
2 2 Do not add any new tests in this file as it his already far too long to run.
3 3
4 4 It contains all the testing of the basic concepts of large file in a single block.
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 Create the repo with a couple of revisions of both large and normal
25 25 files.
26 26 Test status and dirstate of largefiles and that summary output is correct.
27 27
28 28 $ hg init a
29 29 $ cd a
30 30 $ mkdir sub
31 31 $ echo normal1 > normal1
32 32 $ echo normal2 > sub/normal2
33 33 $ echo large1 > large1
34 34 $ echo large2 > sub/large2
35 35 $ hg add normal1 sub/normal2
36 36 $ hg add --large large1 sub/large2
37 37 $ hg commit -m "add files"
38 38 Invoking status precommit hook
39 39 A large1
40 40 A normal1
41 41 A sub/large2
42 42 A sub/normal2
43 43 $ touch large1 sub/large2
44 44 $ sleep 1
45 45 $ hg st
46 46 $ hg debugstate --nodates
47 47 n 644 41 .hglf/large1
48 48 n 644 41 .hglf/sub/large2
49 49 n 644 8 normal1
50 50 n 644 8 sub/normal2
51 51 $ hg debugstate --large --nodates
52 52 n 644 7 large1
53 53 n 644 7 sub/large2
54 54 $ echo normal11 > normal1
55 55 $ echo normal22 > sub/normal2
56 56 $ echo large11 > large1
57 57 $ echo large22 > sub/large2
58 58 $ hg commit -m "edit files"
59 59 Invoking status precommit hook
60 60 M large1
61 61 M normal1
62 62 M sub/large2
63 63 M sub/normal2
64 64 $ hg sum --large
65 65 parent: 1:ce8896473775 tip
66 66 edit files
67 67 branch: default
68 68 commit: (clean)
69 69 update: (current)
70 70 largefiles: (no remote repo)
71 71
72 72 Commit preserved largefile contents.
73 73
74 74 $ cat normal1
75 75 normal11
76 76 $ cat large1
77 77 large11
78 78 $ cat sub/normal2
79 79 normal22
80 80 $ cat sub/large2
81 81 large22
82 82
83 83 Test status, subdir and unknown files
84 84
85 85 $ echo unknown > sub/unknown
86 86 $ hg st --all
87 87 ? sub/unknown
88 88 C large1
89 89 C normal1
90 90 C sub/large2
91 91 C sub/normal2
92 92 $ hg st --all sub
93 93 ? sub/unknown
94 94 C sub/large2
95 95 C sub/normal2
96 96 $ rm sub/unknown
97 97
98 98 Test messages and exit codes for remove warning cases
99 99
100 100 $ hg remove -A large1
101 101 not removing large1: file still exists
102 102 [1]
103 103 $ echo 'modified' > large1
104 104 $ hg remove large1
105 105 not removing large1: file is modified (use -f to force removal)
106 106 [1]
107 107 $ echo 'new' > normalnew
108 108 $ hg add normalnew
109 109 $ echo 'new' > largenew
110 110 $ hg add --large normalnew
111 111 normalnew already tracked!
112 112 $ hg remove normalnew largenew
113 113 not removing largenew: file is untracked
114 114 not removing normalnew: file has been marked for add (use forget to undo)
115 115 [1]
116 116 $ rm normalnew largenew
117 117 $ hg up -Cq
118 118
119 119 Remove both largefiles and normal files.
120 120
121 121 $ hg remove normal1 large1
122 122 $ hg status large1
123 123 R large1
124 124 $ hg commit -m "remove files"
125 125 Invoking status precommit hook
126 126 R large1
127 127 R normal1
128 128 $ ls
129 129 sub
130 130 $ echo "testlargefile" > large1-test
131 131 $ hg add --large large1-test
132 132 $ hg st
133 133 A large1-test
134 134 $ hg rm large1-test
135 135 not removing large1-test: file has been marked for add (use forget to undo)
136 136 [1]
137 137 $ hg st
138 138 A large1-test
139 139 $ hg forget large1-test
140 140 $ hg st
141 141 ? large1-test
142 142 $ hg remove large1-test
143 143 not removing large1-test: file is untracked
144 144 [1]
145 145 $ hg forget large1-test
146 146 not removing large1-test: file is already untracked
147 147 [1]
148 148 $ rm large1-test
149 149
150 150 Copy both largefiles and normal files (testing that status output is correct).
151 151
152 152 $ hg cp sub/normal2 normal1
153 153 $ hg cp sub/large2 large1
154 154 $ hg commit -m "copy files"
155 155 Invoking status precommit hook
156 156 A large1
157 157 A normal1
158 158 $ cat normal1
159 159 normal22
160 160 $ cat large1
161 161 large22
162 162
163 163 Test moving largefiles and verify that normal files are also unaffected.
164 164
165 165 $ hg mv normal1 normal3
166 166 $ hg mv large1 large3
167 167 $ hg mv sub/normal2 sub/normal4
168 168 $ hg mv sub/large2 sub/large4
169 169 $ hg commit -m "move files"
170 170 Invoking status precommit hook
171 171 A large3
172 172 A normal3
173 173 A sub/large4
174 174 A sub/normal4
175 175 R large1
176 176 R normal1
177 177 R sub/large2
178 178 R sub/normal2
179 179 $ cat normal3
180 180 normal22
181 181 $ cat large3
182 182 large22
183 183 $ cat sub/normal4
184 184 normal22
185 185 $ cat sub/large4
186 186 large22
187 187
188 188
189 189 #if serve
190 190 Test display of largefiles in hgweb
191 191
192 192 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
193 193 $ cat ../hg.pid >> $DAEMON_PIDS
194 194 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/?style=raw'
195 195 200 Script output follows
196 196
197 197
198 198 drwxr-xr-x sub
199 199 -rw-r--r-- 41 large3
200 200 -rw-r--r-- 9 normal3
201 201
202 202
203 203 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw'
204 204 200 Script output follows
205 205
206 206
207 207 -rw-r--r-- 41 large4
208 208 -rw-r--r-- 9 normal4
209 209
210 210
211 211 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
212 212 #endif
213 213
214 214 Test archiving the various revisions. These hit corner cases known with
215 215 archiving.
216 216
217 217 $ hg archive -r 0 ../archive0
218 218 $ hg archive -r 1 ../archive1
219 219 $ hg archive -r 2 ../archive2
220 220 $ hg archive -r 3 ../archive3
221 221 $ hg archive -r 4 ../archive4
222 222 $ cd ../archive0
223 223 $ cat normal1
224 224 normal1
225 225 $ cat large1
226 226 large1
227 227 $ cat sub/normal2
228 228 normal2
229 229 $ cat sub/large2
230 230 large2
231 231 $ cd ../archive1
232 232 $ cat normal1
233 233 normal11
234 234 $ cat large1
235 235 large11
236 236 $ cat sub/normal2
237 237 normal22
238 238 $ cat sub/large2
239 239 large22
240 240 $ cd ../archive2
241 241 $ ls
242 242 sub
243 243 $ cat sub/normal2
244 244 normal22
245 245 $ cat sub/large2
246 246 large22
247 247 $ cd ../archive3
248 248 $ cat normal1
249 249 normal22
250 250 $ cat large1
251 251 large22
252 252 $ cat sub/normal2
253 253 normal22
254 254 $ cat sub/large2
255 255 large22
256 256 $ cd ../archive4
257 257 $ cat normal3
258 258 normal22
259 259 $ cat large3
260 260 large22
261 261 $ cat sub/normal4
262 262 normal22
263 263 $ cat sub/large4
264 264 large22
265 265
266 266 Commit corner case: specify files to commit.
267 267
268 268 $ cd ../a
269 269 $ echo normal3 > normal3
270 270 $ echo large3 > large3
271 271 $ echo normal4 > sub/normal4
272 272 $ echo large4 > sub/large4
273 273 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
274 274 Invoking status precommit hook
275 275 M large3
276 276 M normal3
277 277 M sub/large4
278 278 M sub/normal4
279 279 $ cat normal3
280 280 normal3
281 281 $ cat large3
282 282 large3
283 283 $ cat sub/normal4
284 284 normal4
285 285 $ cat sub/large4
286 286 large4
287 287
288 288 One more commit corner case: commit from a subdirectory.
289 289
290 290 $ cd ../a
291 291 $ echo normal33 > normal3
292 292 $ echo large33 > large3
293 293 $ echo normal44 > sub/normal4
294 294 $ echo large44 > sub/large4
295 295 $ cd sub
296 296 $ hg commit -m "edit files yet again"
297 297 Invoking status precommit hook
298 298 M large3
299 299 M normal3
300 300 M sub/large4
301 301 M sub/normal4
302 302 $ cat ../normal3
303 303 normal33
304 304 $ cat ../large3
305 305 large33
306 306 $ cat normal4
307 307 normal44
308 308 $ cat large4
309 309 large44
310 310
311 311 Committing standins is not allowed.
312 312
313 313 $ cd ..
314 314 $ echo large3 > large3
315 315 $ hg commit .hglf/large3 -m "try to commit standin"
316 316 abort: file ".hglf/large3" is a largefile standin
317 317 (commit the largefile itself instead)
318 318 [255]
319 319
320 320 Corner cases for adding largefiles.
321 321
322 322 $ echo large5 > large5
323 323 $ hg add --large large5
324 324 $ hg add --large large5
325 325 large5 already a largefile
326 326 $ mkdir sub2
327 327 $ echo large6 > sub2/large6
328 328 $ echo large7 > sub2/large7
329 329 $ hg add --large sub2
330 330 adding sub2/large6 as a largefile (glob)
331 331 adding sub2/large7 as a largefile (glob)
332 332 $ hg st
333 333 M large3
334 334 A large5
335 335 A sub2/large6
336 336 A sub2/large7
337 337
338 338 Committing directories containing only largefiles.
339 339
340 340 $ mkdir -p z/y/x/m
341 341 $ touch z/y/x/m/large1
342 342 $ touch z/y/x/large2
343 343 $ hg add --large z/y/x/m/large1 z/y/x/large2
344 344 $ hg commit -m "Subdir with directory only containing largefiles" z
345 345 Invoking status precommit hook
346 346 M large3
347 347 A large5
348 348 A sub2/large6
349 349 A sub2/large7
350 350 A z/y/x/large2
351 351 A z/y/x/m/large1
352 352
353 353 (and a bit of log testing)
354 354
355 355 $ hg log -T '{rev}\n' z/y/x/m/large1
356 356 7
357 357 $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
358 358 7
359 359
360 360 $ hg rollback --quiet
361 361 $ touch z/y/x/m/normal
362 362 $ hg add z/y/x/m/normal
363 363 $ hg commit -m "Subdir with mixed contents" z
364 364 Invoking status precommit hook
365 365 M large3
366 366 A large5
367 367 A sub2/large6
368 368 A sub2/large7
369 369 A z/y/x/large2
370 370 A z/y/x/m/large1
371 371 A z/y/x/m/normal
372 372 $ hg st
373 373 M large3
374 374 A large5
375 375 A sub2/large6
376 376 A sub2/large7
377 377 $ hg rollback --quiet
378 378 $ hg revert z/y/x/large2 z/y/x/m/large1
379 379 $ rm z/y/x/large2 z/y/x/m/large1
380 380 $ hg commit -m "Subdir with normal contents" z
381 381 Invoking status precommit hook
382 382 M large3
383 383 A large5
384 384 A sub2/large6
385 385 A sub2/large7
386 386 A z/y/x/m/normal
387 387 $ hg st
388 388 M large3
389 389 A large5
390 390 A sub2/large6
391 391 A sub2/large7
392 392 $ hg rollback --quiet
393 393 $ hg revert --quiet z
394 394 $ hg commit -m "Empty subdir" z
395 395 abort: z: no match under directory!
396 396 [255]
397 397 $ rm -rf z
398 398 $ hg ci -m "standin" .hglf
399 399 abort: file ".hglf" is a largefile standin
400 400 (commit the largefile itself instead)
401 401 [255]
402 402
403 403 Test "hg status" with combination of 'file pattern' and 'directory
404 404 pattern' for largefiles:
405 405
406 406 $ hg status sub2/large6 sub2
407 407 A sub2/large6
408 408 A sub2/large7
409 409
410 410 Config settings (pattern **.dat, minsize 2 MB) are respected.
411 411
412 412 $ echo testdata > test.dat
413 413 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
414 414 $ hg add
415 415 adding reallylarge as a largefile
416 416 adding test.dat as a largefile
417 417
418 418 Test that minsize and --lfsize handle float values;
419 419 also tests that --lfsize overrides largefiles.minsize.
420 420 (0.250 MB = 256 kB = 262144 B)
421 421
422 422 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
423 423 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
424 424 $ hg --config largefiles.minsize=.25 add
425 425 adding ratherlarge as a largefile
426 426 adding medium
427 427 $ hg forget medium
428 428 $ hg --config largefiles.minsize=.25 add --lfsize=.125
429 429 adding medium as a largefile
430 430 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
431 431 $ hg --config largefiles.minsize=.25 add --lfsize=.125
432 432 adding notlarge
433 433 $ hg forget notlarge
434 434
435 435 Test forget on largefiles.
436 436
437 437 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
438 438 $ hg commit -m "add/edit more largefiles"
439 439 Invoking status precommit hook
440 440 A sub2/large6
441 441 A sub2/large7
442 442 R large3
443 443 ? large5
444 444 ? medium
445 445 ? notlarge
446 446 ? ratherlarge
447 447 ? reallylarge
448 448 ? test.dat
449 449 $ hg st
450 450 ? large3
451 451 ? large5
452 452 ? medium
453 453 ? notlarge
454 454 ? ratherlarge
455 455 ? reallylarge
456 456 ? test.dat
457 457
458 458 Purge with largefiles: verify that largefiles are still in the working
459 459 dir after a purge.
460 460
461 461 $ hg purge --all
462 462 $ cat sub/large4
463 463 large44
464 464 $ cat sub2/large6
465 465 large6
466 466 $ cat sub2/large7
467 467 large7
468 468
469 469 Test addremove: verify that files that should be added as largefiles are added as
470 470 such and that already-existing largefiles are not added as normal files by
471 471 accident.
472 472
473 473 $ rm normal3
474 474 $ rm sub/large4
475 475 $ echo "testing addremove with patterns" > testaddremove.dat
476 476 $ echo "normaladdremove" > normaladdremove
477 477 $ hg addremove
478 478 removing sub/large4
479 479 adding testaddremove.dat as a largefile
480 480 removing normal3
481 481 adding normaladdremove
482 482
483 483 Test addremove with -R
484 484
485 485 $ hg up -C
486 486 getting changed largefiles
487 487 1 largefiles updated, 0 removed
488 488 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 489 $ rm normal3
490 490 $ rm sub/large4
491 491 $ echo "testing addremove with patterns" > testaddremove.dat
492 492 $ echo "normaladdremove" > normaladdremove
493 493 $ cd ..
494 494 $ hg -R a addremove
495 495 removing sub/large4
496 496 adding a/testaddremove.dat as a largefile (glob)
497 497 removing normal3
498 498 adding normaladdremove
499 499 $ cd a
500 500
501 501 Test 3364
502 502 $ hg clone . ../addrm
503 503 updating to branch default
504 504 getting changed largefiles
505 505 3 largefiles updated, 0 removed
506 506 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
507 507 $ cd ../addrm
508 508 $ cat >> .hg/hgrc <<EOF
509 509 > [hooks]
510 510 > post-commit.stat=sh -c "echo \\"Invoking status postcommit hook\\"; hg status -A"
511 511 > EOF
512 512 $ touch foo
513 513 $ hg add --large foo
514 514 $ hg ci -m "add foo"
515 515 Invoking status precommit hook
516 516 A foo
517 517 Invoking status postcommit hook
518 518 C foo
519 519 C normal3
520 520 C sub/large4
521 521 C sub/normal4
522 522 C sub2/large6
523 523 C sub2/large7
524 524 $ rm foo
525 525 $ hg st
526 526 ! foo
527 527 hmm.. no precommit invoked, but there is a postcommit??
528 528 $ hg ci -m "will not checkin"
529 529 nothing changed
530 530 Invoking status postcommit hook
531 531 ! foo
532 532 C normal3
533 533 C sub/large4
534 534 C sub/normal4
535 535 C sub2/large6
536 536 C sub2/large7
537 537 [1]
538 538 $ hg addremove
539 539 removing foo
540 540 $ hg st
541 541 R foo
542 542 $ hg ci -m "used to say nothing changed"
543 543 Invoking status precommit hook
544 544 R foo
545 545 Invoking status postcommit hook
546 546 C normal3
547 547 C sub/large4
548 548 C sub/normal4
549 549 C sub2/large6
550 550 C sub2/large7
551 551 $ hg st
552 552
553 553 Test 3507 (both normal files and largefiles were a problem)
554 554
555 555 $ touch normal
556 556 $ touch large
557 557 $ hg add normal
558 558 $ hg add --large large
559 559 $ hg ci -m "added"
560 560 Invoking status precommit hook
561 561 A large
562 562 A normal
563 563 Invoking status postcommit hook
564 564 C large
565 565 C normal
566 566 C normal3
567 567 C sub/large4
568 568 C sub/normal4
569 569 C sub2/large6
570 570 C sub2/large7
571 571 $ hg remove normal
572 572 $ hg addremove --traceback
573 573 $ hg ci -m "addremoved normal"
574 574 Invoking status precommit hook
575 575 R normal
576 576 Invoking status postcommit hook
577 577 C large
578 578 C normal3
579 579 C sub/large4
580 580 C sub/normal4
581 581 C sub2/large6
582 582 C sub2/large7
583 583 $ hg up -C '.^'
584 584 getting changed largefiles
585 585 0 largefiles updated, 0 removed
586 586 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 587 $ hg remove large
588 588 $ hg addremove --traceback
589 589 $ hg ci -m "removed large"
590 590 Invoking status precommit hook
591 591 R large
592 592 created new head
593 593 Invoking status postcommit hook
594 594 C normal
595 595 C normal3
596 596 C sub/large4
597 597 C sub/normal4
598 598 C sub2/large6
599 599 C sub2/large7
600 600
601 601 Test commit -A (issue 3542)
602 602 $ echo large8 > large8
603 603 $ hg add --large large8
604 604 $ hg ci -Am 'this used to add large8 as normal and commit both'
605 605 Invoking status precommit hook
606 606 A large8
607 607 Invoking status postcommit hook
608 608 C large8
609 609 C normal
610 610 C normal3
611 611 C sub/large4
612 612 C sub/normal4
613 613 C sub2/large6
614 614 C sub2/large7
615 615 $ rm large8
616 616 $ hg ci -Am 'this used to not notice the rm'
617 617 removing large8
618 618 Invoking status precommit hook
619 619 R large8
620 620 Invoking status postcommit hook
621 621 C normal
622 622 C normal3
623 623 C sub/large4
624 624 C sub/normal4
625 625 C sub2/large6
626 626 C sub2/large7
627 627
628 628 Test that a standin can't be added as a large file
629 629
630 630 $ touch large
631 631 $ hg add --large large
632 632 $ hg ci -m "add"
633 633 Invoking status precommit hook
634 634 A large
635 635 Invoking status postcommit hook
636 636 C large
637 637 C normal
638 638 C normal3
639 639 C sub/large4
640 640 C sub/normal4
641 641 C sub2/large6
642 642 C sub2/large7
643 643 $ hg remove large
644 644 $ touch large
645 645 $ hg addremove --config largefiles.patterns=**large --traceback
646 646 adding large as a largefile
647 647
648 648 Test that outgoing --large works (with revsets too)
649 649 $ hg outgoing --rev '.^' --large
650 650 comparing with $TESTTMP/a (glob)
651 651 searching for changes
652 652 changeset: 8:c02fd3b77ec4
653 653 user: test
654 654 date: Thu Jan 01 00:00:00 1970 +0000
655 655 summary: add foo
656 656
657 657 changeset: 9:289dd08c9bbb
658 658 user: test
659 659 date: Thu Jan 01 00:00:00 1970 +0000
660 660 summary: used to say nothing changed
661 661
662 662 changeset: 10:34f23ac6ac12
663 663 user: test
664 664 date: Thu Jan 01 00:00:00 1970 +0000
665 665 summary: added
666 666
667 667 changeset: 12:710c1b2f523c
668 668 parent: 10:34f23ac6ac12
669 669 user: test
670 670 date: Thu Jan 01 00:00:00 1970 +0000
671 671 summary: removed large
672 672
673 673 changeset: 13:0a3e75774479
674 674 user: test
675 675 date: Thu Jan 01 00:00:00 1970 +0000
676 676 summary: this used to add large8 as normal and commit both
677 677
678 678 changeset: 14:84f3d378175c
679 679 user: test
680 680 date: Thu Jan 01 00:00:00 1970 +0000
681 681 summary: this used to not notice the rm
682 682
683 683 largefiles to upload (1 entities):
684 684 large8
685 685
686 686 $ cd ../a
687 687
688 688 Clone a largefiles repo.
689 689
690 690 $ hg clone . ../b
691 691 updating to branch default
692 692 getting changed largefiles
693 693 3 largefiles updated, 0 removed
694 694 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
695 695 $ cd ../b
696 696 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
697 697 7:daea875e9014 add/edit more largefiles
698 698 6:4355d653f84f edit files yet again
699 699 5:9d5af5072dbd edit files again
700 700 4:74c02385b94c move files
701 701 3:9e8fbc4bce62 copy files
702 702 2:51a0ae4d5864 remove files
703 703 1:ce8896473775 edit files
704 704 0:30d30fe6a5be add files
705 705 $ cat normal3
706 706 normal33
707 707
708 708 Test graph log
709 709
710 710 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
711 711 @ 7:daea875e9014 add/edit more largefiles
712 712 |
713 713 o 6:4355d653f84f edit files yet again
714 714 |
715 715 o 5:9d5af5072dbd edit files again
716 716 |
717 717 o 4:74c02385b94c move files
718 718 |
719 719 o 3:9e8fbc4bce62 copy files
720 720 |
721 721 o 2:51a0ae4d5864 remove files
722 722 |
723 723 o 1:ce8896473775 edit files
724 724 |
725 725 o 0:30d30fe6a5be add files
726 726
727
728 Test log with --patch
729
730 $ hg log --patch -r 6::7
731 changeset: 6:4355d653f84f
732 user: test
733 date: Thu Jan 01 00:00:00 1970 +0000
734 summary: edit files yet again
735
736 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
737 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
738 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
739 @@ -1,1 +1,1 @@
740 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
741 +7838695e10da2bb75ac1156565f40a2595fa2fa0
742 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
743 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
744 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
745 @@ -1,1 +1,1 @@
746 -aeb2210d19f02886dde00dac279729a48471e2f9
747 +971fb41e78fea4f8e0ba5244784239371cb00591
748 diff -r 9d5af5072dbd -r 4355d653f84f normal3
749 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
750 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
751 @@ -1,1 +1,1 @@
752 -normal3
753 +normal33
754 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
755 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
756 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
757 @@ -1,1 +1,1 @@
758 -normal4
759 +normal44
760
761 changeset: 7:daea875e9014
762 tag: tip
763 user: test
764 date: Thu Jan 01 00:00:00 1970 +0000
765 summary: add/edit more largefiles
766
767 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
768 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
769 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
770 @@ -1,1 +0,0 @@
771 -7838695e10da2bb75ac1156565f40a2595fa2fa0
772 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
773 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
774 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
775 @@ -0,0 +1,1 @@
776 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
777 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
778 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
779 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
780 @@ -0,0 +1,1 @@
781 +bb3151689acb10f0c3125c560d5e63df914bc1af
782
783
784 $ hg log --patch -r 6::7 sub/
785 changeset: 6:4355d653f84f
786 user: test
787 date: Thu Jan 01 00:00:00 1970 +0000
788 summary: edit files yet again
789
790 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
791 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
792 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
793 @@ -1,1 +1,1 @@
794 -aeb2210d19f02886dde00dac279729a48471e2f9
795 +971fb41e78fea4f8e0ba5244784239371cb00591
796 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
797 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
798 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
799 @@ -1,1 +1,1 @@
800 -normal4
801 +normal44
802
803
804 log with both --follow and --patch
805
806 $ hg log --follow --patch --limit 2
807 changeset: 7:daea875e9014
808 tag: tip
809 user: test
810 date: Thu Jan 01 00:00:00 1970 +0000
811 summary: add/edit more largefiles
812
813 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
814 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
815 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
816 @@ -1,1 +0,0 @@
817 -7838695e10da2bb75ac1156565f40a2595fa2fa0
818 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
819 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
820 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
821 @@ -0,0 +1,1 @@
822 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
823 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
824 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
825 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
826 @@ -0,0 +1,1 @@
827 +bb3151689acb10f0c3125c560d5e63df914bc1af
828
829 changeset: 6:4355d653f84f
830 user: test
831 date: Thu Jan 01 00:00:00 1970 +0000
832 summary: edit files yet again
833
834 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
835 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
836 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
837 @@ -1,1 +1,1 @@
838 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
839 +7838695e10da2bb75ac1156565f40a2595fa2fa0
840 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
841 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
842 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
843 @@ -1,1 +1,1 @@
844 -aeb2210d19f02886dde00dac279729a48471e2f9
845 +971fb41e78fea4f8e0ba5244784239371cb00591
846 diff -r 9d5af5072dbd -r 4355d653f84f normal3
847 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
848 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
849 @@ -1,1 +1,1 @@
850 -normal3
851 +normal33
852 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
853 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
854 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
855 @@ -1,1 +1,1 @@
856 -normal4
857 +normal44
858
859 $ hg log --follow --patch sub/large4
860 changeset: 6:4355d653f84f
861 user: test
862 date: Thu Jan 01 00:00:00 1970 +0000
863 summary: edit files yet again
864
865 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
866 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
867 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
868 @@ -1,1 +1,1 @@
869 -aeb2210d19f02886dde00dac279729a48471e2f9
870 +971fb41e78fea4f8e0ba5244784239371cb00591
871
872 changeset: 5:9d5af5072dbd
873 user: test
874 date: Thu Jan 01 00:00:00 1970 +0000
875 summary: edit files again
876
877 diff -r 74c02385b94c -r 9d5af5072dbd .hglf/sub/large4
878 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
879 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
880 @@ -1,1 +1,1 @@
881 -eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
882 +aeb2210d19f02886dde00dac279729a48471e2f9
883
884 changeset: 4:74c02385b94c
885 user: test
886 date: Thu Jan 01 00:00:00 1970 +0000
887 summary: move files
888
889 diff -r 9e8fbc4bce62 -r 74c02385b94c .hglf/sub/large4
890 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
891 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
892 @@ -0,0 +1,1 @@
893 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
894
895 changeset: 1:ce8896473775
896 user: test
897 date: Thu Jan 01 00:00:00 1970 +0000
898 summary: edit files
899
900 diff -r 30d30fe6a5be -r ce8896473775 .hglf/sub/large2
901 --- a/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
902 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
903 @@ -1,1 +1,1 @@
904 -1deebade43c8c498a3c8daddac0244dc55d1331d
905 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
906
907 changeset: 0:30d30fe6a5be
908 user: test
909 date: Thu Jan 01 00:00:00 1970 +0000
910 summary: add files
911
912 diff -r 000000000000 -r 30d30fe6a5be .hglf/sub/large2
913 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
914 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
915 @@ -0,0 +1,1 @@
916 +1deebade43c8c498a3c8daddac0244dc55d1331d
917
727 918 $ cat sub/normal4
728 919 normal44
729 920 $ cat sub/large4
730 921 large44
731 922 $ cat sub2/large6
732 923 large6
733 924 $ cat sub2/large7
734 925 large7
735 926 $ hg log -qf sub2/large7
736 927 7:daea875e9014
737 928 $ hg log -Gqf sub2/large7
738 929 @ 7:daea875e9014
739 930 |
740 931 $ cd ..
741 932
742 933 Test log from outside repo
743 934
744 935 $ hg log b/sub -T '{rev}:{node|short} {desc|firstline}\n'
745 936 6:4355d653f84f edit files yet again
746 937 5:9d5af5072dbd edit files again
747 938 4:74c02385b94c move files
748 939 1:ce8896473775 edit files
749 940 0:30d30fe6a5be add files
750 941
751 942 Test clone at revision
752 943
753 944 $ hg clone a -r 3 c
754 945 adding changesets
755 946 adding manifests
756 947 adding file changes
757 948 added 4 changesets with 10 changes to 4 files
758 949 updating to branch default
759 950 getting changed largefiles
760 951 2 largefiles updated, 0 removed
761 952 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
762 953 $ cd c
763 954 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
764 955 3:9e8fbc4bce62 copy files
765 956 2:51a0ae4d5864 remove files
766 957 1:ce8896473775 edit files
767 958 0:30d30fe6a5be add files
768 959 $ cat normal1
769 960 normal22
770 961 $ cat large1
771 962 large22
772 963 $ cat sub/normal2
773 964 normal22
774 965 $ cat sub/large2
775 966 large22
776 967
777 968 Old revisions of a clone have correct largefiles content (this also
778 969 tests update).
779 970
780 971 $ hg update -r 1
781 972 getting changed largefiles
782 973 1 largefiles updated, 0 removed
783 974 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
784 975 $ cat large1
785 976 large11
786 977 $ cat sub/large2
787 978 large22
788 979 $ cd ..
789 980
790 981 Test cloning with --all-largefiles flag
791 982
792 983 $ rm "${USERCACHE}"/*
793 984 $ hg clone --all-largefiles a a-backup
794 985 updating to branch default
795 986 getting changed largefiles
796 987 3 largefiles updated, 0 removed
797 988 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
798 989 8 additional largefiles cached
799 990
800 991 $ rm "${USERCACHE}"/*
801 992 $ hg clone --all-largefiles -u 0 a a-clone0
802 993 updating to branch default
803 994 getting changed largefiles
804 995 2 largefiles updated, 0 removed
805 996 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
806 997 9 additional largefiles cached
807 998 $ hg -R a-clone0 sum
808 999 parent: 0:30d30fe6a5be
809 1000 add files
810 1001 branch: default
811 1002 commit: (clean)
812 1003 update: 7 new changesets (update)
813 1004
814 1005 $ rm "${USERCACHE}"/*
815 1006 $ hg clone --all-largefiles -u 1 a a-clone1
816 1007 updating to branch default
817 1008 getting changed largefiles
818 1009 2 largefiles updated, 0 removed
819 1010 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
820 1011 8 additional largefiles cached
821 1012 $ hg -R a-clone1 verify --large --lfa --lfc
822 1013 checking changesets
823 1014 checking manifests
824 1015 crosschecking files in changesets and manifests
825 1016 checking files
826 1017 10 files, 8 changesets, 24 total revisions
827 1018 searching 8 changesets for largefiles
828 1019 verified contents of 13 revisions of 6 largefiles
829 1020 $ hg -R a-clone1 sum
830 1021 parent: 1:ce8896473775
831 1022 edit files
832 1023 branch: default
833 1024 commit: (clean)
834 1025 update: 6 new changesets (update)
835 1026
836 1027 $ rm "${USERCACHE}"/*
837 1028 $ hg clone --all-largefiles -U a a-clone-u
838 1029 11 additional largefiles cached
839 1030 $ hg -R a-clone-u sum
840 1031 parent: -1:000000000000 (no revision checked out)
841 1032 branch: default
842 1033 commit: (clean)
843 1034 update: 8 new changesets (update)
844 1035
845 1036 Show computed destination directory:
846 1037
847 1038 $ mkdir xyz
848 1039 $ cd xyz
849 1040 $ hg clone ../a
850 1041 destination directory: a
851 1042 updating to branch default
852 1043 getting changed largefiles
853 1044 3 largefiles updated, 0 removed
854 1045 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
855 1046 $ cd ..
856 1047
857 1048 Clone URL without path:
858 1049
859 1050 $ hg clone file://
860 1051 abort: repository / not found!
861 1052 [255]
862 1053
863 1054 Ensure base clone command argument validation
864 1055
865 1056 $ hg clone -U -u 0 a a-clone-failure
866 1057 abort: cannot specify both --noupdate and --updaterev
867 1058 [255]
868 1059
869 1060 $ hg clone --all-largefiles a ssh://localhost/a
870 1061 abort: --all-largefiles is incompatible with non-local destination ssh://localhost/a
871 1062 [255]
872 1063
873 1064 Test pulling with --all-largefiles flag. Also test that the largefiles are
874 1065 downloaded from 'default' instead of 'default-push' when no source is specified
875 1066 (issue3584)
876 1067
877 1068 $ rm -Rf a-backup
878 1069 $ hg clone -r 1 a a-backup
879 1070 adding changesets
880 1071 adding manifests
881 1072 adding file changes
882 1073 added 2 changesets with 8 changes to 4 files
883 1074 updating to branch default
884 1075 getting changed largefiles
885 1076 2 largefiles updated, 0 removed
886 1077 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
887 1078 $ rm "${USERCACHE}"/*
888 1079 $ cd a-backup
889 1080 $ hg pull --all-largefiles --config paths.default-push=bogus/path
890 1081 pulling from $TESTTMP/a (glob)
891 1082 searching for changes
892 1083 adding changesets
893 1084 adding manifests
894 1085 adding file changes
895 1086 added 6 changesets with 16 changes to 8 files
896 1087 (run 'hg update' to get a working copy)
897 1088 6 largefiles cached
898 1089
899 1090 redo pull with --lfrev and check it pulls largefiles for the right revs
900 1091
901 1092 $ hg rollback
902 1093 repository tip rolled back to revision 1 (undo pull)
903 1094 $ hg pull -v --lfrev 'heads(pulled())+min(pulled())'
904 1095 pulling from $TESTTMP/a (glob)
905 1096 searching for changes
906 1097 all local heads known remotely
907 1098 6 changesets found
908 1099 adding changesets
909 1100 adding manifests
910 1101 adding file changes
911 1102 added 6 changesets with 16 changes to 8 files
912 1103 calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
913 1104 (run 'hg update' to get a working copy)
914 1105 pulling largefiles for revision 7
915 1106 found 971fb41e78fea4f8e0ba5244784239371cb00591 in store
916 1107 found 0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30 in store
917 1108 found bb3151689acb10f0c3125c560d5e63df914bc1af in store
918 1109 pulling largefiles for revision 2
919 1110 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
920 1111 0 largefiles cached
921 1112
922 1113 lfpull
923 1114
924 1115 $ hg lfpull -r : --config largefiles.usercache=usercache-lfpull
925 1116 2 largefiles cached
926 1117 $ hg lfpull -v -r 4+2 --config largefiles.usercache=usercache-lfpull
927 1118 pulling largefiles for revision 4
928 1119 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
929 1120 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
930 1121 pulling largefiles for revision 2
931 1122 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
932 1123 0 largefiles cached
933 1124
934 1125 $ ls usercache-lfpull/* | sort
935 1126 usercache-lfpull/1deebade43c8c498a3c8daddac0244dc55d1331d
936 1127 usercache-lfpull/4669e532d5b2c093a78eca010077e708a071bb64
937 1128
938 1129 $ cd ..
939 1130
940 1131 Rebasing between two repositories does not revert largefiles to old
941 1132 revisions (this was a very bad bug that took a lot of work to fix).
942 1133
943 1134 $ hg clone a d
944 1135 updating to branch default
945 1136 getting changed largefiles
946 1137 3 largefiles updated, 0 removed
947 1138 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
948 1139 $ cd b
949 1140 $ echo large4-modified > sub/large4
950 1141 $ echo normal3-modified > normal3
951 1142 $ hg commit -m "modify normal file and largefile in repo b"
952 1143 Invoking status precommit hook
953 1144 M normal3
954 1145 M sub/large4
955 1146 $ cd ../d
956 1147 $ echo large6-modified > sub2/large6
957 1148 $ echo normal4-modified > sub/normal4
958 1149 $ hg commit -m "modify normal file largefile in repo d"
959 1150 Invoking status precommit hook
960 1151 M sub/normal4
961 1152 M sub2/large6
962 1153 $ cd ..
963 1154 $ hg clone d e
964 1155 updating to branch default
965 1156 getting changed largefiles
966 1157 3 largefiles updated, 0 removed
967 1158 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
968 1159 $ cd d
969 1160
970 1161 More rebase testing, but also test that the largefiles are downloaded from
971 1162 'default-push' when no source is specified (issue3584). (The largefile from the
972 1163 pulled revision is however not downloaded but found in the local cache.)
973 1164 Largefiles are fetched for the new pulled revision, not for existing revisions,
974 1165 rebased or not.
975 1166
976 1167 $ [ ! -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
977 1168 $ hg pull --rebase --all-largefiles --config paths.default-push=bogus/path --config paths.default=../b
978 1169 pulling from $TESTTMP/b (glob)
979 1170 searching for changes
980 1171 adding changesets
981 1172 adding manifests
982 1173 adding file changes
983 1174 added 1 changesets with 2 changes to 2 files (+1 heads)
984 1175 Invoking status precommit hook
985 1176 M sub/normal4
986 1177 M sub2/large6
987 1178 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-backup.hg (glob)
988 1179 0 largefiles cached
989 1180 nothing to rebase - working directory parent is also destination
990 1181 $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
991 1182 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
992 1183 9:598410d3eb9a modify normal file largefile in repo d
993 1184 8:a381d2c8c80e modify normal file and largefile in repo b
994 1185 7:daea875e9014 add/edit more largefiles
995 1186 6:4355d653f84f edit files yet again
996 1187 5:9d5af5072dbd edit files again
997 1188 4:74c02385b94c move files
998 1189 3:9e8fbc4bce62 copy files
999 1190 2:51a0ae4d5864 remove files
1000 1191 1:ce8896473775 edit files
1001 1192 0:30d30fe6a5be add files
1002 1193 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
1003 1194 @ 9:598410d3eb9a modify normal file largefile in repo d
1004 1195 |
1005 1196 o 8:a381d2c8c80e modify normal file and largefile in repo b
1006 1197 |
1007 1198 o 7:daea875e9014 add/edit more largefiles
1008 1199 |
1009 1200 o 6:4355d653f84f edit files yet again
1010 1201 |
1011 1202 o 5:9d5af5072dbd edit files again
1012 1203 |
1013 1204 o 4:74c02385b94c move files
1014 1205 |
1015 1206 o 3:9e8fbc4bce62 copy files
1016 1207 |
1017 1208 o 2:51a0ae4d5864 remove files
1018 1209 |
1019 1210 o 1:ce8896473775 edit files
1020 1211 |
1021 1212 o 0:30d30fe6a5be add files
1022 1213
1023 1214 $ cat normal3
1024 1215 normal3-modified
1025 1216 $ cat sub/normal4
1026 1217 normal4-modified
1027 1218 $ cat sub/large4
1028 1219 large4-modified
1029 1220 $ cat sub2/large6
1030 1221 large6-modified
1031 1222 $ cat sub2/large7
1032 1223 large7
1033 1224 $ cd ../e
1034 1225 $ hg pull ../b
1035 1226 pulling from ../b
1036 1227 searching for changes
1037 1228 adding changesets
1038 1229 adding manifests
1039 1230 adding file changes
1040 1231 added 1 changesets with 2 changes to 2 files (+1 heads)
1041 1232 (run 'hg heads' to see heads, 'hg merge' to merge)
1042 1233 $ hg rebase
1043 1234 Invoking status precommit hook
1044 1235 M sub/normal4
1045 1236 M sub2/large6
1046 1237 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-backup.hg (glob)
1047 1238 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1048 1239 9:598410d3eb9a modify normal file largefile in repo d
1049 1240 8:a381d2c8c80e modify normal file and largefile in repo b
1050 1241 7:daea875e9014 add/edit more largefiles
1051 1242 6:4355d653f84f edit files yet again
1052 1243 5:9d5af5072dbd edit files again
1053 1244 4:74c02385b94c move files
1054 1245 3:9e8fbc4bce62 copy files
1055 1246 2:51a0ae4d5864 remove files
1056 1247 1:ce8896473775 edit files
1057 1248 0:30d30fe6a5be add files
1058 1249 $ cat normal3
1059 1250 normal3-modified
1060 1251 $ cat sub/normal4
1061 1252 normal4-modified
1062 1253 $ cat sub/large4
1063 1254 large4-modified
1064 1255 $ cat sub2/large6
1065 1256 large6-modified
1066 1257 $ cat sub2/large7
1067 1258 large7
1068 1259
1069 1260 Log on largefiles
1070 1261
1071 1262 - same output
1072 1263 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1073 1264 8:a381d2c8c80e modify normal file and largefile in repo b
1074 1265 6:4355d653f84f edit files yet again
1075 1266 5:9d5af5072dbd edit files again
1076 1267 4:74c02385b94c move files
1077 1268 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1078 1269 o 8:a381d2c8c80e modify normal file and largefile in repo b
1079 1270 |
1080 1271 o 6:4355d653f84f edit files yet again
1081 1272 |
1082 1273 o 5:9d5af5072dbd edit files again
1083 1274 |
1084 1275 o 4:74c02385b94c move files
1085 1276 |
1086 1277 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub/large4
1087 1278 8:a381d2c8c80e modify normal file and largefile in repo b
1088 1279 6:4355d653f84f edit files yet again
1089 1280 5:9d5af5072dbd edit files again
1090 1281 4:74c02385b94c move files
1091 1282 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1092 1283 o 8:a381d2c8c80e modify normal file and largefile in repo b
1093 1284 |
1094 1285 o 6:4355d653f84f edit files yet again
1095 1286 |
1096 1287 o 5:9d5af5072dbd edit files again
1097 1288 |
1098 1289 o 4:74c02385b94c move files
1099 1290 |
1100 1291
1101 1292 - .hglf only matches largefiles, without .hglf it matches 9 bco sub/normal
1102 1293 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1103 1294 8:a381d2c8c80e modify normal file and largefile in repo b
1104 1295 6:4355d653f84f edit files yet again
1105 1296 5:9d5af5072dbd edit files again
1106 1297 4:74c02385b94c move files
1107 1298 1:ce8896473775 edit files
1108 1299 0:30d30fe6a5be add files
1109 1300 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1110 1301 o 8:a381d2c8c80e modify normal file and largefile in repo b
1111 1302 |
1112 1303 o 6:4355d653f84f edit files yet again
1113 1304 |
1114 1305 o 5:9d5af5072dbd edit files again
1115 1306 |
1116 1307 o 4:74c02385b94c move files
1117 1308 |
1118 1309 o 1:ce8896473775 edit files
1119 1310 |
1120 1311 o 0:30d30fe6a5be add files
1121 1312
1122 1313 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub
1123 1314 9:598410d3eb9a modify normal file largefile in repo d
1124 1315 8:a381d2c8c80e modify normal file and largefile in repo b
1125 1316 6:4355d653f84f edit files yet again
1126 1317 5:9d5af5072dbd edit files again
1127 1318 4:74c02385b94c move files
1128 1319 1:ce8896473775 edit files
1129 1320 0:30d30fe6a5be add files
1130 1321 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' sub
1131 1322 @ 9:598410d3eb9a modify normal file largefile in repo d
1132 1323 |
1133 1324 o 8:a381d2c8c80e modify normal file and largefile in repo b
1134 1325 |
1135 1326 o 6:4355d653f84f edit files yet again
1136 1327 |
1137 1328 o 5:9d5af5072dbd edit files again
1138 1329 |
1139 1330 o 4:74c02385b94c move files
1140 1331 |
1141 1332 o 1:ce8896473775 edit files
1142 1333 |
1143 1334 o 0:30d30fe6a5be add files
1144 1335
1145 1336 - globbing gives same result
1146 1337 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1147 1338 9:598410d3eb9a modify normal file largefile in repo d
1148 1339 8:a381d2c8c80e modify normal file and largefile in repo b
1149 1340 6:4355d653f84f edit files yet again
1150 1341 5:9d5af5072dbd edit files again
1151 1342 4:74c02385b94c move files
1152 1343 1:ce8896473775 edit files
1153 1344 0:30d30fe6a5be add files
1154 1345 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1155 1346 @ 9:598410d3eb9a modify normal file largefile in repo d
1156 1347 |
1157 1348 o 8:a381d2c8c80e modify normal file and largefile in repo b
1158 1349 |
1159 1350 o 6:4355d653f84f edit files yet again
1160 1351 |
1161 1352 o 5:9d5af5072dbd edit files again
1162 1353 |
1163 1354 o 4:74c02385b94c move files
1164 1355 |
1165 1356 o 1:ce8896473775 edit files
1166 1357 |
1167 1358 o 0:30d30fe6a5be add files
1168 1359
1169 1360 Rollback on largefiles.
1170 1361
1171 1362 $ echo large4-modified-again > sub/large4
1172 1363 $ hg commit -m "Modify large4 again"
1173 1364 Invoking status precommit hook
1174 1365 M sub/large4
1175 1366 $ hg rollback
1176 1367 repository tip rolled back to revision 9 (undo commit)
1177 1368 working directory now based on revision 9
1178 1369 $ hg st
1179 1370 M sub/large4
1180 1371 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1181 1372 9:598410d3eb9a modify normal file largefile in repo d
1182 1373 8:a381d2c8c80e modify normal file and largefile in repo b
1183 1374 7:daea875e9014 add/edit more largefiles
1184 1375 6:4355d653f84f edit files yet again
1185 1376 5:9d5af5072dbd edit files again
1186 1377 4:74c02385b94c move files
1187 1378 3:9e8fbc4bce62 copy files
1188 1379 2:51a0ae4d5864 remove files
1189 1380 1:ce8896473775 edit files
1190 1381 0:30d30fe6a5be add files
1191 1382 $ cat sub/large4
1192 1383 large4-modified-again
1193 1384
1194 1385 "update --check" refuses to update with uncommitted changes.
1195 1386 $ hg update --check 8
1196 1387 abort: uncommitted changes
1197 1388 [255]
1198 1389
1199 1390 "update --clean" leaves correct largefiles in working copy, even when there is
1200 1391 .orig files from revert in .hglf.
1201 1392
1202 1393 $ echo mistake > sub2/large7
1203 1394 $ hg revert sub2/large7
1204 1395 $ cat sub2/large7
1205 1396 large7
1206 1397 $ cat sub2/large7.orig
1207 1398 mistake
1208 1399 $ test ! -f .hglf/sub2/large7.orig
1209 1400
1210 1401 $ hg -q update --clean -r null
1211 1402 $ hg update --clean
1212 1403 getting changed largefiles
1213 1404 3 largefiles updated, 0 removed
1214 1405 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1215 1406 $ cat normal3
1216 1407 normal3-modified
1217 1408 $ cat sub/normal4
1218 1409 normal4-modified
1219 1410 $ cat sub/large4
1220 1411 large4-modified
1221 1412 $ cat sub2/large6
1222 1413 large6-modified
1223 1414 $ cat sub2/large7
1224 1415 large7
1225 1416 $ cat sub2/large7.orig
1226 1417 mistake
1227 1418 $ test ! -f .hglf/sub2/large7.orig
1228 1419
1229 1420 verify that largefile .orig file no longer is overwritten on every update -C:
1230 1421 $ hg update --clean
1231 1422 getting changed largefiles
1232 1423 0 largefiles updated, 0 removed
1233 1424 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1234 1425 $ cat sub2/large7.orig
1235 1426 mistake
1236 1427 $ rm sub2/large7.orig
1237 1428
1238 1429 Now "update check" is happy.
1239 1430 $ hg update --check 8
1240 1431 getting changed largefiles
1241 1432 1 largefiles updated, 0 removed
1242 1433 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1243 1434 $ hg update --check
1244 1435 getting changed largefiles
1245 1436 1 largefiles updated, 0 removed
1246 1437 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1247 1438
1248 1439 Test removing empty largefiles directories on update
1249 1440 $ test -d sub2 && echo "sub2 exists"
1250 1441 sub2 exists
1251 1442 $ hg update -q null
1252 1443 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1253 1444 [1]
1254 1445 $ hg update -q
1255 1446
1256 1447 Test hg remove removes empty largefiles directories
1257 1448 $ test -d sub2 && echo "sub2 exists"
1258 1449 sub2 exists
1259 1450 $ hg remove sub2/*
1260 1451 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1261 1452 [1]
1262 1453 $ hg revert sub2/large6 sub2/large7
1263 1454
1264 1455 "revert" works on largefiles (and normal files too).
1265 1456 $ echo hack3 >> normal3
1266 1457 $ echo hack4 >> sub/normal4
1267 1458 $ echo hack4 >> sub/large4
1268 1459 $ rm sub2/large6
1269 1460 $ hg revert sub2/large6
1270 1461 $ hg rm sub2/large6
1271 1462 $ echo new >> sub2/large8
1272 1463 $ hg add --large sub2/large8
1273 1464 # XXX we don't really want to report that we're reverting the standin;
1274 1465 # that's just an implementation detail. But I don't see an obvious fix. ;-(
1275 1466 $ hg revert sub
1276 1467 reverting .hglf/sub/large4 (glob)
1277 1468 reverting sub/normal4 (glob)
1278 1469 $ hg status
1279 1470 M normal3
1280 1471 A sub2/large8
1281 1472 R sub2/large6
1282 1473 ? sub/large4.orig
1283 1474 ? sub/normal4.orig
1284 1475 $ cat sub/normal4
1285 1476 normal4-modified
1286 1477 $ cat sub/large4
1287 1478 large4-modified
1288 1479 $ hg revert -a --no-backup
1289 1480 undeleting .hglf/sub2/large6 (glob)
1290 1481 forgetting .hglf/sub2/large8 (glob)
1291 1482 reverting normal3
1292 1483 $ hg status
1293 1484 ? sub/large4.orig
1294 1485 ? sub/normal4.orig
1295 1486 ? sub2/large8
1296 1487 $ cat normal3
1297 1488 normal3-modified
1298 1489 $ cat sub2/large6
1299 1490 large6-modified
1300 1491 $ rm sub/*.orig sub2/large8
1301 1492
1302 1493 revert some files to an older revision
1303 1494 $ hg revert --no-backup -r 8 sub2
1304 1495 reverting .hglf/sub2/large6 (glob)
1305 1496 $ cat sub2/large6
1306 1497 large6
1307 1498 $ hg revert --no-backup -C -r '.^' sub2
1308 1499 reverting .hglf/sub2/large6 (glob)
1309 1500 $ hg revert --no-backup sub2
1310 1501 reverting .hglf/sub2/large6 (glob)
1311 1502 $ hg status
1312 1503
1313 1504 "verify --large" actually verifies largefiles
1314 1505
1315 1506 - Where Do We Come From? What Are We? Where Are We Going?
1316 1507 $ pwd
1317 1508 $TESTTMP/e
1318 1509 $ hg paths
1319 1510 default = $TESTTMP/d (glob)
1320 1511
1321 1512 $ hg verify --large
1322 1513 checking changesets
1323 1514 checking manifests
1324 1515 crosschecking files in changesets and manifests
1325 1516 checking files
1326 1517 10 files, 10 changesets, 28 total revisions
1327 1518 searching 1 changesets for largefiles
1328 1519 verified existence of 3 revisions of 3 largefiles
1329 1520
1330 1521 - introduce missing blob in local store repo and make sure that this is caught:
1331 1522 $ mv $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 .
1332 1523 $ hg verify --large
1333 1524 checking changesets
1334 1525 checking manifests
1335 1526 crosschecking files in changesets and manifests
1336 1527 checking files
1337 1528 10 files, 10 changesets, 28 total revisions
1338 1529 searching 1 changesets for largefiles
1339 1530 changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 (glob)
1340 1531 verified existence of 3 revisions of 3 largefiles
1341 1532 [1]
1342 1533
1343 1534 - introduce corruption and make sure that it is caught when checking content:
1344 1535 $ echo '5 cents' > $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1345 1536 $ hg verify -q --large --lfc
1346 1537 changeset 9:598410d3eb9a: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 (glob)
1347 1538 [1]
1348 1539
1349 1540 - cleanup
1350 1541 $ mv e166e74c7303192238d60af5a9c4ce9bef0b7928 $TESTTMP/d/.hg/largefiles/
1351 1542
1352 1543 - verifying all revisions will fail because we didn't clone all largefiles to d:
1353 1544 $ echo 'T-shirt' > $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1354 1545 $ hg verify -q --lfa --lfc
1355 1546 changeset 0:30d30fe6a5be: large1 references missing $TESTTMP/d/.hg/largefiles/4669e532d5b2c093a78eca010077e708a071bb64 (glob)
1356 1547 changeset 0:30d30fe6a5be: sub/large2 references missing $TESTTMP/d/.hg/largefiles/1deebade43c8c498a3c8daddac0244dc55d1331d (glob)
1357 1548 changeset 1:ce8896473775: large1 references missing $TESTTMP/d/.hg/largefiles/5f78770c0e77ba4287ad6ef3071c9bf9c379742f (glob)
1358 1549 changeset 1:ce8896473775: sub/large2 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1359 1550 changeset 3:9e8fbc4bce62: large1 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1360 1551 changeset 4:74c02385b94c: large3 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1361 1552 changeset 4:74c02385b94c: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1362 1553 changeset 5:9d5af5072dbd: large3 references missing $TESTTMP/d/.hg/largefiles/baaf12afde9d8d67f25dab6dced0d2bf77dba47c (glob)
1363 1554 changeset 5:9d5af5072dbd: sub/large4 references missing $TESTTMP/d/.hg/largefiles/aeb2210d19f02886dde00dac279729a48471e2f9 (glob)
1364 1555 changeset 6:4355d653f84f: large3 references missing $TESTTMP/d/.hg/largefiles/7838695e10da2bb75ac1156565f40a2595fa2fa0 (glob)
1365 1556 [1]
1366 1557
1367 1558 - cleanup
1368 1559 $ rm $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1369 1560 $ rm -f .hglf/sub/*.orig
1370 1561
1371 1562 Update to revision with missing largefile - and make sure it really is missing
1372 1563
1373 1564 $ rm ${USERCACHE}/7838695e10da2bb75ac1156565f40a2595fa2fa0
1374 1565 $ hg up -r 6
1375 1566 getting changed largefiles
1376 1567 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1377 1568 1 largefiles updated, 2 removed
1378 1569 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
1379 1570 $ rm normal3
1380 1571 $ echo >> sub/normal4
1381 1572 $ hg ci -m 'commit with missing files'
1382 1573 Invoking status precommit hook
1383 1574 M sub/normal4
1384 1575 ! large3
1385 1576 ! normal3
1386 1577 created new head
1387 1578 $ hg st
1388 1579 ! large3
1389 1580 ! normal3
1390 1581 $ hg up -r.
1391 1582 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1392 1583 $ hg st
1393 1584 ! large3
1394 1585 ! normal3
1395 1586 $ hg up -Cr.
1396 1587 getting changed largefiles
1397 1588 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1398 1589 0 largefiles updated, 0 removed
1399 1590 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1400 1591 $ hg st
1401 1592 ! large3
1402 1593 $ hg rollback
1403 1594 repository tip rolled back to revision 9 (undo commit)
1404 1595 working directory now based on revision 6
1405 1596
1406 1597 Merge with revision with missing largefile - and make sure it tries to fetch it.
1407 1598
1408 1599 $ hg up -Cqr null
1409 1600 $ echo f > f
1410 1601 $ hg ci -Am branch
1411 1602 adding f
1412 1603 Invoking status precommit hook
1413 1604 A f
1414 1605 created new head
1415 1606 $ hg merge -r 6
1416 1607 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1417 1608 (branch merge, don't forget to commit)
1418 1609 getting changed largefiles
1419 1610 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1420 1611 1 largefiles updated, 0 removed
1421 1612
1422 1613 $ hg rollback -q
1423 1614 $ hg up -Cq
1424 1615
1425 1616 Pulling 0 revisions with --all-largefiles should not fetch for all revisions
1426 1617
1427 1618 $ hg pull --all-largefiles
1428 1619 pulling from $TESTTMP/d (glob)
1429 1620 searching for changes
1430 1621 no changes found
1431 1622
1432 1623 Merging does not revert to old versions of largefiles and also check
1433 1624 that merging after having pulled from a non-default remote works
1434 1625 correctly.
1435 1626
1436 1627 $ cd ..
1437 1628 $ hg clone -r 7 e temp
1438 1629 adding changesets
1439 1630 adding manifests
1440 1631 adding file changes
1441 1632 added 8 changesets with 24 changes to 10 files
1442 1633 updating to branch default
1443 1634 getting changed largefiles
1444 1635 3 largefiles updated, 0 removed
1445 1636 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1446 1637 $ hg clone temp f
1447 1638 updating to branch default
1448 1639 getting changed largefiles
1449 1640 3 largefiles updated, 0 removed
1450 1641 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1451 1642 # Delete the largefiles in the largefiles system cache so that we have an
1452 1643 # opportunity to test that caching after a pull works.
1453 1644 $ rm "${USERCACHE}"/*
1454 1645 $ cd f
1455 1646 $ echo "large4-merge-test" > sub/large4
1456 1647 $ hg commit -m "Modify large4 to test merge"
1457 1648 Invoking status precommit hook
1458 1649 M sub/large4
1459 1650 # Test --cache-largefiles flag
1460 1651 $ hg pull --lfrev 'heads(pulled())' ../e
1461 1652 pulling from ../e
1462 1653 searching for changes
1463 1654 adding changesets
1464 1655 adding manifests
1465 1656 adding file changes
1466 1657 added 2 changesets with 4 changes to 4 files (+1 heads)
1467 1658 (run 'hg heads' to see heads, 'hg merge' to merge)
1468 1659 2 largefiles cached
1469 1660 $ hg merge
1470 1661 largefile sub/large4 has a merge conflict
1471 1662 ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
1472 1663 keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or
1473 1664 take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928? l
1474 1665 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
1475 1666 (branch merge, don't forget to commit)
1476 1667 getting changed largefiles
1477 1668 1 largefiles updated, 0 removed
1478 1669 $ hg commit -m "Merge repos e and f"
1479 1670 Invoking status precommit hook
1480 1671 M normal3
1481 1672 M sub/normal4
1482 1673 M sub2/large6
1483 1674 $ cat normal3
1484 1675 normal3-modified
1485 1676 $ cat sub/normal4
1486 1677 normal4-modified
1487 1678 $ cat sub/large4
1488 1679 large4-merge-test
1489 1680 $ cat sub2/large6
1490 1681 large6-modified
1491 1682 $ cat sub2/large7
1492 1683 large7
1493 1684
1494 1685 Test status after merging with a branch that introduces a new largefile:
1495 1686
1496 1687 $ echo large > large
1497 1688 $ hg add --large large
1498 1689 $ hg commit -m 'add largefile'
1499 1690 Invoking status precommit hook
1500 1691 A large
1501 1692 $ hg update -q ".^"
1502 1693 $ echo change >> normal3
1503 1694 $ hg commit -m 'some change'
1504 1695 Invoking status precommit hook
1505 1696 M normal3
1506 1697 created new head
1507 1698 $ hg merge
1508 1699 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1509 1700 (branch merge, don't forget to commit)
1510 1701 getting changed largefiles
1511 1702 1 largefiles updated, 0 removed
1512 1703 $ hg status
1513 1704 M large
1514 1705
1515 1706 - make sure update of merge with removed largefiles fails as expected
1516 1707 $ hg rm sub2/large6
1517 1708 $ hg up -r.
1518 1709 abort: outstanding uncommitted merges
1519 1710 [255]
1520 1711
1521 1712 - revert should be able to revert files introduced in a pending merge
1522 1713 $ hg revert --all -r .
1523 1714 removing .hglf/large (glob)
1524 1715 undeleting .hglf/sub2/large6 (glob)
1525 1716
1526 1717 Test that a normal file and a largefile with the same name and path cannot
1527 1718 coexist.
1528 1719
1529 1720 $ rm sub2/large7
1530 1721 $ echo "largeasnormal" > sub2/large7
1531 1722 $ hg add sub2/large7
1532 1723 sub2/large7 already a largefile
1533 1724
1534 1725 Test that transplanting a largefile change works correctly.
1535 1726
1536 1727 $ cd ..
1537 1728 $ hg clone -r 8 d g
1538 1729 adding changesets
1539 1730 adding manifests
1540 1731 adding file changes
1541 1732 added 9 changesets with 26 changes to 10 files
1542 1733 updating to branch default
1543 1734 getting changed largefiles
1544 1735 3 largefiles updated, 0 removed
1545 1736 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1546 1737 $ cd g
1547 1738 $ hg transplant -s ../d 598410d3eb9a
1548 1739 searching for changes
1549 1740 searching for changes
1550 1741 adding changesets
1551 1742 adding manifests
1552 1743 adding file changes
1553 1744 added 1 changesets with 2 changes to 2 files
1554 1745 getting changed largefiles
1555 1746 1 largefiles updated, 0 removed
1556 1747 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1557 1748 9:598410d3eb9a modify normal file largefile in repo d
1558 1749 8:a381d2c8c80e modify normal file and largefile in repo b
1559 1750 7:daea875e9014 add/edit more largefiles
1560 1751 6:4355d653f84f edit files yet again
1561 1752 5:9d5af5072dbd edit files again
1562 1753 4:74c02385b94c move files
1563 1754 3:9e8fbc4bce62 copy files
1564 1755 2:51a0ae4d5864 remove files
1565 1756 1:ce8896473775 edit files
1566 1757 0:30d30fe6a5be add files
1567 1758 $ cat normal3
1568 1759 normal3-modified
1569 1760 $ cat sub/normal4
1570 1761 normal4-modified
1571 1762 $ cat sub/large4
1572 1763 large4-modified
1573 1764 $ cat sub2/large6
1574 1765 large6-modified
1575 1766 $ cat sub2/large7
1576 1767 large7
1577 1768
1578 1769 Cat a largefile
1579 1770 $ hg cat normal3
1580 1771 normal3-modified
1581 1772 $ hg cat sub/large4
1582 1773 large4-modified
1583 1774 $ rm "${USERCACHE}"/*
1584 1775 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
1585 1776 $ cat cat.out
1586 1777 large4-modified
1587 1778 $ rm cat.out
1588 1779 $ hg cat -r a381d2c8c80e normal3
1589 1780 normal3-modified
1590 1781 $ hg cat -r '.^' normal3
1591 1782 normal3-modified
1592 1783 $ hg cat -r '.^' sub/large4 doesntexist
1593 1784 large4-modified
1594 1785 doesntexist: no such file in rev a381d2c8c80e
1595 1786 $ hg --cwd sub cat -r '.^' large4
1596 1787 large4-modified
1597 1788 $ hg --cwd sub cat -r '.^' ../normal3
1598 1789 normal3-modified
1599 1790 Cat a standin
1600 1791 $ hg cat .hglf/sub/large4
1601 1792 e166e74c7303192238d60af5a9c4ce9bef0b7928
1602 1793 $ hg cat .hglf/normal3
1603 1794 .hglf/normal3: no such file in rev 598410d3eb9a
1604 1795 [1]
1605 1796
1606 1797 Test that renaming a largefile results in correct output for status
1607 1798
1608 1799 $ hg rename sub/large4 large4-renamed
1609 1800 $ hg commit -m "test rename output"
1610 1801 Invoking status precommit hook
1611 1802 A large4-renamed
1612 1803 R sub/large4
1613 1804 $ cat large4-renamed
1614 1805 large4-modified
1615 1806 $ cd sub2
1616 1807 $ hg rename large6 large6-renamed
1617 1808 $ hg st
1618 1809 A sub2/large6-renamed
1619 1810 R sub2/large6
1620 1811 $ cd ..
1621 1812
1622 1813 Test --normal flag
1623 1814
1624 1815 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
1625 1816 $ hg add --normal --large new-largefile
1626 1817 abort: --normal cannot be used with --large
1627 1818 [255]
1628 1819 $ hg add --normal new-largefile
1629 1820 new-largefile: up to 69 MB of RAM may be required to manage this file
1630 1821 (use 'hg revert new-largefile' to cancel the pending addition)
1631 1822 $ cd ..
1632 1823
1633 1824
1634 1825
@@ -1,359 +1,359 b''
1 1 This file tests the behavior of run-tests.py itself.
2 2
3 3 Smoke test
4 4 ============
5 5
6 6 $ $TESTDIR/run-tests.py
7 7
8 8 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
9 9
10 10 a succesful test
11 11 =======================
12 12
13 13 $ cat > test-success.t << EOF
14 14 > $ echo babar
15 15 > babar
16 16 > $ echo xyzzy
17 17 > xyzzy
18 18 > EOF
19 19
20 20 $ $TESTDIR/run-tests.py --with-hg=`which hg`
21 21 .
22 22 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
23 23
24 24 failing test
25 25 ==================
26 26
27 27 $ cat > test-failure.t << EOF
28 28 > $ echo babar
29 29 > rataxes
30 30 > This is a noop statement so that
31 31 > this test is still more bytes than success.
32 32 > EOF
33 33
34 34 $ $TESTDIR/run-tests.py --with-hg=`which hg`
35 35
36 --- $TESTTMP/test-failure.t
37 +++ $TESTTMP/test-failure.t.err
36 --- $TESTTMP/test-failure.t (glob)
37 +++ $TESTTMP/test-failure.t.err (glob)
38 38 @@ -1,4 +1,4 @@
39 39 $ echo babar
40 40 - rataxes
41 41 + babar
42 42 This is a noop statement so that
43 43 this test is still more bytes than success.
44 44
45 45 ERROR: test-failure.t output changed
46 46 !.
47 47 Failed test-failure.t: output changed
48 48 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
49 49 python hash seed: * (glob)
50 50 [1]
51 51 test --xunit support
52 52 $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
53 53
54 54 --- $TESTTMP/test-failure.t
55 55 +++ $TESTTMP/test-failure.t.err
56 56 @@ -1,4 +1,4 @@
57 57 $ echo babar
58 58 - rataxes
59 59 + babar
60 60 This is a noop statement so that
61 61 this test is still more bytes than success.
62 62
63 63 ERROR: test-failure.t output changed
64 64 !.
65 65 Failed test-failure.t: output changed
66 66 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
67 67 python hash seed: * (glob)
68 68 [1]
69 69 $ cat xunit.xml
70 70 <?xml version="1.0" encoding="utf-8"?>
71 71 <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2">
72 72 <testcase name="test-success.t" time="*"/> (glob)
73 73 <testcase name="test-failure.t" time="*"> (glob)
74 74 <![CDATA[--- $TESTTMP/test-failure.t
75 75 +++ $TESTTMP/test-failure.t.err
76 76 @@ -1,4 +1,4 @@
77 77 $ echo babar
78 78 - rataxes
79 79 + babar
80 80 This is a noop statement so that
81 81 this test is still more bytes than success.
82 82 ]]> </testcase>
83 83 </testsuite>
84 84
85 85 test for --retest
86 86 ====================
87 87
88 88 $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest
89 89
90 --- $TESTTMP/test-failure.t
91 +++ $TESTTMP/test-failure.t.err
90 --- $TESTTMP/test-failure.t (glob)
91 +++ $TESTTMP/test-failure.t.err (glob)
92 92 @@ -1,4 +1,4 @@
93 93 $ echo babar
94 94 - rataxes
95 95 + babar
96 96 This is a noop statement so that
97 97 this test is still more bytes than success.
98 98
99 99 ERROR: test-failure.t output changed
100 100 !
101 101 Failed test-failure.t: output changed
102 102 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
103 103 python hash seed: * (glob)
104 104 [1]
105 105
106 106 Selecting Tests To Run
107 107 ======================
108 108
109 109 successful
110 110
111 111 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t
112 112 .
113 113 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
114 114
115 115 success w/ keyword
116 116 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy
117 117 .
118 118 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
119 119
120 120 failed
121 121
122 122 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t
123 123
124 --- $TESTTMP/test-failure.t
125 +++ $TESTTMP/test-failure.t.err
124 --- $TESTTMP/test-failure.t (glob)
125 +++ $TESTTMP/test-failure.t.err (glob)
126 126 @@ -1,4 +1,4 @@
127 127 $ echo babar
128 128 - rataxes
129 129 + babar
130 130 This is a noop statement so that
131 131 this test is still more bytes than success.
132 132
133 133 ERROR: test-failure.t output changed
134 134 !
135 135 Failed test-failure.t: output changed
136 136 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
137 137 python hash seed: * (glob)
138 138 [1]
139 139
140 140 failure w/ keyword
141 141 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
142 142
143 143 --- $TESTTMP/test-failure.t
144 144 +++ $TESTTMP/test-failure.t.err
145 145 @@ -1,4 +1,4 @@
146 146 $ echo babar
147 147 - rataxes
148 148 + babar
149 149 This is a noop statement so that
150 150 this test is still more bytes than success.
151 151
152 152 ERROR: test-failure.t output changed
153 153 !
154 154 Failed test-failure.t: output changed
155 155 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
156 156 python hash seed: * (glob)
157 157 [1]
158 158
159 159 Running In Debug Mode
160 160 ======================
161 161
162 162 $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd
163 163 + echo SALT* 0 0 (glob)
164 164 SALT* 0 0 (glob)
165 165 + echo babar
166 166 babar
167 167 + echo SALT* 4 0 (glob)
168 168 SALT* 4 0 (glob)
169 169 .+ echo SALT* 0 0 (glob)
170 170 SALT* 0 0 (glob)
171 171 + echo babar
172 172 babar
173 173 + echo SALT* 2 0 (glob)
174 174 SALT* 2 0 (glob)
175 175 + echo xyzzy
176 176 xyzzy
177 177 + echo SALT* 4 0 (glob)
178 178 SALT* 4 0 (glob)
179 179 .
180 180 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
181 181
182 182 Parallel runs
183 183 ==============
184 184
185 185 (duplicate the failing test to get predictable output)
186 186 $ cp test-failure.t test-failure-copy.t
187 187
188 188 $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t
189 189
190 190 --- $TESTTMP/test-failure*.t (glob)
191 191 +++ $TESTTMP/test-failure*.t.err (glob)
192 192 @@ -1,4 +1,4 @@
193 193 $ echo babar
194 194 - rataxes
195 195 + babar
196 196 This is a noop statement so that
197 197 this test is still more bytes than success.
198 198
199 199 ERROR: test-failure*.t output changed (glob)
200 200 !
201 201 --- $TESTTMP/test-failure*.t (glob)
202 202 +++ $TESTTMP/test-failure*.t.err (glob)
203 203 @@ -1,4 +1,4 @@
204 204 $ echo babar
205 205 - rataxes
206 206 + babar
207 207 This is a noop statement so that
208 208 this test is still more bytes than success.
209 209
210 210 ERROR: test-failure*.t output changed (glob)
211 211 !
212 212 Failed test-failure*.t: output changed (glob)
213 213 Failed test-failure*.t: output changed (glob)
214 214 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
215 215 python hash seed: * (glob)
216 216 [1]
217 217
218 218 (delete the duplicated test file)
219 219 $ rm test-failure-copy.t
220 220
221 221
222 222 Interactive run
223 223 ===============
224 224
225 225 (backup the failing test)
226 226 $ cp test-failure.t backup
227 227
228 228 Refuse the fix
229 229
230 230 $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
231 231
232 232 --- $TESTTMP/test-failure.t
233 233 +++ $TESTTMP/test-failure.t.err
234 234 @@ -1,4 +1,4 @@
235 235 $ echo babar
236 236 - rataxes
237 237 + babar
238 238 This is a noop statement so that
239 239 this test is still more bytes than success.
240 240 Accept this change? [n]
241 241 ERROR: test-failure.t output changed
242 242 !.
243 243 Failed test-failure.t: output changed
244 244 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
245 245 python hash seed: * (glob)
246 246 [1]
247 247
248 248 $ cat test-failure.t
249 249 $ echo babar
250 250 rataxes
251 251 This is a noop statement so that
252 252 this test is still more bytes than success.
253 253
254 254 View the fix
255 255
256 256 $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` --view echo
257 257 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
258 258
259 259 ERROR: test-failure.t output changed
260 260 !.
261 261 Failed test-failure.t: output changed
262 262 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
263 263 python hash seed: * (glob)
264 264 [1]
265 265
266 266 Accept the fix
267 267
268 268 $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
269 269
270 270 --- $TESTTMP/test-failure.t
271 271 +++ $TESTTMP/test-failure.t.err
272 272 @@ -1,4 +1,4 @@
273 273 $ echo babar
274 274 - rataxes
275 275 + babar
276 276 This is a noop statement so that
277 277 this test is still more bytes than success.
278 278 Accept this change? [n] ..
279 279 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
280 280
281 281 $ cat test-failure.t
282 282 $ echo babar
283 283 babar
284 284 This is a noop statement so that
285 285 this test is still more bytes than success.
286 286
287 287 (reinstall)
288 288 $ mv backup test-failure.t
289 289
290 290 No Diff
291 291 ===============
292 292
293 293 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
294 294 !.
295 295 Failed test-failure.t: output changed
296 296 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
297 297 python hash seed: * (glob)
298 298 [1]
299 299
300 300 test for --time
301 301 ==================
302 302
303 303 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time
304 304 .
305 305 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
306 306 # Producing time report
307 307 cuser csys real Test
308 308 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
309 309
310 310 test for --time with --job enabled
311 311 ====================================
312 312
313 313 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
314 314 .
315 315 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
316 316 # Producing time report
317 317 cuser csys real Test
318 318 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
319 319
320 320 Skips
321 321 ================
322 322 $ cat > test-skip.t <<EOF
323 323 > $ echo xyzzy
324 324 > #require false
325 325 > EOF
326 326 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
327 327 !.s
328 328 Skipped test-skip.t: irrelevant
329 329 Failed test-failure.t: output changed
330 330 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
331 331 python hash seed: * (glob)
332 332 [1]
333 333
334 334 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy
335 335 .s
336 336 Skipped test-skip.t: irrelevant
337 337 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
338 338
339 339 Skips with xml
340 340 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \
341 341 > --xunit=xunit.xml
342 342 .s
343 343 Skipped test-skip.t: irrelevant
344 344 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
345 345 $ cat xunit.xml
346 346 <?xml version="1.0" encoding="utf-8"?>
347 347 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
348 348 <testcase name="test-success.t" time="*"/> (glob)
349 349 </testsuite>
350 350
351 351 Missing skips or blacklisted skips don't count as executed:
352 352 $ echo test-failure.t > blacklist
353 353 $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \
354 354 > test-failure.t test-bogus.t
355 355 ss
356 356 Skipped test-bogus.t: Doesn't exist
357 357 Skipped test-failure.t: blacklisted
358 358 # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
359 359
General Comments 0
You need to be logged in to leave comments. Login now