##// END OF EJS Templates
amend: add noise in extra to avoid creating obsolescence cycle (issue3664)...
Pierre-Yves David -
r17811:a8aba292 default
parent child Browse files
Show More
@@ -1,1981 +1,1998 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 subrepo, context, repair, bookmarks, graphmod, revset, phases, obsolete
14 import changelog
14 15 import lock as lockmod
15 16
16 17 def parsealiases(cmd):
17 18 return cmd.lstrip("^").split("|")
18 19
19 20 def findpossible(cmd, table, strict=False):
20 21 """
21 22 Return cmd -> (aliases, command table entry)
22 23 for each matching command.
23 24 Return debug commands (or their aliases) only if no normal command matches.
24 25 """
25 26 choice = {}
26 27 debugchoice = {}
27 28
28 29 if cmd in table:
29 30 # short-circuit exact matches, "log" alias beats "^log|history"
30 31 keys = [cmd]
31 32 else:
32 33 keys = table.keys()
33 34
34 35 for e in keys:
35 36 aliases = parsealiases(e)
36 37 found = None
37 38 if cmd in aliases:
38 39 found = cmd
39 40 elif not strict:
40 41 for a in aliases:
41 42 if a.startswith(cmd):
42 43 found = a
43 44 break
44 45 if found is not None:
45 46 if aliases[0].startswith("debug") or found.startswith("debug"):
46 47 debugchoice[found] = (aliases, table[e])
47 48 else:
48 49 choice[found] = (aliases, table[e])
49 50
50 51 if not choice and debugchoice:
51 52 choice = debugchoice
52 53
53 54 return choice
54 55
55 56 def findcmd(cmd, table, strict=True):
56 57 """Return (aliases, command table entry) for command string."""
57 58 choice = findpossible(cmd, table, strict)
58 59
59 60 if cmd in choice:
60 61 return choice[cmd]
61 62
62 63 if len(choice) > 1:
63 64 clist = choice.keys()
64 65 clist.sort()
65 66 raise error.AmbiguousCommand(cmd, clist)
66 67
67 68 if choice:
68 69 return choice.values()[0]
69 70
70 71 raise error.UnknownCommand(cmd)
71 72
72 73 def findrepo(p):
73 74 while not os.path.isdir(os.path.join(p, ".hg")):
74 75 oldp, p = p, os.path.dirname(p)
75 76 if p == oldp:
76 77 return None
77 78
78 79 return p
79 80
80 81 def bailifchanged(repo):
81 82 if repo.dirstate.p2() != nullid:
82 83 raise util.Abort(_('outstanding uncommitted merge'))
83 84 modified, added, removed, deleted = repo.status()[:4]
84 85 if modified or added or removed or deleted:
85 86 raise util.Abort(_("outstanding uncommitted changes"))
86 87 ctx = repo[None]
87 88 for s in ctx.substate:
88 89 if ctx.sub(s).dirty():
89 90 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
90 91
91 92 def logmessage(ui, opts):
92 93 """ get the log message according to -m and -l option """
93 94 message = opts.get('message')
94 95 logfile = opts.get('logfile')
95 96
96 97 if message and logfile:
97 98 raise util.Abort(_('options --message and --logfile are mutually '
98 99 'exclusive'))
99 100 if not message and logfile:
100 101 try:
101 102 if logfile == '-':
102 103 message = ui.fin.read()
103 104 else:
104 105 message = '\n'.join(util.readfile(logfile).splitlines())
105 106 except IOError, inst:
106 107 raise util.Abort(_("can't read commit message '%s': %s") %
107 108 (logfile, inst.strerror))
108 109 return message
109 110
110 111 def loglimit(opts):
111 112 """get the log limit according to option -l/--limit"""
112 113 limit = opts.get('limit')
113 114 if limit:
114 115 try:
115 116 limit = int(limit)
116 117 except ValueError:
117 118 raise util.Abort(_('limit must be a positive integer'))
118 119 if limit <= 0:
119 120 raise util.Abort(_('limit must be positive'))
120 121 else:
121 122 limit = None
122 123 return limit
123 124
124 125 def makefilename(repo, pat, node, desc=None,
125 126 total=None, seqno=None, revwidth=None, pathname=None):
126 127 node_expander = {
127 128 'H': lambda: hex(node),
128 129 'R': lambda: str(repo.changelog.rev(node)),
129 130 'h': lambda: short(node),
130 131 'm': lambda: re.sub('[^\w]', '_', str(desc))
131 132 }
132 133 expander = {
133 134 '%': lambda: '%',
134 135 'b': lambda: os.path.basename(repo.root),
135 136 }
136 137
137 138 try:
138 139 if node:
139 140 expander.update(node_expander)
140 141 if node:
141 142 expander['r'] = (lambda:
142 143 str(repo.changelog.rev(node)).zfill(revwidth or 0))
143 144 if total is not None:
144 145 expander['N'] = lambda: str(total)
145 146 if seqno is not None:
146 147 expander['n'] = lambda: str(seqno)
147 148 if total is not None and seqno is not None:
148 149 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
149 150 if pathname is not None:
150 151 expander['s'] = lambda: os.path.basename(pathname)
151 152 expander['d'] = lambda: os.path.dirname(pathname) or '.'
152 153 expander['p'] = lambda: pathname
153 154
154 155 newname = []
155 156 patlen = len(pat)
156 157 i = 0
157 158 while i < patlen:
158 159 c = pat[i]
159 160 if c == '%':
160 161 i += 1
161 162 c = pat[i]
162 163 c = expander[c]()
163 164 newname.append(c)
164 165 i += 1
165 166 return ''.join(newname)
166 167 except KeyError, inst:
167 168 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
168 169 inst.args[0])
169 170
170 171 def makefileobj(repo, pat, node=None, desc=None, total=None,
171 172 seqno=None, revwidth=None, mode='wb', pathname=None):
172 173
173 174 writable = mode not in ('r', 'rb')
174 175
175 176 if not pat or pat == '-':
176 177 fp = writable and repo.ui.fout or repo.ui.fin
177 178 if util.safehasattr(fp, 'fileno'):
178 179 return os.fdopen(os.dup(fp.fileno()), mode)
179 180 else:
180 181 # if this fp can't be duped properly, return
181 182 # a dummy object that can be closed
182 183 class wrappedfileobj(object):
183 184 noop = lambda x: None
184 185 def __init__(self, f):
185 186 self.f = f
186 187 def __getattr__(self, attr):
187 188 if attr == 'close':
188 189 return self.noop
189 190 else:
190 191 return getattr(self.f, attr)
191 192
192 193 return wrappedfileobj(fp)
193 194 if util.safehasattr(pat, 'write') and writable:
194 195 return pat
195 196 if util.safehasattr(pat, 'read') and 'r' in mode:
196 197 return pat
197 198 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
198 199 pathname),
199 200 mode)
200 201
201 202 def openrevlog(repo, cmd, file_, opts):
202 203 """opens the changelog, manifest, a filelog or a given revlog"""
203 204 cl = opts['changelog']
204 205 mf = opts['manifest']
205 206 msg = None
206 207 if cl and mf:
207 208 msg = _('cannot specify --changelog and --manifest at the same time')
208 209 elif cl or mf:
209 210 if file_:
210 211 msg = _('cannot specify filename with --changelog or --manifest')
211 212 elif not repo:
212 213 msg = _('cannot specify --changelog or --manifest '
213 214 'without a repository')
214 215 if msg:
215 216 raise util.Abort(msg)
216 217
217 218 r = None
218 219 if repo:
219 220 if cl:
220 221 r = repo.changelog
221 222 elif mf:
222 223 r = repo.manifest
223 224 elif file_:
224 225 filelog = repo.file(file_)
225 226 if len(filelog):
226 227 r = filelog
227 228 if not r:
228 229 if not file_:
229 230 raise error.CommandError(cmd, _('invalid arguments'))
230 231 if not os.path.isfile(file_):
231 232 raise util.Abort(_("revlog '%s' not found") % file_)
232 233 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
233 234 file_[:-2] + ".i")
234 235 return r
235 236
236 237 def copy(ui, repo, pats, opts, rename=False):
237 238 # called with the repo lock held
238 239 #
239 240 # hgsep => pathname that uses "/" to separate directories
240 241 # ossep => pathname that uses os.sep to separate directories
241 242 cwd = repo.getcwd()
242 243 targets = {}
243 244 after = opts.get("after")
244 245 dryrun = opts.get("dry_run")
245 246 wctx = repo[None]
246 247
247 248 def walkpat(pat):
248 249 srcs = []
249 250 badstates = after and '?' or '?r'
250 251 m = scmutil.match(repo[None], [pat], opts, globbed=True)
251 252 for abs in repo.walk(m):
252 253 state = repo.dirstate[abs]
253 254 rel = m.rel(abs)
254 255 exact = m.exact(abs)
255 256 if state in badstates:
256 257 if exact and state == '?':
257 258 ui.warn(_('%s: not copying - file is not managed\n') % rel)
258 259 if exact and state == 'r':
259 260 ui.warn(_('%s: not copying - file has been marked for'
260 261 ' remove\n') % rel)
261 262 continue
262 263 # abs: hgsep
263 264 # rel: ossep
264 265 srcs.append((abs, rel, exact))
265 266 return srcs
266 267
267 268 # abssrc: hgsep
268 269 # relsrc: ossep
269 270 # otarget: ossep
270 271 def copyfile(abssrc, relsrc, otarget, exact):
271 272 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
272 273 if '/' in abstarget:
273 274 # We cannot normalize abstarget itself, this would prevent
274 275 # case only renames, like a => A.
275 276 abspath, absname = abstarget.rsplit('/', 1)
276 277 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
277 278 reltarget = repo.pathto(abstarget, cwd)
278 279 target = repo.wjoin(abstarget)
279 280 src = repo.wjoin(abssrc)
280 281 state = repo.dirstate[abstarget]
281 282
282 283 scmutil.checkportable(ui, abstarget)
283 284
284 285 # check for collisions
285 286 prevsrc = targets.get(abstarget)
286 287 if prevsrc is not None:
287 288 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
288 289 (reltarget, repo.pathto(abssrc, cwd),
289 290 repo.pathto(prevsrc, cwd)))
290 291 return
291 292
292 293 # check for overwrites
293 294 exists = os.path.lexists(target)
294 295 samefile = False
295 296 if exists and abssrc != abstarget:
296 297 if (repo.dirstate.normalize(abssrc) ==
297 298 repo.dirstate.normalize(abstarget)):
298 299 if not rename:
299 300 ui.warn(_("%s: can't copy - same file\n") % reltarget)
300 301 return
301 302 exists = False
302 303 samefile = True
303 304
304 305 if not after and exists or after and state in 'mn':
305 306 if not opts['force']:
306 307 ui.warn(_('%s: not overwriting - file exists\n') %
307 308 reltarget)
308 309 return
309 310
310 311 if after:
311 312 if not exists:
312 313 if rename:
313 314 ui.warn(_('%s: not recording move - %s does not exist\n') %
314 315 (relsrc, reltarget))
315 316 else:
316 317 ui.warn(_('%s: not recording copy - %s does not exist\n') %
317 318 (relsrc, reltarget))
318 319 return
319 320 elif not dryrun:
320 321 try:
321 322 if exists:
322 323 os.unlink(target)
323 324 targetdir = os.path.dirname(target) or '.'
324 325 if not os.path.isdir(targetdir):
325 326 os.makedirs(targetdir)
326 327 if samefile:
327 328 tmp = target + "~hgrename"
328 329 os.rename(src, tmp)
329 330 os.rename(tmp, target)
330 331 else:
331 332 util.copyfile(src, target)
332 333 srcexists = True
333 334 except IOError, inst:
334 335 if inst.errno == errno.ENOENT:
335 336 ui.warn(_('%s: deleted in working copy\n') % relsrc)
336 337 srcexists = False
337 338 else:
338 339 ui.warn(_('%s: cannot copy - %s\n') %
339 340 (relsrc, inst.strerror))
340 341 return True # report a failure
341 342
342 343 if ui.verbose or not exact:
343 344 if rename:
344 345 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
345 346 else:
346 347 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
347 348
348 349 targets[abstarget] = abssrc
349 350
350 351 # fix up dirstate
351 352 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
352 353 dryrun=dryrun, cwd=cwd)
353 354 if rename and not dryrun:
354 355 if not after and srcexists and not samefile:
355 356 util.unlinkpath(repo.wjoin(abssrc))
356 357 wctx.forget([abssrc])
357 358
358 359 # pat: ossep
359 360 # dest ossep
360 361 # srcs: list of (hgsep, hgsep, ossep, bool)
361 362 # return: function that takes hgsep and returns ossep
362 363 def targetpathfn(pat, dest, srcs):
363 364 if os.path.isdir(pat):
364 365 abspfx = scmutil.canonpath(repo.root, cwd, pat)
365 366 abspfx = util.localpath(abspfx)
366 367 if destdirexists:
367 368 striplen = len(os.path.split(abspfx)[0])
368 369 else:
369 370 striplen = len(abspfx)
370 371 if striplen:
371 372 striplen += len(os.sep)
372 373 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
373 374 elif destdirexists:
374 375 res = lambda p: os.path.join(dest,
375 376 os.path.basename(util.localpath(p)))
376 377 else:
377 378 res = lambda p: dest
378 379 return res
379 380
380 381 # pat: ossep
381 382 # dest ossep
382 383 # srcs: list of (hgsep, hgsep, ossep, bool)
383 384 # return: function that takes hgsep and returns ossep
384 385 def targetpathafterfn(pat, dest, srcs):
385 386 if matchmod.patkind(pat):
386 387 # a mercurial pattern
387 388 res = lambda p: os.path.join(dest,
388 389 os.path.basename(util.localpath(p)))
389 390 else:
390 391 abspfx = scmutil.canonpath(repo.root, cwd, pat)
391 392 if len(abspfx) < len(srcs[0][0]):
392 393 # A directory. Either the target path contains the last
393 394 # component of the source path or it does not.
394 395 def evalpath(striplen):
395 396 score = 0
396 397 for s in srcs:
397 398 t = os.path.join(dest, util.localpath(s[0])[striplen:])
398 399 if os.path.lexists(t):
399 400 score += 1
400 401 return score
401 402
402 403 abspfx = util.localpath(abspfx)
403 404 striplen = len(abspfx)
404 405 if striplen:
405 406 striplen += len(os.sep)
406 407 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
407 408 score = evalpath(striplen)
408 409 striplen1 = len(os.path.split(abspfx)[0])
409 410 if striplen1:
410 411 striplen1 += len(os.sep)
411 412 if evalpath(striplen1) > score:
412 413 striplen = striplen1
413 414 res = lambda p: os.path.join(dest,
414 415 util.localpath(p)[striplen:])
415 416 else:
416 417 # a file
417 418 if destdirexists:
418 419 res = lambda p: os.path.join(dest,
419 420 os.path.basename(util.localpath(p)))
420 421 else:
421 422 res = lambda p: dest
422 423 return res
423 424
424 425
425 426 pats = scmutil.expandpats(pats)
426 427 if not pats:
427 428 raise util.Abort(_('no source or destination specified'))
428 429 if len(pats) == 1:
429 430 raise util.Abort(_('no destination specified'))
430 431 dest = pats.pop()
431 432 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
432 433 if not destdirexists:
433 434 if len(pats) > 1 or matchmod.patkind(pats[0]):
434 435 raise util.Abort(_('with multiple sources, destination must be an '
435 436 'existing directory'))
436 437 if util.endswithsep(dest):
437 438 raise util.Abort(_('destination %s is not a directory') % dest)
438 439
439 440 tfn = targetpathfn
440 441 if after:
441 442 tfn = targetpathafterfn
442 443 copylist = []
443 444 for pat in pats:
444 445 srcs = walkpat(pat)
445 446 if not srcs:
446 447 continue
447 448 copylist.append((tfn(pat, dest, srcs), srcs))
448 449 if not copylist:
449 450 raise util.Abort(_('no files to copy'))
450 451
451 452 errors = 0
452 453 for targetpath, srcs in copylist:
453 454 for abssrc, relsrc, exact in srcs:
454 455 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
455 456 errors += 1
456 457
457 458 if errors:
458 459 ui.warn(_('(consider using --after)\n'))
459 460
460 461 return errors != 0
461 462
462 463 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
463 464 runargs=None, appendpid=False):
464 465 '''Run a command as a service.'''
465 466
466 467 if opts['daemon'] and not opts['daemon_pipefds']:
467 468 # Signal child process startup with file removal
468 469 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
469 470 os.close(lockfd)
470 471 try:
471 472 if not runargs:
472 473 runargs = util.hgcmd() + sys.argv[1:]
473 474 runargs.append('--daemon-pipefds=%s' % lockpath)
474 475 # Don't pass --cwd to the child process, because we've already
475 476 # changed directory.
476 477 for i in xrange(1, len(runargs)):
477 478 if runargs[i].startswith('--cwd='):
478 479 del runargs[i]
479 480 break
480 481 elif runargs[i].startswith('--cwd'):
481 482 del runargs[i:i + 2]
482 483 break
483 484 def condfn():
484 485 return not os.path.exists(lockpath)
485 486 pid = util.rundetached(runargs, condfn)
486 487 if pid < 0:
487 488 raise util.Abort(_('child process failed to start'))
488 489 finally:
489 490 try:
490 491 os.unlink(lockpath)
491 492 except OSError, e:
492 493 if e.errno != errno.ENOENT:
493 494 raise
494 495 if parentfn:
495 496 return parentfn(pid)
496 497 else:
497 498 return
498 499
499 500 if initfn:
500 501 initfn()
501 502
502 503 if opts['pid_file']:
503 504 mode = appendpid and 'a' or 'w'
504 505 fp = open(opts['pid_file'], mode)
505 506 fp.write(str(os.getpid()) + '\n')
506 507 fp.close()
507 508
508 509 if opts['daemon_pipefds']:
509 510 lockpath = opts['daemon_pipefds']
510 511 try:
511 512 os.setsid()
512 513 except AttributeError:
513 514 pass
514 515 os.unlink(lockpath)
515 516 util.hidewindow()
516 517 sys.stdout.flush()
517 518 sys.stderr.flush()
518 519
519 520 nullfd = os.open(os.devnull, os.O_RDWR)
520 521 logfilefd = nullfd
521 522 if logfile:
522 523 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
523 524 os.dup2(nullfd, 0)
524 525 os.dup2(logfilefd, 1)
525 526 os.dup2(logfilefd, 2)
526 527 if nullfd not in (0, 1, 2):
527 528 os.close(nullfd)
528 529 if logfile and logfilefd not in (0, 1, 2):
529 530 os.close(logfilefd)
530 531
531 532 if runfn:
532 533 return runfn()
533 534
534 535 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
535 536 opts=None):
536 537 '''export changesets as hg patches.'''
537 538
538 539 total = len(revs)
539 540 revwidth = max([len(str(rev)) for rev in revs])
540 541
541 542 def single(rev, seqno, fp):
542 543 ctx = repo[rev]
543 544 node = ctx.node()
544 545 parents = [p.node() for p in ctx.parents() if p]
545 546 branch = ctx.branch()
546 547 if switch_parent:
547 548 parents.reverse()
548 549 prev = (parents and parents[0]) or nullid
549 550
550 551 shouldclose = False
551 552 if not fp and len(template) > 0:
552 553 desc_lines = ctx.description().rstrip().split('\n')
553 554 desc = desc_lines[0] #Commit always has a first line.
554 555 fp = makefileobj(repo, template, node, desc=desc, total=total,
555 556 seqno=seqno, revwidth=revwidth, mode='ab')
556 557 if fp != template:
557 558 shouldclose = True
558 559 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
559 560 repo.ui.note("%s\n" % fp.name)
560 561
561 562 if not fp:
562 563 write = repo.ui.write
563 564 else:
564 565 def write(s, **kw):
565 566 fp.write(s)
566 567
567 568
568 569 write("# HG changeset patch\n")
569 570 write("# User %s\n" % ctx.user())
570 571 write("# Date %d %d\n" % ctx.date())
571 572 if branch and branch != 'default':
572 573 write("# Branch %s\n" % branch)
573 574 write("# Node ID %s\n" % hex(node))
574 575 write("# Parent %s\n" % hex(prev))
575 576 if len(parents) > 1:
576 577 write("# Parent %s\n" % hex(parents[1]))
577 578 write(ctx.description().rstrip())
578 579 write("\n\n")
579 580
580 581 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
581 582 write(chunk, label=label)
582 583
583 584 if shouldclose:
584 585 fp.close()
585 586
586 587 for seqno, rev in enumerate(revs):
587 588 single(rev, seqno + 1, fp)
588 589
589 590 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
590 591 changes=None, stat=False, fp=None, prefix='',
591 592 listsubrepos=False):
592 593 '''show diff or diffstat.'''
593 594 if fp is None:
594 595 write = ui.write
595 596 else:
596 597 def write(s, **kw):
597 598 fp.write(s)
598 599
599 600 if stat:
600 601 diffopts = diffopts.copy(context=0)
601 602 width = 80
602 603 if not ui.plain():
603 604 width = ui.termwidth()
604 605 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
605 606 prefix=prefix)
606 607 for chunk, label in patch.diffstatui(util.iterlines(chunks),
607 608 width=width,
608 609 git=diffopts.git):
609 610 write(chunk, label=label)
610 611 else:
611 612 for chunk, label in patch.diffui(repo, node1, node2, match,
612 613 changes, diffopts, prefix=prefix):
613 614 write(chunk, label=label)
614 615
615 616 if listsubrepos:
616 617 ctx1 = repo[node1]
617 618 ctx2 = repo[node2]
618 619 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
619 620 tempnode2 = node2
620 621 try:
621 622 if node2 is not None:
622 623 tempnode2 = ctx2.substate[subpath][1]
623 624 except KeyError:
624 625 # A subrepo that existed in node1 was deleted between node1 and
625 626 # node2 (inclusive). Thus, ctx2's substate won't contain that
626 627 # subpath. The best we can do is to ignore it.
627 628 tempnode2 = None
628 629 submatch = matchmod.narrowmatcher(subpath, match)
629 630 sub.diff(diffopts, tempnode2, submatch, changes=changes,
630 631 stat=stat, fp=fp, prefix=prefix)
631 632
632 633 class changeset_printer(object):
633 634 '''show changeset information when templating not requested.'''
634 635
635 636 def __init__(self, ui, repo, patch, diffopts, buffered):
636 637 self.ui = ui
637 638 self.repo = repo
638 639 self.buffered = buffered
639 640 self.patch = patch
640 641 self.diffopts = diffopts
641 642 self.header = {}
642 643 self.hunk = {}
643 644 self.lastheader = None
644 645 self.footer = None
645 646
646 647 def flush(self, rev):
647 648 if rev in self.header:
648 649 h = self.header[rev]
649 650 if h != self.lastheader:
650 651 self.lastheader = h
651 652 self.ui.write(h)
652 653 del self.header[rev]
653 654 if rev in self.hunk:
654 655 self.ui.write(self.hunk[rev])
655 656 del self.hunk[rev]
656 657 return 1
657 658 return 0
658 659
659 660 def close(self):
660 661 if self.footer:
661 662 self.ui.write(self.footer)
662 663
663 664 def show(self, ctx, copies=None, matchfn=None, **props):
664 665 if self.buffered:
665 666 self.ui.pushbuffer()
666 667 self._show(ctx, copies, matchfn, props)
667 668 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
668 669 else:
669 670 self._show(ctx, copies, matchfn, props)
670 671
671 672 def _show(self, ctx, copies, matchfn, props):
672 673 '''show a single changeset or file revision'''
673 674 changenode = ctx.node()
674 675 rev = ctx.rev()
675 676
676 677 if self.ui.quiet:
677 678 self.ui.write("%d:%s\n" % (rev, short(changenode)),
678 679 label='log.node')
679 680 return
680 681
681 682 log = self.repo.changelog
682 683 date = util.datestr(ctx.date())
683 684
684 685 hexfunc = self.ui.debugflag and hex or short
685 686
686 687 parents = [(p, hexfunc(log.node(p)))
687 688 for p in self._meaningful_parentrevs(log, rev)]
688 689
689 690 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
690 691 label='log.changeset changeset.%s' % ctx.phasestr())
691 692
692 693 branch = ctx.branch()
693 694 # don't show the default branch name
694 695 if branch != 'default':
695 696 self.ui.write(_("branch: %s\n") % branch,
696 697 label='log.branch')
697 698 for bookmark in self.repo.nodebookmarks(changenode):
698 699 self.ui.write(_("bookmark: %s\n") % bookmark,
699 700 label='log.bookmark')
700 701 for tag in self.repo.nodetags(changenode):
701 702 self.ui.write(_("tag: %s\n") % tag,
702 703 label='log.tag')
703 704 if self.ui.debugflag and ctx.phase():
704 705 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
705 706 label='log.phase')
706 707 for parent in parents:
707 708 self.ui.write(_("parent: %d:%s\n") % parent,
708 709 label='log.parent changeset.%s' % ctx.phasestr())
709 710
710 711 if self.ui.debugflag:
711 712 mnode = ctx.manifestnode()
712 713 self.ui.write(_("manifest: %d:%s\n") %
713 714 (self.repo.manifest.rev(mnode), hex(mnode)),
714 715 label='ui.debug log.manifest')
715 716 self.ui.write(_("user: %s\n") % ctx.user(),
716 717 label='log.user')
717 718 self.ui.write(_("date: %s\n") % date,
718 719 label='log.date')
719 720
720 721 if self.ui.debugflag:
721 722 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
722 723 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
723 724 files):
724 725 if value:
725 726 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
726 727 label='ui.debug log.files')
727 728 elif ctx.files() and self.ui.verbose:
728 729 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
729 730 label='ui.note log.files')
730 731 if copies and self.ui.verbose:
731 732 copies = ['%s (%s)' % c for c in copies]
732 733 self.ui.write(_("copies: %s\n") % ' '.join(copies),
733 734 label='ui.note log.copies')
734 735
735 736 extra = ctx.extra()
736 737 if extra and self.ui.debugflag:
737 738 for key, value in sorted(extra.items()):
738 739 self.ui.write(_("extra: %s=%s\n")
739 740 % (key, value.encode('string_escape')),
740 741 label='ui.debug log.extra')
741 742
742 743 description = ctx.description().strip()
743 744 if description:
744 745 if self.ui.verbose:
745 746 self.ui.write(_("description:\n"),
746 747 label='ui.note log.description')
747 748 self.ui.write(description,
748 749 label='ui.note log.description')
749 750 self.ui.write("\n\n")
750 751 else:
751 752 self.ui.write(_("summary: %s\n") %
752 753 description.splitlines()[0],
753 754 label='log.summary')
754 755 self.ui.write("\n")
755 756
756 757 self.showpatch(changenode, matchfn)
757 758
758 759 def showpatch(self, node, matchfn):
759 760 if not matchfn:
760 761 matchfn = self.patch
761 762 if matchfn:
762 763 stat = self.diffopts.get('stat')
763 764 diff = self.diffopts.get('patch')
764 765 diffopts = patch.diffopts(self.ui, self.diffopts)
765 766 prev = self.repo.changelog.parents(node)[0]
766 767 if stat:
767 768 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
768 769 match=matchfn, stat=True)
769 770 if diff:
770 771 if stat:
771 772 self.ui.write("\n")
772 773 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
773 774 match=matchfn, stat=False)
774 775 self.ui.write("\n")
775 776
776 777 def _meaningful_parentrevs(self, log, rev):
777 778 """Return list of meaningful (or all if debug) parentrevs for rev.
778 779
779 780 For merges (two non-nullrev revisions) both parents are meaningful.
780 781 Otherwise the first parent revision is considered meaningful if it
781 782 is not the preceding revision.
782 783 """
783 784 parents = log.parentrevs(rev)
784 785 if not self.ui.debugflag and parents[1] == nullrev:
785 786 if parents[0] >= rev - 1:
786 787 parents = []
787 788 else:
788 789 parents = [parents[0]]
789 790 return parents
790 791
791 792
792 793 class changeset_templater(changeset_printer):
793 794 '''format changeset information.'''
794 795
795 796 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
796 797 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
797 798 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
798 799 defaulttempl = {
799 800 'parent': '{rev}:{node|formatnode} ',
800 801 'manifest': '{rev}:{node|formatnode}',
801 802 'file_copy': '{name} ({source})',
802 803 'extra': '{key}={value|stringescape}'
803 804 }
804 805 # filecopy is preserved for compatibility reasons
805 806 defaulttempl['filecopy'] = defaulttempl['file_copy']
806 807 self.t = templater.templater(mapfile, {'formatnode': formatnode},
807 808 cache=defaulttempl)
808 809 self.cache = {}
809 810
810 811 def use_template(self, t):
811 812 '''set template string to use'''
812 813 self.t.cache['changeset'] = t
813 814
814 815 def _meaningful_parentrevs(self, ctx):
815 816 """Return list of meaningful (or all if debug) parentrevs for rev.
816 817 """
817 818 parents = ctx.parents()
818 819 if len(parents) > 1:
819 820 return parents
820 821 if self.ui.debugflag:
821 822 return [parents[0], self.repo['null']]
822 823 if parents[0].rev() >= ctx.rev() - 1:
823 824 return []
824 825 return parents
825 826
826 827 def _show(self, ctx, copies, matchfn, props):
827 828 '''show a single changeset or file revision'''
828 829
829 830 showlist = templatekw.showlist
830 831
831 832 # showparents() behaviour depends on ui trace level which
832 833 # causes unexpected behaviours at templating level and makes
833 834 # it harder to extract it in a standalone function. Its
834 835 # behaviour cannot be changed so leave it here for now.
835 836 def showparents(**args):
836 837 ctx = args['ctx']
837 838 parents = [[('rev', p.rev()), ('node', p.hex())]
838 839 for p in self._meaningful_parentrevs(ctx)]
839 840 return showlist('parent', parents, **args)
840 841
841 842 props = props.copy()
842 843 props.update(templatekw.keywords)
843 844 props['parents'] = showparents
844 845 props['templ'] = self.t
845 846 props['ctx'] = ctx
846 847 props['repo'] = self.repo
847 848 props['revcache'] = {'copies': copies}
848 849 props['cache'] = self.cache
849 850
850 851 # find correct templates for current mode
851 852
852 853 tmplmodes = [
853 854 (True, None),
854 855 (self.ui.verbose, 'verbose'),
855 856 (self.ui.quiet, 'quiet'),
856 857 (self.ui.debugflag, 'debug'),
857 858 ]
858 859
859 860 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
860 861 for mode, postfix in tmplmodes:
861 862 for type in types:
862 863 cur = postfix and ('%s_%s' % (type, postfix)) or type
863 864 if mode and cur in self.t:
864 865 types[type] = cur
865 866
866 867 try:
867 868
868 869 # write header
869 870 if types['header']:
870 871 h = templater.stringify(self.t(types['header'], **props))
871 872 if self.buffered:
872 873 self.header[ctx.rev()] = h
873 874 else:
874 875 if self.lastheader != h:
875 876 self.lastheader = h
876 877 self.ui.write(h)
877 878
878 879 # write changeset metadata, then patch if requested
879 880 key = types['changeset']
880 881 self.ui.write(templater.stringify(self.t(key, **props)))
881 882 self.showpatch(ctx.node(), matchfn)
882 883
883 884 if types['footer']:
884 885 if not self.footer:
885 886 self.footer = templater.stringify(self.t(types['footer'],
886 887 **props))
887 888
888 889 except KeyError, inst:
889 890 msg = _("%s: no key named '%s'")
890 891 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
891 892 except SyntaxError, inst:
892 893 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
893 894
894 895 def show_changeset(ui, repo, opts, buffered=False):
895 896 """show one changeset using template or regular display.
896 897
897 898 Display format will be the first non-empty hit of:
898 899 1. option 'template'
899 900 2. option 'style'
900 901 3. [ui] setting 'logtemplate'
901 902 4. [ui] setting 'style'
902 903 If all of these values are either the unset or the empty string,
903 904 regular display via changeset_printer() is done.
904 905 """
905 906 # options
906 907 patch = False
907 908 if opts.get('patch') or opts.get('stat'):
908 909 patch = scmutil.matchall(repo)
909 910
910 911 tmpl = opts.get('template')
911 912 style = None
912 913 if tmpl:
913 914 tmpl = templater.parsestring(tmpl, quoted=False)
914 915 else:
915 916 style = opts.get('style')
916 917
917 918 # ui settings
918 919 if not (tmpl or style):
919 920 tmpl = ui.config('ui', 'logtemplate')
920 921 if tmpl:
921 922 try:
922 923 tmpl = templater.parsestring(tmpl)
923 924 except SyntaxError:
924 925 tmpl = templater.parsestring(tmpl, quoted=False)
925 926 else:
926 927 style = util.expandpath(ui.config('ui', 'style', ''))
927 928
928 929 if not (tmpl or style):
929 930 return changeset_printer(ui, repo, patch, opts, buffered)
930 931
931 932 mapfile = None
932 933 if style and not tmpl:
933 934 mapfile = style
934 935 if not os.path.split(mapfile)[0]:
935 936 mapname = (templater.templatepath('map-cmdline.' + mapfile)
936 937 or templater.templatepath(mapfile))
937 938 if mapname:
938 939 mapfile = mapname
939 940
940 941 try:
941 942 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
942 943 except SyntaxError, inst:
943 944 raise util.Abort(inst.args[0])
944 945 if tmpl:
945 946 t.use_template(tmpl)
946 947 return t
947 948
948 949 def finddate(ui, repo, date):
949 950 """Find the tipmost changeset that matches the given date spec"""
950 951
951 952 df = util.matchdate(date)
952 953 m = scmutil.matchall(repo)
953 954 results = {}
954 955
955 956 def prep(ctx, fns):
956 957 d = ctx.date()
957 958 if df(d[0]):
958 959 results[ctx.rev()] = d
959 960
960 961 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
961 962 rev = ctx.rev()
962 963 if rev in results:
963 964 ui.status(_("found revision %s from %s\n") %
964 965 (rev, util.datestr(results[rev])))
965 966 return str(rev)
966 967
967 968 raise util.Abort(_("revision matching date not found"))
968 969
969 970 def increasingwindows(start, end, windowsize=8, sizelimit=512):
970 971 if start < end:
971 972 while start < end:
972 973 yield start, min(windowsize, end - start)
973 974 start += windowsize
974 975 if windowsize < sizelimit:
975 976 windowsize *= 2
976 977 else:
977 978 while start > end:
978 979 yield start, min(windowsize, start - end - 1)
979 980 start -= windowsize
980 981 if windowsize < sizelimit:
981 982 windowsize *= 2
982 983
983 984 def walkchangerevs(repo, match, opts, prepare):
984 985 '''Iterate over files and the revs in which they changed.
985 986
986 987 Callers most commonly need to iterate backwards over the history
987 988 in which they are interested. Doing so has awful (quadratic-looking)
988 989 performance, so we use iterators in a "windowed" way.
989 990
990 991 We walk a window of revisions in the desired order. Within the
991 992 window, we first walk forwards to gather data, then in the desired
992 993 order (usually backwards) to display it.
993 994
994 995 This function returns an iterator yielding contexts. Before
995 996 yielding each context, the iterator will first call the prepare
996 997 function on each context in the window in forward order.'''
997 998
998 999 follow = opts.get('follow') or opts.get('follow_first')
999 1000
1000 1001 if not len(repo):
1001 1002 return []
1002 1003 if opts.get('rev'):
1003 1004 revs = scmutil.revrange(repo, opts.get('rev'))
1004 1005 elif follow:
1005 1006 revs = repo.revs('reverse(:.)')
1006 1007 else:
1007 1008 revs = list(repo)
1008 1009 revs.reverse()
1009 1010 if not revs:
1010 1011 return []
1011 1012 wanted = set()
1012 1013 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1013 1014 fncache = {}
1014 1015 change = repo.changectx
1015 1016
1016 1017 # First step is to fill wanted, the set of revisions that we want to yield.
1017 1018 # When it does not induce extra cost, we also fill fncache for revisions in
1018 1019 # wanted: a cache of filenames that were changed (ctx.files()) and that
1019 1020 # match the file filtering conditions.
1020 1021
1021 1022 if not slowpath and not match.files():
1022 1023 # No files, no patterns. Display all revs.
1023 1024 wanted = set(revs)
1024 1025 copies = []
1025 1026
1026 1027 if not slowpath and match.files():
1027 1028 # We only have to read through the filelog to find wanted revisions
1028 1029
1029 1030 minrev, maxrev = min(revs), max(revs)
1030 1031 def filerevgen(filelog, last):
1031 1032 """
1032 1033 Only files, no patterns. Check the history of each file.
1033 1034
1034 1035 Examines filelog entries within minrev, maxrev linkrev range
1035 1036 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1036 1037 tuples in backwards order
1037 1038 """
1038 1039 cl_count = len(repo)
1039 1040 revs = []
1040 1041 for j in xrange(0, last + 1):
1041 1042 linkrev = filelog.linkrev(j)
1042 1043 if linkrev < minrev:
1043 1044 continue
1044 1045 # only yield rev for which we have the changelog, it can
1045 1046 # happen while doing "hg log" during a pull or commit
1046 1047 if linkrev >= cl_count:
1047 1048 break
1048 1049
1049 1050 parentlinkrevs = []
1050 1051 for p in filelog.parentrevs(j):
1051 1052 if p != nullrev:
1052 1053 parentlinkrevs.append(filelog.linkrev(p))
1053 1054 n = filelog.node(j)
1054 1055 revs.append((linkrev, parentlinkrevs,
1055 1056 follow and filelog.renamed(n)))
1056 1057
1057 1058 return reversed(revs)
1058 1059 def iterfiles():
1059 1060 pctx = repo['.']
1060 1061 for filename in match.files():
1061 1062 if follow:
1062 1063 if filename not in pctx:
1063 1064 raise util.Abort(_('cannot follow file not in parent '
1064 1065 'revision: "%s"') % filename)
1065 1066 yield filename, pctx[filename].filenode()
1066 1067 else:
1067 1068 yield filename, None
1068 1069 for filename_node in copies:
1069 1070 yield filename_node
1070 1071 for file_, node in iterfiles():
1071 1072 filelog = repo.file(file_)
1072 1073 if not len(filelog):
1073 1074 if node is None:
1074 1075 # A zero count may be a directory or deleted file, so
1075 1076 # try to find matching entries on the slow path.
1076 1077 if follow:
1077 1078 raise util.Abort(
1078 1079 _('cannot follow nonexistent file: "%s"') % file_)
1079 1080 slowpath = True
1080 1081 break
1081 1082 else:
1082 1083 continue
1083 1084
1084 1085 if node is None:
1085 1086 last = len(filelog) - 1
1086 1087 else:
1087 1088 last = filelog.rev(node)
1088 1089
1089 1090
1090 1091 # keep track of all ancestors of the file
1091 1092 ancestors = set([filelog.linkrev(last)])
1092 1093
1093 1094 # iterate from latest to oldest revision
1094 1095 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1095 1096 if not follow:
1096 1097 if rev > maxrev:
1097 1098 continue
1098 1099 else:
1099 1100 # Note that last might not be the first interesting
1100 1101 # rev to us:
1101 1102 # if the file has been changed after maxrev, we'll
1102 1103 # have linkrev(last) > maxrev, and we still need
1103 1104 # to explore the file graph
1104 1105 if rev not in ancestors:
1105 1106 continue
1106 1107 # XXX insert 1327 fix here
1107 1108 if flparentlinkrevs:
1108 1109 ancestors.update(flparentlinkrevs)
1109 1110
1110 1111 fncache.setdefault(rev, []).append(file_)
1111 1112 wanted.add(rev)
1112 1113 if copied:
1113 1114 copies.append(copied)
1114 1115
1115 1116 # We decided to fall back to the slowpath because at least one
1116 1117 # of the paths was not a file. Check to see if at least one of them
1117 1118 # existed in history, otherwise simply return
1118 1119 if slowpath:
1119 1120 for path in match.files():
1120 1121 if path == '.' or path in repo.store:
1121 1122 break
1122 1123 else:
1123 1124 return []
1124 1125
1125 1126 if slowpath:
1126 1127 # We have to read the changelog to match filenames against
1127 1128 # changed files
1128 1129
1129 1130 if follow:
1130 1131 raise util.Abort(_('can only follow copies/renames for explicit '
1131 1132 'filenames'))
1132 1133
1133 1134 # The slow path checks files modified in every changeset.
1134 1135 for i in sorted(revs):
1135 1136 ctx = change(i)
1136 1137 matches = filter(match, ctx.files())
1137 1138 if matches:
1138 1139 fncache[i] = matches
1139 1140 wanted.add(i)
1140 1141
1141 1142 class followfilter(object):
1142 1143 def __init__(self, onlyfirst=False):
1143 1144 self.startrev = nullrev
1144 1145 self.roots = set()
1145 1146 self.onlyfirst = onlyfirst
1146 1147
1147 1148 def match(self, rev):
1148 1149 def realparents(rev):
1149 1150 if self.onlyfirst:
1150 1151 return repo.changelog.parentrevs(rev)[0:1]
1151 1152 else:
1152 1153 return filter(lambda x: x != nullrev,
1153 1154 repo.changelog.parentrevs(rev))
1154 1155
1155 1156 if self.startrev == nullrev:
1156 1157 self.startrev = rev
1157 1158 return True
1158 1159
1159 1160 if rev > self.startrev:
1160 1161 # forward: all descendants
1161 1162 if not self.roots:
1162 1163 self.roots.add(self.startrev)
1163 1164 for parent in realparents(rev):
1164 1165 if parent in self.roots:
1165 1166 self.roots.add(rev)
1166 1167 return True
1167 1168 else:
1168 1169 # backwards: all parents
1169 1170 if not self.roots:
1170 1171 self.roots.update(realparents(self.startrev))
1171 1172 if rev in self.roots:
1172 1173 self.roots.remove(rev)
1173 1174 self.roots.update(realparents(rev))
1174 1175 return True
1175 1176
1176 1177 return False
1177 1178
1178 1179 # it might be worthwhile to do this in the iterator if the rev range
1179 1180 # is descending and the prune args are all within that range
1180 1181 for rev in opts.get('prune', ()):
1181 1182 rev = repo[rev].rev()
1182 1183 ff = followfilter()
1183 1184 stop = min(revs[0], revs[-1])
1184 1185 for x in xrange(rev, stop - 1, -1):
1185 1186 if ff.match(x):
1186 1187 wanted.discard(x)
1187 1188
1188 1189 # Now that wanted is correctly initialized, we can iterate over the
1189 1190 # revision range, yielding only revisions in wanted.
1190 1191 def iterate():
1191 1192 if follow and not match.files():
1192 1193 ff = followfilter(onlyfirst=opts.get('follow_first'))
1193 1194 def want(rev):
1194 1195 return ff.match(rev) and rev in wanted
1195 1196 else:
1196 1197 def want(rev):
1197 1198 return rev in wanted
1198 1199
1199 1200 for i, window in increasingwindows(0, len(revs)):
1200 1201 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1201 1202 for rev in sorted(nrevs):
1202 1203 fns = fncache.get(rev)
1203 1204 ctx = change(rev)
1204 1205 if not fns:
1205 1206 def fns_generator():
1206 1207 for f in ctx.files():
1207 1208 if match(f):
1208 1209 yield f
1209 1210 fns = fns_generator()
1210 1211 prepare(ctx, fns)
1211 1212 for rev in nrevs:
1212 1213 yield change(rev)
1213 1214 return iterate()
1214 1215
1215 1216 def _makegraphfilematcher(repo, pats, followfirst):
1216 1217 # When displaying a revision with --patch --follow FILE, we have
1217 1218 # to know which file of the revision must be diffed. With
1218 1219 # --follow, we want the names of the ancestors of FILE in the
1219 1220 # revision, stored in "fcache". "fcache" is populated by
1220 1221 # reproducing the graph traversal already done by --follow revset
1221 1222 # and relating linkrevs to file names (which is not "correct" but
1222 1223 # good enough).
1223 1224 fcache = {}
1224 1225 fcacheready = [False]
1225 1226 pctx = repo['.']
1226 1227 wctx = repo[None]
1227 1228
1228 1229 def populate():
1229 1230 for fn in pats:
1230 1231 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1231 1232 for c in i:
1232 1233 fcache.setdefault(c.linkrev(), set()).add(c.path())
1233 1234
1234 1235 def filematcher(rev):
1235 1236 if not fcacheready[0]:
1236 1237 # Lazy initialization
1237 1238 fcacheready[0] = True
1238 1239 populate()
1239 1240 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1240 1241
1241 1242 return filematcher
1242 1243
1243 1244 def _makegraphlogrevset(repo, pats, opts, revs):
1244 1245 """Return (expr, filematcher) where expr is a revset string built
1245 1246 from log options and file patterns or None. If --stat or --patch
1246 1247 are not passed filematcher is None. Otherwise it is a callable
1247 1248 taking a revision number and returning a match objects filtering
1248 1249 the files to be detailed when displaying the revision.
1249 1250 """
1250 1251 opt2revset = {
1251 1252 'no_merges': ('not merge()', None),
1252 1253 'only_merges': ('merge()', None),
1253 1254 '_ancestors': ('ancestors(%(val)s)', None),
1254 1255 '_fancestors': ('_firstancestors(%(val)s)', None),
1255 1256 '_descendants': ('descendants(%(val)s)', None),
1256 1257 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1257 1258 '_matchfiles': ('_matchfiles(%(val)s)', None),
1258 1259 'date': ('date(%(val)r)', None),
1259 1260 'branch': ('branch(%(val)r)', ' or '),
1260 1261 '_patslog': ('filelog(%(val)r)', ' or '),
1261 1262 '_patsfollow': ('follow(%(val)r)', ' or '),
1262 1263 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1263 1264 'keyword': ('keyword(%(val)r)', ' or '),
1264 1265 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1265 1266 'user': ('user(%(val)r)', ' or '),
1266 1267 }
1267 1268
1268 1269 opts = dict(opts)
1269 1270 # follow or not follow?
1270 1271 follow = opts.get('follow') or opts.get('follow_first')
1271 1272 followfirst = opts.get('follow_first') and 1 or 0
1272 1273 # --follow with FILE behaviour depends on revs...
1273 1274 startrev = revs[0]
1274 1275 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1275 1276
1276 1277 # branch and only_branch are really aliases and must be handled at
1277 1278 # the same time
1278 1279 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1279 1280 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1280 1281 # pats/include/exclude are passed to match.match() directly in
1281 1282 # _matchfiles() revset but walkchangerevs() builds its matcher with
1282 1283 # scmutil.match(). The difference is input pats are globbed on
1283 1284 # platforms without shell expansion (windows).
1284 1285 pctx = repo[None]
1285 1286 match, pats = scmutil.matchandpats(pctx, pats, opts)
1286 1287 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1287 1288 if not slowpath:
1288 1289 for f in match.files():
1289 1290 if follow and f not in pctx:
1290 1291 raise util.Abort(_('cannot follow file not in parent '
1291 1292 'revision: "%s"') % f)
1292 1293 filelog = repo.file(f)
1293 1294 if not len(filelog):
1294 1295 # A zero count may be a directory or deleted file, so
1295 1296 # try to find matching entries on the slow path.
1296 1297 if follow:
1297 1298 raise util.Abort(
1298 1299 _('cannot follow nonexistent file: "%s"') % f)
1299 1300 slowpath = True
1300 1301
1301 1302 # We decided to fall back to the slowpath because at least one
1302 1303 # of the paths was not a file. Check to see if at least one of them
1303 1304 # existed in history - in that case, we'll continue down the
1304 1305 # slowpath; otherwise, we can turn off the slowpath
1305 1306 if slowpath:
1306 1307 for path in match.files():
1307 1308 if path == '.' or path in repo.store:
1308 1309 break
1309 1310 else:
1310 1311 slowpath = False
1311 1312
1312 1313 if slowpath:
1313 1314 # See walkchangerevs() slow path.
1314 1315 #
1315 1316 if follow:
1316 1317 raise util.Abort(_('can only follow copies/renames for explicit '
1317 1318 'filenames'))
1318 1319 # pats/include/exclude cannot be represented as separate
1319 1320 # revset expressions as their filtering logic applies at file
1320 1321 # level. For instance "-I a -X a" matches a revision touching
1321 1322 # "a" and "b" while "file(a) and not file(b)" does
1322 1323 # not. Besides, filesets are evaluated against the working
1323 1324 # directory.
1324 1325 matchargs = ['r:', 'd:relpath']
1325 1326 for p in pats:
1326 1327 matchargs.append('p:' + p)
1327 1328 for p in opts.get('include', []):
1328 1329 matchargs.append('i:' + p)
1329 1330 for p in opts.get('exclude', []):
1330 1331 matchargs.append('x:' + p)
1331 1332 matchargs = ','.join(('%r' % p) for p in matchargs)
1332 1333 opts['_matchfiles'] = matchargs
1333 1334 else:
1334 1335 if follow:
1335 1336 fpats = ('_patsfollow', '_patsfollowfirst')
1336 1337 fnopats = (('_ancestors', '_fancestors'),
1337 1338 ('_descendants', '_fdescendants'))
1338 1339 if pats:
1339 1340 # follow() revset interprets its file argument as a
1340 1341 # manifest entry, so use match.files(), not pats.
1341 1342 opts[fpats[followfirst]] = list(match.files())
1342 1343 else:
1343 1344 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1344 1345 else:
1345 1346 opts['_patslog'] = list(pats)
1346 1347
1347 1348 filematcher = None
1348 1349 if opts.get('patch') or opts.get('stat'):
1349 1350 if follow:
1350 1351 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1351 1352 else:
1352 1353 filematcher = lambda rev: match
1353 1354
1354 1355 expr = []
1355 1356 for op, val in opts.iteritems():
1356 1357 if not val:
1357 1358 continue
1358 1359 if op not in opt2revset:
1359 1360 continue
1360 1361 revop, andor = opt2revset[op]
1361 1362 if '%(val)' not in revop:
1362 1363 expr.append(revop)
1363 1364 else:
1364 1365 if not isinstance(val, list):
1365 1366 e = revop % {'val': val}
1366 1367 else:
1367 1368 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1368 1369 expr.append(e)
1369 1370
1370 1371 if expr:
1371 1372 expr = '(' + ' and '.join(expr) + ')'
1372 1373 else:
1373 1374 expr = None
1374 1375 return expr, filematcher
1375 1376
1376 1377 def getgraphlogrevs(repo, pats, opts):
1377 1378 """Return (revs, expr, filematcher) where revs is an iterable of
1378 1379 revision numbers, expr is a revset string built from log options
1379 1380 and file patterns or None, and used to filter 'revs'. If --stat or
1380 1381 --patch are not passed filematcher is None. Otherwise it is a
1381 1382 callable taking a revision number and returning a match objects
1382 1383 filtering the files to be detailed when displaying the revision.
1383 1384 """
1384 1385 def increasingrevs(repo, revs, matcher):
1385 1386 # The sorted input rev sequence is chopped in sub-sequences
1386 1387 # which are sorted in ascending order and passed to the
1387 1388 # matcher. The filtered revs are sorted again as they were in
1388 1389 # the original sub-sequence. This achieve several things:
1389 1390 #
1390 1391 # - getlogrevs() now returns a generator which behaviour is
1391 1392 # adapted to log need. First results come fast, last ones
1392 1393 # are batched for performances.
1393 1394 #
1394 1395 # - revset matchers often operate faster on revision in
1395 1396 # changelog order, because most filters deal with the
1396 1397 # changelog.
1397 1398 #
1398 1399 # - revset matchers can reorder revisions. "A or B" typically
1399 1400 # returns returns the revision matching A then the revision
1400 1401 # matching B. We want to hide this internal implementation
1401 1402 # detail from the caller, and sorting the filtered revision
1402 1403 # again achieves this.
1403 1404 for i, window in increasingwindows(0, len(revs), windowsize=1):
1404 1405 orevs = revs[i:i + window]
1405 1406 nrevs = set(matcher(repo, sorted(orevs)))
1406 1407 for rev in orevs:
1407 1408 if rev in nrevs:
1408 1409 yield rev
1409 1410
1410 1411 if not len(repo):
1411 1412 return iter([]), None, None
1412 1413 # Default --rev value depends on --follow but --follow behaviour
1413 1414 # depends on revisions resolved from --rev...
1414 1415 follow = opts.get('follow') or opts.get('follow_first')
1415 1416 if opts.get('rev'):
1416 1417 revs = scmutil.revrange(repo, opts['rev'])
1417 1418 else:
1418 1419 if follow and len(repo) > 0:
1419 1420 revs = repo.revs('reverse(:.)')
1420 1421 else:
1421 1422 revs = list(repo.changelog)
1422 1423 revs.reverse()
1423 1424 if not revs:
1424 1425 return iter([]), None, None
1425 1426 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1426 1427 if expr:
1427 1428 matcher = revset.match(repo.ui, expr)
1428 1429 revs = increasingrevs(repo, revs, matcher)
1429 1430 if not opts.get('hidden'):
1430 1431 # --hidden is still experimental and not worth a dedicated revset
1431 1432 # yet. Fortunately, filtering revision number is fast.
1432 1433 revs = (r for r in revs if r not in repo.hiddenrevs)
1433 1434 else:
1434 1435 revs = iter(revs)
1435 1436 return revs, expr, filematcher
1436 1437
1437 1438 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1438 1439 filematcher=None):
1439 1440 seen, state = [], graphmod.asciistate()
1440 1441 for rev, type, ctx, parents in dag:
1441 1442 char = 'o'
1442 1443 if ctx.node() in showparents:
1443 1444 char = '@'
1444 1445 elif ctx.obsolete():
1445 1446 char = 'x'
1446 1447 copies = None
1447 1448 if getrenamed and ctx.rev():
1448 1449 copies = []
1449 1450 for fn in ctx.files():
1450 1451 rename = getrenamed(fn, ctx.rev())
1451 1452 if rename:
1452 1453 copies.append((fn, rename[0]))
1453 1454 revmatchfn = None
1454 1455 if filematcher is not None:
1455 1456 revmatchfn = filematcher(ctx.rev())
1456 1457 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1457 1458 lines = displayer.hunk.pop(rev).split('\n')
1458 1459 if not lines[-1]:
1459 1460 del lines[-1]
1460 1461 displayer.flush(rev)
1461 1462 edges = edgefn(type, char, lines, seen, rev, parents)
1462 1463 for type, char, lines, coldata in edges:
1463 1464 graphmod.ascii(ui, state, type, char, lines, coldata)
1464 1465 displayer.close()
1465 1466
1466 1467 def graphlog(ui, repo, *pats, **opts):
1467 1468 # Parameters are identical to log command ones
1468 1469 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1469 1470 revs = sorted(revs, reverse=1)
1470 1471 limit = loglimit(opts)
1471 1472 if limit is not None:
1472 1473 revs = revs[:limit]
1473 1474 revdag = graphmod.dagwalker(repo, revs)
1474 1475
1475 1476 getrenamed = None
1476 1477 if opts.get('copies'):
1477 1478 endrev = None
1478 1479 if opts.get('rev'):
1479 1480 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1480 1481 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1481 1482 displayer = show_changeset(ui, repo, opts, buffered=True)
1482 1483 showparents = [ctx.node() for ctx in repo[None].parents()]
1483 1484 displaygraph(ui, revdag, displayer, showparents,
1484 1485 graphmod.asciiedges, getrenamed, filematcher)
1485 1486
1486 1487 def checkunsupportedgraphflags(pats, opts):
1487 1488 for op in ["newest_first"]:
1488 1489 if op in opts and opts[op]:
1489 1490 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1490 1491 % op.replace("_", "-"))
1491 1492
1492 1493 def graphrevs(repo, nodes, opts):
1493 1494 limit = loglimit(opts)
1494 1495 nodes.reverse()
1495 1496 if limit is not None:
1496 1497 nodes = nodes[:limit]
1497 1498 return graphmod.nodes(repo, nodes)
1498 1499
1499 1500 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1500 1501 join = lambda f: os.path.join(prefix, f)
1501 1502 bad = []
1502 1503 oldbad = match.bad
1503 1504 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1504 1505 names = []
1505 1506 wctx = repo[None]
1506 1507 cca = None
1507 1508 abort, warn = scmutil.checkportabilityalert(ui)
1508 1509 if abort or warn:
1509 1510 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1510 1511 for f in repo.walk(match):
1511 1512 exact = match.exact(f)
1512 1513 if exact or not explicitonly and f not in repo.dirstate:
1513 1514 if cca:
1514 1515 cca(f)
1515 1516 names.append(f)
1516 1517 if ui.verbose or not exact:
1517 1518 ui.status(_('adding %s\n') % match.rel(join(f)))
1518 1519
1519 1520 for subpath in wctx.substate:
1520 1521 sub = wctx.sub(subpath)
1521 1522 try:
1522 1523 submatch = matchmod.narrowmatcher(subpath, match)
1523 1524 if listsubrepos:
1524 1525 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1525 1526 False))
1526 1527 else:
1527 1528 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1528 1529 True))
1529 1530 except error.LookupError:
1530 1531 ui.status(_("skipping missing subrepository: %s\n")
1531 1532 % join(subpath))
1532 1533
1533 1534 if not dryrun:
1534 1535 rejected = wctx.add(names, prefix)
1535 1536 bad.extend(f for f in rejected if f in match.files())
1536 1537 return bad
1537 1538
1538 1539 def forget(ui, repo, match, prefix, explicitonly):
1539 1540 join = lambda f: os.path.join(prefix, f)
1540 1541 bad = []
1541 1542 oldbad = match.bad
1542 1543 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1543 1544 wctx = repo[None]
1544 1545 forgot = []
1545 1546 s = repo.status(match=match, clean=True)
1546 1547 forget = sorted(s[0] + s[1] + s[3] + s[6])
1547 1548 if explicitonly:
1548 1549 forget = [f for f in forget if match.exact(f)]
1549 1550
1550 1551 for subpath in wctx.substate:
1551 1552 sub = wctx.sub(subpath)
1552 1553 try:
1553 1554 submatch = matchmod.narrowmatcher(subpath, match)
1554 1555 subbad, subforgot = sub.forget(ui, submatch, prefix)
1555 1556 bad.extend([subpath + '/' + f for f in subbad])
1556 1557 forgot.extend([subpath + '/' + f for f in subforgot])
1557 1558 except error.LookupError:
1558 1559 ui.status(_("skipping missing subrepository: %s\n")
1559 1560 % join(subpath))
1560 1561
1561 1562 if not explicitonly:
1562 1563 for f in match.files():
1563 1564 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1564 1565 if f not in forgot:
1565 1566 if os.path.exists(match.rel(join(f))):
1566 1567 ui.warn(_('not removing %s: '
1567 1568 'file is already untracked\n')
1568 1569 % match.rel(join(f)))
1569 1570 bad.append(f)
1570 1571
1571 1572 for f in forget:
1572 1573 if ui.verbose or not match.exact(f):
1573 1574 ui.status(_('removing %s\n') % match.rel(join(f)))
1574 1575
1575 1576 rejected = wctx.forget(forget, prefix)
1576 1577 bad.extend(f for f in rejected if f in match.files())
1577 1578 forgot.extend(forget)
1578 1579 return bad, forgot
1579 1580
1580 1581 def duplicatecopies(repo, rev, p1):
1581 1582 "Reproduce copies found in the source revision in the dirstate for grafts"
1582 1583 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1583 1584 repo.dirstate.copy(src, dst)
1584 1585
1585 1586 def commit(ui, repo, commitfunc, pats, opts):
1586 1587 '''commit the specified files or all outstanding changes'''
1587 1588 date = opts.get('date')
1588 1589 if date:
1589 1590 opts['date'] = util.parsedate(date)
1590 1591 message = logmessage(ui, opts)
1591 1592
1592 1593 # extract addremove carefully -- this function can be called from a command
1593 1594 # that doesn't support addremove
1594 1595 if opts.get('addremove'):
1595 1596 scmutil.addremove(repo, pats, opts)
1596 1597
1597 1598 return commitfunc(ui, repo, message,
1598 1599 scmutil.match(repo[None], pats, opts), opts)
1599 1600
1600 1601 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1601 1602 ui.note(_('amending changeset %s\n') % old)
1602 1603 base = old.p1()
1603 1604
1604 1605 wlock = lock = None
1605 1606 try:
1606 1607 wlock = repo.wlock()
1607 1608 lock = repo.lock()
1608 1609 tr = repo.transaction('amend')
1609 1610 try:
1610 1611 # See if we got a message from -m or -l, if not, open the editor
1611 1612 # with the message of the changeset to amend
1612 1613 message = logmessage(ui, opts)
1613 1614 # First, do a regular commit to record all changes in the working
1614 1615 # directory (if there are any)
1615 1616 ui.callhooks = False
1616 1617 try:
1617 1618 opts['message'] = 'temporary amend commit for %s' % old
1618 1619 node = commit(ui, repo, commitfunc, pats, opts)
1619 1620 finally:
1620 1621 ui.callhooks = True
1621 1622 ctx = repo[node]
1622 1623
1623 1624 # Participating changesets:
1624 1625 #
1625 1626 # node/ctx o - new (intermediate) commit that contains changes
1626 1627 # | from working dir to go into amending commit
1627 1628 # | (or a workingctx if there were no changes)
1628 1629 # |
1629 1630 # old o - changeset to amend
1630 1631 # |
1631 1632 # base o - parent of amending changeset
1632 1633
1633 1634 # Update extra dict from amended commit (e.g. to preserve graft
1634 1635 # source)
1635 1636 extra.update(old.extra())
1636 1637
1637 1638 # Also update it from the intermediate commit or from the wctx
1638 1639 extra.update(ctx.extra())
1639 1640
1640 1641 files = set(old.files())
1641 1642
1642 1643 # Second, we use either the commit we just did, or if there were no
1643 1644 # changes the parent of the working directory as the version of the
1644 1645 # files in the final amend commit
1645 1646 if node:
1646 1647 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1647 1648
1648 1649 user = ctx.user()
1649 1650 date = ctx.date()
1650 1651 # Recompute copies (avoid recording a -> b -> a)
1651 1652 copied = copies.pathcopies(base, ctx)
1652 1653
1653 1654 # Prune files which were reverted by the updates: if old
1654 1655 # introduced file X and our intermediate commit, node,
1655 1656 # renamed that file, then those two files are the same and
1656 1657 # we can discard X from our list of files. Likewise if X
1657 1658 # was deleted, it's no longer relevant
1658 1659 files.update(ctx.files())
1659 1660
1660 1661 def samefile(f):
1661 1662 if f in ctx.manifest():
1662 1663 a = ctx.filectx(f)
1663 1664 if f in base.manifest():
1664 1665 b = base.filectx(f)
1665 1666 return (not a.cmp(b)
1666 1667 and a.flags() == b.flags())
1667 1668 else:
1668 1669 return False
1669 1670 else:
1670 1671 return f not in base.manifest()
1671 1672 files = [f for f in files if not samefile(f)]
1672 1673
1673 1674 def filectxfn(repo, ctx_, path):
1674 1675 try:
1675 1676 fctx = ctx[path]
1676 1677 flags = fctx.flags()
1677 1678 mctx = context.memfilectx(fctx.path(), fctx.data(),
1678 1679 islink='l' in flags,
1679 1680 isexec='x' in flags,
1680 1681 copied=copied.get(path))
1681 1682 return mctx
1682 1683 except KeyError:
1683 1684 raise IOError
1684 1685 else:
1685 1686 ui.note(_('copying changeset %s to %s\n') % (old, base))
1686 1687
1687 1688 # Use version of files as in the old cset
1688 1689 def filectxfn(repo, ctx_, path):
1689 1690 try:
1690 1691 return old.filectx(path)
1691 1692 except KeyError:
1692 1693 raise IOError
1693 1694
1694 1695 user = opts.get('user') or old.user()
1695 1696 date = opts.get('date') or old.date()
1696 1697 if not message:
1697 1698 message = old.description()
1698 1699
1700 pureextra = extra.copy()
1701 extra['amend_source'] = old.hex()
1702
1699 1703 new = context.memctx(repo,
1700 1704 parents=[base.node(), nullid],
1701 1705 text=message,
1702 1706 files=files,
1703 1707 filectxfn=filectxfn,
1704 1708 user=user,
1705 1709 date=date,
1706 1710 extra=extra)
1707 1711 new._text = commitforceeditor(repo, new, [])
1712
1713 newdesc = changelog.stripdesc(new.description())
1714 if ((not node)
1715 and newdesc == old.description()
1716 and user == old.user()
1717 and date == old.date()
1718 and pureextra == old.extra()):
1719 # nothing changed. continuing here would create a new node
1720 # anyway because of the amend_source noise.
1721 #
1722 # This not what we expect from amend.
1723 return old.node()
1724
1708 1725 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1709 1726 try:
1710 1727 repo.ui.setconfig('phases', 'new-commit', old.phase())
1711 1728 newid = repo.commitctx(new)
1712 1729 finally:
1713 1730 repo.ui.setconfig('phases', 'new-commit', ph)
1714 1731 if newid != old.node():
1715 1732 # Reroute the working copy parent to the new changeset
1716 1733 repo.setparents(newid, nullid)
1717 1734
1718 1735 # Move bookmarks from old parent to amend commit
1719 1736 bms = repo.nodebookmarks(old.node())
1720 1737 if bms:
1721 1738 for bm in bms:
1722 1739 repo._bookmarks[bm] = newid
1723 1740 bookmarks.write(repo)
1724 1741 #commit the whole amend process
1725 1742 if obsolete._enabled and newid != old.node():
1726 1743 # mark the new changeset as successor of the rewritten one
1727 1744 new = repo[newid]
1728 1745 obs = [(old, (new,))]
1729 1746 if node:
1730 1747 obs.append((ctx, (new,)))
1731 1748
1732 1749 obsolete.createmarkers(repo, obs)
1733 1750 tr.close()
1734 1751 finally:
1735 1752 tr.release()
1736 1753 if (not obsolete._enabled) and newid != old.node():
1737 1754 # Strip the intermediate commit (if there was one) and the amended
1738 1755 # commit
1739 1756 if node:
1740 1757 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1741 1758 ui.note(_('stripping amended changeset %s\n') % old)
1742 1759 repair.strip(ui, repo, old.node(), topic='amend-backup')
1743 1760 finally:
1744 1761 lockmod.release(wlock, lock)
1745 1762 return newid
1746 1763
1747 1764 def commiteditor(repo, ctx, subs):
1748 1765 if ctx.description():
1749 1766 return ctx.description()
1750 1767 return commitforceeditor(repo, ctx, subs)
1751 1768
1752 1769 def commitforceeditor(repo, ctx, subs):
1753 1770 edittext = []
1754 1771 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1755 1772 if ctx.description():
1756 1773 edittext.append(ctx.description())
1757 1774 edittext.append("")
1758 1775 edittext.append("") # Empty line between message and comments.
1759 1776 edittext.append(_("HG: Enter commit message."
1760 1777 " Lines beginning with 'HG:' are removed."))
1761 1778 edittext.append(_("HG: Leave message empty to abort commit."))
1762 1779 edittext.append("HG: --")
1763 1780 edittext.append(_("HG: user: %s") % ctx.user())
1764 1781 if ctx.p2():
1765 1782 edittext.append(_("HG: branch merge"))
1766 1783 if ctx.branch():
1767 1784 edittext.append(_("HG: branch '%s'") % ctx.branch())
1768 1785 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1769 1786 edittext.extend([_("HG: added %s") % f for f in added])
1770 1787 edittext.extend([_("HG: changed %s") % f for f in modified])
1771 1788 edittext.extend([_("HG: removed %s") % f for f in removed])
1772 1789 if not added and not modified and not removed:
1773 1790 edittext.append(_("HG: no files changed"))
1774 1791 edittext.append("")
1775 1792 # run editor in the repository root
1776 1793 olddir = os.getcwd()
1777 1794 os.chdir(repo.root)
1778 1795 text = repo.ui.edit("\n".join(edittext), ctx.user())
1779 1796 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1780 1797 os.chdir(olddir)
1781 1798
1782 1799 if not text.strip():
1783 1800 raise util.Abort(_("empty commit message"))
1784 1801
1785 1802 return text
1786 1803
1787 1804 def revert(ui, repo, ctx, parents, *pats, **opts):
1788 1805 parent, p2 = parents
1789 1806 node = ctx.node()
1790 1807
1791 1808 mf = ctx.manifest()
1792 1809 if node == parent:
1793 1810 pmf = mf
1794 1811 else:
1795 1812 pmf = None
1796 1813
1797 1814 # need all matching names in dirstate and manifest of target rev,
1798 1815 # so have to walk both. do not print errors if files exist in one
1799 1816 # but not other.
1800 1817
1801 1818 names = {}
1802 1819
1803 1820 wlock = repo.wlock()
1804 1821 try:
1805 1822 # walk dirstate.
1806 1823
1807 1824 m = scmutil.match(repo[None], pats, opts)
1808 1825 m.bad = lambda x, y: False
1809 1826 for abs in repo.walk(m):
1810 1827 names[abs] = m.rel(abs), m.exact(abs)
1811 1828
1812 1829 # walk target manifest.
1813 1830
1814 1831 def badfn(path, msg):
1815 1832 if path in names:
1816 1833 return
1817 1834 if path in ctx.substate:
1818 1835 return
1819 1836 path_ = path + '/'
1820 1837 for f in names:
1821 1838 if f.startswith(path_):
1822 1839 return
1823 1840 ui.warn("%s: %s\n" % (m.rel(path), msg))
1824 1841
1825 1842 m = scmutil.match(ctx, pats, opts)
1826 1843 m.bad = badfn
1827 1844 for abs in ctx.walk(m):
1828 1845 if abs not in names:
1829 1846 names[abs] = m.rel(abs), m.exact(abs)
1830 1847
1831 1848 # get the list of subrepos that must be reverted
1832 1849 targetsubs = [s for s in ctx.substate if m(s)]
1833 1850 m = scmutil.matchfiles(repo, names)
1834 1851 changes = repo.status(match=m)[:4]
1835 1852 modified, added, removed, deleted = map(set, changes)
1836 1853
1837 1854 # if f is a rename, also revert the source
1838 1855 cwd = repo.getcwd()
1839 1856 for f in added:
1840 1857 src = repo.dirstate.copied(f)
1841 1858 if src and src not in names and repo.dirstate[src] == 'r':
1842 1859 removed.add(src)
1843 1860 names[src] = (repo.pathto(src, cwd), True)
1844 1861
1845 1862 def removeforget(abs):
1846 1863 if repo.dirstate[abs] == 'a':
1847 1864 return _('forgetting %s\n')
1848 1865 return _('removing %s\n')
1849 1866
1850 1867 revert = ([], _('reverting %s\n'))
1851 1868 add = ([], _('adding %s\n'))
1852 1869 remove = ([], removeforget)
1853 1870 undelete = ([], _('undeleting %s\n'))
1854 1871
1855 1872 disptable = (
1856 1873 # dispatch table:
1857 1874 # file state
1858 1875 # action if in target manifest
1859 1876 # action if not in target manifest
1860 1877 # make backup if in target manifest
1861 1878 # make backup if not in target manifest
1862 1879 (modified, revert, remove, True, True),
1863 1880 (added, revert, remove, True, False),
1864 1881 (removed, undelete, None, False, False),
1865 1882 (deleted, revert, remove, False, False),
1866 1883 )
1867 1884
1868 1885 for abs, (rel, exact) in sorted(names.items()):
1869 1886 mfentry = mf.get(abs)
1870 1887 target = repo.wjoin(abs)
1871 1888 def handle(xlist, dobackup):
1872 1889 xlist[0].append(abs)
1873 1890 if (dobackup and not opts.get('no_backup') and
1874 1891 os.path.lexists(target)):
1875 1892 bakname = "%s.orig" % rel
1876 1893 ui.note(_('saving current version of %s as %s\n') %
1877 1894 (rel, bakname))
1878 1895 if not opts.get('dry_run'):
1879 1896 util.rename(target, bakname)
1880 1897 if ui.verbose or not exact:
1881 1898 msg = xlist[1]
1882 1899 if not isinstance(msg, basestring):
1883 1900 msg = msg(abs)
1884 1901 ui.status(msg % rel)
1885 1902 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1886 1903 if abs not in table:
1887 1904 continue
1888 1905 # file has changed in dirstate
1889 1906 if mfentry:
1890 1907 handle(hitlist, backuphit)
1891 1908 elif misslist is not None:
1892 1909 handle(misslist, backupmiss)
1893 1910 break
1894 1911 else:
1895 1912 if abs not in repo.dirstate:
1896 1913 if mfentry:
1897 1914 handle(add, True)
1898 1915 elif exact:
1899 1916 ui.warn(_('file not managed: %s\n') % rel)
1900 1917 continue
1901 1918 # file has not changed in dirstate
1902 1919 if node == parent:
1903 1920 if exact:
1904 1921 ui.warn(_('no changes needed to %s\n') % rel)
1905 1922 continue
1906 1923 if pmf is None:
1907 1924 # only need parent manifest in this unlikely case,
1908 1925 # so do not read by default
1909 1926 pmf = repo[parent].manifest()
1910 1927 if abs in pmf and mfentry:
1911 1928 # if version of file is same in parent and target
1912 1929 # manifests, do nothing
1913 1930 if (pmf[abs] != mfentry or
1914 1931 pmf.flags(abs) != mf.flags(abs)):
1915 1932 handle(revert, False)
1916 1933 else:
1917 1934 handle(remove, False)
1918 1935
1919 1936 if not opts.get('dry_run'):
1920 1937 def checkout(f):
1921 1938 fc = ctx[f]
1922 1939 repo.wwrite(f, fc.data(), fc.flags())
1923 1940
1924 1941 audit_path = scmutil.pathauditor(repo.root)
1925 1942 for f in remove[0]:
1926 1943 if repo.dirstate[f] == 'a':
1927 1944 repo.dirstate.drop(f)
1928 1945 continue
1929 1946 audit_path(f)
1930 1947 try:
1931 1948 util.unlinkpath(repo.wjoin(f))
1932 1949 except OSError:
1933 1950 pass
1934 1951 repo.dirstate.remove(f)
1935 1952
1936 1953 normal = None
1937 1954 if node == parent:
1938 1955 # We're reverting to our parent. If possible, we'd like status
1939 1956 # to report the file as clean. We have to use normallookup for
1940 1957 # merges to avoid losing information about merged/dirty files.
1941 1958 if p2 != nullid:
1942 1959 normal = repo.dirstate.normallookup
1943 1960 else:
1944 1961 normal = repo.dirstate.normal
1945 1962 for f in revert[0]:
1946 1963 checkout(f)
1947 1964 if normal:
1948 1965 normal(f)
1949 1966
1950 1967 for f in add[0]:
1951 1968 checkout(f)
1952 1969 repo.dirstate.add(f)
1953 1970
1954 1971 normal = repo.dirstate.normallookup
1955 1972 if node == parent and p2 == nullid:
1956 1973 normal = repo.dirstate.normal
1957 1974 for f in undelete[0]:
1958 1975 checkout(f)
1959 1976 normal(f)
1960 1977
1961 1978 if targetsubs:
1962 1979 # Revert the subrepos on the revert list
1963 1980 for sub in targetsubs:
1964 1981 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1965 1982 finally:
1966 1983 wlock.release()
1967 1984
1968 1985 def command(table):
1969 1986 '''returns a function object bound to table which can be used as
1970 1987 a decorator for populating table as a command table'''
1971 1988
1972 1989 def cmd(name, options, synopsis=None):
1973 1990 def decorator(func):
1974 1991 if synopsis:
1975 1992 table[name] = func, options[:], synopsis
1976 1993 else:
1977 1994 table[name] = func, options[:]
1978 1995 return func
1979 1996 return decorator
1980 1997
1981 1998 return cmd
@@ -1,444 +1,456 b''
1 1 $ hg init
2 2
3 3 Setup:
4 4
5 5 $ echo a >> a
6 6 $ hg ci -Am 'base'
7 7 adding a
8 8
9 9 Refuse to amend public csets:
10 10
11 11 $ hg phase -r . -p
12 12 $ hg ci --amend
13 13 abort: cannot amend public changesets
14 14 [255]
15 15 $ hg phase -r . -f -d
16 16
17 17 $ echo a >> a
18 18 $ hg ci -Am 'base1'
19 19
20 20 Nothing to amend:
21 21
22 22 $ hg ci --amend
23 23 nothing changed
24 24 [1]
25 25
26 26 $ cat >> $HGRCPATH <<EOF
27 27 > [hooks]
28 28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
29 29 > EOF
30 30
31 31 Amending changeset with changes in working dir:
32 32
33 33 $ echo a >> a
34 34 $ hg ci --amend -m 'amend base1'
35 pretxncommit 9cd25b479c51be2f4ed2c38e7abdf7ce67d8e0dc
36 9cd25b479c51 tip
35 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
36 43f1ba15f28a tip
37 37 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg (glob)
38 38 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
39 39 $ hg diff -c .
40 diff -r ad120869acf0 -r 9cd25b479c51 a
40 diff -r ad120869acf0 -r 43f1ba15f28a a
41 41 --- a/a Thu Jan 01 00:00:00 1970 +0000
42 42 +++ b/a Thu Jan 01 00:00:00 1970 +0000
43 43 @@ -1,1 +1,3 @@
44 44 a
45 45 +a
46 46 +a
47 47 $ hg log
48 changeset: 1:9cd25b479c51
48 changeset: 1:43f1ba15f28a
49 49 tag: tip
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: amend base1
53 53
54 54 changeset: 0:ad120869acf0
55 55 user: test
56 56 date: Thu Jan 01 00:00:00 1970 +0000
57 57 summary: base
58 58
59 59
60 60 Add new file:
61 61
62 62 $ echo b > b
63 63 $ hg ci --amend -Am 'amend base1 new file'
64 64 adding b
65 saved backup bundle to $TESTTMP/.hg/strip-backup/9cd25b479c51-amend-backup.hg (glob)
65 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-amend-backup.hg (glob)
66 66
67 67 Remove file that was added in amended commit:
68 68
69 69 $ hg rm b
70 70 $ hg ci --amend -m 'amend base1 remove new file'
71 saved backup bundle to $TESTTMP/.hg/strip-backup/e2bb3ecffd2f-amend-backup.hg (glob)
71 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-amend-backup.hg (glob)
72 72
73 73 $ hg cat b
74 b: no such file in rev 664a9b2d60cd
74 b: no such file in rev 74609c7f506e
75 75 [1]
76 76
77 77 No changes, just a different message:
78 78
79 79 $ hg ci -v --amend -m 'no changes, new message'
80 amending changeset 664a9b2d60cd
81 copying changeset 664a9b2d60cd to ad120869acf0
80 amending changeset 74609c7f506e
81 copying changeset 74609c7f506e to ad120869acf0
82 82 a
83 stripping amended changeset 664a9b2d60cd
83 stripping amended changeset 74609c7f506e
84 84 1 changesets found
85 saved backup bundle to $TESTTMP/.hg/strip-backup/664a9b2d60cd-amend-backup.hg (glob)
85 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-amend-backup.hg (glob)
86 86 1 changesets found
87 87 adding branch
88 88 adding changesets
89 89 adding manifests
90 90 adding file changes
91 91 added 1 changesets with 1 changes to 1 files
92 committed changeset 1:ea6e356ff2ad
92 committed changeset 1:1cd866679df8
93 93 $ hg diff -c .
94 diff -r ad120869acf0 -r ea6e356ff2ad a
94 diff -r ad120869acf0 -r 1cd866679df8 a
95 95 --- a/a Thu Jan 01 00:00:00 1970 +0000
96 96 +++ b/a Thu Jan 01 00:00:00 1970 +0000
97 97 @@ -1,1 +1,3 @@
98 98 a
99 99 +a
100 100 +a
101 101 $ hg log
102 changeset: 1:ea6e356ff2ad
102 changeset: 1:1cd866679df8
103 103 tag: tip
104 104 user: test
105 105 date: Thu Jan 01 00:00:00 1970 +0000
106 106 summary: no changes, new message
107 107
108 108 changeset: 0:ad120869acf0
109 109 user: test
110 110 date: Thu Jan 01 00:00:00 1970 +0000
111 111 summary: base
112 112
113 113
114 114 Disable default date on commit so when -d isn't given, the old date is preserved:
115 115
116 116 $ echo '[defaults]' >> $HGRCPATH
117 117 $ echo 'commit=' >> $HGRCPATH
118 118
119 119 Test -u/-d:
120 120
121 121 $ hg ci --amend -u foo -d '1 0'
122 saved backup bundle to $TESTTMP/.hg/strip-backup/ea6e356ff2ad-amend-backup.hg (glob)
122 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-amend-backup.hg (glob)
123 123 $ echo a >> a
124 124 $ hg ci --amend -u foo -d '1 0'
125 saved backup bundle to $TESTTMP/.hg/strip-backup/377b91ce8b56-amend-backup.hg (glob)
125 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-amend-backup.hg (glob)
126 126 $ hg log -r .
127 changeset: 1:2c94e4a5756f
127 changeset: 1:5f357c7560ab
128 128 tag: tip
129 129 user: foo
130 130 date: Thu Jan 01 00:00:01 1970 +0000
131 131 summary: no changes, new message
132 132
133 133
134 134 Open editor with old commit message if a message isn't given otherwise:
135 135
136 136 $ cat > editor.sh << '__EOF__'
137 137 > #!/bin/sh
138 138 > cat $1
139 139 > echo "another precious commit message" > "$1"
140 140 > __EOF__
141 141 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
142 amending changeset 2c94e4a5756f
143 copying changeset 2c94e4a5756f to ad120869acf0
142 amending changeset 5f357c7560ab
143 copying changeset 5f357c7560ab to ad120869acf0
144 144 no changes, new message
145 145
146 146
147 147 HG: Enter commit message. Lines beginning with 'HG:' are removed.
148 148 HG: Leave message empty to abort commit.
149 149 HG: --
150 150 HG: user: foo
151 151 HG: branch 'default'
152 152 HG: changed a
153 153 a
154 stripping amended changeset 2c94e4a5756f
154 stripping amended changeset 5f357c7560ab
155 155 1 changesets found
156 saved backup bundle to $TESTTMP/.hg/strip-backup/2c94e4a5756f-amend-backup.hg (glob)
156 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-amend-backup.hg (glob)
157 157 1 changesets found
158 158 adding branch
159 159 adding changesets
160 160 adding manifests
161 161 adding file changes
162 162 added 1 changesets with 1 changes to 1 files
163 committed changeset 1:ffb49186f961
163 committed changeset 1:7ab3bf440b54
164 164
165 165 Same, but with changes in working dir (different code path):
166 166
167 167 $ echo a >> a
168 168 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
169 amending changeset ffb49186f961
169 amending changeset 7ab3bf440b54
170 170 a
171 copying changeset a4f8a65b7c6a to ad120869acf0
171 copying changeset a0ea9b1a4c8c to ad120869acf0
172 172 another precious commit message
173 173
174 174
175 175 HG: Enter commit message. Lines beginning with 'HG:' are removed.
176 176 HG: Leave message empty to abort commit.
177 177 HG: --
178 178 HG: user: foo
179 179 HG: branch 'default'
180 180 HG: changed a
181 181 a
182 stripping intermediate changeset a4f8a65b7c6a
183 stripping amended changeset ffb49186f961
182 stripping intermediate changeset a0ea9b1a4c8c
183 stripping amended changeset 7ab3bf440b54
184 184 2 changesets found
185 saved backup bundle to $TESTTMP/.hg/strip-backup/ffb49186f961-amend-backup.hg (glob)
185 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-amend-backup.hg (glob)
186 186 1 changesets found
187 187 adding branch
188 188 adding changesets
189 189 adding manifests
190 190 adding file changes
191 191 added 1 changesets with 1 changes to 1 files
192 committed changeset 1:fb6cca43446f
192 committed changeset 1:ea22a388757c
193 193
194 194 $ rm editor.sh
195 195 $ hg log -r .
196 changeset: 1:fb6cca43446f
196 changeset: 1:ea22a388757c
197 197 tag: tip
198 198 user: foo
199 199 date: Thu Jan 01 00:00:01 1970 +0000
200 200 summary: another precious commit message
201 201
202 202
203 203 Moving bookmarks, preserve active bookmark:
204 204
205 205 $ hg book book1
206 206 $ hg book book2
207 207 $ hg ci --amend -m 'move bookmarks'
208 saved backup bundle to $TESTTMP/.hg/strip-backup/fb6cca43446f-amend-backup.hg (glob)
208 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-amend-backup.hg (glob)
209 209 $ hg book
210 book1 1:0cf1c7a51bcf
211 * book2 1:0cf1c7a51bcf
210 book1 1:6cec5aa930e2
211 * book2 1:6cec5aa930e2
212 212 $ echo a >> a
213 213 $ hg ci --amend -m 'move bookmarks'
214 saved backup bundle to $TESTTMP/.hg/strip-backup/0cf1c7a51bcf-amend-backup.hg (glob)
214 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-amend-backup.hg (glob)
215 215 $ hg book
216 book1 1:7344472bd951
217 * book2 1:7344472bd951
216 book1 1:48bb6e53a15f
217 * book2 1:48bb6e53a15f
218 218
219 219 $ echo '[defaults]' >> $HGRCPATH
220 220 $ echo "commit=-d '0 0'" >> $HGRCPATH
221 221
222 222 Moving branches:
223 223
224 224 $ hg branch foo
225 225 marked working directory as branch foo
226 226 (branches are permanent and global, did you want a bookmark?)
227 227 $ echo a >> a
228 228 $ hg ci -m 'branch foo'
229 229 $ hg branch default -f
230 230 marked working directory as branch default
231 231 (branches are permanent and global, did you want a bookmark?)
232 232 $ hg ci --amend -m 'back to default'
233 saved backup bundle to $TESTTMP/.hg/strip-backup/1661ca36a2db-amend-backup.hg (glob)
233 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-amend-backup.hg (glob)
234 234 $ hg branches
235 default 2:f24ee5961967
235 default 2:ce12b0b57d46
236 236
237 237 Close branch:
238 238
239 239 $ hg up -q 0
240 240 $ echo b >> b
241 241 $ hg branch foo
242 242 marked working directory as branch foo
243 243 (branches are permanent and global, did you want a bookmark?)
244 244 $ hg ci -Am 'fork'
245 245 adding b
246 246 $ echo b >> b
247 247 $ hg ci -mb
248 248 $ hg ci --amend --close-branch -m 'closing branch foo'
249 249 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg (glob)
250 250
251 251 Same thing, different code path:
252 252
253 253 $ echo b >> b
254 254 $ hg ci -m 'reopen branch'
255 255 reopening closed branch head 4
256 256 $ echo b >> b
257 257 $ hg ci --amend --close-branch
258 saved backup bundle to $TESTTMP/.hg/strip-backup/5e302dcc12b8-amend-backup.hg (glob)
258 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-amend-backup.hg (glob)
259 259 $ hg branches
260 default 2:f24ee5961967
260 default 2:ce12b0b57d46
261 261
262 262 Refuse to amend merges:
263 263
264 264 $ hg up -q default
265 265 $ hg merge foo
266 266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 267 (branch merge, don't forget to commit)
268 268 $ hg ci --amend
269 269 abort: cannot amend while merging
270 270 [255]
271 271 $ hg ci -m 'merge'
272 272 $ hg ci --amend
273 273 abort: cannot amend merge changesets
274 274 [255]
275 275
276 276 Follow copies/renames:
277 277
278 278 $ hg mv b c
279 279 $ hg ci -m 'b -> c'
280 280 $ hg mv c d
281 281 $ hg ci --amend -m 'b -> d'
282 saved backup bundle to $TESTTMP/.hg/strip-backup/9c207120aa98-amend-backup.hg (glob)
282 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-amend-backup.hg (glob)
283 283 $ hg st --rev '.^' --copies d
284 284 A d
285 285 b
286 286 $ hg cp d e
287 287 $ hg ci -m 'e = d'
288 288 $ hg cp e f
289 289 $ hg ci --amend -m 'f = d'
290 saved backup bundle to $TESTTMP/.hg/strip-backup/fda2b3b27b22-amend-backup.hg (glob)
290 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-amend-backup.hg (glob)
291 291 $ hg st --rev '.^' --copies f
292 292 A f
293 293 d
294 294
295 295 $ mv f f.orig
296 296 $ hg rm -A f
297 297 $ hg ci -m removef
298 298 $ hg cp a f
299 299 $ mv f.orig f
300 300 $ hg ci --amend -m replacef
301 saved backup bundle to $TESTTMP/.hg/strip-backup/20a7413547f9-amend-backup.hg (glob)
301 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-amend-backup.hg (glob)
302 302 $ hg st --change . --copies
303 303 $ hg log -r . --template "{file_copies}\n"
304 304
305 305
306 306 Move added file (issue3410):
307 307
308 308 $ echo g >> g
309 309 $ hg ci -Am g
310 310 adding g
311 311 $ hg mv g h
312 312 $ hg ci --amend
313 saved backup bundle to $TESTTMP/.hg/strip-backup/5daa77a5d616-amend-backup.hg (glob)
313 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-amend-backup.hg (glob)
314 314 $ hg st --change . --copies h
315 315 A h
316 316 $ hg log -r . --template "{file_copies}\n"
317 317
318 318
319 319 Can't rollback an amend:
320 320
321 321 $ hg rollback
322 322 no rollback information available
323 323 [1]
324 324
325 325 Preserve extra dict (issue3430):
326 326
327 327 $ hg branch a
328 328 marked working directory as branch a
329 329 (branches are permanent and global, did you want a bookmark?)
330 330 $ echo a >> a
331 331 $ hg ci -ma
332 332 $ hg ci --amend -m "a'"
333 saved backup bundle to $TESTTMP/.hg/strip-backup/167f8e3031df-amend-backup.hg (glob)
333 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-amend-backup.hg (glob)
334 334 $ hg log -r . --template "{branch}\n"
335 335 a
336 336 $ hg ci --amend -m "a''"
337 saved backup bundle to $TESTTMP/.hg/strip-backup/ceac1a44c806-amend-backup.hg (glob)
337 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-amend-backup.hg (glob)
338 338 $ hg log -r . --template "{branch}\n"
339 339 a
340 340
341 341 Also preserve other entries in the dict that are in the old commit,
342 342 first graft something so there's an additional entry:
343 343
344 344 $ hg up 0 -q
345 345 $ echo z > z
346 346 $ hg ci -Am 'fork'
347 347 adding z
348 348 created new head
349 349 $ hg up 11
350 350 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
351 351 $ hg graft 12
352 352 grafting revision 12
353 353 $ hg ci --amend -m 'graft amend'
354 saved backup bundle to $TESTTMP/.hg/strip-backup/18a5124daf7a-amend-backup.hg (glob)
354 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-amend-backup.hg (glob)
355 355 $ hg log -r . --debug | grep extra
356 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
356 357 extra: branch=a
357 358 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
358 359
359 360 Preserve phase
360 361
361 362 $ hg phase '.^::.'
362 363 11: draft
363 364 13: draft
364 365 $ hg phase --secret --force .
365 366 $ hg phase '.^::.'
366 367 11: draft
367 368 13: secret
368 369 $ hg commit --amend -m 'amend for phase' -q
369 370 $ hg phase '.^::.'
370 371 11: draft
371 372 13: secret
372 373
373 374 Test amend with obsolete
374 375 ---------------------------
375 376
376 377 Enable obsolete
377 378
378 379 $ cat > ${TESTTMP}/obs.py << EOF
379 380 > import mercurial.obsolete
380 381 > mercurial.obsolete._enabled = True
381 382 > EOF
382 383 $ echo '[extensions]' >> $HGRCPATH
383 384 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
384 385
385 386
386 387 Amend with no files changes
387 388
388 389 $ hg id -n
389 390 13
390 391 $ hg ci --amend -m 'babar'
391 392 $ hg id -n
392 393 14
393 394 $ hg log -Gl 3 --style=compact
394 @ 14[tip]:11 43df5a5434ad 1970-01-01 00:00 +0000 test
395 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
395 396 | babar
396 397 |
397 398 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
398 399 | | fork
399 400 | |
400 o | 11 7e09f708a0e9 1970-01-01 00:00 +0000 test
401 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
401 402 | | a''
402 403 | |
403 404 $ hg log -Gl 4 --hidden --style=compact
404 @ 14[tip]:11 43df5a5434ad 1970-01-01 00:00 +0000 test
405 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
405 406 | babar
406 407 |
407 | x 13:11 175fafee6f44 1970-01-01 00:00 +0000 test
408 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
408 409 |/ amend for phase
409 410 |
410 411 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
411 412 | | fork
412 413 | |
413 o | 11 7e09f708a0e9 1970-01-01 00:00 +0000 test
414 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
414 415 | | a''
415 416 | |
416 417
417 418 Amend with files changes
418 419
419 420 (note: the extra commit over 15 is a temporary junk I would be happy to get
420 421 ride of)
421 422
422 423 $ echo 'babar' >> a
423 424 $ hg commit --amend
424 425 $ hg log -Gl 6 --hidden --style=compact
425 @ 16[tip]:11 31e0a4a1b04a 1970-01-01 00:00 +0000 test
426 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
426 427 | babar
427 428 |
428 | x 15 053c696ada75 1970-01-01 00:00 +0000 test
429 | | temporary amend commit for 43df5a5434ad
429 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
430 | | temporary amend commit for b650e6ee8614
430 431 | |
431 | x 14:11 43df5a5434ad 1970-01-01 00:00 +0000 test
432 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
432 433 |/ babar
433 434 |
434 | x 13:11 175fafee6f44 1970-01-01 00:00 +0000 test
435 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
435 436 |/ amend for phase
436 437 |
437 438 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
438 439 | | fork
439 440 | |
440 o | 11 7e09f708a0e9 1970-01-01 00:00 +0000 test
441 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
441 442 | | a''
442 443 | |
443 444
444 445
446 Test that amend does not make it easy to create obsoletescence cycle
447 ---------------------------------------------------------------------
448
449
450 $ hg id -r 14
451 b650e6ee8614 (a)
452 $ hg revert -ar 14
453 reverting a
454 $ hg commit --amend
455 $ hg id
456 b99e5df575f7 (a) tip
@@ -1,1139 +1,1139 b''
1 1 $ cat <<EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > keyword =
4 4 > mq =
5 5 > notify =
6 6 > record =
7 7 > transplant =
8 8 > [ui]
9 9 > interactive = true
10 10 > EOF
11 11
12 12 hide outer repo
13 13 $ hg init
14 14
15 15 Run kwdemo before [keyword] files are set up
16 16 as it would succeed without uisetup otherwise
17 17
18 18 $ hg --quiet kwdemo
19 19 [extensions]
20 20 keyword =
21 21 [keyword]
22 22 demo.txt =
23 23 [keywordset]
24 24 svn = False
25 25 [keywordmaps]
26 26 Author = {author|user}
27 27 Date = {date|utcdate}
28 28 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
29 29 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
30 30 RCSFile = {file|basename},v
31 31 RCSfile = {file|basename},v
32 32 Revision = {node|short}
33 33 Source = {root}/{file},v
34 34 $Author: test $
35 35 $Date: ????/??/?? ??:??:?? $ (glob)
36 36 $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
37 37 $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
38 38 $RCSFile: demo.txt,v $
39 39 $RCSfile: demo.txt,v $
40 40 $Revision: ???????????? $ (glob)
41 41 $Source: */demo.txt,v $ (glob)
42 42
43 43 $ hg --quiet kwdemo "Branch = {branches}"
44 44 [extensions]
45 45 keyword =
46 46 [keyword]
47 47 demo.txt =
48 48 [keywordset]
49 49 svn = False
50 50 [keywordmaps]
51 51 Branch = {branches}
52 52 $Branch: demobranch $
53 53
54 54 $ cat <<EOF >> $HGRCPATH
55 55 > [keyword]
56 56 > ** =
57 57 > b = ignore
58 58 > i = ignore
59 59 > [hooks]
60 60 > EOF
61 61 $ cp $HGRCPATH $HGRCPATH.nohooks
62 62 > cat <<EOF >> $HGRCPATH
63 63 > commit=
64 64 > commit.test=cp a hooktest
65 65 > EOF
66 66
67 67 $ hg init Test-bndl
68 68 $ cd Test-bndl
69 69
70 70 kwshrink should exit silently in empty/invalid repo
71 71
72 72 $ hg kwshrink
73 73
74 74 Symlinks cannot be created on Windows.
75 75 A bundle to test this was made with:
76 76 hg init t
77 77 cd t
78 78 echo a > a
79 79 ln -s a sym
80 80 hg add sym
81 81 hg ci -m addsym -u mercurial
82 82 hg bundle --base null ../test-keyword.hg
83 83
84 84 $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg
85 85 pulling from *test-keyword.hg (glob)
86 86 requesting all changes
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 1 changes to 1 files
91 91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 92
93 93 $ echo 'expand $Id$' > a
94 94 $ echo 'do not process $Id:' >> a
95 95 $ echo 'xxx $' >> a
96 96 $ echo 'ignore $Id$' > b
97 97
98 98 Output files as they were created
99 99
100 100 $ cat a b
101 101 expand $Id$
102 102 do not process $Id:
103 103 xxx $
104 104 ignore $Id$
105 105
106 106 no kwfiles
107 107
108 108 $ hg kwfiles
109 109
110 110 untracked candidates
111 111
112 112 $ hg -v kwfiles --unknown
113 113 k a
114 114
115 115 Add files and check status
116 116
117 117 $ hg addremove
118 118 adding a
119 119 adding b
120 120 $ hg status
121 121 A a
122 122 A b
123 123
124 124
125 125 Default keyword expansion including commit hook
126 126 Interrupted commit should not change state or run commit hook
127 127
128 128 $ hg --debug commit
129 129 abort: empty commit message
130 130 [255]
131 131 $ hg status
132 132 A a
133 133 A b
134 134
135 135 Commit with several checks
136 136
137 137 $ hg --debug commit -mabsym -u 'User Name <user@example.com>'
138 138 a
139 139 b
140 140 overwriting a expanding keywords
141 141 running hook commit.test: cp a hooktest
142 142 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
143 143 $ hg status
144 144 ? hooktest
145 145 $ hg debugrebuildstate
146 146 $ hg --quiet identify
147 147 ef63ca68695b
148 148
149 149 cat files in working directory with keywords expanded
150 150
151 151 $ cat a b
152 152 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
153 153 do not process $Id:
154 154 xxx $
155 155 ignore $Id$
156 156
157 157 hg cat files and symlink, no expansion
158 158
159 159 $ hg cat sym a b && echo
160 160 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
161 161 do not process $Id:
162 162 xxx $
163 163 ignore $Id$
164 164 a
165 165
166 166 $ diff a hooktest
167 167
168 168 $ cp $HGRCPATH.nohooks $HGRCPATH
169 169 $ rm hooktest
170 170
171 171 hg status of kw-ignored binary file starting with '\1\n'
172 172
173 173 >>> open("i", "wb").write("\1\nfoo")
174 174 $ hg -q commit -Am metasep i
175 175 $ hg status
176 176 >>> open("i", "wb").write("\1\nbar")
177 177 $ hg status
178 178 M i
179 179 $ hg -q commit -m "modify metasep" i
180 180 $ hg status --rev 2:3
181 181 M i
182 182 $ touch empty
183 183 $ hg -q commit -A -m "another file"
184 184 $ hg status -A --rev 3:4 i
185 185 C i
186 186
187 187 $ hg -q strip -n 2
188 188
189 189 Test hook execution
190 190
191 191 bundle
192 192
193 193 $ hg bundle --base null ../kw.hg
194 194 2 changesets found
195 195 $ cd ..
196 196 $ hg init Test
197 197 $ cd Test
198 198
199 199 Notify on pull to check whether keywords stay as is in email
200 200 ie. if patch.diff wrapper acts as it should
201 201
202 202 $ cat <<EOF >> $HGRCPATH
203 203 > [hooks]
204 204 > incoming.notify = python:hgext.notify.hook
205 205 > [notify]
206 206 > sources = pull
207 207 > diffstat = False
208 208 > maxsubject = 15
209 209 > [reposubs]
210 210 > * = Test
211 211 > EOF
212 212
213 213 Pull from bundle and trigger notify
214 214
215 215 $ hg pull -u ../kw.hg
216 216 pulling from ../kw.hg
217 217 requesting all changes
218 218 adding changesets
219 219 adding manifests
220 220 adding file changes
221 221 added 2 changesets with 3 changes to 3 files
222 222 Content-Type: text/plain; charset="us-ascii"
223 223 MIME-Version: 1.0
224 224 Content-Transfer-Encoding: 7bit
225 225 Date: * (glob)
226 226 Subject: changeset in...
227 227 From: mercurial
228 228 X-Hg-Notification: changeset a2392c293916
229 229 Message-Id: <hg.a2392c293916*> (glob)
230 230 To: Test
231 231
232 232 changeset a2392c293916 in $TESTTMP/Test (glob)
233 233 details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
234 234 description:
235 235 addsym
236 236
237 237 diffs (6 lines):
238 238
239 239 diff -r 000000000000 -r a2392c293916 sym
240 240 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
241 241 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
242 242 @@ -0,0 +1,1 @@
243 243 +a
244 244 \ No newline at end of file
245 245 Content-Type: text/plain; charset="us-ascii"
246 246 MIME-Version: 1.0
247 247 Content-Transfer-Encoding: 7bit
248 248 Date:* (glob)
249 249 Subject: changeset in...
250 250 From: User Name <user@example.com>
251 251 X-Hg-Notification: changeset ef63ca68695b
252 252 Message-Id: <hg.ef63ca68695b*> (glob)
253 253 To: Test
254 254
255 255 changeset ef63ca68695b in $TESTTMP/Test (glob)
256 256 details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b
257 257 description:
258 258 absym
259 259
260 260 diffs (12 lines):
261 261
262 262 diff -r a2392c293916 -r ef63ca68695b a
263 263 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
264 264 +++ b/a Thu Jan 01 00:00:00 1970 +0000
265 265 @@ -0,0 +1,3 @@
266 266 +expand $Id$
267 267 +do not process $Id:
268 268 +xxx $
269 269 diff -r a2392c293916 -r ef63ca68695b b
270 270 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
271 271 +++ b/b Thu Jan 01 00:00:00 1970 +0000
272 272 @@ -0,0 +1,1 @@
273 273 +ignore $Id$
274 274 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 275
276 276 $ cp $HGRCPATH.nohooks $HGRCPATH
277 277
278 278 Touch files and check with status
279 279
280 280 $ touch a b
281 281 $ hg status
282 282
283 283 Update and expand
284 284
285 285 $ rm sym a b
286 286 $ hg update -C
287 287 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 288 $ cat a b
289 289 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
290 290 do not process $Id:
291 291 xxx $
292 292 ignore $Id$
293 293
294 294 Check whether expansion is filewise and file mode is preserved
295 295
296 296 $ echo '$Id$' > c
297 297 $ echo 'tests for different changenodes' >> c
298 298 #if unix-permissions
299 299 $ chmod 600 c
300 300 $ ls -l c | cut -b 1-10
301 301 -rw-------
302 302 #endif
303 303
304 304 commit file c
305 305
306 306 $ hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
307 307 adding c
308 308 #if unix-permissions
309 309 $ ls -l c | cut -b 1-10
310 310 -rw-------
311 311 #endif
312 312
313 313 force expansion
314 314
315 315 $ hg -v kwexpand
316 316 overwriting a expanding keywords
317 317 overwriting c expanding keywords
318 318
319 319 compare changenodes in a and c
320 320
321 321 $ cat a c
322 322 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
323 323 do not process $Id:
324 324 xxx $
325 325 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
326 326 tests for different changenodes
327 327
328 328 record
329 329
330 330 $ echo '$Id$' > r
331 331 $ hg add r
332 332
333 333 record chunk
334 334
335 335 >>> lines = open('a', 'rb').readlines()
336 336 >>> lines.insert(1, 'foo\n')
337 337 >>> lines.append('bar\n')
338 338 >>> open('a', 'wb').writelines(lines)
339 339 $ hg record -d '10 1' -m rectest a<<EOF
340 340 > y
341 341 > y
342 342 > n
343 343 > EOF
344 344 diff --git a/a b/a
345 345 2 hunks, 2 lines changed
346 346 examine changes to 'a'? [Ynesfdaq?]
347 347 @@ -1,3 +1,4 @@
348 348 expand $Id$
349 349 +foo
350 350 do not process $Id:
351 351 xxx $
352 352 record change 1/2 to 'a'? [Ynesfdaq?]
353 353 @@ -2,2 +3,3 @@
354 354 do not process $Id:
355 355 xxx $
356 356 +bar
357 357 record change 2/2 to 'a'? [Ynesfdaq?]
358 358
359 359 $ hg identify
360 360 5f5eb23505c3+ tip
361 361 $ hg status
362 362 M a
363 363 A r
364 364
365 365 Cat modified file a
366 366
367 367 $ cat a
368 368 expand $Id: a,v 5f5eb23505c3 1970/01/01 00:00:10 test $
369 369 foo
370 370 do not process $Id:
371 371 xxx $
372 372 bar
373 373
374 374 Diff remaining chunk
375 375
376 376 $ hg diff a
377 377 diff -r 5f5eb23505c3 a
378 378 --- a/a Thu Jan 01 00:00:09 1970 -0000
379 379 +++ b/a * (glob)
380 380 @@ -2,3 +2,4 @@
381 381 foo
382 382 do not process $Id:
383 383 xxx $
384 384 +bar
385 385
386 386 $ hg rollback
387 387 repository tip rolled back to revision 2 (undo commit)
388 388 working directory now based on revision 2
389 389
390 390 Record all chunks in file a
391 391
392 392 $ echo foo > msg
393 393
394 394 - do not use "hg record -m" here!
395 395
396 396 $ hg record -l msg -d '11 1' a<<EOF
397 397 > y
398 398 > y
399 399 > y
400 400 > EOF
401 401 diff --git a/a b/a
402 402 2 hunks, 2 lines changed
403 403 examine changes to 'a'? [Ynesfdaq?]
404 404 @@ -1,3 +1,4 @@
405 405 expand $Id$
406 406 +foo
407 407 do not process $Id:
408 408 xxx $
409 409 record change 1/2 to 'a'? [Ynesfdaq?]
410 410 @@ -2,2 +3,3 @@
411 411 do not process $Id:
412 412 xxx $
413 413 +bar
414 414 record change 2/2 to 'a'? [Ynesfdaq?]
415 415
416 416 File a should be clean
417 417
418 418 $ hg status -A a
419 419 C a
420 420
421 421 rollback and revert expansion
422 422
423 423 $ cat a
424 424 expand $Id: a,v 78e0a02d76aa 1970/01/01 00:00:11 test $
425 425 foo
426 426 do not process $Id:
427 427 xxx $
428 428 bar
429 429 $ hg --verbose rollback
430 430 repository tip rolled back to revision 2 (undo commit)
431 431 working directory now based on revision 2
432 432 overwriting a expanding keywords
433 433 $ hg status a
434 434 M a
435 435 $ cat a
436 436 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
437 437 foo
438 438 do not process $Id:
439 439 xxx $
440 440 bar
441 441 $ echo '$Id$' > y
442 442 $ echo '$Id$' > z
443 443 $ hg add y
444 444 $ hg commit -Am "rollback only" z
445 445 $ cat z
446 446 $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $
447 447 $ hg --verbose rollback
448 448 repository tip rolled back to revision 2 (undo commit)
449 449 working directory now based on revision 2
450 450 overwriting z shrinking keywords
451 451
452 452 Only z should be overwritten
453 453
454 454 $ hg status a y z
455 455 M a
456 456 A y
457 457 A z
458 458 $ cat z
459 459 $Id$
460 460 $ hg forget y z
461 461 $ rm y z
462 462
463 463 record added file alone
464 464
465 465 $ hg -v record -l msg -d '12 2' r<<EOF
466 466 > y
467 467 > EOF
468 468 diff --git a/r b/r
469 469 new file mode 100644
470 470 examine changes to 'r'? [Ynesfdaq?]
471 471 r
472 472 committed changeset 3:82a2f715724d
473 473 overwriting r expanding keywords
474 474 - status call required for dirstate.normallookup() check
475 475 $ hg status r
476 476 $ hg --verbose rollback
477 477 repository tip rolled back to revision 2 (undo commit)
478 478 working directory now based on revision 2
479 479 overwriting r shrinking keywords
480 480 $ hg forget r
481 481 $ rm msg r
482 482 $ hg update -C
483 483 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 484
485 485 record added keyword ignored file
486 486
487 487 $ echo '$Id$' > i
488 488 $ hg add i
489 489 $ hg --verbose record -d '13 1' -m recignored<<EOF
490 490 > y
491 491 > EOF
492 492 diff --git a/i b/i
493 493 new file mode 100644
494 494 examine changes to 'i'? [Ynesfdaq?]
495 495 i
496 496 committed changeset 3:9f40ceb5a072
497 497 $ cat i
498 498 $Id$
499 499 $ hg -q rollback
500 500 $ hg forget i
501 501 $ rm i
502 502
503 503 amend
504 504
505 505 $ echo amend >> a
506 506 $ echo amend >> b
507 507 $ hg -q commit -d '14 1' -m 'prepare amend'
508 508
509 509 $ hg --debug commit --amend -d '15 1' -m 'amend without changes' | grep keywords
510 510 overwriting a expanding keywords
511 511 $ hg -q id
512 577e60613a88
512 67d8c481a6be
513 513 $ head -1 a
514 expand $Id: a,v 577e60613a88 1970/01/01 00:00:15 test $
514 expand $Id: a,v 67d8c481a6be 1970/01/01 00:00:15 test $
515 515
516 516 $ hg -q strip -n tip
517 517
518 518 Test patch queue repo
519 519
520 520 $ hg init --mq
521 521 $ hg qimport -r tip -n mqtest.diff
522 522 $ hg commit --mq -m mqtest
523 523
524 524 Keywords should not be expanded in patch
525 525
526 526 $ cat .hg/patches/mqtest.diff
527 527 # HG changeset patch
528 528 # User User Name <user@example.com>
529 529 # Date 1 0
530 530 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
531 531 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
532 532 cndiff
533 533
534 534 diff -r ef63ca68695b -r 40a904bbbe4c c
535 535 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
536 536 +++ b/c Thu Jan 01 00:00:01 1970 +0000
537 537 @@ -0,0 +1,2 @@
538 538 +$Id$
539 539 +tests for different changenodes
540 540
541 541 $ hg qpop
542 542 popping mqtest.diff
543 543 patch queue now empty
544 544
545 545 qgoto, implying qpush, should expand
546 546
547 547 $ hg qgoto mqtest.diff
548 548 applying mqtest.diff
549 549 now at: mqtest.diff
550 550 $ cat c
551 551 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
552 552 tests for different changenodes
553 553 $ hg cat c
554 554 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
555 555 tests for different changenodes
556 556
557 557 Keywords should not be expanded in filelog
558 558
559 559 $ hg --config 'extensions.keyword=!' cat c
560 560 $Id$
561 561 tests for different changenodes
562 562
563 563 qpop and move on
564 564
565 565 $ hg qpop
566 566 popping mqtest.diff
567 567 patch queue now empty
568 568
569 569 Copy and show added kwfiles
570 570
571 571 $ hg cp a c
572 572 $ hg kwfiles
573 573 a
574 574 c
575 575
576 576 Commit and show expansion in original and copy
577 577
578 578 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
579 579 c
580 580 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
581 581 removing unknown node 40a904bbbe4c from 1-phase boundary
582 582 overwriting c expanding keywords
583 583 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
584 584 $ cat a c
585 585 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
586 586 do not process $Id:
587 587 xxx $
588 588 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
589 589 do not process $Id:
590 590 xxx $
591 591
592 592 Touch copied c and check its status
593 593
594 594 $ touch c
595 595 $ hg status
596 596
597 597 Copy kwfile to keyword ignored file unexpanding keywords
598 598
599 599 $ hg --verbose copy a i
600 600 copying a to i
601 601 overwriting i shrinking keywords
602 602 $ head -n 1 i
603 603 expand $Id$
604 604 $ hg forget i
605 605 $ rm i
606 606
607 607 Copy ignored file to ignored file: no overwriting
608 608
609 609 $ hg --verbose copy b i
610 610 copying b to i
611 611 $ hg forget i
612 612 $ rm i
613 613
614 614 cp symlink file; hg cp -A symlink file (part1)
615 615 - copied symlink points to kwfile: overwrite
616 616
617 617 #if symlink
618 618 $ cp sym i
619 619 $ ls -l i
620 620 -rw-r--r--* (glob)
621 621 $ head -1 i
622 622 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
623 623 $ hg copy --after --verbose sym i
624 624 copying sym to i
625 625 overwriting i shrinking keywords
626 626 $ head -1 i
627 627 expand $Id$
628 628 $ hg forget i
629 629 $ rm i
630 630 #endif
631 631
632 632 Test different options of hg kwfiles
633 633
634 634 $ hg kwfiles
635 635 a
636 636 c
637 637 $ hg -v kwfiles --ignore
638 638 I b
639 639 I sym
640 640 $ hg kwfiles --all
641 641 K a
642 642 K c
643 643 I b
644 644 I sym
645 645
646 646 Diff specific revision
647 647
648 648 $ hg diff --rev 1
649 649 diff -r ef63ca68695b c
650 650 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
651 651 +++ b/c * (glob)
652 652 @@ -0,0 +1,3 @@
653 653 +expand $Id$
654 654 +do not process $Id:
655 655 +xxx $
656 656
657 657 Status after rollback:
658 658
659 659 $ hg rollback
660 660 repository tip rolled back to revision 1 (undo commit)
661 661 working directory now based on revision 1
662 662 $ hg status
663 663 A c
664 664 $ hg update --clean
665 665 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
666 666
667 667 #if symlink
668 668
669 669 cp symlink file; hg cp -A symlink file (part2)
670 670 - copied symlink points to kw ignored file: do not overwrite
671 671
672 672 $ cat a > i
673 673 $ ln -s i symignored
674 674 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
675 675 $ cp symignored x
676 676 $ hg copy --after --verbose symignored x
677 677 copying symignored to x
678 678 $ head -n 1 x
679 679 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
680 680 $ hg forget x
681 681 $ rm x
682 682
683 683 $ hg rollback
684 684 repository tip rolled back to revision 1 (undo commit)
685 685 working directory now based on revision 1
686 686 $ hg update --clean
687 687 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
688 688 $ rm i symignored
689 689
690 690 #endif
691 691
692 692 Custom keywordmaps as argument to kwdemo
693 693
694 694 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
695 695 [extensions]
696 696 keyword =
697 697 [keyword]
698 698 ** =
699 699 b = ignore
700 700 demo.txt =
701 701 i = ignore
702 702 [keywordset]
703 703 svn = False
704 704 [keywordmaps]
705 705 Xinfo = {author}: {desc}
706 706 $Xinfo: test: hg keyword configuration and expansion example $
707 707
708 708 Configure custom keywordmaps
709 709
710 710 $ cat <<EOF >>$HGRCPATH
711 711 > [keywordmaps]
712 712 > Id = {file} {node|short} {date|rfc822date} {author|user}
713 713 > Xinfo = {author}: {desc}
714 714 > EOF
715 715
716 716 Cat and hg cat files before custom expansion
717 717
718 718 $ cat a b
719 719 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
720 720 do not process $Id:
721 721 xxx $
722 722 ignore $Id$
723 723 $ hg cat sym a b && echo
724 724 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
725 725 do not process $Id:
726 726 xxx $
727 727 ignore $Id$
728 728 a
729 729
730 730 Write custom keyword and prepare multi-line commit message
731 731
732 732 $ echo '$Xinfo$' >> a
733 733 $ cat <<EOF >> log
734 734 > firstline
735 735 > secondline
736 736 > EOF
737 737
738 738 Interrupted commit should not change state
739 739
740 740 $ hg commit
741 741 abort: empty commit message
742 742 [255]
743 743 $ hg status
744 744 M a
745 745 ? c
746 746 ? log
747 747
748 748 Commit with multi-line message and custom expansion
749 749
750 750 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
751 751 a
752 752 removing unknown node 40a904bbbe4c from 1-phase boundary
753 753 overwriting a expanding keywords
754 754 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
755 755 $ rm log
756 756
757 757 Stat, verify and show custom expansion (firstline)
758 758
759 759 $ hg status
760 760 ? c
761 761 $ hg verify
762 762 checking changesets
763 763 checking manifests
764 764 crosschecking files in changesets and manifests
765 765 checking files
766 766 3 files, 3 changesets, 4 total revisions
767 767 $ cat a b
768 768 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
769 769 do not process $Id:
770 770 xxx $
771 771 $Xinfo: User Name <user@example.com>: firstline $
772 772 ignore $Id$
773 773 $ hg cat sym a b && echo
774 774 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
775 775 do not process $Id:
776 776 xxx $
777 777 $Xinfo: User Name <user@example.com>: firstline $
778 778 ignore $Id$
779 779 a
780 780
781 781 annotate
782 782
783 783 $ hg annotate a
784 784 1: expand $Id$
785 785 1: do not process $Id:
786 786 1: xxx $
787 787 2: $Xinfo$
788 788
789 789 remove with status checks
790 790
791 791 $ hg debugrebuildstate
792 792 $ hg remove a
793 793 $ hg --debug commit -m rma
794 794 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
795 795 $ hg status
796 796 ? c
797 797
798 798 Rollback, revert, and check expansion
799 799
800 800 $ hg rollback
801 801 repository tip rolled back to revision 2 (undo commit)
802 802 working directory now based on revision 2
803 803 $ hg status
804 804 R a
805 805 ? c
806 806 $ hg revert --no-backup --rev tip a
807 807 $ cat a
808 808 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
809 809 do not process $Id:
810 810 xxx $
811 811 $Xinfo: User Name <user@example.com>: firstline $
812 812
813 813 Clone to test global and local configurations
814 814
815 815 $ cd ..
816 816
817 817 Expansion in destination with global configuration
818 818
819 819 $ hg --quiet clone Test globalconf
820 820 $ cat globalconf/a
821 821 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
822 822 do not process $Id:
823 823 xxx $
824 824 $Xinfo: User Name <user@example.com>: firstline $
825 825
826 826 No expansion in destination with local configuration in origin only
827 827
828 828 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
829 829 $ cat localconf/a
830 830 expand $Id$
831 831 do not process $Id:
832 832 xxx $
833 833 $Xinfo$
834 834
835 835 Clone to test incoming
836 836
837 837 $ hg clone -r1 Test Test-a
838 838 adding changesets
839 839 adding manifests
840 840 adding file changes
841 841 added 2 changesets with 3 changes to 3 files
842 842 updating to branch default
843 843 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 844 $ cd Test-a
845 845 $ cat <<EOF >> .hg/hgrc
846 846 > [paths]
847 847 > default = ../Test
848 848 > EOF
849 849 $ hg incoming
850 850 comparing with $TESTTMP/Test (glob)
851 851 searching for changes
852 852 changeset: 2:bb948857c743
853 853 tag: tip
854 854 user: User Name <user@example.com>
855 855 date: Thu Jan 01 00:00:02 1970 +0000
856 856 summary: firstline
857 857
858 858 Imported patch should not be rejected
859 859
860 860 >>> import re
861 861 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
862 862 >>> open('a', 'wb').write(text)
863 863 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
864 864 a
865 865 overwriting a expanding keywords
866 866 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
867 867 $ hg export -o ../rejecttest.diff tip
868 868 $ cd ../Test
869 869 $ hg import ../rejecttest.diff
870 870 applying ../rejecttest.diff
871 871 $ cat a b
872 872 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
873 873 do not process $Id: rejecttest
874 874 xxx $
875 875 $Xinfo: User Name <user@example.com>: rejects? $
876 876 ignore $Id$
877 877
878 878 $ hg rollback
879 879 repository tip rolled back to revision 2 (undo import)
880 880 working directory now based on revision 2
881 881 $ hg update --clean
882 882 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
883 883
884 884 kwexpand/kwshrink on selected files
885 885
886 886 $ mkdir x
887 887 $ hg copy a x/a
888 888 $ hg --verbose kwshrink a
889 889 overwriting a shrinking keywords
890 890 - sleep required for dirstate.normal() check
891 891 $ sleep 1
892 892 $ hg status a
893 893 $ hg --verbose kwexpand a
894 894 overwriting a expanding keywords
895 895 $ hg status a
896 896
897 897 kwexpand x/a should abort
898 898
899 899 $ hg --verbose kwexpand x/a
900 900 abort: outstanding uncommitted changes
901 901 [255]
902 902 $ cd x
903 903 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
904 904 x/a
905 905 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
906 906 overwriting x/a expanding keywords
907 907 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
908 908 $ cat a
909 909 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
910 910 do not process $Id:
911 911 xxx $
912 912 $Xinfo: User Name <user@example.com>: xa $
913 913
914 914 kwshrink a inside directory x
915 915
916 916 $ hg --verbose kwshrink a
917 917 overwriting x/a shrinking keywords
918 918 $ cat a
919 919 expand $Id$
920 920 do not process $Id:
921 921 xxx $
922 922 $Xinfo$
923 923 $ cd ..
924 924
925 925 kwexpand nonexistent
926 926
927 927 $ hg kwexpand nonexistent
928 928 nonexistent:* (glob)
929 929
930 930
931 931 #if serve
932 932 hg serve
933 933 - expand with hgweb file
934 934 - no expansion with hgweb annotate/changeset/filediff
935 935 - check errors
936 936
937 937 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
938 938 $ cat hg.pid >> $DAEMON_PIDS
939 939 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
940 940 200 Script output follows
941 941
942 942 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
943 943 do not process $Id:
944 944 xxx $
945 945 $Xinfo: User Name <user@example.com>: firstline $
946 946 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
947 947 200 Script output follows
948 948
949 949
950 950 user@1: expand $Id$
951 951 user@1: do not process $Id:
952 952 user@1: xxx $
953 953 user@2: $Xinfo$
954 954
955 955
956 956
957 957
958 958 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
959 959 200 Script output follows
960 960
961 961
962 962 # HG changeset patch
963 963 # User User Name <user@example.com>
964 964 # Date 3 0
965 965 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
966 966 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
967 967 xa
968 968
969 969 diff -r bb948857c743 -r b4560182a3f9 x/a
970 970 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
971 971 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
972 972 @@ -0,0 +1,4 @@
973 973 +expand $Id$
974 974 +do not process $Id:
975 975 +xxx $
976 976 +$Xinfo$
977 977
978 978 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
979 979 200 Script output follows
980 980
981 981
982 982 diff -r ef63ca68695b -r bb948857c743 a
983 983 --- a/a Thu Jan 01 00:00:00 1970 +0000
984 984 +++ b/a Thu Jan 01 00:00:02 1970 +0000
985 985 @@ -1,3 +1,4 @@
986 986 expand $Id$
987 987 do not process $Id:
988 988 xxx $
989 989 +$Xinfo$
990 990
991 991
992 992
993 993
994 994 $ cat errors.log
995 995 #endif
996 996
997 997 Prepare merge and resolve tests
998 998
999 999 $ echo '$Id$' > m
1000 1000 $ hg add m
1001 1001 $ hg commit -m 4kw
1002 1002 $ echo foo >> m
1003 1003 $ hg commit -m 5foo
1004 1004
1005 1005 simplemerge
1006 1006
1007 1007 $ hg update 4
1008 1008 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1009 1009 $ echo foo >> m
1010 1010 $ hg commit -m 6foo
1011 1011 created new head
1012 1012 $ hg merge
1013 1013 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1014 1014 (branch merge, don't forget to commit)
1015 1015 $ hg commit -m simplemerge
1016 1016 $ cat m
1017 1017 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
1018 1018 foo
1019 1019
1020 1020 conflict: keyword should stay outside conflict zone
1021 1021
1022 1022 $ hg update 4
1023 1023 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1024 1024 $ echo bar >> m
1025 1025 $ hg commit -m 8bar
1026 1026 created new head
1027 1027 $ hg merge
1028 1028 merging m
1029 1029 warning: conflicts during merge.
1030 1030 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1031 1031 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1032 1032 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1033 1033 [1]
1034 1034 $ cat m
1035 1035 $Id$
1036 1036 <<<<<<< local
1037 1037 bar
1038 1038 =======
1039 1039 foo
1040 1040 >>>>>>> other
1041 1041
1042 1042 resolve to local
1043 1043
1044 1044 $ HGMERGE=internal:local hg resolve -a
1045 1045 $ hg commit -m localresolve
1046 1046 $ cat m
1047 1047 $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $
1048 1048 bar
1049 1049
1050 1050 Test restricted mode with transplant -b
1051 1051
1052 1052 $ hg update 6
1053 1053 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1054 1054 $ hg branch foo
1055 1055 marked working directory as branch foo
1056 1056 (branches are permanent and global, did you want a bookmark?)
1057 1057 $ mv a a.bak
1058 1058 $ echo foobranch > a
1059 1059 $ cat a.bak >> a
1060 1060 $ rm a.bak
1061 1061 $ hg commit -m 9foobranch
1062 1062 $ hg update default
1063 1063 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1064 1064 $ hg -y transplant -b foo tip
1065 1065 applying 4aa30d025d50
1066 1066 4aa30d025d50 transplanted to e00abbf63521
1067 1067
1068 1068 Expansion in changeset but not in file
1069 1069
1070 1070 $ hg tip -p
1071 1071 changeset: 11:e00abbf63521
1072 1072 tag: tip
1073 1073 parent: 9:800511b3a22d
1074 1074 user: test
1075 1075 date: Thu Jan 01 00:00:00 1970 +0000
1076 1076 summary: 9foobranch
1077 1077
1078 1078 diff -r 800511b3a22d -r e00abbf63521 a
1079 1079 --- a/a Thu Jan 01 00:00:00 1970 +0000
1080 1080 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1081 1081 @@ -1,3 +1,4 @@
1082 1082 +foobranch
1083 1083 expand $Id$
1084 1084 do not process $Id:
1085 1085 xxx $
1086 1086
1087 1087 $ head -n 2 a
1088 1088 foobranch
1089 1089 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1090 1090
1091 1091 Turn off expansion
1092 1092
1093 1093 $ hg -q rollback
1094 1094 $ hg -q update -C
1095 1095
1096 1096 kwshrink with unknown file u
1097 1097
1098 1098 $ cp a u
1099 1099 $ hg --verbose kwshrink
1100 1100 overwriting a shrinking keywords
1101 1101 overwriting m shrinking keywords
1102 1102 overwriting x/a shrinking keywords
1103 1103
1104 1104 Keywords shrunk in working directory, but not yet disabled
1105 1105 - cat shows unexpanded keywords
1106 1106 - hg cat shows expanded keywords
1107 1107
1108 1108 $ cat a b
1109 1109 expand $Id$
1110 1110 do not process $Id:
1111 1111 xxx $
1112 1112 $Xinfo$
1113 1113 ignore $Id$
1114 1114 $ hg cat sym a b && echo
1115 1115 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1116 1116 do not process $Id:
1117 1117 xxx $
1118 1118 $Xinfo: User Name <user@example.com>: firstline $
1119 1119 ignore $Id$
1120 1120 a
1121 1121
1122 1122 Now disable keyword expansion
1123 1123
1124 1124 $ rm "$HGRCPATH"
1125 1125 $ cat a b
1126 1126 expand $Id$
1127 1127 do not process $Id:
1128 1128 xxx $
1129 1129 $Xinfo$
1130 1130 ignore $Id$
1131 1131 $ hg cat sym a b && echo
1132 1132 expand $Id$
1133 1133 do not process $Id:
1134 1134 xxx $
1135 1135 $Xinfo$
1136 1136 ignore $Id$
1137 1137 a
1138 1138
1139 1139 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now