##// END OF EJS Templates
log: display closing-branch nodes as "_" (BC)...
Jordi Gutiérrez Hermoso -
r24216:4bb348ae default
parent child Browse files
Show More
@@ -1,2965 +1,2967
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import encoding
17 17 import lock as lockmod
18 18
19 19 def parsealiases(cmd):
20 20 return cmd.lstrip("^").split("|")
21 21
22 22 def findpossible(cmd, table, strict=False):
23 23 """
24 24 Return cmd -> (aliases, command table entry)
25 25 for each matching command.
26 26 Return debug commands (or their aliases) only if no normal command matches.
27 27 """
28 28 choice = {}
29 29 debugchoice = {}
30 30
31 31 if cmd in table:
32 32 # short-circuit exact matches, "log" alias beats "^log|history"
33 33 keys = [cmd]
34 34 else:
35 35 keys = table.keys()
36 36
37 37 for e in keys:
38 38 aliases = parsealiases(e)
39 39 found = None
40 40 if cmd in aliases:
41 41 found = cmd
42 42 elif not strict:
43 43 for a in aliases:
44 44 if a.startswith(cmd):
45 45 found = a
46 46 break
47 47 if found is not None:
48 48 if aliases[0].startswith("debug") or found.startswith("debug"):
49 49 debugchoice[found] = (aliases, table[e])
50 50 else:
51 51 choice[found] = (aliases, table[e])
52 52
53 53 if not choice and debugchoice:
54 54 choice = debugchoice
55 55
56 56 return choice
57 57
58 58 def findcmd(cmd, table, strict=True):
59 59 """Return (aliases, command table entry) for command string."""
60 60 choice = findpossible(cmd, table, strict)
61 61
62 62 if cmd in choice:
63 63 return choice[cmd]
64 64
65 65 if len(choice) > 1:
66 66 clist = choice.keys()
67 67 clist.sort()
68 68 raise error.AmbiguousCommand(cmd, clist)
69 69
70 70 if choice:
71 71 return choice.values()[0]
72 72
73 73 raise error.UnknownCommand(cmd)
74 74
75 75 def findrepo(p):
76 76 while not os.path.isdir(os.path.join(p, ".hg")):
77 77 oldp, p = p, os.path.dirname(p)
78 78 if p == oldp:
79 79 return None
80 80
81 81 return p
82 82
83 83 def bailifchanged(repo):
84 84 if repo.dirstate.p2() != nullid:
85 85 raise util.Abort(_('outstanding uncommitted merge'))
86 86 modified, added, removed, deleted = repo.status()[:4]
87 87 if modified or added or removed or deleted:
88 88 raise util.Abort(_('uncommitted changes'))
89 89 ctx = repo[None]
90 90 for s in sorted(ctx.substate):
91 91 if ctx.sub(s).dirty():
92 92 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
93 93
94 94 def logmessage(ui, opts):
95 95 """ get the log message according to -m and -l option """
96 96 message = opts.get('message')
97 97 logfile = opts.get('logfile')
98 98
99 99 if message and logfile:
100 100 raise util.Abort(_('options --message and --logfile are mutually '
101 101 'exclusive'))
102 102 if not message and logfile:
103 103 try:
104 104 if logfile == '-':
105 105 message = ui.fin.read()
106 106 else:
107 107 message = '\n'.join(util.readfile(logfile).splitlines())
108 108 except IOError, inst:
109 109 raise util.Abort(_("can't read commit message '%s': %s") %
110 110 (logfile, inst.strerror))
111 111 return message
112 112
113 113 def mergeeditform(ctxorbool, baseformname):
114 114 """return appropriate editform name (referencing a committemplate)
115 115
116 116 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
117 117 merging is committed.
118 118
119 119 This returns baseformname with '.merge' appended if it is a merge,
120 120 otherwise '.normal' is appended.
121 121 """
122 122 if isinstance(ctxorbool, bool):
123 123 if ctxorbool:
124 124 return baseformname + ".merge"
125 125 elif 1 < len(ctxorbool.parents()):
126 126 return baseformname + ".merge"
127 127
128 128 return baseformname + ".normal"
129 129
130 130 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
131 131 editform='', **opts):
132 132 """get appropriate commit message editor according to '--edit' option
133 133
134 134 'finishdesc' is a function to be called with edited commit message
135 135 (= 'description' of the new changeset) just after editing, but
136 136 before checking empty-ness. It should return actual text to be
137 137 stored into history. This allows to change description before
138 138 storing.
139 139
140 140 'extramsg' is a extra message to be shown in the editor instead of
141 141 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
142 142 is automatically added.
143 143
144 144 'editform' is a dot-separated list of names, to distinguish
145 145 the purpose of commit text editing.
146 146
147 147 'getcommiteditor' returns 'commitforceeditor' regardless of
148 148 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
149 149 they are specific for usage in MQ.
150 150 """
151 151 if edit or finishdesc or extramsg:
152 152 return lambda r, c, s: commitforceeditor(r, c, s,
153 153 finishdesc=finishdesc,
154 154 extramsg=extramsg,
155 155 editform=editform)
156 156 elif editform:
157 157 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
158 158 else:
159 159 return commiteditor
160 160
161 161 def loglimit(opts):
162 162 """get the log limit according to option -l/--limit"""
163 163 limit = opts.get('limit')
164 164 if limit:
165 165 try:
166 166 limit = int(limit)
167 167 except ValueError:
168 168 raise util.Abort(_('limit must be a positive integer'))
169 169 if limit <= 0:
170 170 raise util.Abort(_('limit must be positive'))
171 171 else:
172 172 limit = None
173 173 return limit
174 174
175 175 def makefilename(repo, pat, node, desc=None,
176 176 total=None, seqno=None, revwidth=None, pathname=None):
177 177 node_expander = {
178 178 'H': lambda: hex(node),
179 179 'R': lambda: str(repo.changelog.rev(node)),
180 180 'h': lambda: short(node),
181 181 'm': lambda: re.sub('[^\w]', '_', str(desc))
182 182 }
183 183 expander = {
184 184 '%': lambda: '%',
185 185 'b': lambda: os.path.basename(repo.root),
186 186 }
187 187
188 188 try:
189 189 if node:
190 190 expander.update(node_expander)
191 191 if node:
192 192 expander['r'] = (lambda:
193 193 str(repo.changelog.rev(node)).zfill(revwidth or 0))
194 194 if total is not None:
195 195 expander['N'] = lambda: str(total)
196 196 if seqno is not None:
197 197 expander['n'] = lambda: str(seqno)
198 198 if total is not None and seqno is not None:
199 199 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
200 200 if pathname is not None:
201 201 expander['s'] = lambda: os.path.basename(pathname)
202 202 expander['d'] = lambda: os.path.dirname(pathname) or '.'
203 203 expander['p'] = lambda: pathname
204 204
205 205 newname = []
206 206 patlen = len(pat)
207 207 i = 0
208 208 while i < patlen:
209 209 c = pat[i]
210 210 if c == '%':
211 211 i += 1
212 212 c = pat[i]
213 213 c = expander[c]()
214 214 newname.append(c)
215 215 i += 1
216 216 return ''.join(newname)
217 217 except KeyError, inst:
218 218 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
219 219 inst.args[0])
220 220
221 221 def makefileobj(repo, pat, node=None, desc=None, total=None,
222 222 seqno=None, revwidth=None, mode='wb', modemap=None,
223 223 pathname=None):
224 224
225 225 writable = mode not in ('r', 'rb')
226 226
227 227 if not pat or pat == '-':
228 228 fp = writable and repo.ui.fout or repo.ui.fin
229 229 if util.safehasattr(fp, 'fileno'):
230 230 return os.fdopen(os.dup(fp.fileno()), mode)
231 231 else:
232 232 # if this fp can't be duped properly, return
233 233 # a dummy object that can be closed
234 234 class wrappedfileobj(object):
235 235 noop = lambda x: None
236 236 def __init__(self, f):
237 237 self.f = f
238 238 def __getattr__(self, attr):
239 239 if attr == 'close':
240 240 return self.noop
241 241 else:
242 242 return getattr(self.f, attr)
243 243
244 244 return wrappedfileobj(fp)
245 245 if util.safehasattr(pat, 'write') and writable:
246 246 return pat
247 247 if util.safehasattr(pat, 'read') and 'r' in mode:
248 248 return pat
249 249 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
250 250 if modemap is not None:
251 251 mode = modemap.get(fn, mode)
252 252 if mode == 'wb':
253 253 modemap[fn] = 'ab'
254 254 return open(fn, mode)
255 255
256 256 def openrevlog(repo, cmd, file_, opts):
257 257 """opens the changelog, manifest, a filelog or a given revlog"""
258 258 cl = opts['changelog']
259 259 mf = opts['manifest']
260 260 msg = None
261 261 if cl and mf:
262 262 msg = _('cannot specify --changelog and --manifest at the same time')
263 263 elif cl or mf:
264 264 if file_:
265 265 msg = _('cannot specify filename with --changelog or --manifest')
266 266 elif not repo:
267 267 msg = _('cannot specify --changelog or --manifest '
268 268 'without a repository')
269 269 if msg:
270 270 raise util.Abort(msg)
271 271
272 272 r = None
273 273 if repo:
274 274 if cl:
275 275 r = repo.unfiltered().changelog
276 276 elif mf:
277 277 r = repo.manifest
278 278 elif file_:
279 279 filelog = repo.file(file_)
280 280 if len(filelog):
281 281 r = filelog
282 282 if not r:
283 283 if not file_:
284 284 raise error.CommandError(cmd, _('invalid arguments'))
285 285 if not os.path.isfile(file_):
286 286 raise util.Abort(_("revlog '%s' not found") % file_)
287 287 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
288 288 file_[:-2] + ".i")
289 289 return r
290 290
291 291 def copy(ui, repo, pats, opts, rename=False):
292 292 # called with the repo lock held
293 293 #
294 294 # hgsep => pathname that uses "/" to separate directories
295 295 # ossep => pathname that uses os.sep to separate directories
296 296 cwd = repo.getcwd()
297 297 targets = {}
298 298 after = opts.get("after")
299 299 dryrun = opts.get("dry_run")
300 300 wctx = repo[None]
301 301
302 302 def walkpat(pat):
303 303 srcs = []
304 304 badstates = after and '?' or '?r'
305 305 m = scmutil.match(repo[None], [pat], opts, globbed=True)
306 306 for abs in repo.walk(m):
307 307 state = repo.dirstate[abs]
308 308 rel = m.rel(abs)
309 309 exact = m.exact(abs)
310 310 if state in badstates:
311 311 if exact and state == '?':
312 312 ui.warn(_('%s: not copying - file is not managed\n') % rel)
313 313 if exact and state == 'r':
314 314 ui.warn(_('%s: not copying - file has been marked for'
315 315 ' remove\n') % rel)
316 316 continue
317 317 # abs: hgsep
318 318 # rel: ossep
319 319 srcs.append((abs, rel, exact))
320 320 return srcs
321 321
322 322 # abssrc: hgsep
323 323 # relsrc: ossep
324 324 # otarget: ossep
325 325 def copyfile(abssrc, relsrc, otarget, exact):
326 326 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
327 327 if '/' in abstarget:
328 328 # We cannot normalize abstarget itself, this would prevent
329 329 # case only renames, like a => A.
330 330 abspath, absname = abstarget.rsplit('/', 1)
331 331 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
332 332 reltarget = repo.pathto(abstarget, cwd)
333 333 target = repo.wjoin(abstarget)
334 334 src = repo.wjoin(abssrc)
335 335 state = repo.dirstate[abstarget]
336 336
337 337 scmutil.checkportable(ui, abstarget)
338 338
339 339 # check for collisions
340 340 prevsrc = targets.get(abstarget)
341 341 if prevsrc is not None:
342 342 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
343 343 (reltarget, repo.pathto(abssrc, cwd),
344 344 repo.pathto(prevsrc, cwd)))
345 345 return
346 346
347 347 # check for overwrites
348 348 exists = os.path.lexists(target)
349 349 samefile = False
350 350 if exists and abssrc != abstarget:
351 351 if (repo.dirstate.normalize(abssrc) ==
352 352 repo.dirstate.normalize(abstarget)):
353 353 if not rename:
354 354 ui.warn(_("%s: can't copy - same file\n") % reltarget)
355 355 return
356 356 exists = False
357 357 samefile = True
358 358
359 359 if not after and exists or after and state in 'mn':
360 360 if not opts['force']:
361 361 ui.warn(_('%s: not overwriting - file exists\n') %
362 362 reltarget)
363 363 return
364 364
365 365 if after:
366 366 if not exists:
367 367 if rename:
368 368 ui.warn(_('%s: not recording move - %s does not exist\n') %
369 369 (relsrc, reltarget))
370 370 else:
371 371 ui.warn(_('%s: not recording copy - %s does not exist\n') %
372 372 (relsrc, reltarget))
373 373 return
374 374 elif not dryrun:
375 375 try:
376 376 if exists:
377 377 os.unlink(target)
378 378 targetdir = os.path.dirname(target) or '.'
379 379 if not os.path.isdir(targetdir):
380 380 os.makedirs(targetdir)
381 381 if samefile:
382 382 tmp = target + "~hgrename"
383 383 os.rename(src, tmp)
384 384 os.rename(tmp, target)
385 385 else:
386 386 util.copyfile(src, target)
387 387 srcexists = True
388 388 except IOError, inst:
389 389 if inst.errno == errno.ENOENT:
390 390 ui.warn(_('%s: deleted in working copy\n') % relsrc)
391 391 srcexists = False
392 392 else:
393 393 ui.warn(_('%s: cannot copy - %s\n') %
394 394 (relsrc, inst.strerror))
395 395 return True # report a failure
396 396
397 397 if ui.verbose or not exact:
398 398 if rename:
399 399 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
400 400 else:
401 401 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
402 402
403 403 targets[abstarget] = abssrc
404 404
405 405 # fix up dirstate
406 406 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
407 407 dryrun=dryrun, cwd=cwd)
408 408 if rename and not dryrun:
409 409 if not after and srcexists and not samefile:
410 410 util.unlinkpath(repo.wjoin(abssrc))
411 411 wctx.forget([abssrc])
412 412
413 413 # pat: ossep
414 414 # dest ossep
415 415 # srcs: list of (hgsep, hgsep, ossep, bool)
416 416 # return: function that takes hgsep and returns ossep
417 417 def targetpathfn(pat, dest, srcs):
418 418 if os.path.isdir(pat):
419 419 abspfx = pathutil.canonpath(repo.root, cwd, pat)
420 420 abspfx = util.localpath(abspfx)
421 421 if destdirexists:
422 422 striplen = len(os.path.split(abspfx)[0])
423 423 else:
424 424 striplen = len(abspfx)
425 425 if striplen:
426 426 striplen += len(os.sep)
427 427 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
428 428 elif destdirexists:
429 429 res = lambda p: os.path.join(dest,
430 430 os.path.basename(util.localpath(p)))
431 431 else:
432 432 res = lambda p: dest
433 433 return res
434 434
435 435 # pat: ossep
436 436 # dest ossep
437 437 # srcs: list of (hgsep, hgsep, ossep, bool)
438 438 # return: function that takes hgsep and returns ossep
439 439 def targetpathafterfn(pat, dest, srcs):
440 440 if matchmod.patkind(pat):
441 441 # a mercurial pattern
442 442 res = lambda p: os.path.join(dest,
443 443 os.path.basename(util.localpath(p)))
444 444 else:
445 445 abspfx = pathutil.canonpath(repo.root, cwd, pat)
446 446 if len(abspfx) < len(srcs[0][0]):
447 447 # A directory. Either the target path contains the last
448 448 # component of the source path or it does not.
449 449 def evalpath(striplen):
450 450 score = 0
451 451 for s in srcs:
452 452 t = os.path.join(dest, util.localpath(s[0])[striplen:])
453 453 if os.path.lexists(t):
454 454 score += 1
455 455 return score
456 456
457 457 abspfx = util.localpath(abspfx)
458 458 striplen = len(abspfx)
459 459 if striplen:
460 460 striplen += len(os.sep)
461 461 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
462 462 score = evalpath(striplen)
463 463 striplen1 = len(os.path.split(abspfx)[0])
464 464 if striplen1:
465 465 striplen1 += len(os.sep)
466 466 if evalpath(striplen1) > score:
467 467 striplen = striplen1
468 468 res = lambda p: os.path.join(dest,
469 469 util.localpath(p)[striplen:])
470 470 else:
471 471 # a file
472 472 if destdirexists:
473 473 res = lambda p: os.path.join(dest,
474 474 os.path.basename(util.localpath(p)))
475 475 else:
476 476 res = lambda p: dest
477 477 return res
478 478
479 479
480 480 pats = scmutil.expandpats(pats)
481 481 if not pats:
482 482 raise util.Abort(_('no source or destination specified'))
483 483 if len(pats) == 1:
484 484 raise util.Abort(_('no destination specified'))
485 485 dest = pats.pop()
486 486 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
487 487 if not destdirexists:
488 488 if len(pats) > 1 or matchmod.patkind(pats[0]):
489 489 raise util.Abort(_('with multiple sources, destination must be an '
490 490 'existing directory'))
491 491 if util.endswithsep(dest):
492 492 raise util.Abort(_('destination %s is not a directory') % dest)
493 493
494 494 tfn = targetpathfn
495 495 if after:
496 496 tfn = targetpathafterfn
497 497 copylist = []
498 498 for pat in pats:
499 499 srcs = walkpat(pat)
500 500 if not srcs:
501 501 continue
502 502 copylist.append((tfn(pat, dest, srcs), srcs))
503 503 if not copylist:
504 504 raise util.Abort(_('no files to copy'))
505 505
506 506 errors = 0
507 507 for targetpath, srcs in copylist:
508 508 for abssrc, relsrc, exact in srcs:
509 509 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
510 510 errors += 1
511 511
512 512 if errors:
513 513 ui.warn(_('(consider using --after)\n'))
514 514
515 515 return errors != 0
516 516
517 517 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
518 518 runargs=None, appendpid=False):
519 519 '''Run a command as a service.'''
520 520
521 521 def writepid(pid):
522 522 if opts['pid_file']:
523 523 mode = appendpid and 'a' or 'w'
524 524 fp = open(opts['pid_file'], mode)
525 525 fp.write(str(pid) + '\n')
526 526 fp.close()
527 527
528 528 if opts['daemon'] and not opts['daemon_pipefds']:
529 529 # Signal child process startup with file removal
530 530 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
531 531 os.close(lockfd)
532 532 try:
533 533 if not runargs:
534 534 runargs = util.hgcmd() + sys.argv[1:]
535 535 runargs.append('--daemon-pipefds=%s' % lockpath)
536 536 # Don't pass --cwd to the child process, because we've already
537 537 # changed directory.
538 538 for i in xrange(1, len(runargs)):
539 539 if runargs[i].startswith('--cwd='):
540 540 del runargs[i]
541 541 break
542 542 elif runargs[i].startswith('--cwd'):
543 543 del runargs[i:i + 2]
544 544 break
545 545 def condfn():
546 546 return not os.path.exists(lockpath)
547 547 pid = util.rundetached(runargs, condfn)
548 548 if pid < 0:
549 549 raise util.Abort(_('child process failed to start'))
550 550 writepid(pid)
551 551 finally:
552 552 try:
553 553 os.unlink(lockpath)
554 554 except OSError, e:
555 555 if e.errno != errno.ENOENT:
556 556 raise
557 557 if parentfn:
558 558 return parentfn(pid)
559 559 else:
560 560 return
561 561
562 562 if initfn:
563 563 initfn()
564 564
565 565 if not opts['daemon']:
566 566 writepid(os.getpid())
567 567
568 568 if opts['daemon_pipefds']:
569 569 lockpath = opts['daemon_pipefds']
570 570 try:
571 571 os.setsid()
572 572 except AttributeError:
573 573 pass
574 574 os.unlink(lockpath)
575 575 util.hidewindow()
576 576 sys.stdout.flush()
577 577 sys.stderr.flush()
578 578
579 579 nullfd = os.open(os.devnull, os.O_RDWR)
580 580 logfilefd = nullfd
581 581 if logfile:
582 582 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
583 583 os.dup2(nullfd, 0)
584 584 os.dup2(logfilefd, 1)
585 585 os.dup2(logfilefd, 2)
586 586 if nullfd not in (0, 1, 2):
587 587 os.close(nullfd)
588 588 if logfile and logfilefd not in (0, 1, 2):
589 589 os.close(logfilefd)
590 590
591 591 if runfn:
592 592 return runfn()
593 593
594 594 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
595 595 """Utility function used by commands.import to import a single patch
596 596
597 597 This function is explicitly defined here to help the evolve extension to
598 598 wrap this part of the import logic.
599 599
600 600 The API is currently a bit ugly because it a simple code translation from
601 601 the import command. Feel free to make it better.
602 602
603 603 :hunk: a patch (as a binary string)
604 604 :parents: nodes that will be parent of the created commit
605 605 :opts: the full dict of option passed to the import command
606 606 :msgs: list to save commit message to.
607 607 (used in case we need to save it when failing)
608 608 :updatefunc: a function that update a repo to a given node
609 609 updatefunc(<repo>, <node>)
610 610 """
611 611 tmpname, message, user, date, branch, nodeid, p1, p2 = \
612 612 patch.extract(ui, hunk)
613 613
614 614 update = not opts.get('bypass')
615 615 strip = opts["strip"]
616 616 sim = float(opts.get('similarity') or 0)
617 617 if not tmpname:
618 618 return (None, None, False)
619 619 msg = _('applied to working directory')
620 620
621 621 rejects = False
622 622
623 623 try:
624 624 cmdline_message = logmessage(ui, opts)
625 625 if cmdline_message:
626 626 # pickup the cmdline msg
627 627 message = cmdline_message
628 628 elif message:
629 629 # pickup the patch msg
630 630 message = message.strip()
631 631 else:
632 632 # launch the editor
633 633 message = None
634 634 ui.debug('message:\n%s\n' % message)
635 635
636 636 if len(parents) == 1:
637 637 parents.append(repo[nullid])
638 638 if opts.get('exact'):
639 639 if not nodeid or not p1:
640 640 raise util.Abort(_('not a Mercurial patch'))
641 641 p1 = repo[p1]
642 642 p2 = repo[p2 or nullid]
643 643 elif p2:
644 644 try:
645 645 p1 = repo[p1]
646 646 p2 = repo[p2]
647 647 # Without any options, consider p2 only if the
648 648 # patch is being applied on top of the recorded
649 649 # first parent.
650 650 if p1 != parents[0]:
651 651 p1 = parents[0]
652 652 p2 = repo[nullid]
653 653 except error.RepoError:
654 654 p1, p2 = parents
655 655 if p2.node() == nullid:
656 656 ui.warn(_("warning: import the patch as a normal revision\n"
657 657 "(use --exact to import the patch as a merge)\n"))
658 658 else:
659 659 p1, p2 = parents
660 660
661 661 n = None
662 662 if update:
663 663 repo.dirstate.beginparentchange()
664 664 if p1 != parents[0]:
665 665 updatefunc(repo, p1.node())
666 666 if p2 != parents[1]:
667 667 repo.setparents(p1.node(), p2.node())
668 668
669 669 if opts.get('exact') or opts.get('import_branch'):
670 670 repo.dirstate.setbranch(branch or 'default')
671 671
672 672 partial = opts.get('partial', False)
673 673 files = set()
674 674 try:
675 675 patch.patch(ui, repo, tmpname, strip=strip, files=files,
676 676 eolmode=None, similarity=sim / 100.0)
677 677 except patch.PatchError, e:
678 678 if not partial:
679 679 raise util.Abort(str(e))
680 680 if partial:
681 681 rejects = True
682 682
683 683 files = list(files)
684 684 if opts.get('no_commit'):
685 685 if message:
686 686 msgs.append(message)
687 687 else:
688 688 if opts.get('exact') or p2:
689 689 # If you got here, you either use --force and know what
690 690 # you are doing or used --exact or a merge patch while
691 691 # being updated to its first parent.
692 692 m = None
693 693 else:
694 694 m = scmutil.matchfiles(repo, files or [])
695 695 editform = mergeeditform(repo[None], 'import.normal')
696 696 if opts.get('exact'):
697 697 editor = None
698 698 else:
699 699 editor = getcommiteditor(editform=editform, **opts)
700 700 n = repo.commit(message, opts.get('user') or user,
701 701 opts.get('date') or date, match=m,
702 702 editor=editor, force=partial)
703 703 repo.dirstate.endparentchange()
704 704 else:
705 705 if opts.get('exact') or opts.get('import_branch'):
706 706 branch = branch or 'default'
707 707 else:
708 708 branch = p1.branch()
709 709 store = patch.filestore()
710 710 try:
711 711 files = set()
712 712 try:
713 713 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
714 714 files, eolmode=None)
715 715 except patch.PatchError, e:
716 716 raise util.Abort(str(e))
717 717 if opts.get('exact'):
718 718 editor = None
719 719 else:
720 720 editor = getcommiteditor(editform='import.bypass')
721 721 memctx = context.makememctx(repo, (p1.node(), p2.node()),
722 722 message,
723 723 opts.get('user') or user,
724 724 opts.get('date') or date,
725 725 branch, files, store,
726 726 editor=editor)
727 727 n = memctx.commit()
728 728 finally:
729 729 store.close()
730 730 if opts.get('exact') and opts.get('no_commit'):
731 731 # --exact with --no-commit is still useful in that it does merge
732 732 # and branch bits
733 733 ui.warn(_("warning: can't check exact import with --no-commit\n"))
734 734 elif opts.get('exact') and hex(n) != nodeid:
735 735 raise util.Abort(_('patch is damaged or loses information'))
736 736 if n:
737 737 # i18n: refers to a short changeset id
738 738 msg = _('created %s') % short(n)
739 739 return (msg, n, rejects)
740 740 finally:
741 741 os.unlink(tmpname)
742 742
743 743 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
744 744 opts=None):
745 745 '''export changesets as hg patches.'''
746 746
747 747 total = len(revs)
748 748 revwidth = max([len(str(rev)) for rev in revs])
749 749 filemode = {}
750 750
751 751 def single(rev, seqno, fp):
752 752 ctx = repo[rev]
753 753 node = ctx.node()
754 754 parents = [p.node() for p in ctx.parents() if p]
755 755 branch = ctx.branch()
756 756 if switch_parent:
757 757 parents.reverse()
758 758 prev = (parents and parents[0]) or nullid
759 759
760 760 shouldclose = False
761 761 if not fp and len(template) > 0:
762 762 desc_lines = ctx.description().rstrip().split('\n')
763 763 desc = desc_lines[0] #Commit always has a first line.
764 764 fp = makefileobj(repo, template, node, desc=desc, total=total,
765 765 seqno=seqno, revwidth=revwidth, mode='wb',
766 766 modemap=filemode)
767 767 if fp != template:
768 768 shouldclose = True
769 769 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
770 770 repo.ui.note("%s\n" % fp.name)
771 771
772 772 if not fp:
773 773 write = repo.ui.write
774 774 else:
775 775 def write(s, **kw):
776 776 fp.write(s)
777 777
778 778
779 779 write("# HG changeset patch\n")
780 780 write("# User %s\n" % ctx.user())
781 781 write("# Date %d %d\n" % ctx.date())
782 782 write("# %s\n" % util.datestr(ctx.date()))
783 783 if branch and branch != 'default':
784 784 write("# Branch %s\n" % branch)
785 785 write("# Node ID %s\n" % hex(node))
786 786 write("# Parent %s\n" % hex(prev))
787 787 if len(parents) > 1:
788 788 write("# Parent %s\n" % hex(parents[1]))
789 789 write(ctx.description().rstrip())
790 790 write("\n\n")
791 791
792 792 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
793 793 write(chunk, label=label)
794 794
795 795 if shouldclose:
796 796 fp.close()
797 797
798 798 for seqno, rev in enumerate(revs):
799 799 single(rev, seqno + 1, fp)
800 800
801 801 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
802 802 changes=None, stat=False, fp=None, prefix='',
803 803 listsubrepos=False):
804 804 '''show diff or diffstat.'''
805 805 if fp is None:
806 806 write = ui.write
807 807 else:
808 808 def write(s, **kw):
809 809 fp.write(s)
810 810
811 811 if stat:
812 812 diffopts = diffopts.copy(context=0)
813 813 width = 80
814 814 if not ui.plain():
815 815 width = ui.termwidth()
816 816 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
817 817 prefix=prefix)
818 818 for chunk, label in patch.diffstatui(util.iterlines(chunks),
819 819 width=width,
820 820 git=diffopts.git):
821 821 write(chunk, label=label)
822 822 else:
823 823 for chunk, label in patch.diffui(repo, node1, node2, match,
824 824 changes, diffopts, prefix=prefix):
825 825 write(chunk, label=label)
826 826
827 827 if listsubrepos:
828 828 ctx1 = repo[node1]
829 829 ctx2 = repo[node2]
830 830 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
831 831 tempnode2 = node2
832 832 try:
833 833 if node2 is not None:
834 834 tempnode2 = ctx2.substate[subpath][1]
835 835 except KeyError:
836 836 # A subrepo that existed in node1 was deleted between node1 and
837 837 # node2 (inclusive). Thus, ctx2's substate won't contain that
838 838 # subpath. The best we can do is to ignore it.
839 839 tempnode2 = None
840 840 submatch = matchmod.narrowmatcher(subpath, match)
841 841 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
842 842 stat=stat, fp=fp, prefix=prefix)
843 843
844 844 class changeset_printer(object):
845 845 '''show changeset information when templating not requested.'''
846 846
847 847 def __init__(self, ui, repo, matchfn, diffopts, buffered):
848 848 self.ui = ui
849 849 self.repo = repo
850 850 self.buffered = buffered
851 851 self.matchfn = matchfn
852 852 self.diffopts = diffopts
853 853 self.header = {}
854 854 self.hunk = {}
855 855 self.lastheader = None
856 856 self.footer = None
857 857
858 858 def flush(self, rev):
859 859 if rev in self.header:
860 860 h = self.header[rev]
861 861 if h != self.lastheader:
862 862 self.lastheader = h
863 863 self.ui.write(h)
864 864 del self.header[rev]
865 865 if rev in self.hunk:
866 866 self.ui.write(self.hunk[rev])
867 867 del self.hunk[rev]
868 868 return 1
869 869 return 0
870 870
871 871 def close(self):
872 872 if self.footer:
873 873 self.ui.write(self.footer)
874 874
875 875 def show(self, ctx, copies=None, matchfn=None, **props):
876 876 if self.buffered:
877 877 self.ui.pushbuffer()
878 878 self._show(ctx, copies, matchfn, props)
879 879 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
880 880 else:
881 881 self._show(ctx, copies, matchfn, props)
882 882
883 883 def _show(self, ctx, copies, matchfn, props):
884 884 '''show a single changeset or file revision'''
885 885 changenode = ctx.node()
886 886 rev = ctx.rev()
887 887
888 888 if self.ui.quiet:
889 889 self.ui.write("%d:%s\n" % (rev, short(changenode)),
890 890 label='log.node')
891 891 return
892 892
893 893 log = self.repo.changelog
894 894 date = util.datestr(ctx.date())
895 895
896 896 hexfunc = self.ui.debugflag and hex or short
897 897
898 898 parents = [(p, hexfunc(log.node(p)))
899 899 for p in self._meaningful_parentrevs(log, rev)]
900 900
901 901 # i18n: column positioning for "hg log"
902 902 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
903 903 label='log.changeset changeset.%s' % ctx.phasestr())
904 904
905 905 # branches are shown first before any other names due to backwards
906 906 # compatibility
907 907 branch = ctx.branch()
908 908 # don't show the default branch name
909 909 if branch != 'default':
910 910 # i18n: column positioning for "hg log"
911 911 self.ui.write(_("branch: %s\n") % branch,
912 912 label='log.branch')
913 913
914 914 for name, ns in self.repo.names.iteritems():
915 915 # branches has special logic already handled above, so here we just
916 916 # skip it
917 917 if name == 'branches':
918 918 continue
919 919 # we will use the templatename as the color name since those two
920 920 # should be the same
921 921 for name in ns.names(self.repo, changenode):
922 922 self.ui.write(ns.logfmt % name,
923 923 label='log.%s' % ns.colorname)
924 924 if self.ui.debugflag:
925 925 # i18n: column positioning for "hg log"
926 926 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
927 927 label='log.phase')
928 928 for parent in parents:
929 929 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
930 930 # i18n: column positioning for "hg log"
931 931 self.ui.write(_("parent: %d:%s\n") % parent,
932 932 label=label)
933 933
934 934 if self.ui.debugflag:
935 935 mnode = ctx.manifestnode()
936 936 # i18n: column positioning for "hg log"
937 937 self.ui.write(_("manifest: %d:%s\n") %
938 938 (self.repo.manifest.rev(mnode), hex(mnode)),
939 939 label='ui.debug log.manifest')
940 940 # i18n: column positioning for "hg log"
941 941 self.ui.write(_("user: %s\n") % ctx.user(),
942 942 label='log.user')
943 943 # i18n: column positioning for "hg log"
944 944 self.ui.write(_("date: %s\n") % date,
945 945 label='log.date')
946 946
947 947 if self.ui.debugflag:
948 948 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
949 949 for key, value in zip([# i18n: column positioning for "hg log"
950 950 _("files:"),
951 951 # i18n: column positioning for "hg log"
952 952 _("files+:"),
953 953 # i18n: column positioning for "hg log"
954 954 _("files-:")], files):
955 955 if value:
956 956 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
957 957 label='ui.debug log.files')
958 958 elif ctx.files() and self.ui.verbose:
959 959 # i18n: column positioning for "hg log"
960 960 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
961 961 label='ui.note log.files')
962 962 if copies and self.ui.verbose:
963 963 copies = ['%s (%s)' % c for c in copies]
964 964 # i18n: column positioning for "hg log"
965 965 self.ui.write(_("copies: %s\n") % ' '.join(copies),
966 966 label='ui.note log.copies')
967 967
968 968 extra = ctx.extra()
969 969 if extra and self.ui.debugflag:
970 970 for key, value in sorted(extra.items()):
971 971 # i18n: column positioning for "hg log"
972 972 self.ui.write(_("extra: %s=%s\n")
973 973 % (key, value.encode('string_escape')),
974 974 label='ui.debug log.extra')
975 975
976 976 description = ctx.description().strip()
977 977 if description:
978 978 if self.ui.verbose:
979 979 self.ui.write(_("description:\n"),
980 980 label='ui.note log.description')
981 981 self.ui.write(description,
982 982 label='ui.note log.description')
983 983 self.ui.write("\n\n")
984 984 else:
985 985 # i18n: column positioning for "hg log"
986 986 self.ui.write(_("summary: %s\n") %
987 987 description.splitlines()[0],
988 988 label='log.summary')
989 989 self.ui.write("\n")
990 990
991 991 self.showpatch(changenode, matchfn)
992 992
993 993 def showpatch(self, node, matchfn):
994 994 if not matchfn:
995 995 matchfn = self.matchfn
996 996 if matchfn:
997 997 stat = self.diffopts.get('stat')
998 998 diff = self.diffopts.get('patch')
999 999 diffopts = patch.diffallopts(self.ui, self.diffopts)
1000 1000 prev = self.repo.changelog.parents(node)[0]
1001 1001 if stat:
1002 1002 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1003 1003 match=matchfn, stat=True)
1004 1004 if diff:
1005 1005 if stat:
1006 1006 self.ui.write("\n")
1007 1007 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1008 1008 match=matchfn, stat=False)
1009 1009 self.ui.write("\n")
1010 1010
1011 1011 def _meaningful_parentrevs(self, log, rev):
1012 1012 """Return list of meaningful (or all if debug) parentrevs for rev.
1013 1013
1014 1014 For merges (two non-nullrev revisions) both parents are meaningful.
1015 1015 Otherwise the first parent revision is considered meaningful if it
1016 1016 is not the preceding revision.
1017 1017 """
1018 1018 parents = log.parentrevs(rev)
1019 1019 if not self.ui.debugflag and parents[1] == nullrev:
1020 1020 if parents[0] >= rev - 1:
1021 1021 parents = []
1022 1022 else:
1023 1023 parents = [parents[0]]
1024 1024 return parents
1025 1025
1026 1026 class jsonchangeset(changeset_printer):
1027 1027 '''format changeset information.'''
1028 1028
1029 1029 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1030 1030 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1031 1031 self.cache = {}
1032 1032 self._first = True
1033 1033
1034 1034 def close(self):
1035 1035 if not self._first:
1036 1036 self.ui.write("\n]\n")
1037 1037 else:
1038 1038 self.ui.write("[]\n")
1039 1039
1040 1040 def _show(self, ctx, copies, matchfn, props):
1041 1041 '''show a single changeset or file revision'''
1042 1042 hexnode = hex(ctx.node())
1043 1043 rev = ctx.rev()
1044 1044 j = encoding.jsonescape
1045 1045
1046 1046 if self._first:
1047 1047 self.ui.write("[\n {")
1048 1048 self._first = False
1049 1049 else:
1050 1050 self.ui.write(",\n {")
1051 1051
1052 1052 if self.ui.quiet:
1053 1053 self.ui.write('\n "rev": %d' % rev)
1054 1054 self.ui.write(',\n "node": "%s"' % hexnode)
1055 1055 self.ui.write('\n }')
1056 1056 return
1057 1057
1058 1058 self.ui.write('\n "rev": %d' % rev)
1059 1059 self.ui.write(',\n "node": "%s"' % hexnode)
1060 1060 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1061 1061 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1062 1062 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1063 1063 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1064 1064 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1065 1065
1066 1066 self.ui.write(',\n "bookmarks": [%s]' %
1067 1067 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1068 1068 self.ui.write(',\n "tags": [%s]' %
1069 1069 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1070 1070 self.ui.write(',\n "parents": [%s]' %
1071 1071 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1072 1072
1073 1073 if self.ui.debugflag:
1074 1074 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1075 1075
1076 1076 self.ui.write(',\n "extra": {%s}' %
1077 1077 ", ".join('"%s": "%s"' % (j(k), j(v))
1078 1078 for k, v in ctx.extra().items()))
1079 1079
1080 1080 files = ctx.p1().status(ctx)
1081 1081 self.ui.write(',\n "modified": [%s]' %
1082 1082 ", ".join('"%s"' % j(f) for f in files[0]))
1083 1083 self.ui.write(',\n "added": [%s]' %
1084 1084 ", ".join('"%s"' % j(f) for f in files[1]))
1085 1085 self.ui.write(',\n "removed": [%s]' %
1086 1086 ", ".join('"%s"' % j(f) for f in files[2]))
1087 1087
1088 1088 elif self.ui.verbose:
1089 1089 self.ui.write(',\n "files": [%s]' %
1090 1090 ", ".join('"%s"' % j(f) for f in ctx.files()))
1091 1091
1092 1092 if copies:
1093 1093 self.ui.write(',\n "copies": {%s}' %
1094 1094 ", ".join('"%s": "%s"' % (j(k), j(v))
1095 1095 for k, v in copies))
1096 1096
1097 1097 matchfn = self.matchfn
1098 1098 if matchfn:
1099 1099 stat = self.diffopts.get('stat')
1100 1100 diff = self.diffopts.get('patch')
1101 1101 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1102 1102 node, prev = ctx.node(), ctx.p1().node()
1103 1103 if stat:
1104 1104 self.ui.pushbuffer()
1105 1105 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1106 1106 match=matchfn, stat=True)
1107 1107 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1108 1108 if diff:
1109 1109 self.ui.pushbuffer()
1110 1110 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1111 1111 match=matchfn, stat=False)
1112 1112 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1113 1113
1114 1114 self.ui.write("\n }")
1115 1115
1116 1116 class changeset_templater(changeset_printer):
1117 1117 '''format changeset information.'''
1118 1118
1119 1119 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1120 1120 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1121 1121 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1122 1122 defaulttempl = {
1123 1123 'parent': '{rev}:{node|formatnode} ',
1124 1124 'manifest': '{rev}:{node|formatnode}',
1125 1125 'file_copy': '{name} ({source})',
1126 1126 'extra': '{key}={value|stringescape}'
1127 1127 }
1128 1128 # filecopy is preserved for compatibility reasons
1129 1129 defaulttempl['filecopy'] = defaulttempl['file_copy']
1130 1130 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1131 1131 cache=defaulttempl)
1132 1132 if tmpl:
1133 1133 self.t.cache['changeset'] = tmpl
1134 1134
1135 1135 self.cache = {}
1136 1136
1137 1137 def _meaningful_parentrevs(self, ctx):
1138 1138 """Return list of meaningful (or all if debug) parentrevs for rev.
1139 1139 """
1140 1140 parents = ctx.parents()
1141 1141 if len(parents) > 1:
1142 1142 return parents
1143 1143 if self.ui.debugflag:
1144 1144 return [parents[0], self.repo['null']]
1145 1145 if parents[0].rev() >= ctx.rev() - 1:
1146 1146 return []
1147 1147 return parents
1148 1148
1149 1149 def _show(self, ctx, copies, matchfn, props):
1150 1150 '''show a single changeset or file revision'''
1151 1151
1152 1152 showlist = templatekw.showlist
1153 1153
1154 1154 # showparents() behaviour depends on ui trace level which
1155 1155 # causes unexpected behaviours at templating level and makes
1156 1156 # it harder to extract it in a standalone function. Its
1157 1157 # behaviour cannot be changed so leave it here for now.
1158 1158 def showparents(**args):
1159 1159 ctx = args['ctx']
1160 1160 parents = [[('rev', p.rev()),
1161 1161 ('node', p.hex()),
1162 1162 ('phase', p.phasestr())]
1163 1163 for p in self._meaningful_parentrevs(ctx)]
1164 1164 return showlist('parent', parents, **args)
1165 1165
1166 1166 props = props.copy()
1167 1167 props.update(templatekw.keywords)
1168 1168 props['parents'] = showparents
1169 1169 props['templ'] = self.t
1170 1170 props['ctx'] = ctx
1171 1171 props['repo'] = self.repo
1172 1172 props['revcache'] = {'copies': copies}
1173 1173 props['cache'] = self.cache
1174 1174
1175 1175 # find correct templates for current mode
1176 1176
1177 1177 tmplmodes = [
1178 1178 (True, None),
1179 1179 (self.ui.verbose, 'verbose'),
1180 1180 (self.ui.quiet, 'quiet'),
1181 1181 (self.ui.debugflag, 'debug'),
1182 1182 ]
1183 1183
1184 1184 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1185 1185 for mode, postfix in tmplmodes:
1186 1186 for type in types:
1187 1187 cur = postfix and ('%s_%s' % (type, postfix)) or type
1188 1188 if mode and cur in self.t:
1189 1189 types[type] = cur
1190 1190
1191 1191 try:
1192 1192
1193 1193 # write header
1194 1194 if types['header']:
1195 1195 h = templater.stringify(self.t(types['header'], **props))
1196 1196 if self.buffered:
1197 1197 self.header[ctx.rev()] = h
1198 1198 else:
1199 1199 if self.lastheader != h:
1200 1200 self.lastheader = h
1201 1201 self.ui.write(h)
1202 1202
1203 1203 # write changeset metadata, then patch if requested
1204 1204 key = types['changeset']
1205 1205 self.ui.write(templater.stringify(self.t(key, **props)))
1206 1206 self.showpatch(ctx.node(), matchfn)
1207 1207
1208 1208 if types['footer']:
1209 1209 if not self.footer:
1210 1210 self.footer = templater.stringify(self.t(types['footer'],
1211 1211 **props))
1212 1212
1213 1213 except KeyError, inst:
1214 1214 msg = _("%s: no key named '%s'")
1215 1215 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1216 1216 except SyntaxError, inst:
1217 1217 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1218 1218
1219 1219 def gettemplate(ui, tmpl, style):
1220 1220 """
1221 1221 Find the template matching the given template spec or style.
1222 1222 """
1223 1223
1224 1224 # ui settings
1225 1225 if not tmpl and not style: # template are stronger than style
1226 1226 tmpl = ui.config('ui', 'logtemplate')
1227 1227 if tmpl:
1228 1228 try:
1229 1229 tmpl = templater.parsestring(tmpl)
1230 1230 except SyntaxError:
1231 1231 tmpl = templater.parsestring(tmpl, quoted=False)
1232 1232 return tmpl, None
1233 1233 else:
1234 1234 style = util.expandpath(ui.config('ui', 'style', ''))
1235 1235
1236 1236 if not tmpl and style:
1237 1237 mapfile = style
1238 1238 if not os.path.split(mapfile)[0]:
1239 1239 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1240 1240 or templater.templatepath(mapfile))
1241 1241 if mapname:
1242 1242 mapfile = mapname
1243 1243 return None, mapfile
1244 1244
1245 1245 if not tmpl:
1246 1246 return None, None
1247 1247
1248 1248 # looks like a literal template?
1249 1249 if '{' in tmpl:
1250 1250 return tmpl, None
1251 1251
1252 1252 # perhaps a stock style?
1253 1253 if not os.path.split(tmpl)[0]:
1254 1254 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1255 1255 or templater.templatepath(tmpl))
1256 1256 if mapname and os.path.isfile(mapname):
1257 1257 return None, mapname
1258 1258
1259 1259 # perhaps it's a reference to [templates]
1260 1260 t = ui.config('templates', tmpl)
1261 1261 if t:
1262 1262 try:
1263 1263 tmpl = templater.parsestring(t)
1264 1264 except SyntaxError:
1265 1265 tmpl = templater.parsestring(t, quoted=False)
1266 1266 return tmpl, None
1267 1267
1268 1268 if tmpl == 'list':
1269 1269 ui.write(_("available styles: %s\n") % templater.stylelist())
1270 1270 raise util.Abort(_("specify a template"))
1271 1271
1272 1272 # perhaps it's a path to a map or a template
1273 1273 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1274 1274 # is it a mapfile for a style?
1275 1275 if os.path.basename(tmpl).startswith("map-"):
1276 1276 return None, os.path.realpath(tmpl)
1277 1277 tmpl = open(tmpl).read()
1278 1278 return tmpl, None
1279 1279
1280 1280 # constant string?
1281 1281 return tmpl, None
1282 1282
1283 1283 def show_changeset(ui, repo, opts, buffered=False):
1284 1284 """show one changeset using template or regular display.
1285 1285
1286 1286 Display format will be the first non-empty hit of:
1287 1287 1. option 'template'
1288 1288 2. option 'style'
1289 1289 3. [ui] setting 'logtemplate'
1290 1290 4. [ui] setting 'style'
1291 1291 If all of these values are either the unset or the empty string,
1292 1292 regular display via changeset_printer() is done.
1293 1293 """
1294 1294 # options
1295 1295 matchfn = None
1296 1296 if opts.get('patch') or opts.get('stat'):
1297 1297 matchfn = scmutil.matchall(repo)
1298 1298
1299 1299 if opts.get('template') == 'json':
1300 1300 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1301 1301
1302 1302 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1303 1303
1304 1304 if not tmpl and not mapfile:
1305 1305 return changeset_printer(ui, repo, matchfn, opts, buffered)
1306 1306
1307 1307 try:
1308 1308 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1309 1309 buffered)
1310 1310 except SyntaxError, inst:
1311 1311 raise util.Abort(inst.args[0])
1312 1312 return t
1313 1313
1314 1314 def showmarker(ui, marker):
1315 1315 """utility function to display obsolescence marker in a readable way
1316 1316
1317 1317 To be used by debug function."""
1318 1318 ui.write(hex(marker.precnode()))
1319 1319 for repl in marker.succnodes():
1320 1320 ui.write(' ')
1321 1321 ui.write(hex(repl))
1322 1322 ui.write(' %X ' % marker.flags())
1323 1323 parents = marker.parentnodes()
1324 1324 if parents is not None:
1325 1325 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1326 1326 ui.write('(%s) ' % util.datestr(marker.date()))
1327 1327 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1328 1328 sorted(marker.metadata().items())
1329 1329 if t[0] != 'date')))
1330 1330 ui.write('\n')
1331 1331
1332 1332 def finddate(ui, repo, date):
1333 1333 """Find the tipmost changeset that matches the given date spec"""
1334 1334
1335 1335 df = util.matchdate(date)
1336 1336 m = scmutil.matchall(repo)
1337 1337 results = {}
1338 1338
1339 1339 def prep(ctx, fns):
1340 1340 d = ctx.date()
1341 1341 if df(d[0]):
1342 1342 results[ctx.rev()] = d
1343 1343
1344 1344 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1345 1345 rev = ctx.rev()
1346 1346 if rev in results:
1347 1347 ui.status(_("found revision %s from %s\n") %
1348 1348 (rev, util.datestr(results[rev])))
1349 1349 return str(rev)
1350 1350
1351 1351 raise util.Abort(_("revision matching date not found"))
1352 1352
1353 1353 def increasingwindows(windowsize=8, sizelimit=512):
1354 1354 while True:
1355 1355 yield windowsize
1356 1356 if windowsize < sizelimit:
1357 1357 windowsize *= 2
1358 1358
1359 1359 class FileWalkError(Exception):
1360 1360 pass
1361 1361
1362 1362 def walkfilerevs(repo, match, follow, revs, fncache):
1363 1363 '''Walks the file history for the matched files.
1364 1364
1365 1365 Returns the changeset revs that are involved in the file history.
1366 1366
1367 1367 Throws FileWalkError if the file history can't be walked using
1368 1368 filelogs alone.
1369 1369 '''
1370 1370 wanted = set()
1371 1371 copies = []
1372 1372 minrev, maxrev = min(revs), max(revs)
1373 1373 def filerevgen(filelog, last):
1374 1374 """
1375 1375 Only files, no patterns. Check the history of each file.
1376 1376
1377 1377 Examines filelog entries within minrev, maxrev linkrev range
1378 1378 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1379 1379 tuples in backwards order
1380 1380 """
1381 1381 cl_count = len(repo)
1382 1382 revs = []
1383 1383 for j in xrange(0, last + 1):
1384 1384 linkrev = filelog.linkrev(j)
1385 1385 if linkrev < minrev:
1386 1386 continue
1387 1387 # only yield rev for which we have the changelog, it can
1388 1388 # happen while doing "hg log" during a pull or commit
1389 1389 if linkrev >= cl_count:
1390 1390 break
1391 1391
1392 1392 parentlinkrevs = []
1393 1393 for p in filelog.parentrevs(j):
1394 1394 if p != nullrev:
1395 1395 parentlinkrevs.append(filelog.linkrev(p))
1396 1396 n = filelog.node(j)
1397 1397 revs.append((linkrev, parentlinkrevs,
1398 1398 follow and filelog.renamed(n)))
1399 1399
1400 1400 return reversed(revs)
1401 1401 def iterfiles():
1402 1402 pctx = repo['.']
1403 1403 for filename in match.files():
1404 1404 if follow:
1405 1405 if filename not in pctx:
1406 1406 raise util.Abort(_('cannot follow file not in parent '
1407 1407 'revision: "%s"') % filename)
1408 1408 yield filename, pctx[filename].filenode()
1409 1409 else:
1410 1410 yield filename, None
1411 1411 for filename_node in copies:
1412 1412 yield filename_node
1413 1413
1414 1414 for file_, node in iterfiles():
1415 1415 filelog = repo.file(file_)
1416 1416 if not len(filelog):
1417 1417 if node is None:
1418 1418 # A zero count may be a directory or deleted file, so
1419 1419 # try to find matching entries on the slow path.
1420 1420 if follow:
1421 1421 raise util.Abort(
1422 1422 _('cannot follow nonexistent file: "%s"') % file_)
1423 1423 raise FileWalkError("Cannot walk via filelog")
1424 1424 else:
1425 1425 continue
1426 1426
1427 1427 if node is None:
1428 1428 last = len(filelog) - 1
1429 1429 else:
1430 1430 last = filelog.rev(node)
1431 1431
1432 1432
1433 1433 # keep track of all ancestors of the file
1434 1434 ancestors = set([filelog.linkrev(last)])
1435 1435
1436 1436 # iterate from latest to oldest revision
1437 1437 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1438 1438 if not follow:
1439 1439 if rev > maxrev:
1440 1440 continue
1441 1441 else:
1442 1442 # Note that last might not be the first interesting
1443 1443 # rev to us:
1444 1444 # if the file has been changed after maxrev, we'll
1445 1445 # have linkrev(last) > maxrev, and we still need
1446 1446 # to explore the file graph
1447 1447 if rev not in ancestors:
1448 1448 continue
1449 1449 # XXX insert 1327 fix here
1450 1450 if flparentlinkrevs:
1451 1451 ancestors.update(flparentlinkrevs)
1452 1452
1453 1453 fncache.setdefault(rev, []).append(file_)
1454 1454 wanted.add(rev)
1455 1455 if copied:
1456 1456 copies.append(copied)
1457 1457
1458 1458 return wanted
1459 1459
1460 1460 def walkchangerevs(repo, match, opts, prepare):
1461 1461 '''Iterate over files and the revs in which they changed.
1462 1462
1463 1463 Callers most commonly need to iterate backwards over the history
1464 1464 in which they are interested. Doing so has awful (quadratic-looking)
1465 1465 performance, so we use iterators in a "windowed" way.
1466 1466
1467 1467 We walk a window of revisions in the desired order. Within the
1468 1468 window, we first walk forwards to gather data, then in the desired
1469 1469 order (usually backwards) to display it.
1470 1470
1471 1471 This function returns an iterator yielding contexts. Before
1472 1472 yielding each context, the iterator will first call the prepare
1473 1473 function on each context in the window in forward order.'''
1474 1474
1475 1475 follow = opts.get('follow') or opts.get('follow_first')
1476 1476 revs = _logrevs(repo, opts)
1477 1477 if not revs:
1478 1478 return []
1479 1479 wanted = set()
1480 1480 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1481 1481 fncache = {}
1482 1482 change = repo.changectx
1483 1483
1484 1484 # First step is to fill wanted, the set of revisions that we want to yield.
1485 1485 # When it does not induce extra cost, we also fill fncache for revisions in
1486 1486 # wanted: a cache of filenames that were changed (ctx.files()) and that
1487 1487 # match the file filtering conditions.
1488 1488
1489 1489 if not slowpath and not match.files():
1490 1490 # No files, no patterns. Display all revs.
1491 1491 wanted = revs
1492 1492
1493 1493 if not slowpath and match.files():
1494 1494 # We only have to read through the filelog to find wanted revisions
1495 1495
1496 1496 try:
1497 1497 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1498 1498 except FileWalkError:
1499 1499 slowpath = True
1500 1500
1501 1501 # We decided to fall back to the slowpath because at least one
1502 1502 # of the paths was not a file. Check to see if at least one of them
1503 1503 # existed in history, otherwise simply return
1504 1504 for path in match.files():
1505 1505 if path == '.' or path in repo.store:
1506 1506 break
1507 1507 else:
1508 1508 return []
1509 1509
1510 1510 if slowpath:
1511 1511 # We have to read the changelog to match filenames against
1512 1512 # changed files
1513 1513
1514 1514 if follow:
1515 1515 raise util.Abort(_('can only follow copies/renames for explicit '
1516 1516 'filenames'))
1517 1517
1518 1518 # The slow path checks files modified in every changeset.
1519 1519 # This is really slow on large repos, so compute the set lazily.
1520 1520 class lazywantedset(object):
1521 1521 def __init__(self):
1522 1522 self.set = set()
1523 1523 self.revs = set(revs)
1524 1524
1525 1525 # No need to worry about locality here because it will be accessed
1526 1526 # in the same order as the increasing window below.
1527 1527 def __contains__(self, value):
1528 1528 if value in self.set:
1529 1529 return True
1530 1530 elif not value in self.revs:
1531 1531 return False
1532 1532 else:
1533 1533 self.revs.discard(value)
1534 1534 ctx = change(value)
1535 1535 matches = filter(match, ctx.files())
1536 1536 if matches:
1537 1537 fncache[value] = matches
1538 1538 self.set.add(value)
1539 1539 return True
1540 1540 return False
1541 1541
1542 1542 def discard(self, value):
1543 1543 self.revs.discard(value)
1544 1544 self.set.discard(value)
1545 1545
1546 1546 wanted = lazywantedset()
1547 1547
1548 1548 class followfilter(object):
1549 1549 def __init__(self, onlyfirst=False):
1550 1550 self.startrev = nullrev
1551 1551 self.roots = set()
1552 1552 self.onlyfirst = onlyfirst
1553 1553
1554 1554 def match(self, rev):
1555 1555 def realparents(rev):
1556 1556 if self.onlyfirst:
1557 1557 return repo.changelog.parentrevs(rev)[0:1]
1558 1558 else:
1559 1559 return filter(lambda x: x != nullrev,
1560 1560 repo.changelog.parentrevs(rev))
1561 1561
1562 1562 if self.startrev == nullrev:
1563 1563 self.startrev = rev
1564 1564 return True
1565 1565
1566 1566 if rev > self.startrev:
1567 1567 # forward: all descendants
1568 1568 if not self.roots:
1569 1569 self.roots.add(self.startrev)
1570 1570 for parent in realparents(rev):
1571 1571 if parent in self.roots:
1572 1572 self.roots.add(rev)
1573 1573 return True
1574 1574 else:
1575 1575 # backwards: all parents
1576 1576 if not self.roots:
1577 1577 self.roots.update(realparents(self.startrev))
1578 1578 if rev in self.roots:
1579 1579 self.roots.remove(rev)
1580 1580 self.roots.update(realparents(rev))
1581 1581 return True
1582 1582
1583 1583 return False
1584 1584
1585 1585 # it might be worthwhile to do this in the iterator if the rev range
1586 1586 # is descending and the prune args are all within that range
1587 1587 for rev in opts.get('prune', ()):
1588 1588 rev = repo[rev].rev()
1589 1589 ff = followfilter()
1590 1590 stop = min(revs[0], revs[-1])
1591 1591 for x in xrange(rev, stop - 1, -1):
1592 1592 if ff.match(x):
1593 1593 wanted = wanted - [x]
1594 1594
1595 1595 # Now that wanted is correctly initialized, we can iterate over the
1596 1596 # revision range, yielding only revisions in wanted.
1597 1597 def iterate():
1598 1598 if follow and not match.files():
1599 1599 ff = followfilter(onlyfirst=opts.get('follow_first'))
1600 1600 def want(rev):
1601 1601 return ff.match(rev) and rev in wanted
1602 1602 else:
1603 1603 def want(rev):
1604 1604 return rev in wanted
1605 1605
1606 1606 it = iter(revs)
1607 1607 stopiteration = False
1608 1608 for windowsize in increasingwindows():
1609 1609 nrevs = []
1610 1610 for i in xrange(windowsize):
1611 1611 try:
1612 1612 rev = it.next()
1613 1613 if want(rev):
1614 1614 nrevs.append(rev)
1615 1615 except (StopIteration):
1616 1616 stopiteration = True
1617 1617 break
1618 1618 for rev in sorted(nrevs):
1619 1619 fns = fncache.get(rev)
1620 1620 ctx = change(rev)
1621 1621 if not fns:
1622 1622 def fns_generator():
1623 1623 for f in ctx.files():
1624 1624 if match(f):
1625 1625 yield f
1626 1626 fns = fns_generator()
1627 1627 prepare(ctx, fns)
1628 1628 for rev in nrevs:
1629 1629 yield change(rev)
1630 1630
1631 1631 if stopiteration:
1632 1632 break
1633 1633
1634 1634 return iterate()
1635 1635
1636 1636 def _makefollowlogfilematcher(repo, files, followfirst):
1637 1637 # When displaying a revision with --patch --follow FILE, we have
1638 1638 # to know which file of the revision must be diffed. With
1639 1639 # --follow, we want the names of the ancestors of FILE in the
1640 1640 # revision, stored in "fcache". "fcache" is populated by
1641 1641 # reproducing the graph traversal already done by --follow revset
1642 1642 # and relating linkrevs to file names (which is not "correct" but
1643 1643 # good enough).
1644 1644 fcache = {}
1645 1645 fcacheready = [False]
1646 1646 pctx = repo['.']
1647 1647
1648 1648 def populate():
1649 1649 for fn in files:
1650 1650 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1651 1651 for c in i:
1652 1652 fcache.setdefault(c.linkrev(), set()).add(c.path())
1653 1653
1654 1654 def filematcher(rev):
1655 1655 if not fcacheready[0]:
1656 1656 # Lazy initialization
1657 1657 fcacheready[0] = True
1658 1658 populate()
1659 1659 return scmutil.matchfiles(repo, fcache.get(rev, []))
1660 1660
1661 1661 return filematcher
1662 1662
1663 1663 def _makenofollowlogfilematcher(repo, pats, opts):
1664 1664 '''hook for extensions to override the filematcher for non-follow cases'''
1665 1665 return None
1666 1666
1667 1667 def _makelogrevset(repo, pats, opts, revs):
1668 1668 """Return (expr, filematcher) where expr is a revset string built
1669 1669 from log options and file patterns or None. If --stat or --patch
1670 1670 are not passed filematcher is None. Otherwise it is a callable
1671 1671 taking a revision number and returning a match objects filtering
1672 1672 the files to be detailed when displaying the revision.
1673 1673 """
1674 1674 opt2revset = {
1675 1675 'no_merges': ('not merge()', None),
1676 1676 'only_merges': ('merge()', None),
1677 1677 '_ancestors': ('ancestors(%(val)s)', None),
1678 1678 '_fancestors': ('_firstancestors(%(val)s)', None),
1679 1679 '_descendants': ('descendants(%(val)s)', None),
1680 1680 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1681 1681 '_matchfiles': ('_matchfiles(%(val)s)', None),
1682 1682 'date': ('date(%(val)r)', None),
1683 1683 'branch': ('branch(%(val)r)', ' or '),
1684 1684 '_patslog': ('filelog(%(val)r)', ' or '),
1685 1685 '_patsfollow': ('follow(%(val)r)', ' or '),
1686 1686 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1687 1687 'keyword': ('keyword(%(val)r)', ' or '),
1688 1688 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1689 1689 'user': ('user(%(val)r)', ' or '),
1690 1690 }
1691 1691
1692 1692 opts = dict(opts)
1693 1693 # follow or not follow?
1694 1694 follow = opts.get('follow') or opts.get('follow_first')
1695 1695 followfirst = opts.get('follow_first') and 1 or 0
1696 1696 # --follow with FILE behaviour depends on revs...
1697 1697 it = iter(revs)
1698 1698 startrev = it.next()
1699 1699 try:
1700 1700 followdescendants = startrev < it.next()
1701 1701 except (StopIteration):
1702 1702 followdescendants = False
1703 1703
1704 1704 # branch and only_branch are really aliases and must be handled at
1705 1705 # the same time
1706 1706 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1707 1707 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1708 1708 # pats/include/exclude are passed to match.match() directly in
1709 1709 # _matchfiles() revset but walkchangerevs() builds its matcher with
1710 1710 # scmutil.match(). The difference is input pats are globbed on
1711 1711 # platforms without shell expansion (windows).
1712 1712 pctx = repo[None]
1713 1713 match, pats = scmutil.matchandpats(pctx, pats, opts)
1714 1714 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1715 1715 if not slowpath:
1716 1716 for f in match.files():
1717 1717 if follow and f not in pctx:
1718 1718 # If the file exists, it may be a directory, so let it
1719 1719 # take the slow path.
1720 1720 if os.path.exists(repo.wjoin(f)):
1721 1721 slowpath = True
1722 1722 continue
1723 1723 else:
1724 1724 raise util.Abort(_('cannot follow file not in parent '
1725 1725 'revision: "%s"') % f)
1726 1726 filelog = repo.file(f)
1727 1727 if not filelog:
1728 1728 # A zero count may be a directory or deleted file, so
1729 1729 # try to find matching entries on the slow path.
1730 1730 if follow:
1731 1731 raise util.Abort(
1732 1732 _('cannot follow nonexistent file: "%s"') % f)
1733 1733 slowpath = True
1734 1734
1735 1735 # We decided to fall back to the slowpath because at least one
1736 1736 # of the paths was not a file. Check to see if at least one of them
1737 1737 # existed in history - in that case, we'll continue down the
1738 1738 # slowpath; otherwise, we can turn off the slowpath
1739 1739 if slowpath:
1740 1740 for path in match.files():
1741 1741 if path == '.' or path in repo.store:
1742 1742 break
1743 1743 else:
1744 1744 slowpath = False
1745 1745
1746 1746 fpats = ('_patsfollow', '_patsfollowfirst')
1747 1747 fnopats = (('_ancestors', '_fancestors'),
1748 1748 ('_descendants', '_fdescendants'))
1749 1749 if slowpath:
1750 1750 # See walkchangerevs() slow path.
1751 1751 #
1752 1752 # pats/include/exclude cannot be represented as separate
1753 1753 # revset expressions as their filtering logic applies at file
1754 1754 # level. For instance "-I a -X a" matches a revision touching
1755 1755 # "a" and "b" while "file(a) and not file(b)" does
1756 1756 # not. Besides, filesets are evaluated against the working
1757 1757 # directory.
1758 1758 matchargs = ['r:', 'd:relpath']
1759 1759 for p in pats:
1760 1760 matchargs.append('p:' + p)
1761 1761 for p in opts.get('include', []):
1762 1762 matchargs.append('i:' + p)
1763 1763 for p in opts.get('exclude', []):
1764 1764 matchargs.append('x:' + p)
1765 1765 matchargs = ','.join(('%r' % p) for p in matchargs)
1766 1766 opts['_matchfiles'] = matchargs
1767 1767 if follow:
1768 1768 opts[fnopats[0][followfirst]] = '.'
1769 1769 else:
1770 1770 if follow:
1771 1771 if pats:
1772 1772 # follow() revset interprets its file argument as a
1773 1773 # manifest entry, so use match.files(), not pats.
1774 1774 opts[fpats[followfirst]] = list(match.files())
1775 1775 else:
1776 1776 op = fnopats[followdescendants][followfirst]
1777 1777 opts[op] = 'rev(%d)' % startrev
1778 1778 else:
1779 1779 opts['_patslog'] = list(pats)
1780 1780
1781 1781 filematcher = None
1782 1782 if opts.get('patch') or opts.get('stat'):
1783 1783 # When following files, track renames via a special matcher.
1784 1784 # If we're forced to take the slowpath it means we're following
1785 1785 # at least one pattern/directory, so don't bother with rename tracking.
1786 1786 if follow and not match.always() and not slowpath:
1787 1787 # _makefollowlogfilematcher expects its files argument to be
1788 1788 # relative to the repo root, so use match.files(), not pats.
1789 1789 filematcher = _makefollowlogfilematcher(repo, match.files(),
1790 1790 followfirst)
1791 1791 else:
1792 1792 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1793 1793 if filematcher is None:
1794 1794 filematcher = lambda rev: match
1795 1795
1796 1796 expr = []
1797 1797 for op, val in sorted(opts.iteritems()):
1798 1798 if not val:
1799 1799 continue
1800 1800 if op not in opt2revset:
1801 1801 continue
1802 1802 revop, andor = opt2revset[op]
1803 1803 if '%(val)' not in revop:
1804 1804 expr.append(revop)
1805 1805 else:
1806 1806 if not isinstance(val, list):
1807 1807 e = revop % {'val': val}
1808 1808 else:
1809 1809 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1810 1810 expr.append(e)
1811 1811
1812 1812 if expr:
1813 1813 expr = '(' + ' and '.join(expr) + ')'
1814 1814 else:
1815 1815 expr = None
1816 1816 return expr, filematcher
1817 1817
1818 1818 def _logrevs(repo, opts):
1819 1819 # Default --rev value depends on --follow but --follow behaviour
1820 1820 # depends on revisions resolved from --rev...
1821 1821 follow = opts.get('follow') or opts.get('follow_first')
1822 1822 if opts.get('rev'):
1823 1823 revs = scmutil.revrange(repo, opts['rev'])
1824 1824 elif follow and repo.dirstate.p1() == nullid:
1825 1825 revs = revset.baseset()
1826 1826 elif follow:
1827 1827 revs = repo.revs('reverse(:.)')
1828 1828 else:
1829 1829 revs = revset.spanset(repo)
1830 1830 revs.reverse()
1831 1831 return revs
1832 1832
1833 1833 def getgraphlogrevs(repo, pats, opts):
1834 1834 """Return (revs, expr, filematcher) where revs is an iterable of
1835 1835 revision numbers, expr is a revset string built from log options
1836 1836 and file patterns or None, and used to filter 'revs'. If --stat or
1837 1837 --patch are not passed filematcher is None. Otherwise it is a
1838 1838 callable taking a revision number and returning a match objects
1839 1839 filtering the files to be detailed when displaying the revision.
1840 1840 """
1841 1841 limit = loglimit(opts)
1842 1842 revs = _logrevs(repo, opts)
1843 1843 if not revs:
1844 1844 return revset.baseset(), None, None
1845 1845 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1846 1846 if opts.get('rev'):
1847 1847 # User-specified revs might be unsorted, but don't sort before
1848 1848 # _makelogrevset because it might depend on the order of revs
1849 1849 revs.sort(reverse=True)
1850 1850 if expr:
1851 1851 # Revset matchers often operate faster on revisions in changelog
1852 1852 # order, because most filters deal with the changelog.
1853 1853 revs.reverse()
1854 1854 matcher = revset.match(repo.ui, expr)
1855 1855 # Revset matches can reorder revisions. "A or B" typically returns
1856 1856 # returns the revision matching A then the revision matching B. Sort
1857 1857 # again to fix that.
1858 1858 revs = matcher(repo, revs)
1859 1859 revs.sort(reverse=True)
1860 1860 if limit is not None:
1861 1861 limitedrevs = []
1862 1862 for idx, rev in enumerate(revs):
1863 1863 if idx >= limit:
1864 1864 break
1865 1865 limitedrevs.append(rev)
1866 1866 revs = revset.baseset(limitedrevs)
1867 1867
1868 1868 return revs, expr, filematcher
1869 1869
1870 1870 def getlogrevs(repo, pats, opts):
1871 1871 """Return (revs, expr, filematcher) where revs is an iterable of
1872 1872 revision numbers, expr is a revset string built from log options
1873 1873 and file patterns or None, and used to filter 'revs'. If --stat or
1874 1874 --patch are not passed filematcher is None. Otherwise it is a
1875 1875 callable taking a revision number and returning a match objects
1876 1876 filtering the files to be detailed when displaying the revision.
1877 1877 """
1878 1878 limit = loglimit(opts)
1879 1879 revs = _logrevs(repo, opts)
1880 1880 if not revs:
1881 1881 return revset.baseset([]), None, None
1882 1882 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1883 1883 if expr:
1884 1884 # Revset matchers often operate faster on revisions in changelog
1885 1885 # order, because most filters deal with the changelog.
1886 1886 if not opts.get('rev'):
1887 1887 revs.reverse()
1888 1888 matcher = revset.match(repo.ui, expr)
1889 1889 # Revset matches can reorder revisions. "A or B" typically returns
1890 1890 # returns the revision matching A then the revision matching B. Sort
1891 1891 # again to fix that.
1892 1892 revs = matcher(repo, revs)
1893 1893 if not opts.get('rev'):
1894 1894 revs.sort(reverse=True)
1895 1895 if limit is not None:
1896 1896 count = 0
1897 1897 limitedrevs = []
1898 1898 it = iter(revs)
1899 1899 while count < limit:
1900 1900 try:
1901 1901 limitedrevs.append(it.next())
1902 1902 except (StopIteration):
1903 1903 break
1904 1904 count += 1
1905 1905 revs = revset.baseset(limitedrevs)
1906 1906
1907 1907 return revs, expr, filematcher
1908 1908
1909 1909 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1910 1910 filematcher=None):
1911 1911 seen, state = [], graphmod.asciistate()
1912 1912 for rev, type, ctx, parents in dag:
1913 1913 char = 'o'
1914 1914 if ctx.node() in showparents:
1915 1915 char = '@'
1916 1916 elif ctx.obsolete():
1917 1917 char = 'x'
1918 elif ctx.closesbranch():
1919 char = '_'
1918 1920 copies = None
1919 1921 if getrenamed and ctx.rev():
1920 1922 copies = []
1921 1923 for fn in ctx.files():
1922 1924 rename = getrenamed(fn, ctx.rev())
1923 1925 if rename:
1924 1926 copies.append((fn, rename[0]))
1925 1927 revmatchfn = None
1926 1928 if filematcher is not None:
1927 1929 revmatchfn = filematcher(ctx.rev())
1928 1930 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1929 1931 lines = displayer.hunk.pop(rev).split('\n')
1930 1932 if not lines[-1]:
1931 1933 del lines[-1]
1932 1934 displayer.flush(rev)
1933 1935 edges = edgefn(type, char, lines, seen, rev, parents)
1934 1936 for type, char, lines, coldata in edges:
1935 1937 graphmod.ascii(ui, state, type, char, lines, coldata)
1936 1938 displayer.close()
1937 1939
1938 1940 def graphlog(ui, repo, *pats, **opts):
1939 1941 # Parameters are identical to log command ones
1940 1942 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1941 1943 revdag = graphmod.dagwalker(repo, revs)
1942 1944
1943 1945 getrenamed = None
1944 1946 if opts.get('copies'):
1945 1947 endrev = None
1946 1948 if opts.get('rev'):
1947 1949 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1948 1950 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1949 1951 displayer = show_changeset(ui, repo, opts, buffered=True)
1950 1952 showparents = [ctx.node() for ctx in repo[None].parents()]
1951 1953 displaygraph(ui, revdag, displayer, showparents,
1952 1954 graphmod.asciiedges, getrenamed, filematcher)
1953 1955
1954 1956 def checkunsupportedgraphflags(pats, opts):
1955 1957 for op in ["newest_first"]:
1956 1958 if op in opts and opts[op]:
1957 1959 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1958 1960 % op.replace("_", "-"))
1959 1961
1960 1962 def graphrevs(repo, nodes, opts):
1961 1963 limit = loglimit(opts)
1962 1964 nodes.reverse()
1963 1965 if limit is not None:
1964 1966 nodes = nodes[:limit]
1965 1967 return graphmod.nodes(repo, nodes)
1966 1968
1967 1969 def add(ui, repo, match, prefix, explicitonly, **opts):
1968 1970 join = lambda f: os.path.join(prefix, f)
1969 1971 bad = []
1970 1972 oldbad = match.bad
1971 1973 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1972 1974 names = []
1973 1975 wctx = repo[None]
1974 1976 cca = None
1975 1977 abort, warn = scmutil.checkportabilityalert(ui)
1976 1978 if abort or warn:
1977 1979 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1978 1980 for f in wctx.walk(match):
1979 1981 exact = match.exact(f)
1980 1982 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1981 1983 if cca:
1982 1984 cca(f)
1983 1985 names.append(f)
1984 1986 if ui.verbose or not exact:
1985 1987 ui.status(_('adding %s\n') % match.rel(f))
1986 1988
1987 1989 for subpath in sorted(wctx.substate):
1988 1990 sub = wctx.sub(subpath)
1989 1991 try:
1990 1992 submatch = matchmod.narrowmatcher(subpath, match)
1991 1993 if opts.get('subrepos'):
1992 1994 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1993 1995 else:
1994 1996 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1995 1997 except error.LookupError:
1996 1998 ui.status(_("skipping missing subrepository: %s\n")
1997 1999 % join(subpath))
1998 2000
1999 2001 if not opts.get('dry_run'):
2000 2002 rejected = wctx.add(names, prefix)
2001 2003 bad.extend(f for f in rejected if f in match.files())
2002 2004 return bad
2003 2005
2004 2006 def forget(ui, repo, match, prefix, explicitonly):
2005 2007 join = lambda f: os.path.join(prefix, f)
2006 2008 bad = []
2007 2009 oldbad = match.bad
2008 2010 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2009 2011 wctx = repo[None]
2010 2012 forgot = []
2011 2013 s = repo.status(match=match, clean=True)
2012 2014 forget = sorted(s[0] + s[1] + s[3] + s[6])
2013 2015 if explicitonly:
2014 2016 forget = [f for f in forget if match.exact(f)]
2015 2017
2016 2018 for subpath in sorted(wctx.substate):
2017 2019 sub = wctx.sub(subpath)
2018 2020 try:
2019 2021 submatch = matchmod.narrowmatcher(subpath, match)
2020 2022 subbad, subforgot = sub.forget(submatch, prefix)
2021 2023 bad.extend([subpath + '/' + f for f in subbad])
2022 2024 forgot.extend([subpath + '/' + f for f in subforgot])
2023 2025 except error.LookupError:
2024 2026 ui.status(_("skipping missing subrepository: %s\n")
2025 2027 % join(subpath))
2026 2028
2027 2029 if not explicitonly:
2028 2030 for f in match.files():
2029 2031 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2030 2032 if f not in forgot:
2031 2033 if repo.wvfs.exists(f):
2032 2034 ui.warn(_('not removing %s: '
2033 2035 'file is already untracked\n')
2034 2036 % match.rel(f))
2035 2037 bad.append(f)
2036 2038
2037 2039 for f in forget:
2038 2040 if ui.verbose or not match.exact(f):
2039 2041 ui.status(_('removing %s\n') % match.rel(f))
2040 2042
2041 2043 rejected = wctx.forget(forget, prefix)
2042 2044 bad.extend(f for f in rejected if f in match.files())
2043 2045 forgot.extend(f for f in forget if f not in rejected)
2044 2046 return bad, forgot
2045 2047
2046 2048 def remove(ui, repo, m, prefix, after, force, subrepos):
2047 2049 join = lambda f: os.path.join(prefix, f)
2048 2050 ret = 0
2049 2051 s = repo.status(match=m, clean=True)
2050 2052 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2051 2053
2052 2054 wctx = repo[None]
2053 2055
2054 2056 for subpath in sorted(wctx.substate):
2055 2057 def matchessubrepo(matcher, subpath):
2056 2058 if matcher.exact(subpath):
2057 2059 return True
2058 2060 for f in matcher.files():
2059 2061 if f.startswith(subpath):
2060 2062 return True
2061 2063 return False
2062 2064
2063 2065 if subrepos or matchessubrepo(m, subpath):
2064 2066 sub = wctx.sub(subpath)
2065 2067 try:
2066 2068 submatch = matchmod.narrowmatcher(subpath, m)
2067 2069 if sub.removefiles(submatch, prefix, after, force, subrepos):
2068 2070 ret = 1
2069 2071 except error.LookupError:
2070 2072 ui.status(_("skipping missing subrepository: %s\n")
2071 2073 % join(subpath))
2072 2074
2073 2075 # warn about failure to delete explicit files/dirs
2074 2076 deleteddirs = scmutil.dirs(deleted)
2075 2077 for f in m.files():
2076 2078 def insubrepo():
2077 2079 for subpath in wctx.substate:
2078 2080 if f.startswith(subpath):
2079 2081 return True
2080 2082 return False
2081 2083
2082 2084 isdir = f in deleteddirs or f in wctx.dirs()
2083 2085 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2084 2086 continue
2085 2087
2086 2088 if repo.wvfs.exists(f):
2087 2089 if repo.wvfs.isdir(f):
2088 2090 ui.warn(_('not removing %s: no tracked files\n')
2089 2091 % m.rel(f))
2090 2092 else:
2091 2093 ui.warn(_('not removing %s: file is untracked\n')
2092 2094 % m.rel(f))
2093 2095 # missing files will generate a warning elsewhere
2094 2096 ret = 1
2095 2097
2096 2098 if force:
2097 2099 list = modified + deleted + clean + added
2098 2100 elif after:
2099 2101 list = deleted
2100 2102 for f in modified + added + clean:
2101 2103 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2102 2104 ret = 1
2103 2105 else:
2104 2106 list = deleted + clean
2105 2107 for f in modified:
2106 2108 ui.warn(_('not removing %s: file is modified (use -f'
2107 2109 ' to force removal)\n') % m.rel(f))
2108 2110 ret = 1
2109 2111 for f in added:
2110 2112 ui.warn(_('not removing %s: file has been marked for add'
2111 2113 ' (use forget to undo)\n') % m.rel(f))
2112 2114 ret = 1
2113 2115
2114 2116 for f in sorted(list):
2115 2117 if ui.verbose or not m.exact(f):
2116 2118 ui.status(_('removing %s\n') % m.rel(f))
2117 2119
2118 2120 wlock = repo.wlock()
2119 2121 try:
2120 2122 if not after:
2121 2123 for f in list:
2122 2124 if f in added:
2123 2125 continue # we never unlink added files on remove
2124 2126 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2125 2127 repo[None].forget(list)
2126 2128 finally:
2127 2129 wlock.release()
2128 2130
2129 2131 return ret
2130 2132
2131 2133 def cat(ui, repo, ctx, matcher, prefix, **opts):
2132 2134 err = 1
2133 2135
2134 2136 def write(path):
2135 2137 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2136 2138 pathname=os.path.join(prefix, path))
2137 2139 data = ctx[path].data()
2138 2140 if opts.get('decode'):
2139 2141 data = repo.wwritedata(path, data)
2140 2142 fp.write(data)
2141 2143 fp.close()
2142 2144
2143 2145 # Automation often uses hg cat on single files, so special case it
2144 2146 # for performance to avoid the cost of parsing the manifest.
2145 2147 if len(matcher.files()) == 1 and not matcher.anypats():
2146 2148 file = matcher.files()[0]
2147 2149 mf = repo.manifest
2148 2150 mfnode = ctx._changeset[0]
2149 2151 if mf.find(mfnode, file)[0]:
2150 2152 write(file)
2151 2153 return 0
2152 2154
2153 2155 # Don't warn about "missing" files that are really in subrepos
2154 2156 bad = matcher.bad
2155 2157
2156 2158 def badfn(path, msg):
2157 2159 for subpath in ctx.substate:
2158 2160 if path.startswith(subpath):
2159 2161 return
2160 2162 bad(path, msg)
2161 2163
2162 2164 matcher.bad = badfn
2163 2165
2164 2166 for abs in ctx.walk(matcher):
2165 2167 write(abs)
2166 2168 err = 0
2167 2169
2168 2170 matcher.bad = bad
2169 2171
2170 2172 for subpath in sorted(ctx.substate):
2171 2173 sub = ctx.sub(subpath)
2172 2174 try:
2173 2175 submatch = matchmod.narrowmatcher(subpath, matcher)
2174 2176
2175 2177 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2176 2178 **opts):
2177 2179 err = 0
2178 2180 except error.RepoLookupError:
2179 2181 ui.status(_("skipping missing subrepository: %s\n")
2180 2182 % os.path.join(prefix, subpath))
2181 2183
2182 2184 return err
2183 2185
2184 2186 def commit(ui, repo, commitfunc, pats, opts):
2185 2187 '''commit the specified files or all outstanding changes'''
2186 2188 date = opts.get('date')
2187 2189 if date:
2188 2190 opts['date'] = util.parsedate(date)
2189 2191 message = logmessage(ui, opts)
2190 2192 matcher = scmutil.match(repo[None], pats, opts)
2191 2193
2192 2194 # extract addremove carefully -- this function can be called from a command
2193 2195 # that doesn't support addremove
2194 2196 if opts.get('addremove'):
2195 2197 if scmutil.addremove(repo, matcher, "", opts) != 0:
2196 2198 raise util.Abort(
2197 2199 _("failed to mark all new/missing files as added/removed"))
2198 2200
2199 2201 return commitfunc(ui, repo, message, matcher, opts)
2200 2202
2201 2203 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2202 2204 # amend will reuse the existing user if not specified, but the obsolete
2203 2205 # marker creation requires that the current user's name is specified.
2204 2206 if obsolete._enabled:
2205 2207 ui.username() # raise exception if username not set
2206 2208
2207 2209 ui.note(_('amending changeset %s\n') % old)
2208 2210 base = old.p1()
2209 2211
2210 2212 wlock = lock = newid = None
2211 2213 try:
2212 2214 wlock = repo.wlock()
2213 2215 lock = repo.lock()
2214 2216 tr = repo.transaction('amend')
2215 2217 try:
2216 2218 # See if we got a message from -m or -l, if not, open the editor
2217 2219 # with the message of the changeset to amend
2218 2220 message = logmessage(ui, opts)
2219 2221 # ensure logfile does not conflict with later enforcement of the
2220 2222 # message. potential logfile content has been processed by
2221 2223 # `logmessage` anyway.
2222 2224 opts.pop('logfile')
2223 2225 # First, do a regular commit to record all changes in the working
2224 2226 # directory (if there are any)
2225 2227 ui.callhooks = False
2226 2228 currentbookmark = repo._bookmarkcurrent
2227 2229 try:
2228 2230 repo._bookmarkcurrent = None
2229 2231 opts['message'] = 'temporary amend commit for %s' % old
2230 2232 node = commit(ui, repo, commitfunc, pats, opts)
2231 2233 finally:
2232 2234 repo._bookmarkcurrent = currentbookmark
2233 2235 ui.callhooks = True
2234 2236 ctx = repo[node]
2235 2237
2236 2238 # Participating changesets:
2237 2239 #
2238 2240 # node/ctx o - new (intermediate) commit that contains changes
2239 2241 # | from working dir to go into amending commit
2240 2242 # | (or a workingctx if there were no changes)
2241 2243 # |
2242 2244 # old o - changeset to amend
2243 2245 # |
2244 2246 # base o - parent of amending changeset
2245 2247
2246 2248 # Update extra dict from amended commit (e.g. to preserve graft
2247 2249 # source)
2248 2250 extra.update(old.extra())
2249 2251
2250 2252 # Also update it from the intermediate commit or from the wctx
2251 2253 extra.update(ctx.extra())
2252 2254
2253 2255 if len(old.parents()) > 1:
2254 2256 # ctx.files() isn't reliable for merges, so fall back to the
2255 2257 # slower repo.status() method
2256 2258 files = set([fn for st in repo.status(base, old)[:3]
2257 2259 for fn in st])
2258 2260 else:
2259 2261 files = set(old.files())
2260 2262
2261 2263 # Second, we use either the commit we just did, or if there were no
2262 2264 # changes the parent of the working directory as the version of the
2263 2265 # files in the final amend commit
2264 2266 if node:
2265 2267 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2266 2268
2267 2269 user = ctx.user()
2268 2270 date = ctx.date()
2269 2271 # Recompute copies (avoid recording a -> b -> a)
2270 2272 copied = copies.pathcopies(base, ctx)
2271 2273 if old.p2:
2272 2274 copied.update(copies.pathcopies(old.p2(), ctx))
2273 2275
2274 2276 # Prune files which were reverted by the updates: if old
2275 2277 # introduced file X and our intermediate commit, node,
2276 2278 # renamed that file, then those two files are the same and
2277 2279 # we can discard X from our list of files. Likewise if X
2278 2280 # was deleted, it's no longer relevant
2279 2281 files.update(ctx.files())
2280 2282
2281 2283 def samefile(f):
2282 2284 if f in ctx.manifest():
2283 2285 a = ctx.filectx(f)
2284 2286 if f in base.manifest():
2285 2287 b = base.filectx(f)
2286 2288 return (not a.cmp(b)
2287 2289 and a.flags() == b.flags())
2288 2290 else:
2289 2291 return False
2290 2292 else:
2291 2293 return f not in base.manifest()
2292 2294 files = [f for f in files if not samefile(f)]
2293 2295
2294 2296 def filectxfn(repo, ctx_, path):
2295 2297 try:
2296 2298 fctx = ctx[path]
2297 2299 flags = fctx.flags()
2298 2300 mctx = context.memfilectx(repo,
2299 2301 fctx.path(), fctx.data(),
2300 2302 islink='l' in flags,
2301 2303 isexec='x' in flags,
2302 2304 copied=copied.get(path))
2303 2305 return mctx
2304 2306 except KeyError:
2305 2307 return None
2306 2308 else:
2307 2309 ui.note(_('copying changeset %s to %s\n') % (old, base))
2308 2310
2309 2311 # Use version of files as in the old cset
2310 2312 def filectxfn(repo, ctx_, path):
2311 2313 try:
2312 2314 return old.filectx(path)
2313 2315 except KeyError:
2314 2316 return None
2315 2317
2316 2318 user = opts.get('user') or old.user()
2317 2319 date = opts.get('date') or old.date()
2318 2320 editform = mergeeditform(old, 'commit.amend')
2319 2321 editor = getcommiteditor(editform=editform, **opts)
2320 2322 if not message:
2321 2323 editor = getcommiteditor(edit=True, editform=editform)
2322 2324 message = old.description()
2323 2325
2324 2326 pureextra = extra.copy()
2325 2327 extra['amend_source'] = old.hex()
2326 2328
2327 2329 new = context.memctx(repo,
2328 2330 parents=[base.node(), old.p2().node()],
2329 2331 text=message,
2330 2332 files=files,
2331 2333 filectxfn=filectxfn,
2332 2334 user=user,
2333 2335 date=date,
2334 2336 extra=extra,
2335 2337 editor=editor)
2336 2338
2337 2339 newdesc = changelog.stripdesc(new.description())
2338 2340 if ((not node)
2339 2341 and newdesc == old.description()
2340 2342 and user == old.user()
2341 2343 and date == old.date()
2342 2344 and pureextra == old.extra()):
2343 2345 # nothing changed. continuing here would create a new node
2344 2346 # anyway because of the amend_source noise.
2345 2347 #
2346 2348 # This not what we expect from amend.
2347 2349 return old.node()
2348 2350
2349 2351 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2350 2352 try:
2351 2353 if opts.get('secret'):
2352 2354 commitphase = 'secret'
2353 2355 else:
2354 2356 commitphase = old.phase()
2355 2357 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2356 2358 newid = repo.commitctx(new)
2357 2359 finally:
2358 2360 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2359 2361 if newid != old.node():
2360 2362 # Reroute the working copy parent to the new changeset
2361 2363 repo.setparents(newid, nullid)
2362 2364
2363 2365 # Move bookmarks from old parent to amend commit
2364 2366 bms = repo.nodebookmarks(old.node())
2365 2367 if bms:
2366 2368 marks = repo._bookmarks
2367 2369 for bm in bms:
2368 2370 marks[bm] = newid
2369 2371 marks.write()
2370 2372 #commit the whole amend process
2371 2373 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2372 2374 if createmarkers and newid != old.node():
2373 2375 # mark the new changeset as successor of the rewritten one
2374 2376 new = repo[newid]
2375 2377 obs = [(old, (new,))]
2376 2378 if node:
2377 2379 obs.append((ctx, ()))
2378 2380
2379 2381 obsolete.createmarkers(repo, obs)
2380 2382 tr.close()
2381 2383 finally:
2382 2384 tr.release()
2383 2385 if not createmarkers and newid != old.node():
2384 2386 # Strip the intermediate commit (if there was one) and the amended
2385 2387 # commit
2386 2388 if node:
2387 2389 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2388 2390 ui.note(_('stripping amended changeset %s\n') % old)
2389 2391 repair.strip(ui, repo, old.node(), topic='amend-backup')
2390 2392 finally:
2391 2393 if newid is None:
2392 2394 repo.dirstate.invalidate()
2393 2395 lockmod.release(lock, wlock)
2394 2396 return newid
2395 2397
2396 2398 def commiteditor(repo, ctx, subs, editform=''):
2397 2399 if ctx.description():
2398 2400 return ctx.description()
2399 2401 return commitforceeditor(repo, ctx, subs, editform=editform)
2400 2402
2401 2403 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2402 2404 editform=''):
2403 2405 if not extramsg:
2404 2406 extramsg = _("Leave message empty to abort commit.")
2405 2407
2406 2408 forms = [e for e in editform.split('.') if e]
2407 2409 forms.insert(0, 'changeset')
2408 2410 while forms:
2409 2411 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2410 2412 if tmpl:
2411 2413 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2412 2414 break
2413 2415 forms.pop()
2414 2416 else:
2415 2417 committext = buildcommittext(repo, ctx, subs, extramsg)
2416 2418
2417 2419 # run editor in the repository root
2418 2420 olddir = os.getcwd()
2419 2421 os.chdir(repo.root)
2420 2422 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2421 2423 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2422 2424 os.chdir(olddir)
2423 2425
2424 2426 if finishdesc:
2425 2427 text = finishdesc(text)
2426 2428 if not text.strip():
2427 2429 raise util.Abort(_("empty commit message"))
2428 2430
2429 2431 return text
2430 2432
2431 2433 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2432 2434 ui = repo.ui
2433 2435 tmpl, mapfile = gettemplate(ui, tmpl, None)
2434 2436
2435 2437 try:
2436 2438 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2437 2439 except SyntaxError, inst:
2438 2440 raise util.Abort(inst.args[0])
2439 2441
2440 2442 for k, v in repo.ui.configitems('committemplate'):
2441 2443 if k != 'changeset':
2442 2444 t.t.cache[k] = v
2443 2445
2444 2446 if not extramsg:
2445 2447 extramsg = '' # ensure that extramsg is string
2446 2448
2447 2449 ui.pushbuffer()
2448 2450 t.show(ctx, extramsg=extramsg)
2449 2451 return ui.popbuffer()
2450 2452
2451 2453 def buildcommittext(repo, ctx, subs, extramsg):
2452 2454 edittext = []
2453 2455 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2454 2456 if ctx.description():
2455 2457 edittext.append(ctx.description())
2456 2458 edittext.append("")
2457 2459 edittext.append("") # Empty line between message and comments.
2458 2460 edittext.append(_("HG: Enter commit message."
2459 2461 " Lines beginning with 'HG:' are removed."))
2460 2462 edittext.append("HG: %s" % extramsg)
2461 2463 edittext.append("HG: --")
2462 2464 edittext.append(_("HG: user: %s") % ctx.user())
2463 2465 if ctx.p2():
2464 2466 edittext.append(_("HG: branch merge"))
2465 2467 if ctx.branch():
2466 2468 edittext.append(_("HG: branch '%s'") % ctx.branch())
2467 2469 if bookmarks.iscurrent(repo):
2468 2470 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2469 2471 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2470 2472 edittext.extend([_("HG: added %s") % f for f in added])
2471 2473 edittext.extend([_("HG: changed %s") % f for f in modified])
2472 2474 edittext.extend([_("HG: removed %s") % f for f in removed])
2473 2475 if not added and not modified and not removed:
2474 2476 edittext.append(_("HG: no files changed"))
2475 2477 edittext.append("")
2476 2478
2477 2479 return "\n".join(edittext)
2478 2480
2479 2481 def commitstatus(repo, node, branch, bheads=None, opts={}):
2480 2482 ctx = repo[node]
2481 2483 parents = ctx.parents()
2482 2484
2483 2485 if (not opts.get('amend') and bheads and node not in bheads and not
2484 2486 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2485 2487 repo.ui.status(_('created new head\n'))
2486 2488 # The message is not printed for initial roots. For the other
2487 2489 # changesets, it is printed in the following situations:
2488 2490 #
2489 2491 # Par column: for the 2 parents with ...
2490 2492 # N: null or no parent
2491 2493 # B: parent is on another named branch
2492 2494 # C: parent is a regular non head changeset
2493 2495 # H: parent was a branch head of the current branch
2494 2496 # Msg column: whether we print "created new head" message
2495 2497 # In the following, it is assumed that there already exists some
2496 2498 # initial branch heads of the current branch, otherwise nothing is
2497 2499 # printed anyway.
2498 2500 #
2499 2501 # Par Msg Comment
2500 2502 # N N y additional topo root
2501 2503 #
2502 2504 # B N y additional branch root
2503 2505 # C N y additional topo head
2504 2506 # H N n usual case
2505 2507 #
2506 2508 # B B y weird additional branch root
2507 2509 # C B y branch merge
2508 2510 # H B n merge with named branch
2509 2511 #
2510 2512 # C C y additional head from merge
2511 2513 # C H n merge with a head
2512 2514 #
2513 2515 # H H n head merge: head count decreases
2514 2516
2515 2517 if not opts.get('close_branch'):
2516 2518 for r in parents:
2517 2519 if r.closesbranch() and r.branch() == branch:
2518 2520 repo.ui.status(_('reopening closed branch head %d\n') % r)
2519 2521
2520 2522 if repo.ui.debugflag:
2521 2523 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2522 2524 elif repo.ui.verbose:
2523 2525 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2524 2526
2525 2527 def revert(ui, repo, ctx, parents, *pats, **opts):
2526 2528 parent, p2 = parents
2527 2529 node = ctx.node()
2528 2530
2529 2531 mf = ctx.manifest()
2530 2532 if node == p2:
2531 2533 parent = p2
2532 2534 if node == parent:
2533 2535 pmf = mf
2534 2536 else:
2535 2537 pmf = None
2536 2538
2537 2539 # need all matching names in dirstate and manifest of target rev,
2538 2540 # so have to walk both. do not print errors if files exist in one
2539 2541 # but not other.
2540 2542
2541 2543 # `names` is a mapping for all elements in working copy and target revision
2542 2544 # The mapping is in the form:
2543 2545 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2544 2546 names = {}
2545 2547
2546 2548 wlock = repo.wlock()
2547 2549 try:
2548 2550 ## filling of the `names` mapping
2549 2551 # walk dirstate to fill `names`
2550 2552
2551 2553 m = scmutil.match(repo[None], pats, opts)
2552 2554 if not m.always() or node != parent:
2553 2555 m.bad = lambda x, y: False
2554 2556 for abs in repo.walk(m):
2555 2557 names[abs] = m.rel(abs), m.exact(abs)
2556 2558
2557 2559 # walk target manifest to fill `names`
2558 2560
2559 2561 def badfn(path, msg):
2560 2562 if path in names:
2561 2563 return
2562 2564 if path in ctx.substate:
2563 2565 return
2564 2566 path_ = path + '/'
2565 2567 for f in names:
2566 2568 if f.startswith(path_):
2567 2569 return
2568 2570 ui.warn("%s: %s\n" % (m.rel(path), msg))
2569 2571
2570 2572 m = scmutil.match(ctx, pats, opts)
2571 2573 m.bad = badfn
2572 2574 for abs in ctx.walk(m):
2573 2575 if abs not in names:
2574 2576 names[abs] = m.rel(abs), m.exact(abs)
2575 2577
2576 2578 # Find status of all file in `names`.
2577 2579 m = scmutil.matchfiles(repo, names)
2578 2580
2579 2581 changes = repo.status(node1=node, match=m,
2580 2582 unknown=True, ignored=True, clean=True)
2581 2583 else:
2582 2584 changes = repo.status(match=m)
2583 2585 for kind in changes:
2584 2586 for abs in kind:
2585 2587 names[abs] = m.rel(abs), m.exact(abs)
2586 2588
2587 2589 m = scmutil.matchfiles(repo, names)
2588 2590
2589 2591 modified = set(changes.modified)
2590 2592 added = set(changes.added)
2591 2593 removed = set(changes.removed)
2592 2594 _deleted = set(changes.deleted)
2593 2595 unknown = set(changes.unknown)
2594 2596 unknown.update(changes.ignored)
2595 2597 clean = set(changes.clean)
2596 2598 modadded = set()
2597 2599
2598 2600 # split between files known in target manifest and the others
2599 2601 smf = set(mf)
2600 2602
2601 2603 # determine the exact nature of the deleted changesets
2602 2604 deladded = _deleted - smf
2603 2605 deleted = _deleted - deladded
2604 2606
2605 2607 # We need to account for the state of the file in the dirstate,
2606 2608 # even when we revert against something else than parent. This will
2607 2609 # slightly alter the behavior of revert (doing back up or not, delete
2608 2610 # or just forget etc).
2609 2611 if parent == node:
2610 2612 dsmodified = modified
2611 2613 dsadded = added
2612 2614 dsremoved = removed
2613 2615 # store all local modifications, useful later for rename detection
2614 2616 localchanges = dsmodified | dsadded
2615 2617 modified, added, removed = set(), set(), set()
2616 2618 else:
2617 2619 changes = repo.status(node1=parent, match=m)
2618 2620 dsmodified = set(changes.modified)
2619 2621 dsadded = set(changes.added)
2620 2622 dsremoved = set(changes.removed)
2621 2623 # store all local modifications, useful later for rename detection
2622 2624 localchanges = dsmodified | dsadded
2623 2625
2624 2626 # only take into account for removes between wc and target
2625 2627 clean |= dsremoved - removed
2626 2628 dsremoved &= removed
2627 2629 # distinct between dirstate remove and other
2628 2630 removed -= dsremoved
2629 2631
2630 2632 modadded = added & dsmodified
2631 2633 added -= modadded
2632 2634
2633 2635 # tell newly modified apart.
2634 2636 dsmodified &= modified
2635 2637 dsmodified |= modified & dsadded # dirstate added may needs backup
2636 2638 modified -= dsmodified
2637 2639
2638 2640 # We need to wait for some post-processing to update this set
2639 2641 # before making the distinction. The dirstate will be used for
2640 2642 # that purpose.
2641 2643 dsadded = added
2642 2644
2643 2645 # in case of merge, files that are actually added can be reported as
2644 2646 # modified, we need to post process the result
2645 2647 if p2 != nullid:
2646 2648 if pmf is None:
2647 2649 # only need parent manifest in the merge case,
2648 2650 # so do not read by default
2649 2651 pmf = repo[parent].manifest()
2650 2652 mergeadd = dsmodified - set(pmf)
2651 2653 dsadded |= mergeadd
2652 2654 dsmodified -= mergeadd
2653 2655
2654 2656 # if f is a rename, update `names` to also revert the source
2655 2657 cwd = repo.getcwd()
2656 2658 for f in localchanges:
2657 2659 src = repo.dirstate.copied(f)
2658 2660 # XXX should we check for rename down to target node?
2659 2661 if src and src not in names and repo.dirstate[src] == 'r':
2660 2662 dsremoved.add(src)
2661 2663 names[src] = (repo.pathto(src, cwd), True)
2662 2664
2663 2665 # distinguish between file to forget and the other
2664 2666 added = set()
2665 2667 for abs in dsadded:
2666 2668 if repo.dirstate[abs] != 'a':
2667 2669 added.add(abs)
2668 2670 dsadded -= added
2669 2671
2670 2672 for abs in deladded:
2671 2673 if repo.dirstate[abs] == 'a':
2672 2674 dsadded.add(abs)
2673 2675 deladded -= dsadded
2674 2676
2675 2677 # For files marked as removed, we check if an unknown file is present at
2676 2678 # the same path. If a such file exists it may need to be backed up.
2677 2679 # Making the distinction at this stage helps have simpler backup
2678 2680 # logic.
2679 2681 removunk = set()
2680 2682 for abs in removed:
2681 2683 target = repo.wjoin(abs)
2682 2684 if os.path.lexists(target):
2683 2685 removunk.add(abs)
2684 2686 removed -= removunk
2685 2687
2686 2688 dsremovunk = set()
2687 2689 for abs in dsremoved:
2688 2690 target = repo.wjoin(abs)
2689 2691 if os.path.lexists(target):
2690 2692 dsremovunk.add(abs)
2691 2693 dsremoved -= dsremovunk
2692 2694
2693 2695 # action to be actually performed by revert
2694 2696 # (<list of file>, message>) tuple
2695 2697 actions = {'revert': ([], _('reverting %s\n')),
2696 2698 'add': ([], _('adding %s\n')),
2697 2699 'remove': ([], _('removing %s\n')),
2698 2700 'drop': ([], _('removing %s\n')),
2699 2701 'forget': ([], _('forgetting %s\n')),
2700 2702 'undelete': ([], _('undeleting %s\n')),
2701 2703 'noop': (None, _('no changes needed to %s\n')),
2702 2704 'unknown': (None, _('file not managed: %s\n')),
2703 2705 }
2704 2706
2705 2707 # "constant" that convey the backup strategy.
2706 2708 # All set to `discard` if `no-backup` is set do avoid checking
2707 2709 # no_backup lower in the code.
2708 2710 # These values are ordered for comparison purposes
2709 2711 backup = 2 # unconditionally do backup
2710 2712 check = 1 # check if the existing file differs from target
2711 2713 discard = 0 # never do backup
2712 2714 if opts.get('no_backup'):
2713 2715 backup = check = discard
2714 2716
2715 2717 backupanddel = actions['remove']
2716 2718 if not opts.get('no_backup'):
2717 2719 backupanddel = actions['drop']
2718 2720
2719 2721 disptable = (
2720 2722 # dispatch table:
2721 2723 # file state
2722 2724 # action
2723 2725 # make backup
2724 2726
2725 2727 ## Sets that results that will change file on disk
2726 2728 # Modified compared to target, no local change
2727 2729 (modified, actions['revert'], discard),
2728 2730 # Modified compared to target, but local file is deleted
2729 2731 (deleted, actions['revert'], discard),
2730 2732 # Modified compared to target, local change
2731 2733 (dsmodified, actions['revert'], backup),
2732 2734 # Added since target
2733 2735 (added, actions['remove'], discard),
2734 2736 # Added in working directory
2735 2737 (dsadded, actions['forget'], discard),
2736 2738 # Added since target, have local modification
2737 2739 (modadded, backupanddel, backup),
2738 2740 # Added since target but file is missing in working directory
2739 2741 (deladded, actions['drop'], discard),
2740 2742 # Removed since target, before working copy parent
2741 2743 (removed, actions['add'], discard),
2742 2744 # Same as `removed` but an unknown file exists at the same path
2743 2745 (removunk, actions['add'], check),
2744 2746 # Removed since targe, marked as such in working copy parent
2745 2747 (dsremoved, actions['undelete'], discard),
2746 2748 # Same as `dsremoved` but an unknown file exists at the same path
2747 2749 (dsremovunk, actions['undelete'], check),
2748 2750 ## the following sets does not result in any file changes
2749 2751 # File with no modification
2750 2752 (clean, actions['noop'], discard),
2751 2753 # Existing file, not tracked anywhere
2752 2754 (unknown, actions['unknown'], discard),
2753 2755 )
2754 2756
2755 2757 wctx = repo[None]
2756 2758 for abs, (rel, exact) in sorted(names.items()):
2757 2759 # target file to be touch on disk (relative to cwd)
2758 2760 target = repo.wjoin(abs)
2759 2761 # search the entry in the dispatch table.
2760 2762 # if the file is in any of these sets, it was touched in the working
2761 2763 # directory parent and we are sure it needs to be reverted.
2762 2764 for table, (xlist, msg), dobackup in disptable:
2763 2765 if abs not in table:
2764 2766 continue
2765 2767 if xlist is not None:
2766 2768 xlist.append(abs)
2767 2769 if dobackup and (backup <= dobackup
2768 2770 or wctx[abs].cmp(ctx[abs])):
2769 2771 bakname = "%s.orig" % rel
2770 2772 ui.note(_('saving current version of %s as %s\n') %
2771 2773 (rel, bakname))
2772 2774 if not opts.get('dry_run'):
2773 2775 util.rename(target, bakname)
2774 2776 if ui.verbose or not exact:
2775 2777 if not isinstance(msg, basestring):
2776 2778 msg = msg(abs)
2777 2779 ui.status(msg % rel)
2778 2780 elif exact:
2779 2781 ui.warn(msg % rel)
2780 2782 break
2781 2783
2782 2784
2783 2785 if not opts.get('dry_run'):
2784 2786 needdata = ('revert', 'add', 'undelete')
2785 2787 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2786 2788
2787 2789 _performrevert(repo, parents, ctx, actions)
2788 2790
2789 2791 # get the list of subrepos that must be reverted
2790 2792 subrepomatch = scmutil.match(ctx, pats, opts)
2791 2793 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2792 2794
2793 2795 if targetsubs:
2794 2796 # Revert the subrepos on the revert list
2795 2797 for sub in targetsubs:
2796 2798 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2797 2799 finally:
2798 2800 wlock.release()
2799 2801
2800 2802 def _revertprefetch(repo, ctx, *files):
2801 2803 """Let extension changing the storage layer prefetch content"""
2802 2804 pass
2803 2805
2804 2806 def _performrevert(repo, parents, ctx, actions):
2805 2807 """function that actually perform all the actions computed for revert
2806 2808
2807 2809 This is an independent function to let extension to plug in and react to
2808 2810 the imminent revert.
2809 2811
2810 2812 Make sure you have the working directory locked when calling this function.
2811 2813 """
2812 2814 parent, p2 = parents
2813 2815 node = ctx.node()
2814 2816 def checkout(f):
2815 2817 fc = ctx[f]
2816 2818 repo.wwrite(f, fc.data(), fc.flags())
2817 2819
2818 2820 audit_path = pathutil.pathauditor(repo.root)
2819 2821 for f in actions['forget'][0]:
2820 2822 repo.dirstate.drop(f)
2821 2823 for f in actions['remove'][0]:
2822 2824 audit_path(f)
2823 2825 util.unlinkpath(repo.wjoin(f))
2824 2826 repo.dirstate.remove(f)
2825 2827 for f in actions['drop'][0]:
2826 2828 audit_path(f)
2827 2829 repo.dirstate.remove(f)
2828 2830
2829 2831 normal = None
2830 2832 if node == parent:
2831 2833 # We're reverting to our parent. If possible, we'd like status
2832 2834 # to report the file as clean. We have to use normallookup for
2833 2835 # merges to avoid losing information about merged/dirty files.
2834 2836 if p2 != nullid:
2835 2837 normal = repo.dirstate.normallookup
2836 2838 else:
2837 2839 normal = repo.dirstate.normal
2838 2840 for f in actions['revert'][0]:
2839 2841 checkout(f)
2840 2842 if normal:
2841 2843 normal(f)
2842 2844
2843 2845 for f in actions['add'][0]:
2844 2846 checkout(f)
2845 2847 repo.dirstate.add(f)
2846 2848
2847 2849 normal = repo.dirstate.normallookup
2848 2850 if node == parent and p2 == nullid:
2849 2851 normal = repo.dirstate.normal
2850 2852 for f in actions['undelete'][0]:
2851 2853 checkout(f)
2852 2854 normal(f)
2853 2855
2854 2856 copied = copies.pathcopies(repo[parent], ctx)
2855 2857
2856 2858 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2857 2859 if f in copied:
2858 2860 repo.dirstate.copy(copied[f], f)
2859 2861
2860 2862 def command(table):
2861 2863 """Returns a function object to be used as a decorator for making commands.
2862 2864
2863 2865 This function receives a command table as its argument. The table should
2864 2866 be a dict.
2865 2867
2866 2868 The returned function can be used as a decorator for adding commands
2867 2869 to that command table. This function accepts multiple arguments to define
2868 2870 a command.
2869 2871
2870 2872 The first argument is the command name.
2871 2873
2872 2874 The options argument is an iterable of tuples defining command arguments.
2873 2875 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2874 2876
2875 2877 The synopsis argument defines a short, one line summary of how to use the
2876 2878 command. This shows up in the help output.
2877 2879
2878 2880 The norepo argument defines whether the command does not require a
2879 2881 local repository. Most commands operate against a repository, thus the
2880 2882 default is False.
2881 2883
2882 2884 The optionalrepo argument defines whether the command optionally requires
2883 2885 a local repository.
2884 2886
2885 2887 The inferrepo argument defines whether to try to find a repository from the
2886 2888 command line arguments. If True, arguments will be examined for potential
2887 2889 repository locations. See ``findrepo()``. If a repository is found, it
2888 2890 will be used.
2889 2891 """
2890 2892 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2891 2893 inferrepo=False):
2892 2894 def decorator(func):
2893 2895 if synopsis:
2894 2896 table[name] = func, list(options), synopsis
2895 2897 else:
2896 2898 table[name] = func, list(options)
2897 2899
2898 2900 if norepo:
2899 2901 # Avoid import cycle.
2900 2902 import commands
2901 2903 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2902 2904
2903 2905 if optionalrepo:
2904 2906 import commands
2905 2907 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2906 2908
2907 2909 if inferrepo:
2908 2910 import commands
2909 2911 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2910 2912
2911 2913 return func
2912 2914 return decorator
2913 2915
2914 2916 return cmd
2915 2917
2916 2918 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2917 2919 # commands.outgoing. "missing" is "missing" of the result of
2918 2920 # "findcommonoutgoing()"
2919 2921 outgoinghooks = util.hooks()
2920 2922
2921 2923 # a list of (ui, repo) functions called by commands.summary
2922 2924 summaryhooks = util.hooks()
2923 2925
2924 2926 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2925 2927 #
2926 2928 # functions should return tuple of booleans below, if 'changes' is None:
2927 2929 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2928 2930 #
2929 2931 # otherwise, 'changes' is a tuple of tuples below:
2930 2932 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2931 2933 # - (desturl, destbranch, destpeer, outgoing)
2932 2934 summaryremotehooks = util.hooks()
2933 2935
2934 2936 # A list of state files kept by multistep operations like graft.
2935 2937 # Since graft cannot be aborted, it is considered 'clearable' by update.
2936 2938 # note: bisect is intentionally excluded
2937 2939 # (state file, clearable, allowcommit, error, hint)
2938 2940 unfinishedstates = [
2939 2941 ('graftstate', True, False, _('graft in progress'),
2940 2942 _("use 'hg graft --continue' or 'hg update' to abort")),
2941 2943 ('updatestate', True, False, _('last update was interrupted'),
2942 2944 _("use 'hg update' to get a consistent checkout"))
2943 2945 ]
2944 2946
2945 2947 def checkunfinished(repo, commit=False):
2946 2948 '''Look for an unfinished multistep operation, like graft, and abort
2947 2949 if found. It's probably good to check this right before
2948 2950 bailifchanged().
2949 2951 '''
2950 2952 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2951 2953 if commit and allowcommit:
2952 2954 continue
2953 2955 if repo.vfs.exists(f):
2954 2956 raise util.Abort(msg, hint=hint)
2955 2957
2956 2958 def clearunfinished(repo):
2957 2959 '''Check for unfinished operations (as above), and clear the ones
2958 2960 that are clearable.
2959 2961 '''
2960 2962 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2961 2963 if not clearable and repo.vfs.exists(f):
2962 2964 raise util.Abort(msg, hint=hint)
2963 2965 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2964 2966 if clearable and repo.vfs.exists(f):
2965 2967 util.unlink(repo.join(f))
@@ -1,213 +1,213
1 1
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > convert=
5 5 > EOF
6 6 $ hg init t
7 7 $ cd t
8 8 $ echo a >> a
9 9 $ hg ci -Am a0 -d '1 0'
10 10 adding a
11 11 $ hg branch brancha
12 12 marked working directory as branch brancha
13 13 (branches are permanent and global, did you want a bookmark?)
14 14 $ echo a >> a
15 15 $ hg ci -m a1 -d '2 0'
16 16 $ echo a >> a
17 17 $ hg ci -m a2 -d '3 0'
18 18 $ echo a >> a
19 19 $ hg ci -m a3 -d '4 0'
20 20 $ hg up -C 0
21 21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 22 $ hg branch branchb
23 23 marked working directory as branch branchb
24 24 (branches are permanent and global, did you want a bookmark?)
25 25 $ echo b >> b
26 26 $ hg ci -Am b0 -d '6 0'
27 27 adding b
28 28 $ hg up -C brancha
29 29 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
30 30 $ echo a >> a
31 31 $ hg ci -m a4 -d '5 0'
32 32 $ echo a >> a
33 33 $ hg ci -m a5 -d '7 0'
34 34 $ echo a >> a
35 35 $ hg ci -m a6 -d '8 0'
36 36 $ hg up -C branchb
37 37 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 $ echo b >> b
39 39 $ hg ci -m b1 -d '9 0'
40 40 $ hg up -C 0
41 41 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
42 42 $ echo c >> c
43 43 $ hg branch branchc
44 44 marked working directory as branch branchc
45 45 (branches are permanent and global, did you want a bookmark?)
46 46 $ hg ci -Am c0 -d '10 0'
47 47 adding c
48 48 $ hg up -C brancha
49 49 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
50 50 $ hg ci --close-branch -m a7x -d '11 0'
51 51 $ hg up -C branchb
52 52 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 53 $ hg ci --close-branch -m b2x -d '12 0'
54 54 $ hg up -C branchc
55 55 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
56 56 $ hg merge branchb
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 (branch merge, don't forget to commit)
59 59 $ hg ci -m c1 -d '13 0'
60 60 $ cd ..
61 61
62 62 convert with datesort
63 63
64 64 $ hg convert --datesort t t-datesort
65 65 initializing destination t-datesort repository
66 66 scanning source...
67 67 sorting...
68 68 converting...
69 69 12 a0
70 70 11 a1
71 71 10 a2
72 72 9 a3
73 73 8 a4
74 74 7 b0
75 75 6 a5
76 76 5 a6
77 77 4 b1
78 78 3 c0
79 79 2 a7x
80 80 1 b2x
81 81 0 c1
82 82
83 83 graph converted repo
84 84
85 85 $ hg -R t-datesort log -G --template '{rev} "{desc}"\n'
86 86 o 12 "c1"
87 87 |\
88 | o 11 "b2x"
88 | _ 11 "b2x"
89 89 | |
90 | | o 10 "a7x"
90 | | _ 10 "a7x"
91 91 | | |
92 92 o | | 9 "c0"
93 93 | | |
94 94 | o | 8 "b1"
95 95 | | |
96 96 | | o 7 "a6"
97 97 | | |
98 98 | | o 6 "a5"
99 99 | | |
100 100 | o | 5 "b0"
101 101 |/ /
102 102 | o 4 "a4"
103 103 | |
104 104 | o 3 "a3"
105 105 | |
106 106 | o 2 "a2"
107 107 | |
108 108 | o 1 "a1"
109 109 |/
110 110 o 0 "a0"
111 111
112 112
113 113 convert with datesort (default mode)
114 114
115 115 $ hg convert t t-sourcesort
116 116 initializing destination t-sourcesort repository
117 117 scanning source...
118 118 sorting...
119 119 converting...
120 120 12 a0
121 121 11 a1
122 122 10 a2
123 123 9 a3
124 124 8 b0
125 125 7 a4
126 126 6 a5
127 127 5 a6
128 128 4 b1
129 129 3 c0
130 130 2 a7x
131 131 1 b2x
132 132 0 c1
133 133
134 134 graph converted repo
135 135
136 136 $ hg -R t-sourcesort log -G --template '{rev} "{desc}"\n'
137 137 o 12 "c1"
138 138 |\
139 | o 11 "b2x"
139 | _ 11 "b2x"
140 140 | |
141 | | o 10 "a7x"
141 | | _ 10 "a7x"
142 142 | | |
143 143 o | | 9 "c0"
144 144 | | |
145 145 | o | 8 "b1"
146 146 | | |
147 147 | | o 7 "a6"
148 148 | | |
149 149 | | o 6 "a5"
150 150 | | |
151 151 | | o 5 "a4"
152 152 | | |
153 153 | o | 4 "b0"
154 154 |/ /
155 155 | o 3 "a3"
156 156 | |
157 157 | o 2 "a2"
158 158 | |
159 159 | o 1 "a1"
160 160 |/
161 161 o 0 "a0"
162 162
163 163
164 164 convert with closesort
165 165
166 166 $ hg convert --closesort t t-closesort
167 167 initializing destination t-closesort repository
168 168 scanning source...
169 169 sorting...
170 170 converting...
171 171 12 a0
172 172 11 a1
173 173 10 a2
174 174 9 a3
175 175 8 b0
176 176 7 a4
177 177 6 a5
178 178 5 a6
179 179 4 a7x
180 180 3 b1
181 181 2 b2x
182 182 1 c0
183 183 0 c1
184 184
185 185 graph converted repo
186 186
187 187 $ hg -R t-closesort log -G --template '{rev} "{desc}"\n'
188 188 o 12 "c1"
189 189 |\
190 190 | o 11 "c0"
191 191 | |
192 o | 10 "b2x"
192 _ | 10 "b2x"
193 193 | |
194 194 o | 9 "b1"
195 195 | |
196 | | o 8 "a7x"
196 | | _ 8 "a7x"
197 197 | | |
198 198 | | o 7 "a6"
199 199 | | |
200 200 | | o 6 "a5"
201 201 | | |
202 202 | | o 5 "a4"
203 203 | | |
204 204 o | | 4 "b0"
205 205 |/ /
206 206 | o 3 "a3"
207 207 | |
208 208 | o 2 "a2"
209 209 | |
210 210 | o 1 "a1"
211 211 |/
212 212 o 0 "a0"
213 213
@@ -1,673 +1,673
1 1
2 2 $ HGMERGE=true; export HGMERGE
3 3 $ echo '[extensions]' >> $HGRCPATH
4 4 $ echo 'convert =' >> $HGRCPATH
5 5 $ glog()
6 6 > {
7 7 > hg log -G --template '{rev} "{desc}" files: {files}\n' "$@"
8 8 > }
9 9 $ hg init source
10 10 $ cd source
11 11 $ echo foo > foo
12 12 $ echo baz > baz
13 13 $ mkdir -p dir/subdir
14 14 $ echo dir/file >> dir/file
15 15 $ echo dir/file2 >> dir/file2
16 16 $ echo dir/file3 >> dir/file3 # to be corrupted in rev 0
17 17 $ echo dir/subdir/file3 >> dir/subdir/file3
18 18 $ echo dir/subdir/file4 >> dir/subdir/file4
19 19 $ hg ci -d '0 0' -qAm '0: add foo baz dir/'
20 20 $ echo bar > bar
21 21 $ echo quux > quux
22 22 $ echo dir/file4 >> dir/file4 # to be corrupted in rev 1
23 23 $ hg copy foo copied
24 24 $ hg ci -d '1 0' -qAm '1: add bar quux; copy foo to copied'
25 25 $ echo >> foo
26 26 $ hg ci -d '2 0' -m '2: change foo'
27 27 $ hg up -qC 1
28 28 $ echo >> bar
29 29 $ echo >> quux
30 30 $ hg ci -d '3 0' -m '3: change bar quux'
31 31 created new head
32 32 $ hg up -qC 2
33 33 $ hg merge -qr 3
34 34 $ echo >> bar
35 35 $ echo >> baz
36 36 $ hg ci -d '4 0' -m '4: first merge; change bar baz'
37 37 $ echo >> bar
38 38 $ echo 1 >> baz
39 39 $ echo >> quux
40 40 $ hg ci -d '5 0' -m '5: change bar baz quux'
41 41 $ hg up -qC 4
42 42 $ echo >> foo
43 43 $ echo 2 >> baz
44 44 $ hg ci -d '6 0' -m '6: change foo baz'
45 45 created new head
46 46 $ hg up -qC 5
47 47 $ hg merge -qr 6
48 48 $ echo >> bar
49 49 $ hg ci -d '7 0' -m '7: second merge; change bar'
50 50 $ echo >> foo
51 51 $ hg ci -m '8: change foo'
52 52 $ glog
53 53 @ 8 "8: change foo" files: foo
54 54 |
55 55 o 7 "7: second merge; change bar" files: bar baz
56 56 |\
57 57 | o 6 "6: change foo baz" files: baz foo
58 58 | |
59 59 o | 5 "5: change bar baz quux" files: bar baz quux
60 60 |/
61 61 o 4 "4: first merge; change bar baz" files: bar baz
62 62 |\
63 63 | o 3 "3: change bar quux" files: bar quux
64 64 | |
65 65 o | 2 "2: change foo" files: foo
66 66 |/
67 67 o 1 "1: add bar quux; copy foo to copied" files: bar copied dir/file4 quux
68 68 |
69 69 o 0 "0: add foo baz dir/" files: baz dir/file dir/file2 dir/file3 dir/subdir/file3 dir/subdir/file4 foo
70 70
71 71
72 72 final file versions in this repo:
73 73
74 74 $ hg manifest --debug
75 75 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
76 76 94c1be4dfde2ee8d78db8bbfcf81210813307c3d 644 baz
77 77 7711d36246cc83e61fb29cd6d4ef394c63f1ceaf 644 copied
78 78 3e20847584beff41d7cd16136b7331ab3d754be0 644 dir/file
79 79 75e6d3f8328f5f6ace6bf10b98df793416a09dca 644 dir/file2
80 80 e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c 644 dir/file3
81 81 6edd55f559cdce67132b12ca09e09cee08b60442 644 dir/file4
82 82 5fe139720576e18e34bcc9f79174db8897c8afe9 644 dir/subdir/file3
83 83 57a1c1511590f3de52874adfa04effe8a77d64af 644 dir/subdir/file4
84 84 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
85 85 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
86 86 $ hg debugrename copied
87 87 copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
88 88
89 89 $ cd ..
90 90
91 91
92 92 Test interaction with startrev and verify that changing it is handled properly:
93 93
94 94 $ > empty
95 95 $ hg convert --filemap empty source movingstart --config convert.hg.startrev=3 -r4
96 96 initializing destination movingstart repository
97 97 scanning source...
98 98 sorting...
99 99 converting...
100 100 1 3: change bar quux
101 101 0 4: first merge; change bar baz
102 102 $ hg convert --filemap empty source movingstart
103 103 scanning source...
104 104 sorting...
105 105 converting...
106 106 3 5: change bar baz quux
107 107 2 6: change foo baz
108 108 1 7: second merge; change bar
109 109 warning: af455ce4166b3c9c88e6309c2b9332171dcea595 parent 61e22ca76c3b3e93df20338c4e02ce286898e825 is missing
110 110 warning: cf908b3eeedc301c9272ebae931da966d5b326c7 parent 59e1ab45c888289513b7354484dac8a88217beab is missing
111 111 0 8: change foo
112 112
113 113
114 114 splitrepo tests
115 115
116 116 $ splitrepo()
117 117 > {
118 118 > msg="$1"
119 119 > files="$2"
120 120 > opts=$3
121 121 > echo "% $files: $msg"
122 122 > prefix=`echo "$files" | sed -e 's/ /-/g'`
123 123 > fmap="$prefix.fmap"
124 124 > repo="$prefix.repo"
125 125 > for i in $files; do
126 126 > echo "include $i" >> "$fmap"
127 127 > done
128 128 > hg -q convert $opts --filemap "$fmap" --datesort source "$repo"
129 129 > hg up -q -R "$repo"
130 130 > glog -R "$repo"
131 131 > hg -R "$repo" manifest --debug
132 132 > }
133 133 $ splitrepo 'skip unwanted merges; use 1st parent in 1st merge, 2nd in 2nd' foo
134 134 % foo: skip unwanted merges; use 1st parent in 1st merge, 2nd in 2nd
135 135 @ 3 "8: change foo" files: foo
136 136 |
137 137 o 2 "6: change foo baz" files: foo
138 138 |
139 139 o 1 "2: change foo" files: foo
140 140 |
141 141 o 0 "0: add foo baz dir/" files: foo
142 142
143 143 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
144 144 $ splitrepo 'merges are not merges anymore' bar
145 145 % bar: merges are not merges anymore
146 146 @ 4 "7: second merge; change bar" files: bar
147 147 |
148 148 o 3 "5: change bar baz quux" files: bar
149 149 |
150 150 o 2 "4: first merge; change bar baz" files: bar
151 151 |
152 152 o 1 "3: change bar quux" files: bar
153 153 |
154 154 o 0 "1: add bar quux; copy foo to copied" files: bar
155 155
156 156 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
157 157 $ splitrepo '1st merge is not a merge anymore; 2nd still is' baz
158 158 % baz: 1st merge is not a merge anymore; 2nd still is
159 159 @ 4 "7: second merge; change bar" files: baz
160 160 |\
161 161 | o 3 "6: change foo baz" files: baz
162 162 | |
163 163 o | 2 "5: change bar baz quux" files: baz
164 164 |/
165 165 o 1 "4: first merge; change bar baz" files: baz
166 166 |
167 167 o 0 "0: add foo baz dir/" files: baz
168 168
169 169 94c1be4dfde2ee8d78db8bbfcf81210813307c3d 644 baz
170 170 $ splitrepo 'we add additional merges when they are interesting' 'foo quux'
171 171 % foo quux: we add additional merges when they are interesting
172 172 @ 8 "8: change foo" files: foo
173 173 |
174 174 o 7 "7: second merge; change bar" files:
175 175 |\
176 176 | o 6 "6: change foo baz" files: foo
177 177 | |
178 178 o | 5 "5: change bar baz quux" files: quux
179 179 |/
180 180 o 4 "4: first merge; change bar baz" files:
181 181 |\
182 182 | o 3 "3: change bar quux" files: quux
183 183 | |
184 184 o | 2 "2: change foo" files: foo
185 185 |/
186 186 o 1 "1: add bar quux; copy foo to copied" files: quux
187 187 |
188 188 o 0 "0: add foo baz dir/" files: foo
189 189
190 190 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
191 191 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
192 192 $ splitrepo 'partial conversion' 'bar quux' '-r 3'
193 193 % bar quux: partial conversion
194 194 @ 1 "3: change bar quux" files: bar quux
195 195 |
196 196 o 0 "1: add bar quux; copy foo to copied" files: bar quux
197 197
198 198 b79105bedc55102f394e90a789c9c380117c1b4a 644 bar
199 199 db0421cc6b685a458c8d86c7d5c004f94429ea23 644 quux
200 200 $ splitrepo 'complete the partial conversion' 'bar quux'
201 201 % bar quux: complete the partial conversion
202 202 @ 4 "7: second merge; change bar" files: bar
203 203 |
204 204 o 3 "5: change bar baz quux" files: bar quux
205 205 |
206 206 o 2 "4: first merge; change bar baz" files: bar
207 207 |
208 208 o 1 "3: change bar quux" files: bar quux
209 209 |
210 210 o 0 "1: add bar quux; copy foo to copied" files: bar quux
211 211
212 212 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
213 213 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
214 214 $ rm -r foo.repo
215 215 $ splitrepo 'partial conversion' 'foo' '-r 3'
216 216 % foo: partial conversion
217 217 @ 0 "0: add foo baz dir/" files: foo
218 218
219 219 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
220 220 $ splitrepo 'complete the partial conversion' 'foo'
221 221 % foo: complete the partial conversion
222 222 @ 3 "8: change foo" files: foo
223 223 |
224 224 o 2 "6: change foo baz" files: foo
225 225 |
226 226 o 1 "2: change foo" files: foo
227 227 |
228 228 o 0 "0: add foo baz dir/" files: foo
229 229
230 230 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
231 231 $ splitrepo 'copied file; source not included in new repo' copied
232 232 % copied: copied file; source not included in new repo
233 233 @ 0 "1: add bar quux; copy foo to copied" files: copied
234 234
235 235 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 copied
236 236 $ hg --cwd copied.repo debugrename copied
237 237 copied not renamed
238 238 $ splitrepo 'copied file; source included in new repo' 'foo copied'
239 239 % foo copied: copied file; source included in new repo
240 240 @ 4 "8: change foo" files: foo
241 241 |
242 242 o 3 "6: change foo baz" files: foo
243 243 |
244 244 o 2 "2: change foo" files: foo
245 245 |
246 246 o 1 "1: add bar quux; copy foo to copied" files: copied
247 247 |
248 248 o 0 "0: add foo baz dir/" files: foo
249 249
250 250 7711d36246cc83e61fb29cd6d4ef394c63f1ceaf 644 copied
251 251 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
252 252 $ hg --cwd foo-copied.repo debugrename copied
253 253 copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
254 254
255 255 verify the top level 'include .' if there is no other includes:
256 256
257 257 $ echo "exclude something" > default.fmap
258 258 $ hg convert -q --filemap default.fmap -r1 source dummydest2
259 259 $ hg -R dummydest2 log --template '{rev} {node|short} {desc|firstline}\n'
260 260 1 61e22ca76c3b 1: add bar quux; copy foo to copied
261 261 0 c085cf2ee7fe 0: add foo baz dir/
262 262
263 263 $ echo "include somethingelse" >> default.fmap
264 264 $ hg convert -q --filemap default.fmap -r1 source dummydest3
265 265 $ hg -R dummydest3 log --template '{rev} {node|short} {desc|firstline}\n'
266 266
267 267 $ echo "include ." >> default.fmap
268 268 $ hg convert -q --filemap default.fmap -r1 source dummydest4
269 269 $ hg -R dummydest4 log --template '{rev} {node|short} {desc|firstline}\n'
270 270 1 61e22ca76c3b 1: add bar quux; copy foo to copied
271 271 0 c085cf2ee7fe 0: add foo baz dir/
272 272
273 273 ensure that the filemap contains duplicated slashes (issue3612)
274 274
275 275 $ cat > renames.fmap <<EOF
276 276 > include dir
277 277 > exclude dir/file2
278 278 > rename dir dir2//dir3
279 279 > include foo
280 280 > include copied
281 281 > rename foo foo2/
282 282 > rename copied ./copied2
283 283 > exclude dir/subdir
284 284 > include dir/subdir/file3
285 285 > EOF
286 286 $ rm source/.hg/store/data/dir/file3.i
287 287 $ rm source/.hg/store/data/dir/file4.i
288 288 $ hg -q convert --filemap renames.fmap --datesort source dummydest
289 289 abort: data/dir/file3.i@e96dce0bc6a2: no match found!
290 290 [255]
291 291 $ hg -q convert --filemap renames.fmap --datesort --config convert.hg.ignoreerrors=1 source renames.repo
292 292 ignoring: data/dir/file3.i@e96dce0bc6a2: no match found
293 293 ignoring: data/dir/file4.i@6edd55f559cd: no match found
294 294 $ hg up -q -R renames.repo
295 295 $ glog -R renames.repo
296 296 @ 4 "8: change foo" files: foo2
297 297 |
298 298 o 3 "6: change foo baz" files: foo2
299 299 |
300 300 o 2 "2: change foo" files: foo2
301 301 |
302 302 o 1 "1: add bar quux; copy foo to copied" files: copied2
303 303 |
304 304 o 0 "0: add foo baz dir/" files: dir2/dir3/file dir2/dir3/subdir/file3 foo2
305 305
306 306 $ hg -R renames.repo verify
307 307 checking changesets
308 308 checking manifests
309 309 crosschecking files in changesets and manifests
310 310 checking files
311 311 4 files, 5 changesets, 7 total revisions
312 312
313 313 $ hg -R renames.repo manifest --debug
314 314 d43feacba7a4f1f2080dde4a4b985bd8a0236d46 644 copied2
315 315 3e20847584beff41d7cd16136b7331ab3d754be0 644 dir2/dir3/file
316 316 5fe139720576e18e34bcc9f79174db8897c8afe9 644 dir2/dir3/subdir/file3
317 317 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo2
318 318 $ hg --cwd renames.repo debugrename copied2
319 319 copied2 renamed from foo2:2ed2a3912a0b24502043eae84ee4b279c18b90dd
320 320
321 321 copied:
322 322
323 323 $ hg --cwd source cat copied
324 324 foo
325 325
326 326 copied2:
327 327
328 328 $ hg --cwd renames.repo cat copied2
329 329 foo
330 330
331 331 filemap errors
332 332
333 333 $ cat > errors.fmap <<EOF
334 334 > include dir/ # beware that comments changes error line numbers!
335 335 > exclude /dir
336 336 > rename dir//dir /dir//dir/ "out of sync"
337 337 > include
338 338 > EOF
339 339 $ hg -q convert --filemap errors.fmap source errors.repo
340 340 errors.fmap:3: superfluous / in include '/dir'
341 341 errors.fmap:3: superfluous / in rename '/dir'
342 342 errors.fmap:4: unknown directive 'out of sync'
343 343 errors.fmap:5: path to exclude is missing
344 344 abort: errors in filemap
345 345 [255]
346 346
347 347 test branch closing revision pruning if branch is pruned
348 348
349 349 $ hg init branchpruning
350 350 $ cd branchpruning
351 351 $ hg branch foo
352 352 marked working directory as branch foo
353 353 (branches are permanent and global, did you want a bookmark?)
354 354 $ echo a > a
355 355 $ hg ci -Am adda
356 356 adding a
357 357 $ hg ci --close-branch -m closefoo
358 358 $ hg up 0
359 359 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
360 360 $ hg branch empty
361 361 marked working directory as branch empty
362 362 (branches are permanent and global, did you want a bookmark?)
363 363 $ hg ci -m emptybranch
364 364 $ hg ci --close-branch -m closeempty
365 365 $ hg up 0
366 366 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 367 $ hg branch default
368 368 marked working directory as branch default
369 369 (branches are permanent and global, did you want a bookmark?)
370 370 $ echo b > b
371 371 $ hg ci -Am addb
372 372 adding b
373 373 $ hg ci --close-branch -m closedefault
374 374 $ cat > filemap <<EOF
375 375 > include b
376 376 > EOF
377 377 $ cd ..
378 378 $ hg convert branchpruning branchpruning-hg1
379 379 initializing destination branchpruning-hg1 repository
380 380 scanning source...
381 381 sorting...
382 382 converting...
383 383 5 adda
384 384 4 closefoo
385 385 3 emptybranch
386 386 2 closeempty
387 387 1 addb
388 388 0 closedefault
389 389 $ glog -R branchpruning-hg1
390 o 5 "closedefault" files:
390 _ 5 "closedefault" files:
391 391 |
392 392 o 4 "addb" files: b
393 393 |
394 | o 3 "closeempty" files:
394 | _ 3 "closeempty" files:
395 395 | |
396 396 | o 2 "emptybranch" files:
397 397 |/
398 | o 1 "closefoo" files:
398 | _ 1 "closefoo" files:
399 399 |/
400 400 o 0 "adda" files: a
401 401
402 402
403 403 exercise incremental conversion at the same time
404 404
405 405 $ hg convert -r0 --filemap branchpruning/filemap branchpruning branchpruning-hg2
406 406 initializing destination branchpruning-hg2 repository
407 407 scanning source...
408 408 sorting...
409 409 converting...
410 410 0 adda
411 411 $ hg convert -r4 --filemap branchpruning/filemap branchpruning branchpruning-hg2
412 412 scanning source...
413 413 sorting...
414 414 converting...
415 415 0 addb
416 416 $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2
417 417 scanning source...
418 418 sorting...
419 419 converting...
420 420 3 closefoo
421 421 2 emptybranch
422 422 1 closeempty
423 423 0 closedefault
424 424 $ glog -R branchpruning-hg2
425 o 1 "closedefault" files:
425 _ 1 "closedefault" files:
426 426 |
427 427 o 0 "addb" files: b
428 428
429 429
430 430 Test rebuilding of map with unknown revisions in shamap - it used to crash
431 431
432 432 $ cd branchpruning
433 433 $ hg up -r 2
434 434 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
435 435 $ hg merge 4
436 436 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 437 (branch merge, don't forget to commit)
438 438 $ hg ci -m 'merging something'
439 439 $ cd ..
440 440 $ echo "53792d18237d2b64971fa571936869156655338d 6d955580116e82c4b029bd30f321323bae71a7f0" >> branchpruning-hg2/.hg/shamap
441 441 $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug
442 442 run hg source pre-conversion action
443 443 run hg sink pre-conversion action
444 444 scanning source...
445 445 scanning: 1 revisions
446 446 sorting...
447 447 converting...
448 448 0 merging something
449 449 source: 2503605b178fe50e8fbbb0e77b97939540aa8c87
450 450 converting: 0/1 revisions (0.00%)
451 451 unknown revmap source: 53792d18237d2b64971fa571936869156655338d
452 452 run hg sink post-conversion action
453 453 run hg source post-conversion action
454 454
455 455
456 456 filemap rename undoing revision rename
457 457
458 458 $ hg init renameundo
459 459 $ cd renameundo
460 460 $ echo 1 > a
461 461 $ echo 1 > c
462 462 $ hg ci -qAm add
463 463 $ hg mv -q a b/a
464 464 $ hg mv -q c b/c
465 465 $ hg ci -qm rename
466 466 $ echo 2 > b/a
467 467 $ echo 2 > b/c
468 468 $ hg ci -qm modify
469 469 $ cd ..
470 470
471 471 $ echo "rename b ." > renameundo.fmap
472 472 $ hg convert --filemap renameundo.fmap renameundo renameundo2
473 473 initializing destination renameundo2 repository
474 474 scanning source...
475 475 sorting...
476 476 converting...
477 477 2 add
478 478 1 rename
479 479 filtering out empty revision
480 480 repository tip rolled back to revision 0 (undo commit)
481 481 0 modify
482 482 $ glog -R renameundo2
483 483 o 1 "modify" files: a c
484 484 |
485 485 o 0 "add" files: a c
486 486
487 487
488 488
489 489 test merge parents/empty merges pruning
490 490
491 491 $ glog()
492 492 > {
493 493 > hg log -G --template '{rev}:{node|short}@{branch} "{desc}" files: {files}\n' "$@"
494 494 > }
495 495
496 496 test anonymous branch pruning
497 497
498 498 $ hg init anonymousbranch
499 499 $ cd anonymousbranch
500 500 $ echo a > a
501 501 $ echo b > b
502 502 $ hg ci -Am add
503 503 adding a
504 504 adding b
505 505 $ echo a >> a
506 506 $ hg ci -m changea
507 507 $ hg up 0
508 508 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
509 509 $ echo b >> b
510 510 $ hg ci -m changeb
511 511 created new head
512 512 $ hg up 1
513 513 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
514 514 $ hg merge
515 515 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
516 516 (branch merge, don't forget to commit)
517 517 $ hg ci -m merge
518 518 $ cd ..
519 519
520 520 $ cat > filemap <<EOF
521 521 > include a
522 522 > EOF
523 523 $ hg convert --filemap filemap anonymousbranch anonymousbranch-hg
524 524 initializing destination anonymousbranch-hg repository
525 525 scanning source...
526 526 sorting...
527 527 converting...
528 528 3 add
529 529 2 changea
530 530 1 changeb
531 531 0 merge
532 532 $ glog -R anonymousbranch
533 533 @ 3:c71d5201a498@default "merge" files:
534 534 |\
535 535 | o 2:607eb44b17f9@default "changeb" files: b
536 536 | |
537 537 o | 1:1f60ea617824@default "changea" files: a
538 538 |/
539 539 o 0:0146e6129113@default "add" files: a b
540 540
541 541 $ glog -R anonymousbranch-hg
542 542 o 1:cda818e7219b@default "changea" files: a
543 543 |
544 544 o 0:c334dc3be0da@default "add" files: a
545 545
546 546 $ cat anonymousbranch-hg/.hg/shamap
547 547 0146e6129113dba9ac90207cfdf2d7ed35257ae5 c334dc3be0daa2a4e9ce4d2e2bdcba40c09d4916
548 548 1f60ea61782421edf8d051ff4fcb61b330f26a4a cda818e7219b5f7f3fb9f49780054ed6a1905ec3
549 549 607eb44b17f9348cd5cbd26e16af87ba77b0b037 c334dc3be0daa2a4e9ce4d2e2bdcba40c09d4916
550 550 c71d5201a498b2658d105a6bf69d7a0df2649aea cda818e7219b5f7f3fb9f49780054ed6a1905ec3
551 551
552 552 $ cat > filemap <<EOF
553 553 > include b
554 554 > EOF
555 555 $ hg convert --filemap filemap anonymousbranch anonymousbranch-hg2
556 556 initializing destination anonymousbranch-hg2 repository
557 557 scanning source...
558 558 sorting...
559 559 converting...
560 560 3 add
561 561 2 changea
562 562 1 changeb
563 563 0 merge
564 564 $ glog -R anonymousbranch
565 565 @ 3:c71d5201a498@default "merge" files:
566 566 |\
567 567 | o 2:607eb44b17f9@default "changeb" files: b
568 568 | |
569 569 o | 1:1f60ea617824@default "changea" files: a
570 570 |/
571 571 o 0:0146e6129113@default "add" files: a b
572 572
573 573 $ glog -R anonymousbranch-hg2
574 574 o 1:62dd350b0df6@default "changeb" files: b
575 575 |
576 576 o 0:4b9ced861657@default "add" files: b
577 577
578 578 $ cat anonymousbranch-hg2/.hg/shamap
579 579 0146e6129113dba9ac90207cfdf2d7ed35257ae5 4b9ced86165703791653059a1db6ed864630a523
580 580 1f60ea61782421edf8d051ff4fcb61b330f26a4a 4b9ced86165703791653059a1db6ed864630a523
581 581 607eb44b17f9348cd5cbd26e16af87ba77b0b037 62dd350b0df695f7d2c82a02e0499b16fd790f22
582 582 c71d5201a498b2658d105a6bf69d7a0df2649aea 62dd350b0df695f7d2c82a02e0499b16fd790f22
583 583
584 584 test named branch pruning
585 585
586 586 $ hg init namedbranch
587 587 $ cd namedbranch
588 588 $ echo a > a
589 589 $ echo b > b
590 590 $ hg ci -Am add
591 591 adding a
592 592 adding b
593 593 $ echo a >> a
594 594 $ hg ci -m changea
595 595 $ hg up 0
596 596 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
597 597 $ hg branch foo
598 598 marked working directory as branch foo
599 599 (branches are permanent and global, did you want a bookmark?)
600 600 $ echo b >> b
601 601 $ hg ci -m changeb
602 602 $ hg up default
603 603 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
604 604 $ hg merge foo
605 605 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
606 606 (branch merge, don't forget to commit)
607 607 $ hg ci -m merge
608 608 $ cd ..
609 609
610 610 $ cat > filemap <<EOF
611 611 > include a
612 612 > EOF
613 613 $ hg convert --filemap filemap namedbranch namedbranch-hg
614 614 initializing destination namedbranch-hg repository
615 615 scanning source...
616 616 sorting...
617 617 converting...
618 618 3 add
619 619 2 changea
620 620 1 changeb
621 621 0 merge
622 622 $ glog -R namedbranch
623 623 @ 3:73899bcbe45c@default "merge" files:
624 624 |\
625 625 | o 2:8097982d19fc@foo "changeb" files: b
626 626 | |
627 627 o | 1:1f60ea617824@default "changea" files: a
628 628 |/
629 629 o 0:0146e6129113@default "add" files: a b
630 630
631 631 $ glog -R namedbranch-hg
632 632 o 1:cda818e7219b@default "changea" files: a
633 633 |
634 634 o 0:c334dc3be0da@default "add" files: a
635 635
636 636
637 637 $ cd namedbranch
638 638 $ hg --config extensions.mq= strip tip
639 639 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
640 640 saved backup bundle to $TESTTMP/namedbranch/.hg/strip-backup/73899bcbe45c-92adf160-backup.hg (glob)
641 641 $ hg up foo
642 642 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
643 643 $ hg merge default
644 644 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
645 645 (branch merge, don't forget to commit)
646 646 $ hg ci -m merge
647 647 $ cd ..
648 648
649 649 $ hg convert --filemap filemap namedbranch namedbranch-hg2
650 650 initializing destination namedbranch-hg2 repository
651 651 scanning source...
652 652 sorting...
653 653 converting...
654 654 3 add
655 655 2 changea
656 656 1 changeb
657 657 0 merge
658 658 $ glog -R namedbranch
659 659 @ 3:e1959de76e1b@foo "merge" files:
660 660 |\
661 661 | o 2:8097982d19fc@foo "changeb" files: b
662 662 | |
663 663 o | 1:1f60ea617824@default "changea" files: a
664 664 |/
665 665 o 0:0146e6129113@default "add" files: a b
666 666
667 667 $ glog -R namedbranch-hg2
668 668 o 2:dcf314454667@foo "merge" files:
669 669 |\
670 670 | o 1:cda818e7219b@default "changea" files: a
671 671 |/
672 672 o 0:c334dc3be0da@default "add" files: a
673 673
@@ -1,380 +1,380
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
10 10 > EOF
11 11
12 12 $ hg init a
13 13 $ cd a
14 14 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
15 15 adding changesets
16 16 adding manifests
17 17 adding file changes
18 18 added 8 changesets with 7 changes to 7 files (+2 heads)
19 19 (run 'hg heads' to see heads, 'hg merge' to merge)
20 20 $ hg up tip
21 21 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 22 $ cd ..
23 23
24 24 $ hg clone -q -u . a a1
25 25
26 26 $ cd a1
27 27
28 28 $ hg update 3
29 29 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
30 30 $ hg branch dev-one
31 31 marked working directory as branch dev-one
32 32 (branches are permanent and global, did you want a bookmark?)
33 33 $ hg ci -m 'dev-one named branch'
34 34
35 35 $ hg update 7
36 36 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
37 37 $ hg branch dev-two
38 38 marked working directory as branch dev-two
39 39 (branches are permanent and global, did you want a bookmark?)
40 40
41 41 $ echo x > x
42 42
43 43 $ hg add x
44 44
45 45 $ hg ci -m 'dev-two named branch'
46 46
47 47 $ hg tglog
48 48 @ 9: 'dev-two named branch' dev-two
49 49 |
50 50 | o 8: 'dev-one named branch' dev-one
51 51 | |
52 52 o | 7: 'H'
53 53 | |
54 54 +---o 6: 'G'
55 55 | | |
56 56 o | | 5: 'F'
57 57 | | |
58 58 +---o 4: 'E'
59 59 | |
60 60 | o 3: 'D'
61 61 | |
62 62 | o 2: 'C'
63 63 | |
64 64 | o 1: 'B'
65 65 |/
66 66 o 0: 'A'
67 67
68 68
69 69 Branch name containing a dash (issue3181)
70 70
71 71 $ hg rebase -b dev-two -d dev-one --keepbranches
72 72 rebasing 5:24b6387c8c8c "F"
73 73 rebasing 6:eea13746799a "G"
74 74 rebasing 7:02de42196ebe "H"
75 75 rebasing 9:cb039b7cae8e "dev-two named branch" (tip)
76 76 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/24b6387c8c8c-24cb8001-backup.hg (glob)
77 77
78 78 $ hg tglog
79 79 @ 9: 'dev-two named branch' dev-two
80 80 |
81 81 o 8: 'H'
82 82 |
83 83 | o 7: 'G'
84 84 |/|
85 85 o | 6: 'F'
86 86 | |
87 87 o | 5: 'dev-one named branch' dev-one
88 88 | |
89 89 | o 4: 'E'
90 90 | |
91 91 o | 3: 'D'
92 92 | |
93 93 o | 2: 'C'
94 94 | |
95 95 o | 1: 'B'
96 96 |/
97 97 o 0: 'A'
98 98
99 99 $ hg rebase -s dev-one -d 0 --keepbranches
100 100 rebasing 5:643fc9128048 "dev-one named branch"
101 101 note: rebase of 5:643fc9128048 created no changes to commit
102 102 rebasing 6:24de4aff8e28 "F"
103 103 rebasing 7:4b988a958030 "G"
104 104 rebasing 8:31d0e4ba75e6 "H"
105 105 rebasing 9:9e70cd31750f "dev-two named branch" (tip)
106 106 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/643fc9128048-c4ee9ef5-backup.hg (glob)
107 107
108 108 $ hg tglog
109 109 @ 8: 'dev-two named branch' dev-two
110 110 |
111 111 o 7: 'H'
112 112 |
113 113 | o 6: 'G'
114 114 |/|
115 115 o | 5: 'F'
116 116 | |
117 117 | o 4: 'E'
118 118 |/
119 119 | o 3: 'D'
120 120 | |
121 121 | o 2: 'C'
122 122 | |
123 123 | o 1: 'B'
124 124 |/
125 125 o 0: 'A'
126 126
127 127 $ hg update 3
128 128 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
129 129 $ hg branch dev-one
130 130 marked working directory as branch dev-one
131 131 (branches are permanent and global, did you want a bookmark?)
132 132 $ hg ci -m 'dev-one named branch'
133 133
134 134 $ hg tglog
135 135 @ 9: 'dev-one named branch' dev-one
136 136 |
137 137 | o 8: 'dev-two named branch' dev-two
138 138 | |
139 139 | o 7: 'H'
140 140 | |
141 141 | | o 6: 'G'
142 142 | |/|
143 143 | o | 5: 'F'
144 144 | | |
145 145 | | o 4: 'E'
146 146 | |/
147 147 o | 3: 'D'
148 148 | |
149 149 o | 2: 'C'
150 150 | |
151 151 o | 1: 'B'
152 152 |/
153 153 o 0: 'A'
154 154
155 155 $ hg rebase -b 'max(branch("dev-two"))' -d dev-one --keepbranches
156 156 rebasing 5:77854864208c "F"
157 157 rebasing 6:63b4f9c788a1 "G"
158 158 rebasing 7:87861e68abd3 "H"
159 159 rebasing 8:ec00d4e0efca "dev-two named branch"
160 160 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/77854864208c-74d59436-backup.hg (glob)
161 161
162 162 $ hg tglog
163 163 o 9: 'dev-two named branch' dev-two
164 164 |
165 165 o 8: 'H'
166 166 |
167 167 | o 7: 'G'
168 168 |/|
169 169 o | 6: 'F'
170 170 | |
171 171 @ | 5: 'dev-one named branch' dev-one
172 172 | |
173 173 | o 4: 'E'
174 174 | |
175 175 o | 3: 'D'
176 176 | |
177 177 o | 2: 'C'
178 178 | |
179 179 o | 1: 'B'
180 180 |/
181 181 o 0: 'A'
182 182
183 183 $ hg rebase -s 'max(branch("dev-one"))' -d 0 --keepbranches
184 184 rebasing 5:643fc9128048 "dev-one named branch"
185 185 note: rebase of 5:643fc9128048 created no changes to commit
186 186 rebasing 6:05584c618d45 "F"
187 187 rebasing 7:471695f5257d "G"
188 188 rebasing 8:8382a539a2df "H"
189 189 rebasing 9:11f718458b32 "dev-two named branch" (tip)
190 190 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/643fc9128048-177f3c5c-backup.hg (glob)
191 191
192 192 $ hg tglog
193 193 o 8: 'dev-two named branch' dev-two
194 194 |
195 195 o 7: 'H'
196 196 |
197 197 | o 6: 'G'
198 198 |/|
199 199 o | 5: 'F'
200 200 | |
201 201 | o 4: 'E'
202 202 |/
203 203 | o 3: 'D'
204 204 | |
205 205 | o 2: 'C'
206 206 | |
207 207 | o 1: 'B'
208 208 |/
209 209 @ 0: 'A'
210 210
211 211
212 212 Rebasing descendant onto ancestor across different named branches
213 213
214 214 $ hg rebase -s 1 -d 8 --keepbranches
215 215 rebasing 1:42ccdea3bb16 "B"
216 216 rebasing 2:5fddd98957c8 "C"
217 217 rebasing 3:32af7686d403 "D"
218 218 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/42ccdea3bb16-3cb021d3-backup.hg (glob)
219 219
220 220 $ hg tglog
221 221 o 8: 'D'
222 222 |
223 223 o 7: 'C'
224 224 |
225 225 o 6: 'B'
226 226 |
227 227 o 5: 'dev-two named branch' dev-two
228 228 |
229 229 o 4: 'H'
230 230 |
231 231 | o 3: 'G'
232 232 |/|
233 233 o | 2: 'F'
234 234 | |
235 235 | o 1: 'E'
236 236 |/
237 237 @ 0: 'A'
238 238
239 239 $ hg rebase -s 4 -d 5
240 240 abort: source is ancestor of destination
241 241 [255]
242 242
243 243 $ hg rebase -s 5 -d 4
244 244 rebasing 5:32d3b0de7f37 "dev-two named branch"
245 245 rebasing 6:580fcd9fd48f "B"
246 246 rebasing 7:32aba0402ed2 "C"
247 247 rebasing 8:e4787b575338 "D" (tip)
248 248 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32d3b0de7f37-c37815ca-backup.hg (glob)
249 249
250 250 $ hg tglog
251 251 o 8: 'D'
252 252 |
253 253 o 7: 'C'
254 254 |
255 255 o 6: 'B'
256 256 |
257 257 o 5: 'dev-two named branch'
258 258 |
259 259 o 4: 'H'
260 260 |
261 261 | o 3: 'G'
262 262 |/|
263 263 o | 2: 'F'
264 264 | |
265 265 | o 1: 'E'
266 266 |/
267 267 @ 0: 'A'
268 268
269 269
270 270 Reopen branch by rebase
271 271
272 272 $ hg up -qr3
273 273 $ hg branch -q b
274 274 $ hg ci -m 'create b'
275 275 $ hg ci -m 'close b' --close
276 276 $ hg rebase -b 8 -d b
277 277 reopening closed branch head ea9de14a36c6
278 278 rebasing 4:86693275b2ef "H"
279 279 rebasing 5:2149726d0970 "dev-two named branch"
280 280 rebasing 6:81e55225e95d "B"
281 281 rebasing 7:09eda3dc3195 "C"
282 282 rebasing 8:31298fc9d159 "D"
283 283 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/86693275b2ef-f9fcf4e2-backup.hg (glob)
284 284
285 285 $ cd ..
286 286
287 287 Rebase to other head on branch
288 288
289 289 Set up a case:
290 290
291 291 $ hg init case1
292 292 $ cd case1
293 293 $ touch f
294 294 $ hg ci -qAm0
295 295 $ hg branch -q b
296 296 $ echo >> f
297 297 $ hg ci -qAm 'b1'
298 298 $ hg up -qr -2
299 299 $ hg branch -qf b
300 300 $ hg ci -qm 'b2'
301 301 $ hg up -qr -3
302 302 $ hg branch -q c
303 303 $ hg ci -m 'c1'
304 304
305 305 $ hg tglog
306 306 @ 3: 'c1' c
307 307 |
308 308 | o 2: 'b2' b
309 309 |/
310 310 | o 1: 'b1' b
311 311 |/
312 312 o 0: '0'
313 313
314 314 $ hg clone -q . ../case2
315 315
316 316 rebase 'b2' to another lower branch head
317 317
318 318 $ hg up -qr 2
319 319 $ hg rebase
320 320 nothing to rebase - working directory parent is also destination
321 321 [1]
322 322 $ hg tglog
323 323 o 3: 'c1' c
324 324 |
325 325 | @ 2: 'b2' b
326 326 |/
327 327 | o 1: 'b1' b
328 328 |/
329 329 o 0: '0'
330 330
331 331
332 332 rebase 'b1' on top of the tip of the branch ('b2') - ignoring the tip branch ('c1')
333 333
334 334 $ cd ../case2
335 335 $ hg up -qr 1
336 336 $ hg rebase
337 337 rebasing 1:40039acb7ca5 "b1"
338 338 saved backup bundle to $TESTTMP/case2/.hg/strip-backup/40039acb7ca5-342b72d1-backup.hg (glob)
339 339 $ hg tglog
340 340 @ 3: 'b1' b
341 341 |
342 342 | o 2: 'c1' c
343 343 | |
344 344 o | 1: 'b2' b
345 345 |/
346 346 o 0: '0'
347 347
348 348
349 349 rebase 'c1' to the branch head 'c2' that is closed
350 350
351 351 $ hg branch -qf c
352 352 $ hg ci -qm 'c2 closed' --close
353 353 $ hg up -qr 2
354 354 $ hg tglog
355 o 4: 'c2 closed' c
355 _ 4: 'c2 closed' c
356 356 |
357 357 o 3: 'b1' b
358 358 |
359 359 | @ 2: 'c1' c
360 360 | |
361 361 o | 1: 'b2' b
362 362 |/
363 363 o 0: '0'
364 364
365 365 $ hg rebase
366 366 nothing to rebase - working directory parent is also destination
367 367 [1]
368 368 $ hg tglog
369 o 4: 'c2 closed' c
369 _ 4: 'c2 closed' c
370 370 |
371 371 o 3: 'b1' b
372 372 |
373 373 | @ 2: 'c1' c
374 374 | |
375 375 o | 1: 'b2' b
376 376 |/
377 377 o 0: '0'
378 378
379 379
380 380 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now