##// END OF EJS Templates
add: pass options via keyword args...
Matt Harbison -
r23885:9994f45b default
parent child Browse files
Show More
@@ -1,2979 +1,2977 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import 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, baseform):
114 114 """build appropriate editform from ctxorbool and baseform
115 115
116 116 'ctxorbool' is one of a ctx to be committed, or a bool whether
117 117 merging is committed.
118 118
119 119 This returns editform 'baseform' with '.merge' if merging is
120 120 committed, or one with '.normal' suffix otherwise.
121 121 """
122 122 if isinstance(ctxorbool, bool):
123 123 if ctxorbool:
124 124 return baseform + ".merge"
125 125 elif 1 < len(ctxorbool.parents()):
126 126 return baseform + ".merge"
127 127
128 128 return baseform + ".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 # i18n: column positioning for "hg log"
923 923 name = _(("%s:" % ns.logname).ljust(13) + "%s\n") % name
924 924 self.ui.write("%s" % name, label='log.%s' % ns.colorname)
925 925 if self.ui.debugflag:
926 926 # i18n: column positioning for "hg log"
927 927 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
928 928 label='log.phase')
929 929 for parent in parents:
930 930 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
931 931 # i18n: column positioning for "hg log"
932 932 self.ui.write(_("parent: %d:%s\n") % parent,
933 933 label=label)
934 934
935 935 if self.ui.debugflag:
936 936 mnode = ctx.manifestnode()
937 937 # i18n: column positioning for "hg log"
938 938 self.ui.write(_("manifest: %d:%s\n") %
939 939 (self.repo.manifest.rev(mnode), hex(mnode)),
940 940 label='ui.debug log.manifest')
941 941 # i18n: column positioning for "hg log"
942 942 self.ui.write(_("user: %s\n") % ctx.user(),
943 943 label='log.user')
944 944 # i18n: column positioning for "hg log"
945 945 self.ui.write(_("date: %s\n") % date,
946 946 label='log.date')
947 947
948 948 if self.ui.debugflag:
949 949 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
950 950 for key, value in zip([# i18n: column positioning for "hg log"
951 951 _("files:"),
952 952 # i18n: column positioning for "hg log"
953 953 _("files+:"),
954 954 # i18n: column positioning for "hg log"
955 955 _("files-:")], files):
956 956 if value:
957 957 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
958 958 label='ui.debug log.files')
959 959 elif ctx.files() and self.ui.verbose:
960 960 # i18n: column positioning for "hg log"
961 961 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
962 962 label='ui.note log.files')
963 963 if copies and self.ui.verbose:
964 964 copies = ['%s (%s)' % c for c in copies]
965 965 # i18n: column positioning for "hg log"
966 966 self.ui.write(_("copies: %s\n") % ' '.join(copies),
967 967 label='ui.note log.copies')
968 968
969 969 extra = ctx.extra()
970 970 if extra and self.ui.debugflag:
971 971 for key, value in sorted(extra.items()):
972 972 # i18n: column positioning for "hg log"
973 973 self.ui.write(_("extra: %s=%s\n")
974 974 % (key, value.encode('string_escape')),
975 975 label='ui.debug log.extra')
976 976
977 977 description = ctx.description().strip()
978 978 if description:
979 979 if self.ui.verbose:
980 980 self.ui.write(_("description:\n"),
981 981 label='ui.note log.description')
982 982 self.ui.write(description,
983 983 label='ui.note log.description')
984 984 self.ui.write("\n\n")
985 985 else:
986 986 # i18n: column positioning for "hg log"
987 987 self.ui.write(_("summary: %s\n") %
988 988 description.splitlines()[0],
989 989 label='log.summary')
990 990 self.ui.write("\n")
991 991
992 992 self.showpatch(changenode, matchfn)
993 993
994 994 def showpatch(self, node, matchfn):
995 995 if not matchfn:
996 996 matchfn = self.matchfn
997 997 if matchfn:
998 998 stat = self.diffopts.get('stat')
999 999 diff = self.diffopts.get('patch')
1000 1000 diffopts = patch.diffallopts(self.ui, self.diffopts)
1001 1001 prev = self.repo.changelog.parents(node)[0]
1002 1002 if stat:
1003 1003 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1004 1004 match=matchfn, stat=True)
1005 1005 if diff:
1006 1006 if stat:
1007 1007 self.ui.write("\n")
1008 1008 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1009 1009 match=matchfn, stat=False)
1010 1010 self.ui.write("\n")
1011 1011
1012 1012 def _meaningful_parentrevs(self, log, rev):
1013 1013 """Return list of meaningful (or all if debug) parentrevs for rev.
1014 1014
1015 1015 For merges (two non-nullrev revisions) both parents are meaningful.
1016 1016 Otherwise the first parent revision is considered meaningful if it
1017 1017 is not the preceding revision.
1018 1018 """
1019 1019 parents = log.parentrevs(rev)
1020 1020 if not self.ui.debugflag and parents[1] == nullrev:
1021 1021 if parents[0] >= rev - 1:
1022 1022 parents = []
1023 1023 else:
1024 1024 parents = [parents[0]]
1025 1025 return parents
1026 1026
1027 1027 class jsonchangeset(changeset_printer):
1028 1028 '''format changeset information.'''
1029 1029
1030 1030 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1031 1031 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1032 1032 self.cache = {}
1033 1033 self._first = True
1034 1034
1035 1035 def close(self):
1036 1036 if not self._first:
1037 1037 self.ui.write("\n]\n")
1038 1038 else:
1039 1039 self.ui.write("[]\n")
1040 1040
1041 1041 def _show(self, ctx, copies, matchfn, props):
1042 1042 '''show a single changeset or file revision'''
1043 1043 hexnode = hex(ctx.node())
1044 1044 rev = ctx.rev()
1045 1045 j = encoding.jsonescape
1046 1046
1047 1047 if self._first:
1048 1048 self.ui.write("[\n {")
1049 1049 self._first = False
1050 1050 else:
1051 1051 self.ui.write(",\n {")
1052 1052
1053 1053 if self.ui.quiet:
1054 1054 self.ui.write('\n "rev": %d' % rev)
1055 1055 self.ui.write(',\n "node": "%s"' % hexnode)
1056 1056 self.ui.write('\n }')
1057 1057 return
1058 1058
1059 1059 self.ui.write('\n "rev": %d' % rev)
1060 1060 self.ui.write(',\n "node": "%s"' % hexnode)
1061 1061 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1062 1062 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1063 1063 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1064 1064 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1065 1065 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1066 1066
1067 1067 self.ui.write(',\n "bookmarks": [%s]' %
1068 1068 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1069 1069 self.ui.write(',\n "tags": [%s]' %
1070 1070 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1071 1071 self.ui.write(',\n "parents": [%s]' %
1072 1072 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1073 1073
1074 1074 if self.ui.debugflag:
1075 1075 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1076 1076
1077 1077 self.ui.write(',\n "extra": {%s}' %
1078 1078 ", ".join('"%s": "%s"' % (j(k), j(v))
1079 1079 for k, v in ctx.extra().items()))
1080 1080
1081 1081 files = ctx.p1().status(ctx)
1082 1082 self.ui.write(',\n "modified": [%s]' %
1083 1083 ", ".join('"%s"' % j(f) for f in files[0]))
1084 1084 self.ui.write(',\n "added": [%s]' %
1085 1085 ", ".join('"%s"' % j(f) for f in files[1]))
1086 1086 self.ui.write(',\n "removed": [%s]' %
1087 1087 ", ".join('"%s"' % j(f) for f in files[2]))
1088 1088
1089 1089 elif self.ui.verbose:
1090 1090 self.ui.write(',\n "files": [%s]' %
1091 1091 ", ".join('"%s"' % j(f) for f in ctx.files()))
1092 1092
1093 1093 if copies:
1094 1094 self.ui.write(',\n "copies": {%s}' %
1095 1095 ", ".join('"%s": %s' % (j(k), j(copies[k]))
1096 1096 for k in copies))
1097 1097
1098 1098 matchfn = self.matchfn
1099 1099 if matchfn:
1100 1100 stat = self.diffopts.get('stat')
1101 1101 diff = self.diffopts.get('patch')
1102 1102 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1103 1103 node, prev = ctx.node(), ctx.p1().node()
1104 1104 if stat:
1105 1105 self.ui.pushbuffer()
1106 1106 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1107 1107 match=matchfn, stat=True)
1108 1108 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1109 1109 if diff:
1110 1110 self.ui.pushbuffer()
1111 1111 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1112 1112 match=matchfn, stat=False)
1113 1113 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1114 1114
1115 1115 self.ui.write("\n }")
1116 1116
1117 1117 class changeset_templater(changeset_printer):
1118 1118 '''format changeset information.'''
1119 1119
1120 1120 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1121 1121 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1122 1122 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1123 1123 defaulttempl = {
1124 1124 'parent': '{rev}:{node|formatnode} ',
1125 1125 'manifest': '{rev}:{node|formatnode}',
1126 1126 'file_copy': '{name} ({source})',
1127 1127 'extra': '{key}={value|stringescape}'
1128 1128 }
1129 1129 # filecopy is preserved for compatibility reasons
1130 1130 defaulttempl['filecopy'] = defaulttempl['file_copy']
1131 1131 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1132 1132 cache=defaulttempl)
1133 1133 if tmpl:
1134 1134 self.t.cache['changeset'] = tmpl
1135 1135
1136 1136 self.cache = {}
1137 1137
1138 1138 def _meaningful_parentrevs(self, ctx):
1139 1139 """Return list of meaningful (or all if debug) parentrevs for rev.
1140 1140 """
1141 1141 parents = ctx.parents()
1142 1142 if len(parents) > 1:
1143 1143 return parents
1144 1144 if self.ui.debugflag:
1145 1145 return [parents[0], self.repo['null']]
1146 1146 if parents[0].rev() >= ctx.rev() - 1:
1147 1147 return []
1148 1148 return parents
1149 1149
1150 1150 def _show(self, ctx, copies, matchfn, props):
1151 1151 '''show a single changeset or file revision'''
1152 1152
1153 1153 showlist = templatekw.showlist
1154 1154
1155 1155 # showparents() behaviour depends on ui trace level which
1156 1156 # causes unexpected behaviours at templating level and makes
1157 1157 # it harder to extract it in a standalone function. Its
1158 1158 # behaviour cannot be changed so leave it here for now.
1159 1159 def showparents(**args):
1160 1160 ctx = args['ctx']
1161 1161 parents = [[('rev', p.rev()),
1162 1162 ('node', p.hex()),
1163 1163 ('phase', p.phasestr())]
1164 1164 for p in self._meaningful_parentrevs(ctx)]
1165 1165 return showlist('parent', parents, **args)
1166 1166
1167 1167 props = props.copy()
1168 1168 props.update(templatekw.keywords)
1169 1169 props['parents'] = showparents
1170 1170 props['templ'] = self.t
1171 1171 props['ctx'] = ctx
1172 1172 props['repo'] = self.repo
1173 1173 props['revcache'] = {'copies': copies}
1174 1174 props['cache'] = self.cache
1175 1175
1176 1176 # find correct templates for current mode
1177 1177
1178 1178 tmplmodes = [
1179 1179 (True, None),
1180 1180 (self.ui.verbose, 'verbose'),
1181 1181 (self.ui.quiet, 'quiet'),
1182 1182 (self.ui.debugflag, 'debug'),
1183 1183 ]
1184 1184
1185 1185 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1186 1186 for mode, postfix in tmplmodes:
1187 1187 for type in types:
1188 1188 cur = postfix and ('%s_%s' % (type, postfix)) or type
1189 1189 if mode and cur in self.t:
1190 1190 types[type] = cur
1191 1191
1192 1192 try:
1193 1193
1194 1194 # write header
1195 1195 if types['header']:
1196 1196 h = templater.stringify(self.t(types['header'], **props))
1197 1197 if self.buffered:
1198 1198 self.header[ctx.rev()] = h
1199 1199 else:
1200 1200 if self.lastheader != h:
1201 1201 self.lastheader = h
1202 1202 self.ui.write(h)
1203 1203
1204 1204 # write changeset metadata, then patch if requested
1205 1205 key = types['changeset']
1206 1206 self.ui.write(templater.stringify(self.t(key, **props)))
1207 1207 self.showpatch(ctx.node(), matchfn)
1208 1208
1209 1209 if types['footer']:
1210 1210 if not self.footer:
1211 1211 self.footer = templater.stringify(self.t(types['footer'],
1212 1212 **props))
1213 1213
1214 1214 except KeyError, inst:
1215 1215 msg = _("%s: no key named '%s'")
1216 1216 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1217 1217 except SyntaxError, inst:
1218 1218 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1219 1219
1220 1220 def gettemplate(ui, tmpl, style):
1221 1221 """
1222 1222 Find the template matching the given template spec or style.
1223 1223 """
1224 1224
1225 1225 # ui settings
1226 1226 if not tmpl and not style: # template are stronger than style
1227 1227 tmpl = ui.config('ui', 'logtemplate')
1228 1228 if tmpl:
1229 1229 try:
1230 1230 tmpl = templater.parsestring(tmpl)
1231 1231 except SyntaxError:
1232 1232 tmpl = templater.parsestring(tmpl, quoted=False)
1233 1233 return tmpl, None
1234 1234 else:
1235 1235 style = util.expandpath(ui.config('ui', 'style', ''))
1236 1236
1237 1237 if not tmpl and style:
1238 1238 mapfile = style
1239 1239 if not os.path.split(mapfile)[0]:
1240 1240 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1241 1241 or templater.templatepath(mapfile))
1242 1242 if mapname:
1243 1243 mapfile = mapname
1244 1244 return None, mapfile
1245 1245
1246 1246 if not tmpl:
1247 1247 return None, None
1248 1248
1249 1249 # looks like a literal template?
1250 1250 if '{' in tmpl:
1251 1251 return tmpl, None
1252 1252
1253 1253 # perhaps a stock style?
1254 1254 if not os.path.split(tmpl)[0]:
1255 1255 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1256 1256 or templater.templatepath(tmpl))
1257 1257 if mapname and os.path.isfile(mapname):
1258 1258 return None, mapname
1259 1259
1260 1260 # perhaps it's a reference to [templates]
1261 1261 t = ui.config('templates', tmpl)
1262 1262 if t:
1263 1263 try:
1264 1264 tmpl = templater.parsestring(t)
1265 1265 except SyntaxError:
1266 1266 tmpl = templater.parsestring(t, quoted=False)
1267 1267 return tmpl, None
1268 1268
1269 1269 if tmpl == 'list':
1270 1270 ui.write(_("available styles: %s\n") % templater.stylelist())
1271 1271 raise util.Abort(_("specify a template"))
1272 1272
1273 1273 # perhaps it's a path to a map or a template
1274 1274 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1275 1275 # is it a mapfile for a style?
1276 1276 if os.path.basename(tmpl).startswith("map-"):
1277 1277 return None, os.path.realpath(tmpl)
1278 1278 tmpl = open(tmpl).read()
1279 1279 return tmpl, None
1280 1280
1281 1281 # constant string?
1282 1282 return tmpl, None
1283 1283
1284 1284 def show_changeset(ui, repo, opts, buffered=False):
1285 1285 """show one changeset using template or regular display.
1286 1286
1287 1287 Display format will be the first non-empty hit of:
1288 1288 1. option 'template'
1289 1289 2. option 'style'
1290 1290 3. [ui] setting 'logtemplate'
1291 1291 4. [ui] setting 'style'
1292 1292 If all of these values are either the unset or the empty string,
1293 1293 regular display via changeset_printer() is done.
1294 1294 """
1295 1295 # options
1296 1296 matchfn = None
1297 1297 if opts.get('patch') or opts.get('stat'):
1298 1298 matchfn = scmutil.matchall(repo)
1299 1299
1300 1300 if opts.get('template') == 'json':
1301 1301 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1302 1302
1303 1303 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1304 1304
1305 1305 if not tmpl and not mapfile:
1306 1306 return changeset_printer(ui, repo, matchfn, opts, buffered)
1307 1307
1308 1308 try:
1309 1309 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1310 1310 buffered)
1311 1311 except SyntaxError, inst:
1312 1312 raise util.Abort(inst.args[0])
1313 1313 return t
1314 1314
1315 1315 def showmarker(ui, marker):
1316 1316 """utility function to display obsolescence marker in a readable way
1317 1317
1318 1318 To be used by debug function."""
1319 1319 ui.write(hex(marker.precnode()))
1320 1320 for repl in marker.succnodes():
1321 1321 ui.write(' ')
1322 1322 ui.write(hex(repl))
1323 1323 ui.write(' %X ' % marker.flags())
1324 1324 parents = marker.parentnodes()
1325 1325 if parents is not None:
1326 1326 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1327 1327 ui.write('(%s) ' % util.datestr(marker.date()))
1328 1328 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1329 1329 sorted(marker.metadata().items())
1330 1330 if t[0] != 'date')))
1331 1331 ui.write('\n')
1332 1332
1333 1333 def finddate(ui, repo, date):
1334 1334 """Find the tipmost changeset that matches the given date spec"""
1335 1335
1336 1336 df = util.matchdate(date)
1337 1337 m = scmutil.matchall(repo)
1338 1338 results = {}
1339 1339
1340 1340 def prep(ctx, fns):
1341 1341 d = ctx.date()
1342 1342 if df(d[0]):
1343 1343 results[ctx.rev()] = d
1344 1344
1345 1345 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1346 1346 rev = ctx.rev()
1347 1347 if rev in results:
1348 1348 ui.status(_("found revision %s from %s\n") %
1349 1349 (rev, util.datestr(results[rev])))
1350 1350 return str(rev)
1351 1351
1352 1352 raise util.Abort(_("revision matching date not found"))
1353 1353
1354 1354 def increasingwindows(windowsize=8, sizelimit=512):
1355 1355 while True:
1356 1356 yield windowsize
1357 1357 if windowsize < sizelimit:
1358 1358 windowsize *= 2
1359 1359
1360 1360 class FileWalkError(Exception):
1361 1361 pass
1362 1362
1363 1363 def walkfilerevs(repo, match, follow, revs, fncache):
1364 1364 '''Walks the file history for the matched files.
1365 1365
1366 1366 Returns the changeset revs that are involved in the file history.
1367 1367
1368 1368 Throws FileWalkError if the file history can't be walked using
1369 1369 filelogs alone.
1370 1370 '''
1371 1371 wanted = set()
1372 1372 copies = []
1373 1373 minrev, maxrev = min(revs), max(revs)
1374 1374 def filerevgen(filelog, last):
1375 1375 """
1376 1376 Only files, no patterns. Check the history of each file.
1377 1377
1378 1378 Examines filelog entries within minrev, maxrev linkrev range
1379 1379 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1380 1380 tuples in backwards order
1381 1381 """
1382 1382 cl_count = len(repo)
1383 1383 revs = []
1384 1384 for j in xrange(0, last + 1):
1385 1385 linkrev = filelog.linkrev(j)
1386 1386 if linkrev < minrev:
1387 1387 continue
1388 1388 # only yield rev for which we have the changelog, it can
1389 1389 # happen while doing "hg log" during a pull or commit
1390 1390 if linkrev >= cl_count:
1391 1391 break
1392 1392
1393 1393 parentlinkrevs = []
1394 1394 for p in filelog.parentrevs(j):
1395 1395 if p != nullrev:
1396 1396 parentlinkrevs.append(filelog.linkrev(p))
1397 1397 n = filelog.node(j)
1398 1398 revs.append((linkrev, parentlinkrevs,
1399 1399 follow and filelog.renamed(n)))
1400 1400
1401 1401 return reversed(revs)
1402 1402 def iterfiles():
1403 1403 pctx = repo['.']
1404 1404 for filename in match.files():
1405 1405 if follow:
1406 1406 if filename not in pctx:
1407 1407 raise util.Abort(_('cannot follow file not in parent '
1408 1408 'revision: "%s"') % filename)
1409 1409 yield filename, pctx[filename].filenode()
1410 1410 else:
1411 1411 yield filename, None
1412 1412 for filename_node in copies:
1413 1413 yield filename_node
1414 1414
1415 1415 for file_, node in iterfiles():
1416 1416 filelog = repo.file(file_)
1417 1417 if not len(filelog):
1418 1418 if node is None:
1419 1419 # A zero count may be a directory or deleted file, so
1420 1420 # try to find matching entries on the slow path.
1421 1421 if follow:
1422 1422 raise util.Abort(
1423 1423 _('cannot follow nonexistent file: "%s"') % file_)
1424 1424 raise FileWalkError("Cannot walk via filelog")
1425 1425 else:
1426 1426 continue
1427 1427
1428 1428 if node is None:
1429 1429 last = len(filelog) - 1
1430 1430 else:
1431 1431 last = filelog.rev(node)
1432 1432
1433 1433
1434 1434 # keep track of all ancestors of the file
1435 1435 ancestors = set([filelog.linkrev(last)])
1436 1436
1437 1437 # iterate from latest to oldest revision
1438 1438 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1439 1439 if not follow:
1440 1440 if rev > maxrev:
1441 1441 continue
1442 1442 else:
1443 1443 # Note that last might not be the first interesting
1444 1444 # rev to us:
1445 1445 # if the file has been changed after maxrev, we'll
1446 1446 # have linkrev(last) > maxrev, and we still need
1447 1447 # to explore the file graph
1448 1448 if rev not in ancestors:
1449 1449 continue
1450 1450 # XXX insert 1327 fix here
1451 1451 if flparentlinkrevs:
1452 1452 ancestors.update(flparentlinkrevs)
1453 1453
1454 1454 fncache.setdefault(rev, []).append(file_)
1455 1455 wanted.add(rev)
1456 1456 if copied:
1457 1457 copies.append(copied)
1458 1458
1459 1459 return wanted
1460 1460
1461 1461 def walkchangerevs(repo, match, opts, prepare):
1462 1462 '''Iterate over files and the revs in which they changed.
1463 1463
1464 1464 Callers most commonly need to iterate backwards over the history
1465 1465 in which they are interested. Doing so has awful (quadratic-looking)
1466 1466 performance, so we use iterators in a "windowed" way.
1467 1467
1468 1468 We walk a window of revisions in the desired order. Within the
1469 1469 window, we first walk forwards to gather data, then in the desired
1470 1470 order (usually backwards) to display it.
1471 1471
1472 1472 This function returns an iterator yielding contexts. Before
1473 1473 yielding each context, the iterator will first call the prepare
1474 1474 function on each context in the window in forward order.'''
1475 1475
1476 1476 follow = opts.get('follow') or opts.get('follow_first')
1477 1477
1478 1478 if opts.get('rev'):
1479 1479 revs = scmutil.revrange(repo, opts.get('rev'))
1480 1480 elif follow:
1481 1481 revs = repo.revs('reverse(:.)')
1482 1482 else:
1483 1483 revs = revset.spanset(repo)
1484 1484 revs.reverse()
1485 1485 if not revs:
1486 1486 return []
1487 1487 wanted = set()
1488 1488 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1489 1489 fncache = {}
1490 1490 change = repo.changectx
1491 1491
1492 1492 # First step is to fill wanted, the set of revisions that we want to yield.
1493 1493 # When it does not induce extra cost, we also fill fncache for revisions in
1494 1494 # wanted: a cache of filenames that were changed (ctx.files()) and that
1495 1495 # match the file filtering conditions.
1496 1496
1497 1497 if not slowpath and not match.files():
1498 1498 # No files, no patterns. Display all revs.
1499 1499 wanted = revs
1500 1500
1501 1501 if not slowpath and match.files():
1502 1502 # We only have to read through the filelog to find wanted revisions
1503 1503
1504 1504 try:
1505 1505 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1506 1506 except FileWalkError:
1507 1507 slowpath = True
1508 1508
1509 1509 # We decided to fall back to the slowpath because at least one
1510 1510 # of the paths was not a file. Check to see if at least one of them
1511 1511 # existed in history, otherwise simply return
1512 1512 for path in match.files():
1513 1513 if path == '.' or path in repo.store:
1514 1514 break
1515 1515 else:
1516 1516 return []
1517 1517
1518 1518 if slowpath:
1519 1519 # We have to read the changelog to match filenames against
1520 1520 # changed files
1521 1521
1522 1522 if follow:
1523 1523 raise util.Abort(_('can only follow copies/renames for explicit '
1524 1524 'filenames'))
1525 1525
1526 1526 # The slow path checks files modified in every changeset.
1527 1527 # This is really slow on large repos, so compute the set lazily.
1528 1528 class lazywantedset(object):
1529 1529 def __init__(self):
1530 1530 self.set = set()
1531 1531 self.revs = set(revs)
1532 1532
1533 1533 # No need to worry about locality here because it will be accessed
1534 1534 # in the same order as the increasing window below.
1535 1535 def __contains__(self, value):
1536 1536 if value in self.set:
1537 1537 return True
1538 1538 elif not value in self.revs:
1539 1539 return False
1540 1540 else:
1541 1541 self.revs.discard(value)
1542 1542 ctx = change(value)
1543 1543 matches = filter(match, ctx.files())
1544 1544 if matches:
1545 1545 fncache[value] = matches
1546 1546 self.set.add(value)
1547 1547 return True
1548 1548 return False
1549 1549
1550 1550 def discard(self, value):
1551 1551 self.revs.discard(value)
1552 1552 self.set.discard(value)
1553 1553
1554 1554 wanted = lazywantedset()
1555 1555
1556 1556 class followfilter(object):
1557 1557 def __init__(self, onlyfirst=False):
1558 1558 self.startrev = nullrev
1559 1559 self.roots = set()
1560 1560 self.onlyfirst = onlyfirst
1561 1561
1562 1562 def match(self, rev):
1563 1563 def realparents(rev):
1564 1564 if self.onlyfirst:
1565 1565 return repo.changelog.parentrevs(rev)[0:1]
1566 1566 else:
1567 1567 return filter(lambda x: x != nullrev,
1568 1568 repo.changelog.parentrevs(rev))
1569 1569
1570 1570 if self.startrev == nullrev:
1571 1571 self.startrev = rev
1572 1572 return True
1573 1573
1574 1574 if rev > self.startrev:
1575 1575 # forward: all descendants
1576 1576 if not self.roots:
1577 1577 self.roots.add(self.startrev)
1578 1578 for parent in realparents(rev):
1579 1579 if parent in self.roots:
1580 1580 self.roots.add(rev)
1581 1581 return True
1582 1582 else:
1583 1583 # backwards: all parents
1584 1584 if not self.roots:
1585 1585 self.roots.update(realparents(self.startrev))
1586 1586 if rev in self.roots:
1587 1587 self.roots.remove(rev)
1588 1588 self.roots.update(realparents(rev))
1589 1589 return True
1590 1590
1591 1591 return False
1592 1592
1593 1593 # it might be worthwhile to do this in the iterator if the rev range
1594 1594 # is descending and the prune args are all within that range
1595 1595 for rev in opts.get('prune', ()):
1596 1596 rev = repo[rev].rev()
1597 1597 ff = followfilter()
1598 1598 stop = min(revs[0], revs[-1])
1599 1599 for x in xrange(rev, stop - 1, -1):
1600 1600 if ff.match(x):
1601 1601 wanted = wanted - [x]
1602 1602
1603 1603 # Now that wanted is correctly initialized, we can iterate over the
1604 1604 # revision range, yielding only revisions in wanted.
1605 1605 def iterate():
1606 1606 if follow and not match.files():
1607 1607 ff = followfilter(onlyfirst=opts.get('follow_first'))
1608 1608 def want(rev):
1609 1609 return ff.match(rev) and rev in wanted
1610 1610 else:
1611 1611 def want(rev):
1612 1612 return rev in wanted
1613 1613
1614 1614 it = iter(revs)
1615 1615 stopiteration = False
1616 1616 for windowsize in increasingwindows():
1617 1617 nrevs = []
1618 1618 for i in xrange(windowsize):
1619 1619 try:
1620 1620 rev = it.next()
1621 1621 if want(rev):
1622 1622 nrevs.append(rev)
1623 1623 except (StopIteration):
1624 1624 stopiteration = True
1625 1625 break
1626 1626 for rev in sorted(nrevs):
1627 1627 fns = fncache.get(rev)
1628 1628 ctx = change(rev)
1629 1629 if not fns:
1630 1630 def fns_generator():
1631 1631 for f in ctx.files():
1632 1632 if match(f):
1633 1633 yield f
1634 1634 fns = fns_generator()
1635 1635 prepare(ctx, fns)
1636 1636 for rev in nrevs:
1637 1637 yield change(rev)
1638 1638
1639 1639 if stopiteration:
1640 1640 break
1641 1641
1642 1642 return iterate()
1643 1643
1644 1644 def _makefollowlogfilematcher(repo, files, followfirst):
1645 1645 # When displaying a revision with --patch --follow FILE, we have
1646 1646 # to know which file of the revision must be diffed. With
1647 1647 # --follow, we want the names of the ancestors of FILE in the
1648 1648 # revision, stored in "fcache". "fcache" is populated by
1649 1649 # reproducing the graph traversal already done by --follow revset
1650 1650 # and relating linkrevs to file names (which is not "correct" but
1651 1651 # good enough).
1652 1652 fcache = {}
1653 1653 fcacheready = [False]
1654 1654 pctx = repo['.']
1655 1655
1656 1656 def populate():
1657 1657 for fn in files:
1658 1658 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1659 1659 for c in i:
1660 1660 fcache.setdefault(c.linkrev(), set()).add(c.path())
1661 1661
1662 1662 def filematcher(rev):
1663 1663 if not fcacheready[0]:
1664 1664 # Lazy initialization
1665 1665 fcacheready[0] = True
1666 1666 populate()
1667 1667 return scmutil.matchfiles(repo, fcache.get(rev, []))
1668 1668
1669 1669 return filematcher
1670 1670
1671 1671 def _makenofollowlogfilematcher(repo, pats, opts):
1672 1672 '''hook for extensions to override the filematcher for non-follow cases'''
1673 1673 return None
1674 1674
1675 1675 def _makelogrevset(repo, pats, opts, revs):
1676 1676 """Return (expr, filematcher) where expr is a revset string built
1677 1677 from log options and file patterns or None. If --stat or --patch
1678 1678 are not passed filematcher is None. Otherwise it is a callable
1679 1679 taking a revision number and returning a match objects filtering
1680 1680 the files to be detailed when displaying the revision.
1681 1681 """
1682 1682 opt2revset = {
1683 1683 'no_merges': ('not merge()', None),
1684 1684 'only_merges': ('merge()', None),
1685 1685 '_ancestors': ('ancestors(%(val)s)', None),
1686 1686 '_fancestors': ('_firstancestors(%(val)s)', None),
1687 1687 '_descendants': ('descendants(%(val)s)', None),
1688 1688 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1689 1689 '_matchfiles': ('_matchfiles(%(val)s)', None),
1690 1690 'date': ('date(%(val)r)', None),
1691 1691 'branch': ('branch(%(val)r)', ' or '),
1692 1692 '_patslog': ('filelog(%(val)r)', ' or '),
1693 1693 '_patsfollow': ('follow(%(val)r)', ' or '),
1694 1694 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1695 1695 'keyword': ('keyword(%(val)r)', ' or '),
1696 1696 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1697 1697 'user': ('user(%(val)r)', ' or '),
1698 1698 }
1699 1699
1700 1700 opts = dict(opts)
1701 1701 # follow or not follow?
1702 1702 follow = opts.get('follow') or opts.get('follow_first')
1703 1703 followfirst = opts.get('follow_first') and 1 or 0
1704 1704 # --follow with FILE behaviour depends on revs...
1705 1705 it = iter(revs)
1706 1706 startrev = it.next()
1707 1707 try:
1708 1708 followdescendants = startrev < it.next()
1709 1709 except (StopIteration):
1710 1710 followdescendants = False
1711 1711
1712 1712 # branch and only_branch are really aliases and must be handled at
1713 1713 # the same time
1714 1714 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1715 1715 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1716 1716 # pats/include/exclude are passed to match.match() directly in
1717 1717 # _matchfiles() revset but walkchangerevs() builds its matcher with
1718 1718 # scmutil.match(). The difference is input pats are globbed on
1719 1719 # platforms without shell expansion (windows).
1720 1720 pctx = repo[None]
1721 1721 match, pats = scmutil.matchandpats(pctx, pats, opts)
1722 1722 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1723 1723 if not slowpath:
1724 1724 for f in match.files():
1725 1725 if follow and f not in pctx:
1726 1726 # If the file exists, it may be a directory, so let it
1727 1727 # take the slow path.
1728 1728 if os.path.exists(repo.wjoin(f)):
1729 1729 slowpath = True
1730 1730 continue
1731 1731 else:
1732 1732 raise util.Abort(_('cannot follow file not in parent '
1733 1733 'revision: "%s"') % f)
1734 1734 filelog = repo.file(f)
1735 1735 if not filelog:
1736 1736 # A zero count may be a directory or deleted file, so
1737 1737 # try to find matching entries on the slow path.
1738 1738 if follow:
1739 1739 raise util.Abort(
1740 1740 _('cannot follow nonexistent file: "%s"') % f)
1741 1741 slowpath = True
1742 1742
1743 1743 # We decided to fall back to the slowpath because at least one
1744 1744 # of the paths was not a file. Check to see if at least one of them
1745 1745 # existed in history - in that case, we'll continue down the
1746 1746 # slowpath; otherwise, we can turn off the slowpath
1747 1747 if slowpath:
1748 1748 for path in match.files():
1749 1749 if path == '.' or path in repo.store:
1750 1750 break
1751 1751 else:
1752 1752 slowpath = False
1753 1753
1754 1754 fpats = ('_patsfollow', '_patsfollowfirst')
1755 1755 fnopats = (('_ancestors', '_fancestors'),
1756 1756 ('_descendants', '_fdescendants'))
1757 1757 if slowpath:
1758 1758 # See walkchangerevs() slow path.
1759 1759 #
1760 1760 # pats/include/exclude cannot be represented as separate
1761 1761 # revset expressions as their filtering logic applies at file
1762 1762 # level. For instance "-I a -X a" matches a revision touching
1763 1763 # "a" and "b" while "file(a) and not file(b)" does
1764 1764 # not. Besides, filesets are evaluated against the working
1765 1765 # directory.
1766 1766 matchargs = ['r:', 'd:relpath']
1767 1767 for p in pats:
1768 1768 matchargs.append('p:' + p)
1769 1769 for p in opts.get('include', []):
1770 1770 matchargs.append('i:' + p)
1771 1771 for p in opts.get('exclude', []):
1772 1772 matchargs.append('x:' + p)
1773 1773 matchargs = ','.join(('%r' % p) for p in matchargs)
1774 1774 opts['_matchfiles'] = matchargs
1775 1775 if follow:
1776 1776 opts[fnopats[0][followfirst]] = '.'
1777 1777 else:
1778 1778 if follow:
1779 1779 if pats:
1780 1780 # follow() revset interprets its file argument as a
1781 1781 # manifest entry, so use match.files(), not pats.
1782 1782 opts[fpats[followfirst]] = list(match.files())
1783 1783 else:
1784 1784 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1785 1785 else:
1786 1786 opts['_patslog'] = list(pats)
1787 1787
1788 1788 filematcher = None
1789 1789 if opts.get('patch') or opts.get('stat'):
1790 1790 # When following files, track renames via a special matcher.
1791 1791 # If we're forced to take the slowpath it means we're following
1792 1792 # at least one pattern/directory, so don't bother with rename tracking.
1793 1793 if follow and not match.always() and not slowpath:
1794 1794 # _makefollowlogfilematcher expects its files argument to be
1795 1795 # relative to the repo root, so use match.files(), not pats.
1796 1796 filematcher = _makefollowlogfilematcher(repo, match.files(),
1797 1797 followfirst)
1798 1798 else:
1799 1799 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1800 1800 if filematcher is None:
1801 1801 filematcher = lambda rev: match
1802 1802
1803 1803 expr = []
1804 1804 for op, val in sorted(opts.iteritems()):
1805 1805 if not val:
1806 1806 continue
1807 1807 if op not in opt2revset:
1808 1808 continue
1809 1809 revop, andor = opt2revset[op]
1810 1810 if '%(val)' not in revop:
1811 1811 expr.append(revop)
1812 1812 else:
1813 1813 if not isinstance(val, list):
1814 1814 e = revop % {'val': val}
1815 1815 else:
1816 1816 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1817 1817 expr.append(e)
1818 1818
1819 1819 if expr:
1820 1820 expr = '(' + ' and '.join(expr) + ')'
1821 1821 else:
1822 1822 expr = None
1823 1823 return expr, filematcher
1824 1824
1825 1825 def getgraphlogrevs(repo, pats, opts):
1826 1826 """Return (revs, expr, filematcher) where revs is an iterable of
1827 1827 revision numbers, expr is a revset string built from log options
1828 1828 and file patterns or None, and used to filter 'revs'. If --stat or
1829 1829 --patch are not passed filematcher is None. Otherwise it is a
1830 1830 callable taking a revision number and returning a match objects
1831 1831 filtering the files to be detailed when displaying the revision.
1832 1832 """
1833 1833 if not len(repo):
1834 1834 return [], None, None
1835 1835 limit = loglimit(opts)
1836 1836 # Default --rev value depends on --follow but --follow behaviour
1837 1837 # depends on revisions resolved from --rev...
1838 1838 follow = opts.get('follow') or opts.get('follow_first')
1839 1839 possiblyunsorted = False # whether revs might need sorting
1840 1840 if opts.get('rev'):
1841 1841 revs = scmutil.revrange(repo, opts['rev'])
1842 1842 # Don't sort here because _makelogrevset might depend on the
1843 1843 # order of revs
1844 1844 possiblyunsorted = True
1845 1845 else:
1846 1846 if follow and len(repo) > 0:
1847 1847 revs = repo.revs('reverse(:.)')
1848 1848 else:
1849 1849 revs = revset.spanset(repo)
1850 1850 revs.reverse()
1851 1851 if not revs:
1852 1852 return revset.baseset(), None, None
1853 1853 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1854 1854 if possiblyunsorted:
1855 1855 revs.sort(reverse=True)
1856 1856 if expr:
1857 1857 # Revset matchers often operate faster on revisions in changelog
1858 1858 # order, because most filters deal with the changelog.
1859 1859 revs.reverse()
1860 1860 matcher = revset.match(repo.ui, expr)
1861 1861 # Revset matches can reorder revisions. "A or B" typically returns
1862 1862 # returns the revision matching A then the revision matching B. Sort
1863 1863 # again to fix that.
1864 1864 revs = matcher(repo, revs)
1865 1865 revs.sort(reverse=True)
1866 1866 if limit is not None:
1867 1867 limitedrevs = []
1868 1868 for idx, rev in enumerate(revs):
1869 1869 if idx >= limit:
1870 1870 break
1871 1871 limitedrevs.append(rev)
1872 1872 revs = revset.baseset(limitedrevs)
1873 1873
1874 1874 return revs, expr, filematcher
1875 1875
1876 1876 def getlogrevs(repo, pats, opts):
1877 1877 """Return (revs, expr, filematcher) where revs is an iterable of
1878 1878 revision numbers, expr is a revset string built from log options
1879 1879 and file patterns or None, and used to filter 'revs'. If --stat or
1880 1880 --patch are not passed filematcher is None. Otherwise it is a
1881 1881 callable taking a revision number and returning a match objects
1882 1882 filtering the files to be detailed when displaying the revision.
1883 1883 """
1884 1884 limit = loglimit(opts)
1885 1885 # Default --rev value depends on --follow but --follow behaviour
1886 1886 # depends on revisions resolved from --rev...
1887 1887 follow = opts.get('follow') or opts.get('follow_first')
1888 1888 if opts.get('rev'):
1889 1889 revs = scmutil.revrange(repo, opts['rev'])
1890 1890 elif follow:
1891 1891 revs = repo.revs('reverse(:.)')
1892 1892 else:
1893 1893 revs = revset.spanset(repo)
1894 1894 revs.reverse()
1895 1895 if not revs:
1896 1896 return revset.baseset([]), None, None
1897 1897 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1898 1898 if expr:
1899 1899 # Revset matchers often operate faster on revisions in changelog
1900 1900 # order, because most filters deal with the changelog.
1901 1901 if not opts.get('rev'):
1902 1902 revs.reverse()
1903 1903 matcher = revset.match(repo.ui, expr)
1904 1904 # Revset matches can reorder revisions. "A or B" typically returns
1905 1905 # returns the revision matching A then the revision matching B. Sort
1906 1906 # again to fix that.
1907 1907 revs = matcher(repo, revs)
1908 1908 if not opts.get('rev'):
1909 1909 revs.sort(reverse=True)
1910 1910 if limit is not None:
1911 1911 count = 0
1912 1912 limitedrevs = []
1913 1913 it = iter(revs)
1914 1914 while count < limit:
1915 1915 try:
1916 1916 limitedrevs.append(it.next())
1917 1917 except (StopIteration):
1918 1918 break
1919 1919 count += 1
1920 1920 revs = revset.baseset(limitedrevs)
1921 1921
1922 1922 return revs, expr, filematcher
1923 1923
1924 1924 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1925 1925 filematcher=None):
1926 1926 seen, state = [], graphmod.asciistate()
1927 1927 for rev, type, ctx, parents in dag:
1928 1928 char = 'o'
1929 1929 if ctx.node() in showparents:
1930 1930 char = '@'
1931 1931 elif ctx.obsolete():
1932 1932 char = 'x'
1933 1933 copies = None
1934 1934 if getrenamed and ctx.rev():
1935 1935 copies = []
1936 1936 for fn in ctx.files():
1937 1937 rename = getrenamed(fn, ctx.rev())
1938 1938 if rename:
1939 1939 copies.append((fn, rename[0]))
1940 1940 revmatchfn = None
1941 1941 if filematcher is not None:
1942 1942 revmatchfn = filematcher(ctx.rev())
1943 1943 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1944 1944 lines = displayer.hunk.pop(rev).split('\n')
1945 1945 if not lines[-1]:
1946 1946 del lines[-1]
1947 1947 displayer.flush(rev)
1948 1948 edges = edgefn(type, char, lines, seen, rev, parents)
1949 1949 for type, char, lines, coldata in edges:
1950 1950 graphmod.ascii(ui, state, type, char, lines, coldata)
1951 1951 displayer.close()
1952 1952
1953 1953 def graphlog(ui, repo, *pats, **opts):
1954 1954 # Parameters are identical to log command ones
1955 1955 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1956 1956 revdag = graphmod.dagwalker(repo, revs)
1957 1957
1958 1958 getrenamed = None
1959 1959 if opts.get('copies'):
1960 1960 endrev = None
1961 1961 if opts.get('rev'):
1962 1962 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1963 1963 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1964 1964 displayer = show_changeset(ui, repo, opts, buffered=True)
1965 1965 showparents = [ctx.node() for ctx in repo[None].parents()]
1966 1966 displaygraph(ui, revdag, displayer, showparents,
1967 1967 graphmod.asciiedges, getrenamed, filematcher)
1968 1968
1969 1969 def checkunsupportedgraphflags(pats, opts):
1970 1970 for op in ["newest_first"]:
1971 1971 if op in opts and opts[op]:
1972 1972 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1973 1973 % op.replace("_", "-"))
1974 1974
1975 1975 def graphrevs(repo, nodes, opts):
1976 1976 limit = loglimit(opts)
1977 1977 nodes.reverse()
1978 1978 if limit is not None:
1979 1979 nodes = nodes[:limit]
1980 1980 return graphmod.nodes(repo, nodes)
1981 1981
1982 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1982 def add(ui, repo, match, prefix, explicitonly, **opts):
1983 1983 join = lambda f: os.path.join(prefix, f)
1984 1984 bad = []
1985 1985 oldbad = match.bad
1986 1986 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1987 1987 names = []
1988 1988 wctx = repo[None]
1989 1989 cca = None
1990 1990 abort, warn = scmutil.checkportabilityalert(ui)
1991 1991 if abort or warn:
1992 1992 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1993 1993 for f in wctx.walk(match):
1994 1994 exact = match.exact(f)
1995 1995 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1996 1996 if cca:
1997 1997 cca(f)
1998 1998 names.append(f)
1999 1999 if ui.verbose or not exact:
2000 2000 ui.status(_('adding %s\n') % match.rel(f))
2001 2001
2002 2002 for subpath in sorted(wctx.substate):
2003 2003 sub = wctx.sub(subpath)
2004 2004 try:
2005 2005 submatch = matchmod.narrowmatcher(subpath, match)
2006 if listsubrepos:
2007 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
2008 False))
2006 if opts.get('subrepos'):
2007 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2009 2008 else:
2010 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
2011 True))
2009 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2012 2010 except error.LookupError:
2013 2011 ui.status(_("skipping missing subrepository: %s\n")
2014 2012 % join(subpath))
2015 2013
2016 if not dryrun:
2014 if not opts.get('dry_run'):
2017 2015 rejected = wctx.add(names, prefix)
2018 2016 bad.extend(f for f in rejected if f in match.files())
2019 2017 return bad
2020 2018
2021 2019 def forget(ui, repo, match, prefix, explicitonly):
2022 2020 join = lambda f: os.path.join(prefix, f)
2023 2021 bad = []
2024 2022 oldbad = match.bad
2025 2023 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2026 2024 wctx = repo[None]
2027 2025 forgot = []
2028 2026 s = repo.status(match=match, clean=True)
2029 2027 forget = sorted(s[0] + s[1] + s[3] + s[6])
2030 2028 if explicitonly:
2031 2029 forget = [f for f in forget if match.exact(f)]
2032 2030
2033 2031 for subpath in sorted(wctx.substate):
2034 2032 sub = wctx.sub(subpath)
2035 2033 try:
2036 2034 submatch = matchmod.narrowmatcher(subpath, match)
2037 2035 subbad, subforgot = sub.forget(submatch, prefix)
2038 2036 bad.extend([subpath + '/' + f for f in subbad])
2039 2037 forgot.extend([subpath + '/' + f for f in subforgot])
2040 2038 except error.LookupError:
2041 2039 ui.status(_("skipping missing subrepository: %s\n")
2042 2040 % join(subpath))
2043 2041
2044 2042 if not explicitonly:
2045 2043 for f in match.files():
2046 2044 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2047 2045 if f not in forgot:
2048 2046 if repo.wvfs.exists(f):
2049 2047 ui.warn(_('not removing %s: '
2050 2048 'file is already untracked\n')
2051 2049 % match.rel(f))
2052 2050 bad.append(f)
2053 2051
2054 2052 for f in forget:
2055 2053 if ui.verbose or not match.exact(f):
2056 2054 ui.status(_('removing %s\n') % match.rel(f))
2057 2055
2058 2056 rejected = wctx.forget(forget, prefix)
2059 2057 bad.extend(f for f in rejected if f in match.files())
2060 2058 forgot.extend(f for f in forget if f not in rejected)
2061 2059 return bad, forgot
2062 2060
2063 2061 def remove(ui, repo, m, prefix, after, force, subrepos):
2064 2062 join = lambda f: os.path.join(prefix, f)
2065 2063 ret = 0
2066 2064 s = repo.status(match=m, clean=True)
2067 2065 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2068 2066
2069 2067 wctx = repo[None]
2070 2068
2071 2069 for subpath in sorted(wctx.substate):
2072 2070 def matchessubrepo(matcher, subpath):
2073 2071 if matcher.exact(subpath):
2074 2072 return True
2075 2073 for f in matcher.files():
2076 2074 if f.startswith(subpath):
2077 2075 return True
2078 2076 return False
2079 2077
2080 2078 if subrepos or matchessubrepo(m, subpath):
2081 2079 sub = wctx.sub(subpath)
2082 2080 try:
2083 2081 submatch = matchmod.narrowmatcher(subpath, m)
2084 2082 if sub.removefiles(submatch, prefix, after, force, subrepos):
2085 2083 ret = 1
2086 2084 except error.LookupError:
2087 2085 ui.status(_("skipping missing subrepository: %s\n")
2088 2086 % join(subpath))
2089 2087
2090 2088 # warn about failure to delete explicit files/dirs
2091 2089 for f in m.files():
2092 2090 def insubrepo():
2093 2091 for subpath in wctx.substate:
2094 2092 if f.startswith(subpath):
2095 2093 return True
2096 2094 return False
2097 2095
2098 2096 if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
2099 2097 continue
2100 2098
2101 2099 if repo.wvfs.exists(f):
2102 2100 if repo.wvfs.isdir(f):
2103 2101 ui.warn(_('not removing %s: no tracked files\n')
2104 2102 % m.rel(f))
2105 2103 else:
2106 2104 ui.warn(_('not removing %s: file is untracked\n')
2107 2105 % m.rel(f))
2108 2106 # missing files will generate a warning elsewhere
2109 2107 ret = 1
2110 2108
2111 2109 if force:
2112 2110 list = modified + deleted + clean + added
2113 2111 elif after:
2114 2112 list = deleted
2115 2113 for f in modified + added + clean:
2116 2114 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2117 2115 ret = 1
2118 2116 else:
2119 2117 list = deleted + clean
2120 2118 for f in modified:
2121 2119 ui.warn(_('not removing %s: file is modified (use -f'
2122 2120 ' to force removal)\n') % m.rel(f))
2123 2121 ret = 1
2124 2122 for f in added:
2125 2123 ui.warn(_('not removing %s: file has been marked for add'
2126 2124 ' (use forget to undo)\n') % m.rel(f))
2127 2125 ret = 1
2128 2126
2129 2127 for f in sorted(list):
2130 2128 if ui.verbose or not m.exact(f):
2131 2129 ui.status(_('removing %s\n') % m.rel(f))
2132 2130
2133 2131 wlock = repo.wlock()
2134 2132 try:
2135 2133 if not after:
2136 2134 for f in list:
2137 2135 if f in added:
2138 2136 continue # we never unlink added files on remove
2139 2137 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2140 2138 repo[None].forget(list)
2141 2139 finally:
2142 2140 wlock.release()
2143 2141
2144 2142 return ret
2145 2143
2146 2144 def cat(ui, repo, ctx, matcher, prefix, **opts):
2147 2145 err = 1
2148 2146
2149 2147 def write(path):
2150 2148 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2151 2149 pathname=os.path.join(prefix, path))
2152 2150 data = ctx[path].data()
2153 2151 if opts.get('decode'):
2154 2152 data = repo.wwritedata(path, data)
2155 2153 fp.write(data)
2156 2154 fp.close()
2157 2155
2158 2156 # Automation often uses hg cat on single files, so special case it
2159 2157 # for performance to avoid the cost of parsing the manifest.
2160 2158 if len(matcher.files()) == 1 and not matcher.anypats():
2161 2159 file = matcher.files()[0]
2162 2160 mf = repo.manifest
2163 2161 mfnode = ctx._changeset[0]
2164 2162 if mf.find(mfnode, file)[0]:
2165 2163 write(file)
2166 2164 return 0
2167 2165
2168 2166 # Don't warn about "missing" files that are really in subrepos
2169 2167 bad = matcher.bad
2170 2168
2171 2169 def badfn(path, msg):
2172 2170 for subpath in ctx.substate:
2173 2171 if path.startswith(subpath):
2174 2172 return
2175 2173 bad(path, msg)
2176 2174
2177 2175 matcher.bad = badfn
2178 2176
2179 2177 for abs in ctx.walk(matcher):
2180 2178 write(abs)
2181 2179 err = 0
2182 2180
2183 2181 matcher.bad = bad
2184 2182
2185 2183 for subpath in sorted(ctx.substate):
2186 2184 sub = ctx.sub(subpath)
2187 2185 try:
2188 2186 submatch = matchmod.narrowmatcher(subpath, matcher)
2189 2187
2190 2188 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2191 2189 **opts):
2192 2190 err = 0
2193 2191 except error.RepoLookupError:
2194 2192 ui.status(_("skipping missing subrepository: %s\n")
2195 2193 % os.path.join(prefix, subpath))
2196 2194
2197 2195 return err
2198 2196
2199 2197 def commit(ui, repo, commitfunc, pats, opts):
2200 2198 '''commit the specified files or all outstanding changes'''
2201 2199 date = opts.get('date')
2202 2200 if date:
2203 2201 opts['date'] = util.parsedate(date)
2204 2202 message = logmessage(ui, opts)
2205 2203 matcher = scmutil.match(repo[None], pats, opts)
2206 2204
2207 2205 # extract addremove carefully -- this function can be called from a command
2208 2206 # that doesn't support addremove
2209 2207 if opts.get('addremove'):
2210 2208 if scmutil.addremove(repo, matcher, "", opts) != 0:
2211 2209 raise util.Abort(
2212 2210 _("failed to mark all new/missing files as added/removed"))
2213 2211
2214 2212 return commitfunc(ui, repo, message, matcher, opts)
2215 2213
2216 2214 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2217 2215 # amend will reuse the existing user if not specified, but the obsolete
2218 2216 # marker creation requires that the current user's name is specified.
2219 2217 if obsolete._enabled:
2220 2218 ui.username() # raise exception if username not set
2221 2219
2222 2220 ui.note(_('amending changeset %s\n') % old)
2223 2221 base = old.p1()
2224 2222
2225 2223 wlock = lock = newid = None
2226 2224 try:
2227 2225 wlock = repo.wlock()
2228 2226 lock = repo.lock()
2229 2227 tr = repo.transaction('amend')
2230 2228 try:
2231 2229 # See if we got a message from -m or -l, if not, open the editor
2232 2230 # with the message of the changeset to amend
2233 2231 message = logmessage(ui, opts)
2234 2232 # ensure logfile does not conflict with later enforcement of the
2235 2233 # message. potential logfile content has been processed by
2236 2234 # `logmessage` anyway.
2237 2235 opts.pop('logfile')
2238 2236 # First, do a regular commit to record all changes in the working
2239 2237 # directory (if there are any)
2240 2238 ui.callhooks = False
2241 2239 currentbookmark = repo._bookmarkcurrent
2242 2240 try:
2243 2241 repo._bookmarkcurrent = None
2244 2242 opts['message'] = 'temporary amend commit for %s' % old
2245 2243 node = commit(ui, repo, commitfunc, pats, opts)
2246 2244 finally:
2247 2245 repo._bookmarkcurrent = currentbookmark
2248 2246 ui.callhooks = True
2249 2247 ctx = repo[node]
2250 2248
2251 2249 # Participating changesets:
2252 2250 #
2253 2251 # node/ctx o - new (intermediate) commit that contains changes
2254 2252 # | from working dir to go into amending commit
2255 2253 # | (or a workingctx if there were no changes)
2256 2254 # |
2257 2255 # old o - changeset to amend
2258 2256 # |
2259 2257 # base o - parent of amending changeset
2260 2258
2261 2259 # Update extra dict from amended commit (e.g. to preserve graft
2262 2260 # source)
2263 2261 extra.update(old.extra())
2264 2262
2265 2263 # Also update it from the intermediate commit or from the wctx
2266 2264 extra.update(ctx.extra())
2267 2265
2268 2266 if len(old.parents()) > 1:
2269 2267 # ctx.files() isn't reliable for merges, so fall back to the
2270 2268 # slower repo.status() method
2271 2269 files = set([fn for st in repo.status(base, old)[:3]
2272 2270 for fn in st])
2273 2271 else:
2274 2272 files = set(old.files())
2275 2273
2276 2274 # Second, we use either the commit we just did, or if there were no
2277 2275 # changes the parent of the working directory as the version of the
2278 2276 # files in the final amend commit
2279 2277 if node:
2280 2278 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2281 2279
2282 2280 user = ctx.user()
2283 2281 date = ctx.date()
2284 2282 # Recompute copies (avoid recording a -> b -> a)
2285 2283 copied = copies.pathcopies(base, ctx)
2286 2284
2287 2285 # Prune files which were reverted by the updates: if old
2288 2286 # introduced file X and our intermediate commit, node,
2289 2287 # renamed that file, then those two files are the same and
2290 2288 # we can discard X from our list of files. Likewise if X
2291 2289 # was deleted, it's no longer relevant
2292 2290 files.update(ctx.files())
2293 2291
2294 2292 def samefile(f):
2295 2293 if f in ctx.manifest():
2296 2294 a = ctx.filectx(f)
2297 2295 if f in base.manifest():
2298 2296 b = base.filectx(f)
2299 2297 return (not a.cmp(b)
2300 2298 and a.flags() == b.flags())
2301 2299 else:
2302 2300 return False
2303 2301 else:
2304 2302 return f not in base.manifest()
2305 2303 files = [f for f in files if not samefile(f)]
2306 2304
2307 2305 def filectxfn(repo, ctx_, path):
2308 2306 try:
2309 2307 fctx = ctx[path]
2310 2308 flags = fctx.flags()
2311 2309 mctx = context.memfilectx(repo,
2312 2310 fctx.path(), fctx.data(),
2313 2311 islink='l' in flags,
2314 2312 isexec='x' in flags,
2315 2313 copied=copied.get(path))
2316 2314 return mctx
2317 2315 except KeyError:
2318 2316 return None
2319 2317 else:
2320 2318 ui.note(_('copying changeset %s to %s\n') % (old, base))
2321 2319
2322 2320 # Use version of files as in the old cset
2323 2321 def filectxfn(repo, ctx_, path):
2324 2322 try:
2325 2323 return old.filectx(path)
2326 2324 except KeyError:
2327 2325 return None
2328 2326
2329 2327 user = opts.get('user') or old.user()
2330 2328 date = opts.get('date') or old.date()
2331 2329 editform = mergeeditform(old, 'commit.amend')
2332 2330 editor = getcommiteditor(editform=editform, **opts)
2333 2331 if not message:
2334 2332 editor = getcommiteditor(edit=True, editform=editform)
2335 2333 message = old.description()
2336 2334
2337 2335 pureextra = extra.copy()
2338 2336 extra['amend_source'] = old.hex()
2339 2337
2340 2338 new = context.memctx(repo,
2341 2339 parents=[base.node(), old.p2().node()],
2342 2340 text=message,
2343 2341 files=files,
2344 2342 filectxfn=filectxfn,
2345 2343 user=user,
2346 2344 date=date,
2347 2345 extra=extra,
2348 2346 editor=editor)
2349 2347
2350 2348 newdesc = changelog.stripdesc(new.description())
2351 2349 if ((not node)
2352 2350 and newdesc == old.description()
2353 2351 and user == old.user()
2354 2352 and date == old.date()
2355 2353 and pureextra == old.extra()):
2356 2354 # nothing changed. continuing here would create a new node
2357 2355 # anyway because of the amend_source noise.
2358 2356 #
2359 2357 # This not what we expect from amend.
2360 2358 return old.node()
2361 2359
2362 2360 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2363 2361 try:
2364 2362 if opts.get('secret'):
2365 2363 commitphase = 'secret'
2366 2364 else:
2367 2365 commitphase = old.phase()
2368 2366 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2369 2367 newid = repo.commitctx(new)
2370 2368 finally:
2371 2369 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2372 2370 if newid != old.node():
2373 2371 # Reroute the working copy parent to the new changeset
2374 2372 repo.setparents(newid, nullid)
2375 2373
2376 2374 # Move bookmarks from old parent to amend commit
2377 2375 bms = repo.nodebookmarks(old.node())
2378 2376 if bms:
2379 2377 marks = repo._bookmarks
2380 2378 for bm in bms:
2381 2379 marks[bm] = newid
2382 2380 marks.write()
2383 2381 #commit the whole amend process
2384 2382 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2385 2383 if createmarkers and newid != old.node():
2386 2384 # mark the new changeset as successor of the rewritten one
2387 2385 new = repo[newid]
2388 2386 obs = [(old, (new,))]
2389 2387 if node:
2390 2388 obs.append((ctx, ()))
2391 2389
2392 2390 obsolete.createmarkers(repo, obs)
2393 2391 tr.close()
2394 2392 finally:
2395 2393 tr.release()
2396 2394 if not createmarkers and newid != old.node():
2397 2395 # Strip the intermediate commit (if there was one) and the amended
2398 2396 # commit
2399 2397 if node:
2400 2398 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2401 2399 ui.note(_('stripping amended changeset %s\n') % old)
2402 2400 repair.strip(ui, repo, old.node(), topic='amend-backup')
2403 2401 finally:
2404 2402 if newid is None:
2405 2403 repo.dirstate.invalidate()
2406 2404 lockmod.release(lock, wlock)
2407 2405 return newid
2408 2406
2409 2407 def commiteditor(repo, ctx, subs, editform=''):
2410 2408 if ctx.description():
2411 2409 return ctx.description()
2412 2410 return commitforceeditor(repo, ctx, subs, editform=editform)
2413 2411
2414 2412 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2415 2413 editform=''):
2416 2414 if not extramsg:
2417 2415 extramsg = _("Leave message empty to abort commit.")
2418 2416
2419 2417 forms = [e for e in editform.split('.') if e]
2420 2418 forms.insert(0, 'changeset')
2421 2419 while forms:
2422 2420 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2423 2421 if tmpl:
2424 2422 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2425 2423 break
2426 2424 forms.pop()
2427 2425 else:
2428 2426 committext = buildcommittext(repo, ctx, subs, extramsg)
2429 2427
2430 2428 # run editor in the repository root
2431 2429 olddir = os.getcwd()
2432 2430 os.chdir(repo.root)
2433 2431 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2434 2432 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2435 2433 os.chdir(olddir)
2436 2434
2437 2435 if finishdesc:
2438 2436 text = finishdesc(text)
2439 2437 if not text.strip():
2440 2438 raise util.Abort(_("empty commit message"))
2441 2439
2442 2440 return text
2443 2441
2444 2442 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2445 2443 ui = repo.ui
2446 2444 tmpl, mapfile = gettemplate(ui, tmpl, None)
2447 2445
2448 2446 try:
2449 2447 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2450 2448 except SyntaxError, inst:
2451 2449 raise util.Abort(inst.args[0])
2452 2450
2453 2451 for k, v in repo.ui.configitems('committemplate'):
2454 2452 if k != 'changeset':
2455 2453 t.t.cache[k] = v
2456 2454
2457 2455 if not extramsg:
2458 2456 extramsg = '' # ensure that extramsg is string
2459 2457
2460 2458 ui.pushbuffer()
2461 2459 t.show(ctx, extramsg=extramsg)
2462 2460 return ui.popbuffer()
2463 2461
2464 2462 def buildcommittext(repo, ctx, subs, extramsg):
2465 2463 edittext = []
2466 2464 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2467 2465 if ctx.description():
2468 2466 edittext.append(ctx.description())
2469 2467 edittext.append("")
2470 2468 edittext.append("") # Empty line between message and comments.
2471 2469 edittext.append(_("HG: Enter commit message."
2472 2470 " Lines beginning with 'HG:' are removed."))
2473 2471 edittext.append("HG: %s" % extramsg)
2474 2472 edittext.append("HG: --")
2475 2473 edittext.append(_("HG: user: %s") % ctx.user())
2476 2474 if ctx.p2():
2477 2475 edittext.append(_("HG: branch merge"))
2478 2476 if ctx.branch():
2479 2477 edittext.append(_("HG: branch '%s'") % ctx.branch())
2480 2478 if bookmarks.iscurrent(repo):
2481 2479 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2482 2480 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2483 2481 edittext.extend([_("HG: added %s") % f for f in added])
2484 2482 edittext.extend([_("HG: changed %s") % f for f in modified])
2485 2483 edittext.extend([_("HG: removed %s") % f for f in removed])
2486 2484 if not added and not modified and not removed:
2487 2485 edittext.append(_("HG: no files changed"))
2488 2486 edittext.append("")
2489 2487
2490 2488 return "\n".join(edittext)
2491 2489
2492 2490 def commitstatus(repo, node, branch, bheads=None, opts={}):
2493 2491 ctx = repo[node]
2494 2492 parents = ctx.parents()
2495 2493
2496 2494 if (not opts.get('amend') and bheads and node not in bheads and not
2497 2495 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2498 2496 repo.ui.status(_('created new head\n'))
2499 2497 # The message is not printed for initial roots. For the other
2500 2498 # changesets, it is printed in the following situations:
2501 2499 #
2502 2500 # Par column: for the 2 parents with ...
2503 2501 # N: null or no parent
2504 2502 # B: parent is on another named branch
2505 2503 # C: parent is a regular non head changeset
2506 2504 # H: parent was a branch head of the current branch
2507 2505 # Msg column: whether we print "created new head" message
2508 2506 # In the following, it is assumed that there already exists some
2509 2507 # initial branch heads of the current branch, otherwise nothing is
2510 2508 # printed anyway.
2511 2509 #
2512 2510 # Par Msg Comment
2513 2511 # N N y additional topo root
2514 2512 #
2515 2513 # B N y additional branch root
2516 2514 # C N y additional topo head
2517 2515 # H N n usual case
2518 2516 #
2519 2517 # B B y weird additional branch root
2520 2518 # C B y branch merge
2521 2519 # H B n merge with named branch
2522 2520 #
2523 2521 # C C y additional head from merge
2524 2522 # C H n merge with a head
2525 2523 #
2526 2524 # H H n head merge: head count decreases
2527 2525
2528 2526 if not opts.get('close_branch'):
2529 2527 for r in parents:
2530 2528 if r.closesbranch() and r.branch() == branch:
2531 2529 repo.ui.status(_('reopening closed branch head %d\n') % r)
2532 2530
2533 2531 if repo.ui.debugflag:
2534 2532 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2535 2533 elif repo.ui.verbose:
2536 2534 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2537 2535
2538 2536 def revert(ui, repo, ctx, parents, *pats, **opts):
2539 2537 parent, p2 = parents
2540 2538 node = ctx.node()
2541 2539
2542 2540 mf = ctx.manifest()
2543 2541 if node == p2:
2544 2542 parent = p2
2545 2543 if node == parent:
2546 2544 pmf = mf
2547 2545 else:
2548 2546 pmf = None
2549 2547
2550 2548 # need all matching names in dirstate and manifest of target rev,
2551 2549 # so have to walk both. do not print errors if files exist in one
2552 2550 # but not other.
2553 2551
2554 2552 # `names` is a mapping for all elements in working copy and target revision
2555 2553 # The mapping is in the form:
2556 2554 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2557 2555 names = {}
2558 2556
2559 2557 wlock = repo.wlock()
2560 2558 try:
2561 2559 ## filling of the `names` mapping
2562 2560 # walk dirstate to fill `names`
2563 2561
2564 2562 m = scmutil.match(repo[None], pats, opts)
2565 2563 if not m.always() or node != parent:
2566 2564 m.bad = lambda x, y: False
2567 2565 for abs in repo.walk(m):
2568 2566 names[abs] = m.rel(abs), m.exact(abs)
2569 2567
2570 2568 # walk target manifest to fill `names`
2571 2569
2572 2570 def badfn(path, msg):
2573 2571 if path in names:
2574 2572 return
2575 2573 if path in ctx.substate:
2576 2574 return
2577 2575 path_ = path + '/'
2578 2576 for f in names:
2579 2577 if f.startswith(path_):
2580 2578 return
2581 2579 ui.warn("%s: %s\n" % (m.rel(path), msg))
2582 2580
2583 2581 m = scmutil.match(ctx, pats, opts)
2584 2582 m.bad = badfn
2585 2583 for abs in ctx.walk(m):
2586 2584 if abs not in names:
2587 2585 names[abs] = m.rel(abs), m.exact(abs)
2588 2586
2589 2587 # Find status of all file in `names`.
2590 2588 m = scmutil.matchfiles(repo, names)
2591 2589
2592 2590 changes = repo.status(node1=node, match=m,
2593 2591 unknown=True, ignored=True, clean=True)
2594 2592 else:
2595 2593 changes = repo.status(match=m)
2596 2594 for kind in changes:
2597 2595 for abs in kind:
2598 2596 names[abs] = m.rel(abs), m.exact(abs)
2599 2597
2600 2598 m = scmutil.matchfiles(repo, names)
2601 2599
2602 2600 modified = set(changes.modified)
2603 2601 added = set(changes.added)
2604 2602 removed = set(changes.removed)
2605 2603 _deleted = set(changes.deleted)
2606 2604 unknown = set(changes.unknown)
2607 2605 unknown.update(changes.ignored)
2608 2606 clean = set(changes.clean)
2609 2607 modadded = set()
2610 2608
2611 2609 # split between files known in target manifest and the others
2612 2610 smf = set(mf)
2613 2611
2614 2612 # determine the exact nature of the deleted changesets
2615 2613 deladded = _deleted - smf
2616 2614 deleted = _deleted - deladded
2617 2615
2618 2616 # We need to account for the state of file in the dirstate.
2619 2617 #
2620 2618 # Even, when we revert against something else than parent. This will
2621 2619 # slightly alter the behavior of revert (doing back up or not, delete
2622 2620 # or just forget etc).
2623 2621 if parent == node:
2624 2622 dsmodified = modified
2625 2623 dsadded = added
2626 2624 dsremoved = removed
2627 2625 # store all local modifications, useful later for rename detection
2628 2626 localchanges = dsmodified | dsadded
2629 2627 modified, added, removed = set(), set(), set()
2630 2628 else:
2631 2629 changes = repo.status(node1=parent, match=m)
2632 2630 dsmodified = set(changes.modified)
2633 2631 dsadded = set(changes.added)
2634 2632 dsremoved = set(changes.removed)
2635 2633 # store all local modifications, useful later for rename detection
2636 2634 localchanges = dsmodified | dsadded
2637 2635
2638 2636 # only take into account for removes between wc and target
2639 2637 clean |= dsremoved - removed
2640 2638 dsremoved &= removed
2641 2639 # distinct between dirstate remove and other
2642 2640 removed -= dsremoved
2643 2641
2644 2642 modadded = added & dsmodified
2645 2643 added -= modadded
2646 2644
2647 2645 # tell newly modified apart.
2648 2646 dsmodified &= modified
2649 2647 dsmodified |= modified & dsadded # dirstate added may needs backup
2650 2648 modified -= dsmodified
2651 2649
2652 2650 # We need to wait for some post-processing to update this set
2653 2651 # before making the distinction. The dirstate will be used for
2654 2652 # that purpose.
2655 2653 dsadded = added
2656 2654
2657 2655 # in case of merge, files that are actually added can be reported as
2658 2656 # modified, we need to post process the result
2659 2657 if p2 != nullid:
2660 2658 if pmf is None:
2661 2659 # only need parent manifest in the merge case,
2662 2660 # so do not read by default
2663 2661 pmf = repo[parent].manifest()
2664 2662 mergeadd = dsmodified - set(pmf)
2665 2663 dsadded |= mergeadd
2666 2664 dsmodified -= mergeadd
2667 2665
2668 2666 # if f is a rename, update `names` to also revert the source
2669 2667 cwd = repo.getcwd()
2670 2668 for f in localchanges:
2671 2669 src = repo.dirstate.copied(f)
2672 2670 # XXX should we check for rename down to target node?
2673 2671 if src and src not in names and repo.dirstate[src] == 'r':
2674 2672 dsremoved.add(src)
2675 2673 names[src] = (repo.pathto(src, cwd), True)
2676 2674
2677 2675 # distinguish between file to forget and the other
2678 2676 added = set()
2679 2677 for abs in dsadded:
2680 2678 if repo.dirstate[abs] != 'a':
2681 2679 added.add(abs)
2682 2680 dsadded -= added
2683 2681
2684 2682 for abs in deladded:
2685 2683 if repo.dirstate[abs] == 'a':
2686 2684 dsadded.add(abs)
2687 2685 deladded -= dsadded
2688 2686
2689 2687 # For files marked as removed, we check if an unknown file is present at
2690 2688 # the same path. If a such file exists it may need to be backed up.
2691 2689 # Making the distinction at this stage helps have simpler backup
2692 2690 # logic.
2693 2691 removunk = set()
2694 2692 for abs in removed:
2695 2693 target = repo.wjoin(abs)
2696 2694 if os.path.lexists(target):
2697 2695 removunk.add(abs)
2698 2696 removed -= removunk
2699 2697
2700 2698 dsremovunk = set()
2701 2699 for abs in dsremoved:
2702 2700 target = repo.wjoin(abs)
2703 2701 if os.path.lexists(target):
2704 2702 dsremovunk.add(abs)
2705 2703 dsremoved -= dsremovunk
2706 2704
2707 2705 # action to be actually performed by revert
2708 2706 # (<list of file>, message>) tuple
2709 2707 actions = {'revert': ([], _('reverting %s\n')),
2710 2708 'add': ([], _('adding %s\n')),
2711 2709 'remove': ([], _('removing %s\n')),
2712 2710 'drop': ([], _('removing %s\n')),
2713 2711 'forget': ([], _('forgetting %s\n')),
2714 2712 'undelete': ([], _('undeleting %s\n')),
2715 2713 'noop': (None, _('no changes needed to %s\n')),
2716 2714 'unknown': (None, _('file not managed: %s\n')),
2717 2715 }
2718 2716
2719 2717 # "constant" that convey the backup strategy.
2720 2718 # All set to `discard` if `no-backup` is set do avoid checking
2721 2719 # no_backup lower in the code.
2722 2720 # These values are ordered for comparison purposes
2723 2721 backup = 2 # unconditionally do backup
2724 2722 check = 1 # check if the existing file differs from target
2725 2723 discard = 0 # never do backup
2726 2724 if opts.get('no_backup'):
2727 2725 backup = check = discard
2728 2726
2729 2727 backupanddel = actions['remove']
2730 2728 if not opts.get('no_backup'):
2731 2729 backupanddel = actions['drop']
2732 2730
2733 2731 disptable = (
2734 2732 # dispatch table:
2735 2733 # file state
2736 2734 # action
2737 2735 # make backup
2738 2736
2739 2737 ## Sets that results that will change file on disk
2740 2738 # Modified compared to target, no local change
2741 2739 (modified, actions['revert'], discard),
2742 2740 # Modified compared to target, but local file is deleted
2743 2741 (deleted, actions['revert'], discard),
2744 2742 # Modified compared to target, local change
2745 2743 (dsmodified, actions['revert'], backup),
2746 2744 # Added since target
2747 2745 (added, actions['remove'], discard),
2748 2746 # Added in working directory
2749 2747 (dsadded, actions['forget'], discard),
2750 2748 # Added since target, have local modification
2751 2749 (modadded, backupanddel, backup),
2752 2750 # Added since target but file is missing in working directory
2753 2751 (deladded, actions['drop'], discard),
2754 2752 # Removed since target, before working copy parent
2755 2753 (removed, actions['add'], discard),
2756 2754 # Same as `removed` but an unknown file exists at the same path
2757 2755 (removunk, actions['add'], check),
2758 2756 # Removed since targe, marked as such in working copy parent
2759 2757 (dsremoved, actions['undelete'], discard),
2760 2758 # Same as `dsremoved` but an unknown file exists at the same path
2761 2759 (dsremovunk, actions['undelete'], check),
2762 2760 ## the following sets does not result in any file changes
2763 2761 # File with no modification
2764 2762 (clean, actions['noop'], discard),
2765 2763 # Existing file, not tracked anywhere
2766 2764 (unknown, actions['unknown'], discard),
2767 2765 )
2768 2766
2769 2767 needdata = ('revert', 'add', 'undelete')
2770 2768 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2771 2769
2772 2770 wctx = repo[None]
2773 2771 for abs, (rel, exact) in sorted(names.items()):
2774 2772 # target file to be touch on disk (relative to cwd)
2775 2773 target = repo.wjoin(abs)
2776 2774 # search the entry in the dispatch table.
2777 2775 # if the file is in any of these sets, it was touched in the working
2778 2776 # directory parent and we are sure it needs to be reverted.
2779 2777 for table, (xlist, msg), dobackup in disptable:
2780 2778 if abs not in table:
2781 2779 continue
2782 2780 if xlist is not None:
2783 2781 xlist.append(abs)
2784 2782 if dobackup and (backup <= dobackup
2785 2783 or wctx[abs].cmp(ctx[abs])):
2786 2784 bakname = "%s.orig" % rel
2787 2785 ui.note(_('saving current version of %s as %s\n') %
2788 2786 (rel, bakname))
2789 2787 if not opts.get('dry_run'):
2790 2788 util.rename(target, bakname)
2791 2789 if ui.verbose or not exact:
2792 2790 if not isinstance(msg, basestring):
2793 2791 msg = msg(abs)
2794 2792 ui.status(msg % rel)
2795 2793 elif exact:
2796 2794 ui.warn(msg % rel)
2797 2795 break
2798 2796
2799 2797
2800 2798 if not opts.get('dry_run'):
2801 2799 _performrevert(repo, parents, ctx, actions)
2802 2800
2803 2801 # get the list of subrepos that must be reverted
2804 2802 subrepomatch = scmutil.match(ctx, pats, opts)
2805 2803 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2806 2804
2807 2805 if targetsubs:
2808 2806 # Revert the subrepos on the revert list
2809 2807 for sub in targetsubs:
2810 2808 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2811 2809 finally:
2812 2810 wlock.release()
2813 2811
2814 2812 def _revertprefetch(repo, ctx, *files):
2815 2813 """Let extension changing the storage layer prefetch content"""
2816 2814 pass
2817 2815
2818 2816 def _performrevert(repo, parents, ctx, actions):
2819 2817 """function that actually perform all the actions computed for revert
2820 2818
2821 2819 This is an independent function to let extension to plug in and react to
2822 2820 the imminent revert.
2823 2821
2824 2822 Make sure you have the working directory locked when calling this function.
2825 2823 """
2826 2824 parent, p2 = parents
2827 2825 node = ctx.node()
2828 2826 def checkout(f):
2829 2827 fc = ctx[f]
2830 2828 repo.wwrite(f, fc.data(), fc.flags())
2831 2829
2832 2830 audit_path = pathutil.pathauditor(repo.root)
2833 2831 for f in actions['forget'][0]:
2834 2832 repo.dirstate.drop(f)
2835 2833 for f in actions['remove'][0]:
2836 2834 audit_path(f)
2837 2835 util.unlinkpath(repo.wjoin(f))
2838 2836 repo.dirstate.remove(f)
2839 2837 for f in actions['drop'][0]:
2840 2838 audit_path(f)
2841 2839 repo.dirstate.remove(f)
2842 2840
2843 2841 normal = None
2844 2842 if node == parent:
2845 2843 # We're reverting to our parent. If possible, we'd like status
2846 2844 # to report the file as clean. We have to use normallookup for
2847 2845 # merges to avoid losing information about merged/dirty files.
2848 2846 if p2 != nullid:
2849 2847 normal = repo.dirstate.normallookup
2850 2848 else:
2851 2849 normal = repo.dirstate.normal
2852 2850 for f in actions['revert'][0]:
2853 2851 checkout(f)
2854 2852 if normal:
2855 2853 normal(f)
2856 2854
2857 2855 for f in actions['add'][0]:
2858 2856 checkout(f)
2859 2857 repo.dirstate.add(f)
2860 2858
2861 2859 normal = repo.dirstate.normallookup
2862 2860 if node == parent and p2 == nullid:
2863 2861 normal = repo.dirstate.normal
2864 2862 for f in actions['undelete'][0]:
2865 2863 checkout(f)
2866 2864 normal(f)
2867 2865
2868 2866 copied = copies.pathcopies(repo[parent], ctx)
2869 2867
2870 2868 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2871 2869 if f in copied:
2872 2870 repo.dirstate.copy(copied[f], f)
2873 2871
2874 2872 def command(table):
2875 2873 """Returns a function object to be used as a decorator for making commands.
2876 2874
2877 2875 This function receives a command table as its argument. The table should
2878 2876 be a dict.
2879 2877
2880 2878 The returned function can be used as a decorator for adding commands
2881 2879 to that command table. This function accepts multiple arguments to define
2882 2880 a command.
2883 2881
2884 2882 The first argument is the command name.
2885 2883
2886 2884 The options argument is an iterable of tuples defining command arguments.
2887 2885 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2888 2886
2889 2887 The synopsis argument defines a short, one line summary of how to use the
2890 2888 command. This shows up in the help output.
2891 2889
2892 2890 The norepo argument defines whether the command does not require a
2893 2891 local repository. Most commands operate against a repository, thus the
2894 2892 default is False.
2895 2893
2896 2894 The optionalrepo argument defines whether the command optionally requires
2897 2895 a local repository.
2898 2896
2899 2897 The inferrepo argument defines whether to try to find a repository from the
2900 2898 command line arguments. If True, arguments will be examined for potential
2901 2899 repository locations. See ``findrepo()``. If a repository is found, it
2902 2900 will be used.
2903 2901 """
2904 2902 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2905 2903 inferrepo=False):
2906 2904 def decorator(func):
2907 2905 if synopsis:
2908 2906 table[name] = func, list(options), synopsis
2909 2907 else:
2910 2908 table[name] = func, list(options)
2911 2909
2912 2910 if norepo:
2913 2911 # Avoid import cycle.
2914 2912 import commands
2915 2913 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2916 2914
2917 2915 if optionalrepo:
2918 2916 import commands
2919 2917 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2920 2918
2921 2919 if inferrepo:
2922 2920 import commands
2923 2921 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2924 2922
2925 2923 return func
2926 2924 return decorator
2927 2925
2928 2926 return cmd
2929 2927
2930 2928 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2931 2929 # commands.outgoing. "missing" is "missing" of the result of
2932 2930 # "findcommonoutgoing()"
2933 2931 outgoinghooks = util.hooks()
2934 2932
2935 2933 # a list of (ui, repo) functions called by commands.summary
2936 2934 summaryhooks = util.hooks()
2937 2935
2938 2936 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2939 2937 #
2940 2938 # functions should return tuple of booleans below, if 'changes' is None:
2941 2939 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2942 2940 #
2943 2941 # otherwise, 'changes' is a tuple of tuples below:
2944 2942 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2945 2943 # - (desturl, destbranch, destpeer, outgoing)
2946 2944 summaryremotehooks = util.hooks()
2947 2945
2948 2946 # A list of state files kept by multistep operations like graft.
2949 2947 # Since graft cannot be aborted, it is considered 'clearable' by update.
2950 2948 # note: bisect is intentionally excluded
2951 2949 # (state file, clearable, allowcommit, error, hint)
2952 2950 unfinishedstates = [
2953 2951 ('graftstate', True, False, _('graft in progress'),
2954 2952 _("use 'hg graft --continue' or 'hg update' to abort")),
2955 2953 ('updatestate', True, False, _('last update was interrupted'),
2956 2954 _("use 'hg update' to get a consistent checkout"))
2957 2955 ]
2958 2956
2959 2957 def checkunfinished(repo, commit=False):
2960 2958 '''Look for an unfinished multistep operation, like graft, and abort
2961 2959 if found. It's probably good to check this right before
2962 2960 bailifchanged().
2963 2961 '''
2964 2962 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2965 2963 if commit and allowcommit:
2966 2964 continue
2967 2965 if repo.vfs.exists(f):
2968 2966 raise util.Abort(msg, hint=hint)
2969 2967
2970 2968 def clearunfinished(repo):
2971 2969 '''Check for unfinished operations (as above), and clear the ones
2972 2970 that are clearable.
2973 2971 '''
2974 2972 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2975 2973 if not clearable and repo.vfs.exists(f):
2976 2974 raise util.Abort(msg, hint=hint)
2977 2975 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2978 2976 if clearable and repo.vfs.exists(f):
2979 2977 util.unlink(repo.join(f))
@@ -1,6282 +1,6281 b''
1 1 # commands.py - command processing for 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, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _
11 11 import os, re, difflib, time, tempfile, errno, shlex
12 12 import sys, socket
13 13 import hg, scmutil, util, revlog, copies, error, bookmarks
14 14 import patch, help, encoding, templatekw, discovery
15 15 import archival, changegroup, cmdutil, hbisect
16 16 import sshserver, hgweb, commandserver
17 17 import extensions
18 18 from hgweb import server as hgweb_server
19 19 import merge as mergemod
20 20 import minirst, revset, fileset
21 21 import dagparser, context, simplemerge, graphmod, copies
22 22 import random
23 23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
24 24 import phases, obsolete, exchange
25 25 import ui as uimod
26 26
27 27 table = {}
28 28
29 29 command = cmdutil.command(table)
30 30
31 31 # Space delimited list of commands that don't require local repositories.
32 32 # This should be populated by passing norepo=True into the @command decorator.
33 33 norepo = ''
34 34 # Space delimited list of commands that optionally require local repositories.
35 35 # This should be populated by passing optionalrepo=True into the @command
36 36 # decorator.
37 37 optionalrepo = ''
38 38 # Space delimited list of commands that will examine arguments looking for
39 39 # a repository. This should be populated by passing inferrepo=True into the
40 40 # @command decorator.
41 41 inferrepo = ''
42 42
43 43 # common command options
44 44
45 45 globalopts = [
46 46 ('R', 'repository', '',
47 47 _('repository root directory or name of overlay bundle file'),
48 48 _('REPO')),
49 49 ('', 'cwd', '',
50 50 _('change working directory'), _('DIR')),
51 51 ('y', 'noninteractive', None,
52 52 _('do not prompt, automatically pick the first choice for all prompts')),
53 53 ('q', 'quiet', None, _('suppress output')),
54 54 ('v', 'verbose', None, _('enable additional output')),
55 55 ('', 'config', [],
56 56 _('set/override config option (use \'section.name=value\')'),
57 57 _('CONFIG')),
58 58 ('', 'debug', None, _('enable debugging output')),
59 59 ('', 'debugger', None, _('start debugger')),
60 60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
61 61 _('ENCODE')),
62 62 ('', 'encodingmode', encoding.encodingmode,
63 63 _('set the charset encoding mode'), _('MODE')),
64 64 ('', 'traceback', None, _('always print a traceback on exception')),
65 65 ('', 'time', None, _('time how long the command takes')),
66 66 ('', 'profile', None, _('print command execution profile')),
67 67 ('', 'version', None, _('output version information and exit')),
68 68 ('h', 'help', None, _('display help and exit')),
69 69 ('', 'hidden', False, _('consider hidden changesets')),
70 70 ]
71 71
72 72 dryrunopts = [('n', 'dry-run', None,
73 73 _('do not perform actions, just print output'))]
74 74
75 75 remoteopts = [
76 76 ('e', 'ssh', '',
77 77 _('specify ssh command to use'), _('CMD')),
78 78 ('', 'remotecmd', '',
79 79 _('specify hg command to run on the remote side'), _('CMD')),
80 80 ('', 'insecure', None,
81 81 _('do not verify server certificate (ignoring web.cacerts config)')),
82 82 ]
83 83
84 84 walkopts = [
85 85 ('I', 'include', [],
86 86 _('include names matching the given patterns'), _('PATTERN')),
87 87 ('X', 'exclude', [],
88 88 _('exclude names matching the given patterns'), _('PATTERN')),
89 89 ]
90 90
91 91 commitopts = [
92 92 ('m', 'message', '',
93 93 _('use text as commit message'), _('TEXT')),
94 94 ('l', 'logfile', '',
95 95 _('read commit message from file'), _('FILE')),
96 96 ]
97 97
98 98 commitopts2 = [
99 99 ('d', 'date', '',
100 100 _('record the specified date as commit date'), _('DATE')),
101 101 ('u', 'user', '',
102 102 _('record the specified user as committer'), _('USER')),
103 103 ]
104 104
105 105 # hidden for now
106 106 formatteropts = [
107 107 ('T', 'template', '',
108 108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
109 109 ]
110 110
111 111 templateopts = [
112 112 ('', 'style', '',
113 113 _('display using template map file (DEPRECATED)'), _('STYLE')),
114 114 ('T', 'template', '',
115 115 _('display with template'), _('TEMPLATE')),
116 116 ]
117 117
118 118 logopts = [
119 119 ('p', 'patch', None, _('show patch')),
120 120 ('g', 'git', None, _('use git extended diff format')),
121 121 ('l', 'limit', '',
122 122 _('limit number of changes displayed'), _('NUM')),
123 123 ('M', 'no-merges', None, _('do not show merges')),
124 124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 125 ('G', 'graph', None, _("show the revision DAG")),
126 126 ] + templateopts
127 127
128 128 diffopts = [
129 129 ('a', 'text', None, _('treat all files as text')),
130 130 ('g', 'git', None, _('use git extended diff format')),
131 131 ('', 'nodates', None, _('omit dates from diff headers'))
132 132 ]
133 133
134 134 diffwsopts = [
135 135 ('w', 'ignore-all-space', None,
136 136 _('ignore white space when comparing lines')),
137 137 ('b', 'ignore-space-change', None,
138 138 _('ignore changes in the amount of white space')),
139 139 ('B', 'ignore-blank-lines', None,
140 140 _('ignore changes whose lines are all blank')),
141 141 ]
142 142
143 143 diffopts2 = [
144 144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 145 ('p', 'show-function', None, _('show which function each change is in')),
146 146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 147 ] + diffwsopts + [
148 148 ('U', 'unified', '',
149 149 _('number of lines of context to show'), _('NUM')),
150 150 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 151 ]
152 152
153 153 mergetoolopts = [
154 154 ('t', 'tool', '', _('specify merge tool')),
155 155 ]
156 156
157 157 similarityopts = [
158 158 ('s', 'similarity', '',
159 159 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
160 160 ]
161 161
162 162 subrepoopts = [
163 163 ('S', 'subrepos', None,
164 164 _('recurse into subrepositories'))
165 165 ]
166 166
167 167 # Commands start here, listed alphabetically
168 168
169 169 @command('^add',
170 170 walkopts + subrepoopts + dryrunopts,
171 171 _('[OPTION]... [FILE]...'),
172 172 inferrepo=True)
173 173 def add(ui, repo, *pats, **opts):
174 174 """add the specified files on the next commit
175 175
176 176 Schedule files to be version controlled and added to the
177 177 repository.
178 178
179 179 The files will be added to the repository at the next commit. To
180 180 undo an add before that, see :hg:`forget`.
181 181
182 182 If no names are given, add all files to the repository.
183 183
184 184 .. container:: verbose
185 185
186 186 An example showing how new (unknown) files are added
187 187 automatically by :hg:`add`::
188 188
189 189 $ ls
190 190 foo.c
191 191 $ hg status
192 192 ? foo.c
193 193 $ hg add
194 194 adding foo.c
195 195 $ hg status
196 196 A foo.c
197 197
198 198 Returns 0 if all files are successfully added.
199 199 """
200 200
201 201 m = scmutil.match(repo[None], pats, opts)
202 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
203 opts.get('subrepos'), prefix="", explicitonly=False)
202 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
204 203 return rejected and 1 or 0
205 204
206 205 @command('addremove',
207 206 similarityopts + subrepoopts + walkopts + dryrunopts,
208 207 _('[OPTION]... [FILE]...'),
209 208 inferrepo=True)
210 209 def addremove(ui, repo, *pats, **opts):
211 210 """add all new files, delete all missing files
212 211
213 212 Add all new files and remove all missing files from the
214 213 repository.
215 214
216 215 New files are ignored if they match any of the patterns in
217 216 ``.hgignore``. As with add, these changes take effect at the next
218 217 commit.
219 218
220 219 Use the -s/--similarity option to detect renamed files. This
221 220 option takes a percentage between 0 (disabled) and 100 (files must
222 221 be identical) as its parameter. With a parameter greater than 0,
223 222 this compares every removed file with every added file and records
224 223 those similar enough as renames. Detecting renamed files this way
225 224 can be expensive. After using this option, :hg:`status -C` can be
226 225 used to check which files were identified as moved or renamed. If
227 226 not specified, -s/--similarity defaults to 100 and only renames of
228 227 identical files are detected.
229 228
230 229 Returns 0 if all files are successfully added.
231 230 """
232 231 try:
233 232 sim = float(opts.get('similarity') or 100)
234 233 except ValueError:
235 234 raise util.Abort(_('similarity must be a number'))
236 235 if sim < 0 or sim > 100:
237 236 raise util.Abort(_('similarity must be between 0 and 100'))
238 237 matcher = scmutil.match(repo[None], pats, opts)
239 238 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
240 239
241 240 @command('^annotate|blame',
242 241 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
243 242 ('', 'follow', None,
244 243 _('follow copies/renames and list the filename (DEPRECATED)')),
245 244 ('', 'no-follow', None, _("don't follow copies and renames")),
246 245 ('a', 'text', None, _('treat all files as text')),
247 246 ('u', 'user', None, _('list the author (long with -v)')),
248 247 ('f', 'file', None, _('list the filename')),
249 248 ('d', 'date', None, _('list the date (short with -q)')),
250 249 ('n', 'number', None, _('list the revision number (default)')),
251 250 ('c', 'changeset', None, _('list the changeset')),
252 251 ('l', 'line-number', None, _('show line number at the first appearance'))
253 252 ] + diffwsopts + walkopts + formatteropts,
254 253 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
255 254 inferrepo=True)
256 255 def annotate(ui, repo, *pats, **opts):
257 256 """show changeset information by line for each file
258 257
259 258 List changes in files, showing the revision id responsible for
260 259 each line
261 260
262 261 This command is useful for discovering when a change was made and
263 262 by whom.
264 263
265 264 Without the -a/--text option, annotate will avoid processing files
266 265 it detects as binary. With -a, annotate will annotate the file
267 266 anyway, although the results will probably be neither useful
268 267 nor desirable.
269 268
270 269 Returns 0 on success.
271 270 """
272 271 if not pats:
273 272 raise util.Abort(_('at least one filename or pattern is required'))
274 273
275 274 if opts.get('follow'):
276 275 # --follow is deprecated and now just an alias for -f/--file
277 276 # to mimic the behavior of Mercurial before version 1.5
278 277 opts['file'] = True
279 278
280 279 fm = ui.formatter('annotate', opts)
281 280 datefunc = ui.quiet and util.shortdate or util.datestr
282 281 hexfn = fm.hexfunc
283 282
284 283 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
285 284 ('number', ' ', lambda x: x[0].rev(), str),
286 285 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
287 286 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
288 287 ('file', ' ', lambda x: x[0].path(), str),
289 288 ('line_number', ':', lambda x: x[1], str),
290 289 ]
291 290 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
292 291
293 292 if (not opts.get('user') and not opts.get('changeset')
294 293 and not opts.get('date') and not opts.get('file')):
295 294 opts['number'] = True
296 295
297 296 linenumber = opts.get('line_number') is not None
298 297 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
299 298 raise util.Abort(_('at least one of -n/-c is required for -l'))
300 299
301 300 if fm:
302 301 def makefunc(get, fmt):
303 302 return get
304 303 else:
305 304 def makefunc(get, fmt):
306 305 return lambda x: fmt(get(x))
307 306 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
308 307 if opts.get(op)]
309 308 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
310 309 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
311 310 if opts.get(op))
312 311
313 312 def bad(x, y):
314 313 raise util.Abort("%s: %s" % (x, y))
315 314
316 315 ctx = scmutil.revsingle(repo, opts.get('rev'))
317 316 m = scmutil.match(ctx, pats, opts)
318 317 m.bad = bad
319 318 follow = not opts.get('no_follow')
320 319 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
321 320 whitespace=True)
322 321 for abs in ctx.walk(m):
323 322 fctx = ctx[abs]
324 323 if not opts.get('text') and util.binary(fctx.data()):
325 324 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
326 325 continue
327 326
328 327 lines = fctx.annotate(follow=follow, linenumber=linenumber,
329 328 diffopts=diffopts)
330 329 formats = []
331 330 pieces = []
332 331
333 332 for f, sep in funcmap:
334 333 l = [f(n) for n, dummy in lines]
335 334 if l:
336 335 if fm:
337 336 formats.append(['%s' for x in l])
338 337 else:
339 338 sizes = [encoding.colwidth(x) for x in l]
340 339 ml = max(sizes)
341 340 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
342 341 pieces.append(l)
343 342
344 343 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
345 344 fm.startitem()
346 345 fm.write(fields, "".join(f), *p)
347 346 fm.write('line', ": %s", l[1])
348 347
349 348 if lines and not lines[-1][1].endswith('\n'):
350 349 fm.plain('\n')
351 350
352 351 fm.end()
353 352
354 353 @command('archive',
355 354 [('', 'no-decode', None, _('do not pass files through decoders')),
356 355 ('p', 'prefix', '', _('directory prefix for files in archive'),
357 356 _('PREFIX')),
358 357 ('r', 'rev', '', _('revision to distribute'), _('REV')),
359 358 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
360 359 ] + subrepoopts + walkopts,
361 360 _('[OPTION]... DEST'))
362 361 def archive(ui, repo, dest, **opts):
363 362 '''create an unversioned archive of a repository revision
364 363
365 364 By default, the revision used is the parent of the working
366 365 directory; use -r/--rev to specify a different revision.
367 366
368 367 The archive type is automatically detected based on file
369 368 extension (or override using -t/--type).
370 369
371 370 .. container:: verbose
372 371
373 372 Examples:
374 373
375 374 - create a zip file containing the 1.0 release::
376 375
377 376 hg archive -r 1.0 project-1.0.zip
378 377
379 378 - create a tarball excluding .hg files::
380 379
381 380 hg archive project.tar.gz -X ".hg*"
382 381
383 382 Valid types are:
384 383
385 384 :``files``: a directory full of files (default)
386 385 :``tar``: tar archive, uncompressed
387 386 :``tbz2``: tar archive, compressed using bzip2
388 387 :``tgz``: tar archive, compressed using gzip
389 388 :``uzip``: zip archive, uncompressed
390 389 :``zip``: zip archive, compressed using deflate
391 390
392 391 The exact name of the destination archive or directory is given
393 392 using a format string; see :hg:`help export` for details.
394 393
395 394 Each member added to an archive file has a directory prefix
396 395 prepended. Use -p/--prefix to specify a format string for the
397 396 prefix. The default is the basename of the archive, with suffixes
398 397 removed.
399 398
400 399 Returns 0 on success.
401 400 '''
402 401
403 402 ctx = scmutil.revsingle(repo, opts.get('rev'))
404 403 if not ctx:
405 404 raise util.Abort(_('no working directory: please specify a revision'))
406 405 node = ctx.node()
407 406 dest = cmdutil.makefilename(repo, dest, node)
408 407 if os.path.realpath(dest) == repo.root:
409 408 raise util.Abort(_('repository root cannot be destination'))
410 409
411 410 kind = opts.get('type') or archival.guesskind(dest) or 'files'
412 411 prefix = opts.get('prefix')
413 412
414 413 if dest == '-':
415 414 if kind == 'files':
416 415 raise util.Abort(_('cannot archive plain files to stdout'))
417 416 dest = cmdutil.makefileobj(repo, dest)
418 417 if not prefix:
419 418 prefix = os.path.basename(repo.root) + '-%h'
420 419
421 420 prefix = cmdutil.makefilename(repo, prefix, node)
422 421 matchfn = scmutil.match(ctx, [], opts)
423 422 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
424 423 matchfn, prefix, subrepos=opts.get('subrepos'))
425 424
426 425 @command('backout',
427 426 [('', 'merge', None, _('merge with old dirstate parent after backout')),
428 427 ('', 'commit', None, _('commit if no conflicts were encountered')),
429 428 ('', 'parent', '',
430 429 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
431 430 ('r', 'rev', '', _('revision to backout'), _('REV')),
432 431 ('e', 'edit', False, _('invoke editor on commit messages')),
433 432 ] + mergetoolopts + walkopts + commitopts + commitopts2,
434 433 _('[OPTION]... [-r] REV'))
435 434 def backout(ui, repo, node=None, rev=None, commit=False, **opts):
436 435 '''reverse effect of earlier changeset
437 436
438 437 Prepare a new changeset with the effect of REV undone in the
439 438 current working directory.
440 439
441 440 If REV is the parent of the working directory, then this new changeset
442 441 is committed automatically. Otherwise, hg needs to merge the
443 442 changes and the merged result is left uncommitted.
444 443
445 444 .. note::
446 445
447 446 backout cannot be used to fix either an unwanted or
448 447 incorrect merge.
449 448
450 449 .. container:: verbose
451 450
452 451 By default, the pending changeset will have one parent,
453 452 maintaining a linear history. With --merge, the pending
454 453 changeset will instead have two parents: the old parent of the
455 454 working directory and a new child of REV that simply undoes REV.
456 455
457 456 Before version 1.7, the behavior without --merge was equivalent
458 457 to specifying --merge followed by :hg:`update --clean .` to
459 458 cancel the merge and leave the child of REV as a head to be
460 459 merged separately.
461 460
462 461 See :hg:`help dates` for a list of formats valid for -d/--date.
463 462
464 463 Returns 0 on success, 1 if nothing to backout or there are unresolved
465 464 files.
466 465 '''
467 466 if rev and node:
468 467 raise util.Abort(_("please specify just one revision"))
469 468
470 469 if not rev:
471 470 rev = node
472 471
473 472 if not rev:
474 473 raise util.Abort(_("please specify a revision to backout"))
475 474
476 475 date = opts.get('date')
477 476 if date:
478 477 opts['date'] = util.parsedate(date)
479 478
480 479 cmdutil.checkunfinished(repo)
481 480 cmdutil.bailifchanged(repo)
482 481 node = scmutil.revsingle(repo, rev).node()
483 482
484 483 op1, op2 = repo.dirstate.parents()
485 484 if not repo.changelog.isancestor(node, op1):
486 485 raise util.Abort(_('cannot backout change that is not an ancestor'))
487 486
488 487 p1, p2 = repo.changelog.parents(node)
489 488 if p1 == nullid:
490 489 raise util.Abort(_('cannot backout a change with no parents'))
491 490 if p2 != nullid:
492 491 if not opts.get('parent'):
493 492 raise util.Abort(_('cannot backout a merge changeset'))
494 493 p = repo.lookup(opts['parent'])
495 494 if p not in (p1, p2):
496 495 raise util.Abort(_('%s is not a parent of %s') %
497 496 (short(p), short(node)))
498 497 parent = p
499 498 else:
500 499 if opts.get('parent'):
501 500 raise util.Abort(_('cannot use --parent on non-merge changeset'))
502 501 parent = p1
503 502
504 503 # the backout should appear on the same branch
505 504 wlock = repo.wlock()
506 505 try:
507 506 branch = repo.dirstate.branch()
508 507 bheads = repo.branchheads(branch)
509 508 rctx = scmutil.revsingle(repo, hex(parent))
510 509 if not opts.get('merge') and op1 != node:
511 510 try:
512 511 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
513 512 'backout')
514 513 repo.dirstate.beginparentchange()
515 514 stats = mergemod.update(repo, parent, True, True, False,
516 515 node, False)
517 516 repo.setparents(op1, op2)
518 517 repo.dirstate.endparentchange()
519 518 hg._showstats(repo, stats)
520 519 if stats[3]:
521 520 repo.ui.status(_("use 'hg resolve' to retry unresolved "
522 521 "file merges\n"))
523 522 return 1
524 523 elif not commit:
525 524 msg = _("changeset %s backed out, "
526 525 "don't forget to commit.\n")
527 526 ui.status(msg % short(node))
528 527 return 0
529 528 finally:
530 529 ui.setconfig('ui', 'forcemerge', '', '')
531 530 else:
532 531 hg.clean(repo, node, show_stats=False)
533 532 repo.dirstate.setbranch(branch)
534 533 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
535 534
536 535
537 536 def commitfunc(ui, repo, message, match, opts):
538 537 editform = 'backout'
539 538 e = cmdutil.getcommiteditor(editform=editform, **opts)
540 539 if not message:
541 540 # we don't translate commit messages
542 541 message = "Backed out changeset %s" % short(node)
543 542 e = cmdutil.getcommiteditor(edit=True, editform=editform)
544 543 return repo.commit(message, opts.get('user'), opts.get('date'),
545 544 match, editor=e)
546 545 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
547 546 if not newnode:
548 547 ui.status(_("nothing changed\n"))
549 548 return 1
550 549 cmdutil.commitstatus(repo, newnode, branch, bheads)
551 550
552 551 def nice(node):
553 552 return '%d:%s' % (repo.changelog.rev(node), short(node))
554 553 ui.status(_('changeset %s backs out changeset %s\n') %
555 554 (nice(repo.changelog.tip()), nice(node)))
556 555 if opts.get('merge') and op1 != node:
557 556 hg.clean(repo, op1, show_stats=False)
558 557 ui.status(_('merging with changeset %s\n')
559 558 % nice(repo.changelog.tip()))
560 559 try:
561 560 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
562 561 'backout')
563 562 return hg.merge(repo, hex(repo.changelog.tip()))
564 563 finally:
565 564 ui.setconfig('ui', 'forcemerge', '', '')
566 565 finally:
567 566 wlock.release()
568 567 return 0
569 568
570 569 @command('bisect',
571 570 [('r', 'reset', False, _('reset bisect state')),
572 571 ('g', 'good', False, _('mark changeset good')),
573 572 ('b', 'bad', False, _('mark changeset bad')),
574 573 ('s', 'skip', False, _('skip testing changeset')),
575 574 ('e', 'extend', False, _('extend the bisect range')),
576 575 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
577 576 ('U', 'noupdate', False, _('do not update to target'))],
578 577 _("[-gbsr] [-U] [-c CMD] [REV]"))
579 578 def bisect(ui, repo, rev=None, extra=None, command=None,
580 579 reset=None, good=None, bad=None, skip=None, extend=None,
581 580 noupdate=None):
582 581 """subdivision search of changesets
583 582
584 583 This command helps to find changesets which introduce problems. To
585 584 use, mark the earliest changeset you know exhibits the problem as
586 585 bad, then mark the latest changeset which is free from the problem
587 586 as good. Bisect will update your working directory to a revision
588 587 for testing (unless the -U/--noupdate option is specified). Once
589 588 you have performed tests, mark the working directory as good or
590 589 bad, and bisect will either update to another candidate changeset
591 590 or announce that it has found the bad revision.
592 591
593 592 As a shortcut, you can also use the revision argument to mark a
594 593 revision as good or bad without checking it out first.
595 594
596 595 If you supply a command, it will be used for automatic bisection.
597 596 The environment variable HG_NODE will contain the ID of the
598 597 changeset being tested. The exit status of the command will be
599 598 used to mark revisions as good or bad: status 0 means good, 125
600 599 means to skip the revision, 127 (command not found) will abort the
601 600 bisection, and any other non-zero exit status means the revision
602 601 is bad.
603 602
604 603 .. container:: verbose
605 604
606 605 Some examples:
607 606
608 607 - start a bisection with known bad revision 34, and good revision 12::
609 608
610 609 hg bisect --bad 34
611 610 hg bisect --good 12
612 611
613 612 - advance the current bisection by marking current revision as good or
614 613 bad::
615 614
616 615 hg bisect --good
617 616 hg bisect --bad
618 617
619 618 - mark the current revision, or a known revision, to be skipped (e.g. if
620 619 that revision is not usable because of another issue)::
621 620
622 621 hg bisect --skip
623 622 hg bisect --skip 23
624 623
625 624 - skip all revisions that do not touch directories ``foo`` or ``bar``::
626 625
627 626 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
628 627
629 628 - forget the current bisection::
630 629
631 630 hg bisect --reset
632 631
633 632 - use 'make && make tests' to automatically find the first broken
634 633 revision::
635 634
636 635 hg bisect --reset
637 636 hg bisect --bad 34
638 637 hg bisect --good 12
639 638 hg bisect --command "make && make tests"
640 639
641 640 - see all changesets whose states are already known in the current
642 641 bisection::
643 642
644 643 hg log -r "bisect(pruned)"
645 644
646 645 - see the changeset currently being bisected (especially useful
647 646 if running with -U/--noupdate)::
648 647
649 648 hg log -r "bisect(current)"
650 649
651 650 - see all changesets that took part in the current bisection::
652 651
653 652 hg log -r "bisect(range)"
654 653
655 654 - you can even get a nice graph::
656 655
657 656 hg log --graph -r "bisect(range)"
658 657
659 658 See :hg:`help revsets` for more about the `bisect()` keyword.
660 659
661 660 Returns 0 on success.
662 661 """
663 662 def extendbisectrange(nodes, good):
664 663 # bisect is incomplete when it ends on a merge node and
665 664 # one of the parent was not checked.
666 665 parents = repo[nodes[0]].parents()
667 666 if len(parents) > 1:
668 667 side = good and state['bad'] or state['good']
669 668 num = len(set(i.node() for i in parents) & set(side))
670 669 if num == 1:
671 670 return parents[0].ancestor(parents[1])
672 671 return None
673 672
674 673 def print_result(nodes, good):
675 674 displayer = cmdutil.show_changeset(ui, repo, {})
676 675 if len(nodes) == 1:
677 676 # narrowed it down to a single revision
678 677 if good:
679 678 ui.write(_("The first good revision is:\n"))
680 679 else:
681 680 ui.write(_("The first bad revision is:\n"))
682 681 displayer.show(repo[nodes[0]])
683 682 extendnode = extendbisectrange(nodes, good)
684 683 if extendnode is not None:
685 684 ui.write(_('Not all ancestors of this changeset have been'
686 685 ' checked.\nUse bisect --extend to continue the '
687 686 'bisection from\nthe common ancestor, %s.\n')
688 687 % extendnode)
689 688 else:
690 689 # multiple possible revisions
691 690 if good:
692 691 ui.write(_("Due to skipped revisions, the first "
693 692 "good revision could be any of:\n"))
694 693 else:
695 694 ui.write(_("Due to skipped revisions, the first "
696 695 "bad revision could be any of:\n"))
697 696 for n in nodes:
698 697 displayer.show(repo[n])
699 698 displayer.close()
700 699
701 700 def check_state(state, interactive=True):
702 701 if not state['good'] or not state['bad']:
703 702 if (good or bad or skip or reset) and interactive:
704 703 return
705 704 if not state['good']:
706 705 raise util.Abort(_('cannot bisect (no known good revisions)'))
707 706 else:
708 707 raise util.Abort(_('cannot bisect (no known bad revisions)'))
709 708 return True
710 709
711 710 # backward compatibility
712 711 if rev in "good bad reset init".split():
713 712 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
714 713 cmd, rev, extra = rev, extra, None
715 714 if cmd == "good":
716 715 good = True
717 716 elif cmd == "bad":
718 717 bad = True
719 718 else:
720 719 reset = True
721 720 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
722 721 raise util.Abort(_('incompatible arguments'))
723 722
724 723 cmdutil.checkunfinished(repo)
725 724
726 725 if reset:
727 726 p = repo.join("bisect.state")
728 727 if os.path.exists(p):
729 728 os.unlink(p)
730 729 return
731 730
732 731 state = hbisect.load_state(repo)
733 732
734 733 if command:
735 734 changesets = 1
736 735 if noupdate:
737 736 try:
738 737 node = state['current'][0]
739 738 except LookupError:
740 739 raise util.Abort(_('current bisect revision is unknown - '
741 740 'start a new bisect to fix'))
742 741 else:
743 742 node, p2 = repo.dirstate.parents()
744 743 if p2 != nullid:
745 744 raise util.Abort(_('current bisect revision is a merge'))
746 745 try:
747 746 while changesets:
748 747 # update state
749 748 state['current'] = [node]
750 749 hbisect.save_state(repo, state)
751 750 status = ui.system(command, environ={'HG_NODE': hex(node)})
752 751 if status == 125:
753 752 transition = "skip"
754 753 elif status == 0:
755 754 transition = "good"
756 755 # status < 0 means process was killed
757 756 elif status == 127:
758 757 raise util.Abort(_("failed to execute %s") % command)
759 758 elif status < 0:
760 759 raise util.Abort(_("%s killed") % command)
761 760 else:
762 761 transition = "bad"
763 762 ctx = scmutil.revsingle(repo, rev, node)
764 763 rev = None # clear for future iterations
765 764 state[transition].append(ctx.node())
766 765 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
767 766 check_state(state, interactive=False)
768 767 # bisect
769 768 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
770 769 # update to next check
771 770 node = nodes[0]
772 771 if not noupdate:
773 772 cmdutil.bailifchanged(repo)
774 773 hg.clean(repo, node, show_stats=False)
775 774 finally:
776 775 state['current'] = [node]
777 776 hbisect.save_state(repo, state)
778 777 print_result(nodes, bgood)
779 778 return
780 779
781 780 # update state
782 781
783 782 if rev:
784 783 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
785 784 else:
786 785 nodes = [repo.lookup('.')]
787 786
788 787 if good or bad or skip:
789 788 if good:
790 789 state['good'] += nodes
791 790 elif bad:
792 791 state['bad'] += nodes
793 792 elif skip:
794 793 state['skip'] += nodes
795 794 hbisect.save_state(repo, state)
796 795
797 796 if not check_state(state):
798 797 return
799 798
800 799 # actually bisect
801 800 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
802 801 if extend:
803 802 if not changesets:
804 803 extendnode = extendbisectrange(nodes, good)
805 804 if extendnode is not None:
806 805 ui.write(_("Extending search to changeset %d:%s\n")
807 806 % (extendnode.rev(), extendnode))
808 807 state['current'] = [extendnode.node()]
809 808 hbisect.save_state(repo, state)
810 809 if noupdate:
811 810 return
812 811 cmdutil.bailifchanged(repo)
813 812 return hg.clean(repo, extendnode.node())
814 813 raise util.Abort(_("nothing to extend"))
815 814
816 815 if changesets == 0:
817 816 print_result(nodes, good)
818 817 else:
819 818 assert len(nodes) == 1 # only a single node can be tested next
820 819 node = nodes[0]
821 820 # compute the approximate number of remaining tests
822 821 tests, size = 0, 2
823 822 while size <= changesets:
824 823 tests, size = tests + 1, size * 2
825 824 rev = repo.changelog.rev(node)
826 825 ui.write(_("Testing changeset %d:%s "
827 826 "(%d changesets remaining, ~%d tests)\n")
828 827 % (rev, short(node), changesets, tests))
829 828 state['current'] = [node]
830 829 hbisect.save_state(repo, state)
831 830 if not noupdate:
832 831 cmdutil.bailifchanged(repo)
833 832 return hg.clean(repo, node)
834 833
835 834 @command('bookmarks|bookmark',
836 835 [('f', 'force', False, _('force')),
837 836 ('r', 'rev', '', _('revision'), _('REV')),
838 837 ('d', 'delete', False, _('delete a given bookmark')),
839 838 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
840 839 ('i', 'inactive', False, _('mark a bookmark inactive')),
841 840 ] + formatteropts,
842 841 _('hg bookmarks [OPTIONS]... [NAME]...'))
843 842 def bookmark(ui, repo, *names, **opts):
844 843 '''create a new bookmark or list existing bookmarks
845 844
846 845 Bookmarks are labels on changesets to help track lines of development.
847 846 Bookmarks are unversioned and can be moved, renamed and deleted.
848 847 Deleting or moving a bookmark has no effect on the associated changesets.
849 848
850 849 Creating or updating to a bookmark causes it to be marked as 'active'.
851 850 The active bookmark is indicated with a '*'.
852 851 When a commit is made, the active bookmark will advance to the new commit.
853 852 A plain :hg:`update` will also advance an active bookmark, if possible.
854 853 Updating away from a bookmark will cause it to be deactivated.
855 854
856 855 Bookmarks can be pushed and pulled between repositories (see
857 856 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
858 857 diverged, a new 'divergent bookmark' of the form 'name@path' will
859 858 be created. Using :hg:`merge` will resolve the divergence.
860 859
861 860 A bookmark named '@' has the special property that :hg:`clone` will
862 861 check it out by default if it exists.
863 862
864 863 .. container:: verbose
865 864
866 865 Examples:
867 866
868 867 - create an active bookmark for a new line of development::
869 868
870 869 hg book new-feature
871 870
872 871 - create an inactive bookmark as a place marker::
873 872
874 873 hg book -i reviewed
875 874
876 875 - create an inactive bookmark on another changeset::
877 876
878 877 hg book -r .^ tested
879 878
880 879 - move the '@' bookmark from another branch::
881 880
882 881 hg book -f @
883 882 '''
884 883 force = opts.get('force')
885 884 rev = opts.get('rev')
886 885 delete = opts.get('delete')
887 886 rename = opts.get('rename')
888 887 inactive = opts.get('inactive')
889 888
890 889 def checkformat(mark):
891 890 mark = mark.strip()
892 891 if not mark:
893 892 raise util.Abort(_("bookmark names cannot consist entirely of "
894 893 "whitespace"))
895 894 scmutil.checknewlabel(repo, mark, 'bookmark')
896 895 return mark
897 896
898 897 def checkconflict(repo, mark, cur, force=False, target=None):
899 898 if mark in marks and not force:
900 899 if target:
901 900 if marks[mark] == target and target == cur:
902 901 # re-activating a bookmark
903 902 return
904 903 anc = repo.changelog.ancestors([repo[target].rev()])
905 904 bmctx = repo[marks[mark]]
906 905 divs = [repo[b].node() for b in marks
907 906 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
908 907
909 908 # allow resolving a single divergent bookmark even if moving
910 909 # the bookmark across branches when a revision is specified
911 910 # that contains a divergent bookmark
912 911 if bmctx.rev() not in anc and target in divs:
913 912 bookmarks.deletedivergent(repo, [target], mark)
914 913 return
915 914
916 915 deletefrom = [b for b in divs
917 916 if repo[b].rev() in anc or b == target]
918 917 bookmarks.deletedivergent(repo, deletefrom, mark)
919 918 if bookmarks.validdest(repo, bmctx, repo[target]):
920 919 ui.status(_("moving bookmark '%s' forward from %s\n") %
921 920 (mark, short(bmctx.node())))
922 921 return
923 922 raise util.Abort(_("bookmark '%s' already exists "
924 923 "(use -f to force)") % mark)
925 924 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
926 925 and not force):
927 926 raise util.Abort(
928 927 _("a bookmark cannot have the name of an existing branch"))
929 928
930 929 if delete and rename:
931 930 raise util.Abort(_("--delete and --rename are incompatible"))
932 931 if delete and rev:
933 932 raise util.Abort(_("--rev is incompatible with --delete"))
934 933 if rename and rev:
935 934 raise util.Abort(_("--rev is incompatible with --rename"))
936 935 if not names and (delete or rev):
937 936 raise util.Abort(_("bookmark name required"))
938 937
939 938 if delete or rename or names or inactive:
940 939 wlock = repo.wlock()
941 940 try:
942 941 cur = repo.changectx('.').node()
943 942 marks = repo._bookmarks
944 943 if delete:
945 944 for mark in names:
946 945 if mark not in marks:
947 946 raise util.Abort(_("bookmark '%s' does not exist") %
948 947 mark)
949 948 if mark == repo._bookmarkcurrent:
950 949 bookmarks.unsetcurrent(repo)
951 950 del marks[mark]
952 951 marks.write()
953 952
954 953 elif rename:
955 954 if not names:
956 955 raise util.Abort(_("new bookmark name required"))
957 956 elif len(names) > 1:
958 957 raise util.Abort(_("only one new bookmark name allowed"))
959 958 mark = checkformat(names[0])
960 959 if rename not in marks:
961 960 raise util.Abort(_("bookmark '%s' does not exist") % rename)
962 961 checkconflict(repo, mark, cur, force)
963 962 marks[mark] = marks[rename]
964 963 if repo._bookmarkcurrent == rename and not inactive:
965 964 bookmarks.setcurrent(repo, mark)
966 965 del marks[rename]
967 966 marks.write()
968 967
969 968 elif names:
970 969 newact = None
971 970 for mark in names:
972 971 mark = checkformat(mark)
973 972 if newact is None:
974 973 newact = mark
975 974 if inactive and mark == repo._bookmarkcurrent:
976 975 bookmarks.unsetcurrent(repo)
977 976 return
978 977 tgt = cur
979 978 if rev:
980 979 tgt = scmutil.revsingle(repo, rev).node()
981 980 checkconflict(repo, mark, cur, force, tgt)
982 981 marks[mark] = tgt
983 982 if not inactive and cur == marks[newact] and not rev:
984 983 bookmarks.setcurrent(repo, newact)
985 984 elif cur != tgt and newact == repo._bookmarkcurrent:
986 985 bookmarks.unsetcurrent(repo)
987 986 marks.write()
988 987
989 988 elif inactive:
990 989 if len(marks) == 0:
991 990 ui.status(_("no bookmarks set\n"))
992 991 elif not repo._bookmarkcurrent:
993 992 ui.status(_("no active bookmark\n"))
994 993 else:
995 994 bookmarks.unsetcurrent(repo)
996 995 finally:
997 996 wlock.release()
998 997 else: # show bookmarks
999 998 fm = ui.formatter('bookmarks', opts)
1000 999 hexfn = fm.hexfunc
1001 1000 marks = repo._bookmarks
1002 1001 if len(marks) == 0 and not fm:
1003 1002 ui.status(_("no bookmarks set\n"))
1004 1003 for bmark, n in sorted(marks.iteritems()):
1005 1004 current = repo._bookmarkcurrent
1006 1005 if bmark == current:
1007 1006 prefix, label = '*', 'bookmarks.current'
1008 1007 else:
1009 1008 prefix, label = ' ', ''
1010 1009
1011 1010 fm.startitem()
1012 1011 if not ui.quiet:
1013 1012 fm.plain(' %s ' % prefix, label=label)
1014 1013 fm.write('bookmark', '%s', bmark, label=label)
1015 1014 pad = " " * (25 - encoding.colwidth(bmark))
1016 1015 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1017 1016 repo.changelog.rev(n), hexfn(n), label=label)
1018 1017 fm.data(active=(bmark == current))
1019 1018 fm.plain('\n')
1020 1019 fm.end()
1021 1020
1022 1021 @command('branch',
1023 1022 [('f', 'force', None,
1024 1023 _('set branch name even if it shadows an existing branch')),
1025 1024 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1026 1025 _('[-fC] [NAME]'))
1027 1026 def branch(ui, repo, label=None, **opts):
1028 1027 """set or show the current branch name
1029 1028
1030 1029 .. note::
1031 1030
1032 1031 Branch names are permanent and global. Use :hg:`bookmark` to create a
1033 1032 light-weight bookmark instead. See :hg:`help glossary` for more
1034 1033 information about named branches and bookmarks.
1035 1034
1036 1035 With no argument, show the current branch name. With one argument,
1037 1036 set the working directory branch name (the branch will not exist
1038 1037 in the repository until the next commit). Standard practice
1039 1038 recommends that primary development take place on the 'default'
1040 1039 branch.
1041 1040
1042 1041 Unless -f/--force is specified, branch will not let you set a
1043 1042 branch name that already exists.
1044 1043
1045 1044 Use -C/--clean to reset the working directory branch to that of
1046 1045 the parent of the working directory, negating a previous branch
1047 1046 change.
1048 1047
1049 1048 Use the command :hg:`update` to switch to an existing branch. Use
1050 1049 :hg:`commit --close-branch` to mark this branch as closed.
1051 1050
1052 1051 Returns 0 on success.
1053 1052 """
1054 1053 if label:
1055 1054 label = label.strip()
1056 1055
1057 1056 if not opts.get('clean') and not label:
1058 1057 ui.write("%s\n" % repo.dirstate.branch())
1059 1058 return
1060 1059
1061 1060 wlock = repo.wlock()
1062 1061 try:
1063 1062 if opts.get('clean'):
1064 1063 label = repo[None].p1().branch()
1065 1064 repo.dirstate.setbranch(label)
1066 1065 ui.status(_('reset working directory to branch %s\n') % label)
1067 1066 elif label:
1068 1067 if not opts.get('force') and label in repo.branchmap():
1069 1068 if label not in [p.branch() for p in repo.parents()]:
1070 1069 raise util.Abort(_('a branch of the same name already'
1071 1070 ' exists'),
1072 1071 # i18n: "it" refers to an existing branch
1073 1072 hint=_("use 'hg update' to switch to it"))
1074 1073 scmutil.checknewlabel(repo, label, 'branch')
1075 1074 repo.dirstate.setbranch(label)
1076 1075 ui.status(_('marked working directory as branch %s\n') % label)
1077 1076 ui.status(_('(branches are permanent and global, '
1078 1077 'did you want a bookmark?)\n'))
1079 1078 finally:
1080 1079 wlock.release()
1081 1080
1082 1081 @command('branches',
1083 1082 [('a', 'active', False,
1084 1083 _('show only branches that have unmerged heads (DEPRECATED)')),
1085 1084 ('c', 'closed', False, _('show normal and closed branches')),
1086 1085 ] + formatteropts,
1087 1086 _('[-ac]'))
1088 1087 def branches(ui, repo, active=False, closed=False, **opts):
1089 1088 """list repository named branches
1090 1089
1091 1090 List the repository's named branches, indicating which ones are
1092 1091 inactive. If -c/--closed is specified, also list branches which have
1093 1092 been marked closed (see :hg:`commit --close-branch`).
1094 1093
1095 1094 Use the command :hg:`update` to switch to an existing branch.
1096 1095
1097 1096 Returns 0.
1098 1097 """
1099 1098
1100 1099 fm = ui.formatter('branches', opts)
1101 1100 hexfunc = fm.hexfunc
1102 1101
1103 1102 allheads = set(repo.heads())
1104 1103 branches = []
1105 1104 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1106 1105 isactive = not isclosed and bool(set(heads) & allheads)
1107 1106 branches.append((tag, repo[tip], isactive, not isclosed))
1108 1107 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1109 1108 reverse=True)
1110 1109
1111 1110 for tag, ctx, isactive, isopen in branches:
1112 1111 if active and not isactive:
1113 1112 continue
1114 1113 if isactive:
1115 1114 label = 'branches.active'
1116 1115 notice = ''
1117 1116 elif not isopen:
1118 1117 if not closed:
1119 1118 continue
1120 1119 label = 'branches.closed'
1121 1120 notice = _(' (closed)')
1122 1121 else:
1123 1122 label = 'branches.inactive'
1124 1123 notice = _(' (inactive)')
1125 1124 current = (tag == repo.dirstate.branch())
1126 1125 if current:
1127 1126 label = 'branches.current'
1128 1127
1129 1128 fm.startitem()
1130 1129 fm.write('branch', '%s', tag, label=label)
1131 1130 rev = ctx.rev()
1132 1131 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1133 1132 fmt = ' ' * padsize + ' %d:%s'
1134 1133 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1135 1134 label='log.changeset changeset.%s' % ctx.phasestr())
1136 1135 fm.data(active=isactive, closed=not isopen, current=current)
1137 1136 if not ui.quiet:
1138 1137 fm.plain(notice)
1139 1138 fm.plain('\n')
1140 1139 fm.end()
1141 1140
1142 1141 @command('bundle',
1143 1142 [('f', 'force', None, _('run even when the destination is unrelated')),
1144 1143 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1145 1144 _('REV')),
1146 1145 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1147 1146 _('BRANCH')),
1148 1147 ('', 'base', [],
1149 1148 _('a base changeset assumed to be available at the destination'),
1150 1149 _('REV')),
1151 1150 ('a', 'all', None, _('bundle all changesets in the repository')),
1152 1151 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1153 1152 ] + remoteopts,
1154 1153 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1155 1154 def bundle(ui, repo, fname, dest=None, **opts):
1156 1155 """create a changegroup file
1157 1156
1158 1157 Generate a compressed changegroup file collecting changesets not
1159 1158 known to be in another repository.
1160 1159
1161 1160 If you omit the destination repository, then hg assumes the
1162 1161 destination will have all the nodes you specify with --base
1163 1162 parameters. To create a bundle containing all changesets, use
1164 1163 -a/--all (or --base null).
1165 1164
1166 1165 You can change compression method with the -t/--type option.
1167 1166 The available compression methods are: none, bzip2, and
1168 1167 gzip (by default, bundles are compressed using bzip2).
1169 1168
1170 1169 The bundle file can then be transferred using conventional means
1171 1170 and applied to another repository with the unbundle or pull
1172 1171 command. This is useful when direct push and pull are not
1173 1172 available or when exporting an entire repository is undesirable.
1174 1173
1175 1174 Applying bundles preserves all changeset contents including
1176 1175 permissions, copy/rename information, and revision history.
1177 1176
1178 1177 Returns 0 on success, 1 if no changes found.
1179 1178 """
1180 1179 revs = None
1181 1180 if 'rev' in opts:
1182 1181 revs = scmutil.revrange(repo, opts['rev'])
1183 1182
1184 1183 bundletype = opts.get('type', 'bzip2').lower()
1185 1184 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1186 1185 bundletype = btypes.get(bundletype)
1187 1186 if bundletype not in changegroup.bundletypes:
1188 1187 raise util.Abort(_('unknown bundle type specified with --type'))
1189 1188
1190 1189 if opts.get('all'):
1191 1190 base = ['null']
1192 1191 else:
1193 1192 base = scmutil.revrange(repo, opts.get('base'))
1194 1193 # TODO: get desired bundlecaps from command line.
1195 1194 bundlecaps = None
1196 1195 if base:
1197 1196 if dest:
1198 1197 raise util.Abort(_("--base is incompatible with specifying "
1199 1198 "a destination"))
1200 1199 common = [repo.lookup(rev) for rev in base]
1201 1200 heads = revs and map(repo.lookup, revs) or revs
1202 1201 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1203 1202 common=common, bundlecaps=bundlecaps)
1204 1203 outgoing = None
1205 1204 else:
1206 1205 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1207 1206 dest, branches = hg.parseurl(dest, opts.get('branch'))
1208 1207 other = hg.peer(repo, opts, dest)
1209 1208 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1210 1209 heads = revs and map(repo.lookup, revs) or revs
1211 1210 outgoing = discovery.findcommonoutgoing(repo, other,
1212 1211 onlyheads=heads,
1213 1212 force=opts.get('force'),
1214 1213 portable=True)
1215 1214 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1216 1215 bundlecaps)
1217 1216 if not cg:
1218 1217 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1219 1218 return 1
1220 1219
1221 1220 changegroup.writebundle(cg, fname, bundletype)
1222 1221
1223 1222 @command('cat',
1224 1223 [('o', 'output', '',
1225 1224 _('print output to file with formatted name'), _('FORMAT')),
1226 1225 ('r', 'rev', '', _('print the given revision'), _('REV')),
1227 1226 ('', 'decode', None, _('apply any matching decode filter')),
1228 1227 ] + walkopts,
1229 1228 _('[OPTION]... FILE...'),
1230 1229 inferrepo=True)
1231 1230 def cat(ui, repo, file1, *pats, **opts):
1232 1231 """output the current or given revision of files
1233 1232
1234 1233 Print the specified files as they were at the given revision. If
1235 1234 no revision is given, the parent of the working directory is used.
1236 1235
1237 1236 Output may be to a file, in which case the name of the file is
1238 1237 given using a format string. The formatting rules as follows:
1239 1238
1240 1239 :``%%``: literal "%" character
1241 1240 :``%s``: basename of file being printed
1242 1241 :``%d``: dirname of file being printed, or '.' if in repository root
1243 1242 :``%p``: root-relative path name of file being printed
1244 1243 :``%H``: changeset hash (40 hexadecimal digits)
1245 1244 :``%R``: changeset revision number
1246 1245 :``%h``: short-form changeset hash (12 hexadecimal digits)
1247 1246 :``%r``: zero-padded changeset revision number
1248 1247 :``%b``: basename of the exporting repository
1249 1248
1250 1249 Returns 0 on success.
1251 1250 """
1252 1251 ctx = scmutil.revsingle(repo, opts.get('rev'))
1253 1252 m = scmutil.match(ctx, (file1,) + pats, opts)
1254 1253
1255 1254 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1256 1255
1257 1256 @command('^clone',
1258 1257 [('U', 'noupdate', None,
1259 1258 _('the clone will include an empty working copy (only a repository)')),
1260 1259 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1261 1260 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1262 1261 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1263 1262 ('', 'pull', None, _('use pull protocol to copy metadata')),
1264 1263 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1265 1264 ] + remoteopts,
1266 1265 _('[OPTION]... SOURCE [DEST]'),
1267 1266 norepo=True)
1268 1267 def clone(ui, source, dest=None, **opts):
1269 1268 """make a copy of an existing repository
1270 1269
1271 1270 Create a copy of an existing repository in a new directory.
1272 1271
1273 1272 If no destination directory name is specified, it defaults to the
1274 1273 basename of the source.
1275 1274
1276 1275 The location of the source is added to the new repository's
1277 1276 ``.hg/hgrc`` file, as the default to be used for future pulls.
1278 1277
1279 1278 Only local paths and ``ssh://`` URLs are supported as
1280 1279 destinations. For ``ssh://`` destinations, no working directory or
1281 1280 ``.hg/hgrc`` will be created on the remote side.
1282 1281
1283 1282 To pull only a subset of changesets, specify one or more revisions
1284 1283 identifiers with -r/--rev or branches with -b/--branch. The
1285 1284 resulting clone will contain only the specified changesets and
1286 1285 their ancestors. These options (or 'clone src#rev dest') imply
1287 1286 --pull, even for local source repositories. Note that specifying a
1288 1287 tag will include the tagged changeset but not the changeset
1289 1288 containing the tag.
1290 1289
1291 1290 If the source repository has a bookmark called '@' set, that
1292 1291 revision will be checked out in the new repository by default.
1293 1292
1294 1293 To check out a particular version, use -u/--update, or
1295 1294 -U/--noupdate to create a clone with no working directory.
1296 1295
1297 1296 .. container:: verbose
1298 1297
1299 1298 For efficiency, hardlinks are used for cloning whenever the
1300 1299 source and destination are on the same filesystem (note this
1301 1300 applies only to the repository data, not to the working
1302 1301 directory). Some filesystems, such as AFS, implement hardlinking
1303 1302 incorrectly, but do not report errors. In these cases, use the
1304 1303 --pull option to avoid hardlinking.
1305 1304
1306 1305 In some cases, you can clone repositories and the working
1307 1306 directory using full hardlinks with ::
1308 1307
1309 1308 $ cp -al REPO REPOCLONE
1310 1309
1311 1310 This is the fastest way to clone, but it is not always safe. The
1312 1311 operation is not atomic (making sure REPO is not modified during
1313 1312 the operation is up to you) and you have to make sure your
1314 1313 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1315 1314 so). Also, this is not compatible with certain extensions that
1316 1315 place their metadata under the .hg directory, such as mq.
1317 1316
1318 1317 Mercurial will update the working directory to the first applicable
1319 1318 revision from this list:
1320 1319
1321 1320 a) null if -U or the source repository has no changesets
1322 1321 b) if -u . and the source repository is local, the first parent of
1323 1322 the source repository's working directory
1324 1323 c) the changeset specified with -u (if a branch name, this means the
1325 1324 latest head of that branch)
1326 1325 d) the changeset specified with -r
1327 1326 e) the tipmost head specified with -b
1328 1327 f) the tipmost head specified with the url#branch source syntax
1329 1328 g) the revision marked with the '@' bookmark, if present
1330 1329 h) the tipmost head of the default branch
1331 1330 i) tip
1332 1331
1333 1332 Examples:
1334 1333
1335 1334 - clone a remote repository to a new directory named hg/::
1336 1335
1337 1336 hg clone http://selenic.com/hg
1338 1337
1339 1338 - create a lightweight local clone::
1340 1339
1341 1340 hg clone project/ project-feature/
1342 1341
1343 1342 - clone from an absolute path on an ssh server (note double-slash)::
1344 1343
1345 1344 hg clone ssh://user@server//home/projects/alpha/
1346 1345
1347 1346 - do a high-speed clone over a LAN while checking out a
1348 1347 specified version::
1349 1348
1350 1349 hg clone --uncompressed http://server/repo -u 1.5
1351 1350
1352 1351 - create a repository without changesets after a particular revision::
1353 1352
1354 1353 hg clone -r 04e544 experimental/ good/
1355 1354
1356 1355 - clone (and track) a particular named branch::
1357 1356
1358 1357 hg clone http://selenic.com/hg#stable
1359 1358
1360 1359 See :hg:`help urls` for details on specifying URLs.
1361 1360
1362 1361 Returns 0 on success.
1363 1362 """
1364 1363 if opts.get('noupdate') and opts.get('updaterev'):
1365 1364 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1366 1365
1367 1366 r = hg.clone(ui, opts, source, dest,
1368 1367 pull=opts.get('pull'),
1369 1368 stream=opts.get('uncompressed'),
1370 1369 rev=opts.get('rev'),
1371 1370 update=opts.get('updaterev') or not opts.get('noupdate'),
1372 1371 branch=opts.get('branch'))
1373 1372
1374 1373 return r is None
1375 1374
1376 1375 @command('^commit|ci',
1377 1376 [('A', 'addremove', None,
1378 1377 _('mark new/missing files as added/removed before committing')),
1379 1378 ('', 'close-branch', None,
1380 1379 _('mark a branch as closed, hiding it from the branch list')),
1381 1380 ('', 'amend', None, _('amend the parent of the working dir')),
1382 1381 ('s', 'secret', None, _('use the secret phase for committing')),
1383 1382 ('e', 'edit', None, _('invoke editor on commit messages')),
1384 1383 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1385 1384 _('[OPTION]... [FILE]...'),
1386 1385 inferrepo=True)
1387 1386 def commit(ui, repo, *pats, **opts):
1388 1387 """commit the specified files or all outstanding changes
1389 1388
1390 1389 Commit changes to the given files into the repository. Unlike a
1391 1390 centralized SCM, this operation is a local operation. See
1392 1391 :hg:`push` for a way to actively distribute your changes.
1393 1392
1394 1393 If a list of files is omitted, all changes reported by :hg:`status`
1395 1394 will be committed.
1396 1395
1397 1396 If you are committing the result of a merge, do not provide any
1398 1397 filenames or -I/-X filters.
1399 1398
1400 1399 If no commit message is specified, Mercurial starts your
1401 1400 configured editor where you can enter a message. In case your
1402 1401 commit fails, you will find a backup of your message in
1403 1402 ``.hg/last-message.txt``.
1404 1403
1405 1404 The --amend flag can be used to amend the parent of the
1406 1405 working directory with a new commit that contains the changes
1407 1406 in the parent in addition to those currently reported by :hg:`status`,
1408 1407 if there are any. The old commit is stored in a backup bundle in
1409 1408 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1410 1409 on how to restore it).
1411 1410
1412 1411 Message, user and date are taken from the amended commit unless
1413 1412 specified. When a message isn't specified on the command line,
1414 1413 the editor will open with the message of the amended commit.
1415 1414
1416 1415 It is not possible to amend public changesets (see :hg:`help phases`)
1417 1416 or changesets that have children.
1418 1417
1419 1418 See :hg:`help dates` for a list of formats valid for -d/--date.
1420 1419
1421 1420 Returns 0 on success, 1 if nothing changed.
1422 1421 """
1423 1422 if opts.get('subrepos'):
1424 1423 if opts.get('amend'):
1425 1424 raise util.Abort(_('cannot amend with --subrepos'))
1426 1425 # Let --subrepos on the command line override config setting.
1427 1426 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1428 1427
1429 1428 cmdutil.checkunfinished(repo, commit=True)
1430 1429
1431 1430 branch = repo[None].branch()
1432 1431 bheads = repo.branchheads(branch)
1433 1432
1434 1433 extra = {}
1435 1434 if opts.get('close_branch'):
1436 1435 extra['close'] = 1
1437 1436
1438 1437 if not bheads:
1439 1438 raise util.Abort(_('can only close branch heads'))
1440 1439 elif opts.get('amend'):
1441 1440 if repo.parents()[0].p1().branch() != branch and \
1442 1441 repo.parents()[0].p2().branch() != branch:
1443 1442 raise util.Abort(_('can only close branch heads'))
1444 1443
1445 1444 if opts.get('amend'):
1446 1445 if ui.configbool('ui', 'commitsubrepos'):
1447 1446 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1448 1447
1449 1448 old = repo['.']
1450 1449 if not old.mutable():
1451 1450 raise util.Abort(_('cannot amend public changesets'))
1452 1451 if len(repo[None].parents()) > 1:
1453 1452 raise util.Abort(_('cannot amend while merging'))
1454 1453 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1455 1454 if not allowunstable and old.children():
1456 1455 raise util.Abort(_('cannot amend changeset with children'))
1457 1456
1458 1457 # commitfunc is used only for temporary amend commit by cmdutil.amend
1459 1458 def commitfunc(ui, repo, message, match, opts):
1460 1459 return repo.commit(message,
1461 1460 opts.get('user') or old.user(),
1462 1461 opts.get('date') or old.date(),
1463 1462 match,
1464 1463 extra=extra)
1465 1464
1466 1465 current = repo._bookmarkcurrent
1467 1466 marks = old.bookmarks()
1468 1467 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1469 1468 if node == old.node():
1470 1469 ui.status(_("nothing changed\n"))
1471 1470 return 1
1472 1471 elif marks:
1473 1472 ui.debug('moving bookmarks %r from %s to %s\n' %
1474 1473 (marks, old.hex(), hex(node)))
1475 1474 newmarks = repo._bookmarks
1476 1475 for bm in marks:
1477 1476 newmarks[bm] = node
1478 1477 if bm == current:
1479 1478 bookmarks.setcurrent(repo, bm)
1480 1479 newmarks.write()
1481 1480 else:
1482 1481 def commitfunc(ui, repo, message, match, opts):
1483 1482 backup = ui.backupconfig('phases', 'new-commit')
1484 1483 baseui = repo.baseui
1485 1484 basebackup = baseui.backupconfig('phases', 'new-commit')
1486 1485 try:
1487 1486 if opts.get('secret'):
1488 1487 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1489 1488 # Propagate to subrepos
1490 1489 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1491 1490
1492 1491 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1493 1492 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1494 1493 return repo.commit(message, opts.get('user'), opts.get('date'),
1495 1494 match,
1496 1495 editor=editor,
1497 1496 extra=extra)
1498 1497 finally:
1499 1498 ui.restoreconfig(backup)
1500 1499 repo.baseui.restoreconfig(basebackup)
1501 1500
1502 1501
1503 1502 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1504 1503
1505 1504 if not node:
1506 1505 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1507 1506 if stat[3]:
1508 1507 ui.status(_("nothing changed (%d missing files, see "
1509 1508 "'hg status')\n") % len(stat[3]))
1510 1509 else:
1511 1510 ui.status(_("nothing changed\n"))
1512 1511 return 1
1513 1512
1514 1513 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1515 1514
1516 1515 @command('config|showconfig|debugconfig',
1517 1516 [('u', 'untrusted', None, _('show untrusted configuration options')),
1518 1517 ('e', 'edit', None, _('edit user config')),
1519 1518 ('l', 'local', None, _('edit repository config')),
1520 1519 ('g', 'global', None, _('edit global config'))],
1521 1520 _('[-u] [NAME]...'),
1522 1521 optionalrepo=True)
1523 1522 def config(ui, repo, *values, **opts):
1524 1523 """show combined config settings from all hgrc files
1525 1524
1526 1525 With no arguments, print names and values of all config items.
1527 1526
1528 1527 With one argument of the form section.name, print just the value
1529 1528 of that config item.
1530 1529
1531 1530 With multiple arguments, print names and values of all config
1532 1531 items with matching section names.
1533 1532
1534 1533 With --edit, start an editor on the user-level config file. With
1535 1534 --global, edit the system-wide config file. With --local, edit the
1536 1535 repository-level config file.
1537 1536
1538 1537 With --debug, the source (filename and line number) is printed
1539 1538 for each config item.
1540 1539
1541 1540 See :hg:`help config` for more information about config files.
1542 1541
1543 1542 Returns 0 on success, 1 if NAME does not exist.
1544 1543
1545 1544 """
1546 1545
1547 1546 if opts.get('edit') or opts.get('local') or opts.get('global'):
1548 1547 if opts.get('local') and opts.get('global'):
1549 1548 raise util.Abort(_("can't use --local and --global together"))
1550 1549
1551 1550 if opts.get('local'):
1552 1551 if not repo:
1553 1552 raise util.Abort(_("can't use --local outside a repository"))
1554 1553 paths = [repo.join('hgrc')]
1555 1554 elif opts.get('global'):
1556 1555 paths = scmutil.systemrcpath()
1557 1556 else:
1558 1557 paths = scmutil.userrcpath()
1559 1558
1560 1559 for f in paths:
1561 1560 if os.path.exists(f):
1562 1561 break
1563 1562 else:
1564 1563 if opts.get('global'):
1565 1564 samplehgrc = uimod.samplehgrcs['global']
1566 1565 elif opts.get('local'):
1567 1566 samplehgrc = uimod.samplehgrcs['local']
1568 1567 else:
1569 1568 samplehgrc = uimod.samplehgrcs['user']
1570 1569
1571 1570 f = paths[0]
1572 1571 fp = open(f, "w")
1573 1572 fp.write(samplehgrc)
1574 1573 fp.close()
1575 1574
1576 1575 editor = ui.geteditor()
1577 1576 ui.system("%s \"%s\"" % (editor, f),
1578 1577 onerr=util.Abort, errprefix=_("edit failed"))
1579 1578 return
1580 1579
1581 1580 for f in scmutil.rcpath():
1582 1581 ui.debug('read config from: %s\n' % f)
1583 1582 untrusted = bool(opts.get('untrusted'))
1584 1583 if values:
1585 1584 sections = [v for v in values if '.' not in v]
1586 1585 items = [v for v in values if '.' in v]
1587 1586 if len(items) > 1 or items and sections:
1588 1587 raise util.Abort(_('only one config item permitted'))
1589 1588 matched = False
1590 1589 for section, name, value in ui.walkconfig(untrusted=untrusted):
1591 1590 value = str(value).replace('\n', '\\n')
1592 1591 sectname = section + '.' + name
1593 1592 if values:
1594 1593 for v in values:
1595 1594 if v == section:
1596 1595 ui.debug('%s: ' %
1597 1596 ui.configsource(section, name, untrusted))
1598 1597 ui.write('%s=%s\n' % (sectname, value))
1599 1598 matched = True
1600 1599 elif v == sectname:
1601 1600 ui.debug('%s: ' %
1602 1601 ui.configsource(section, name, untrusted))
1603 1602 ui.write(value, '\n')
1604 1603 matched = True
1605 1604 else:
1606 1605 ui.debug('%s: ' %
1607 1606 ui.configsource(section, name, untrusted))
1608 1607 ui.write('%s=%s\n' % (sectname, value))
1609 1608 matched = True
1610 1609 if matched:
1611 1610 return 0
1612 1611 return 1
1613 1612
1614 1613 @command('copy|cp',
1615 1614 [('A', 'after', None, _('record a copy that has already occurred')),
1616 1615 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1617 1616 ] + walkopts + dryrunopts,
1618 1617 _('[OPTION]... [SOURCE]... DEST'))
1619 1618 def copy(ui, repo, *pats, **opts):
1620 1619 """mark files as copied for the next commit
1621 1620
1622 1621 Mark dest as having copies of source files. If dest is a
1623 1622 directory, copies are put in that directory. If dest is a file,
1624 1623 the source must be a single file.
1625 1624
1626 1625 By default, this command copies the contents of files as they
1627 1626 exist in the working directory. If invoked with -A/--after, the
1628 1627 operation is recorded, but no copying is performed.
1629 1628
1630 1629 This command takes effect with the next commit. To undo a copy
1631 1630 before that, see :hg:`revert`.
1632 1631
1633 1632 Returns 0 on success, 1 if errors are encountered.
1634 1633 """
1635 1634 wlock = repo.wlock(False)
1636 1635 try:
1637 1636 return cmdutil.copy(ui, repo, pats, opts)
1638 1637 finally:
1639 1638 wlock.release()
1640 1639
1641 1640 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1642 1641 def debugancestor(ui, repo, *args):
1643 1642 """find the ancestor revision of two revisions in a given index"""
1644 1643 if len(args) == 3:
1645 1644 index, rev1, rev2 = args
1646 1645 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1647 1646 lookup = r.lookup
1648 1647 elif len(args) == 2:
1649 1648 if not repo:
1650 1649 raise util.Abort(_("there is no Mercurial repository here "
1651 1650 "(.hg not found)"))
1652 1651 rev1, rev2 = args
1653 1652 r = repo.changelog
1654 1653 lookup = repo.lookup
1655 1654 else:
1656 1655 raise util.Abort(_('either two or three arguments required'))
1657 1656 a = r.ancestor(lookup(rev1), lookup(rev2))
1658 1657 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1659 1658
1660 1659 @command('debugbuilddag',
1661 1660 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1662 1661 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1663 1662 ('n', 'new-file', None, _('add new file at each rev'))],
1664 1663 _('[OPTION]... [TEXT]'))
1665 1664 def debugbuilddag(ui, repo, text=None,
1666 1665 mergeable_file=False,
1667 1666 overwritten_file=False,
1668 1667 new_file=False):
1669 1668 """builds a repo with a given DAG from scratch in the current empty repo
1670 1669
1671 1670 The description of the DAG is read from stdin if not given on the
1672 1671 command line.
1673 1672
1674 1673 Elements:
1675 1674
1676 1675 - "+n" is a linear run of n nodes based on the current default parent
1677 1676 - "." is a single node based on the current default parent
1678 1677 - "$" resets the default parent to null (implied at the start);
1679 1678 otherwise the default parent is always the last node created
1680 1679 - "<p" sets the default parent to the backref p
1681 1680 - "*p" is a fork at parent p, which is a backref
1682 1681 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1683 1682 - "/p2" is a merge of the preceding node and p2
1684 1683 - ":tag" defines a local tag for the preceding node
1685 1684 - "@branch" sets the named branch for subsequent nodes
1686 1685 - "#...\\n" is a comment up to the end of the line
1687 1686
1688 1687 Whitespace between the above elements is ignored.
1689 1688
1690 1689 A backref is either
1691 1690
1692 1691 - a number n, which references the node curr-n, where curr is the current
1693 1692 node, or
1694 1693 - the name of a local tag you placed earlier using ":tag", or
1695 1694 - empty to denote the default parent.
1696 1695
1697 1696 All string valued-elements are either strictly alphanumeric, or must
1698 1697 be enclosed in double quotes ("..."), with "\\" as escape character.
1699 1698 """
1700 1699
1701 1700 if text is None:
1702 1701 ui.status(_("reading DAG from stdin\n"))
1703 1702 text = ui.fin.read()
1704 1703
1705 1704 cl = repo.changelog
1706 1705 if len(cl) > 0:
1707 1706 raise util.Abort(_('repository is not empty'))
1708 1707
1709 1708 # determine number of revs in DAG
1710 1709 total = 0
1711 1710 for type, data in dagparser.parsedag(text):
1712 1711 if type == 'n':
1713 1712 total += 1
1714 1713
1715 1714 if mergeable_file:
1716 1715 linesperrev = 2
1717 1716 # make a file with k lines per rev
1718 1717 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1719 1718 initialmergedlines.append("")
1720 1719
1721 1720 tags = []
1722 1721
1723 1722 lock = tr = None
1724 1723 try:
1725 1724 lock = repo.lock()
1726 1725 tr = repo.transaction("builddag")
1727 1726
1728 1727 at = -1
1729 1728 atbranch = 'default'
1730 1729 nodeids = []
1731 1730 id = 0
1732 1731 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1733 1732 for type, data in dagparser.parsedag(text):
1734 1733 if type == 'n':
1735 1734 ui.note(('node %s\n' % str(data)))
1736 1735 id, ps = data
1737 1736
1738 1737 files = []
1739 1738 fctxs = {}
1740 1739
1741 1740 p2 = None
1742 1741 if mergeable_file:
1743 1742 fn = "mf"
1744 1743 p1 = repo[ps[0]]
1745 1744 if len(ps) > 1:
1746 1745 p2 = repo[ps[1]]
1747 1746 pa = p1.ancestor(p2)
1748 1747 base, local, other = [x[fn].data() for x in (pa, p1,
1749 1748 p2)]
1750 1749 m3 = simplemerge.Merge3Text(base, local, other)
1751 1750 ml = [l.strip() for l in m3.merge_lines()]
1752 1751 ml.append("")
1753 1752 elif at > 0:
1754 1753 ml = p1[fn].data().split("\n")
1755 1754 else:
1756 1755 ml = initialmergedlines
1757 1756 ml[id * linesperrev] += " r%i" % id
1758 1757 mergedtext = "\n".join(ml)
1759 1758 files.append(fn)
1760 1759 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1761 1760
1762 1761 if overwritten_file:
1763 1762 fn = "of"
1764 1763 files.append(fn)
1765 1764 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1766 1765
1767 1766 if new_file:
1768 1767 fn = "nf%i" % id
1769 1768 files.append(fn)
1770 1769 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1771 1770 if len(ps) > 1:
1772 1771 if not p2:
1773 1772 p2 = repo[ps[1]]
1774 1773 for fn in p2:
1775 1774 if fn.startswith("nf"):
1776 1775 files.append(fn)
1777 1776 fctxs[fn] = p2[fn]
1778 1777
1779 1778 def fctxfn(repo, cx, path):
1780 1779 return fctxs.get(path)
1781 1780
1782 1781 if len(ps) == 0 or ps[0] < 0:
1783 1782 pars = [None, None]
1784 1783 elif len(ps) == 1:
1785 1784 pars = [nodeids[ps[0]], None]
1786 1785 else:
1787 1786 pars = [nodeids[p] for p in ps]
1788 1787 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1789 1788 date=(id, 0),
1790 1789 user="debugbuilddag",
1791 1790 extra={'branch': atbranch})
1792 1791 nodeid = repo.commitctx(cx)
1793 1792 nodeids.append(nodeid)
1794 1793 at = id
1795 1794 elif type == 'l':
1796 1795 id, name = data
1797 1796 ui.note(('tag %s\n' % name))
1798 1797 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1799 1798 elif type == 'a':
1800 1799 ui.note(('branch %s\n' % data))
1801 1800 atbranch = data
1802 1801 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1803 1802 tr.close()
1804 1803
1805 1804 if tags:
1806 1805 repo.vfs.write("localtags", "".join(tags))
1807 1806 finally:
1808 1807 ui.progress(_('building'), None)
1809 1808 release(tr, lock)
1810 1809
1811 1810 @command('debugbundle',
1812 1811 [('a', 'all', None, _('show all details'))],
1813 1812 _('FILE'),
1814 1813 norepo=True)
1815 1814 def debugbundle(ui, bundlepath, all=None, **opts):
1816 1815 """lists the contents of a bundle"""
1817 1816 f = hg.openpath(ui, bundlepath)
1818 1817 try:
1819 1818 gen = exchange.readbundle(ui, f, bundlepath)
1820 1819 if all:
1821 1820 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1822 1821
1823 1822 def showchunks(named):
1824 1823 ui.write("\n%s\n" % named)
1825 1824 chain = None
1826 1825 while True:
1827 1826 chunkdata = gen.deltachunk(chain)
1828 1827 if not chunkdata:
1829 1828 break
1830 1829 node = chunkdata['node']
1831 1830 p1 = chunkdata['p1']
1832 1831 p2 = chunkdata['p2']
1833 1832 cs = chunkdata['cs']
1834 1833 deltabase = chunkdata['deltabase']
1835 1834 delta = chunkdata['delta']
1836 1835 ui.write("%s %s %s %s %s %s\n" %
1837 1836 (hex(node), hex(p1), hex(p2),
1838 1837 hex(cs), hex(deltabase), len(delta)))
1839 1838 chain = node
1840 1839
1841 1840 chunkdata = gen.changelogheader()
1842 1841 showchunks("changelog")
1843 1842 chunkdata = gen.manifestheader()
1844 1843 showchunks("manifest")
1845 1844 while True:
1846 1845 chunkdata = gen.filelogheader()
1847 1846 if not chunkdata:
1848 1847 break
1849 1848 fname = chunkdata['filename']
1850 1849 showchunks(fname)
1851 1850 else:
1852 1851 chunkdata = gen.changelogheader()
1853 1852 chain = None
1854 1853 while True:
1855 1854 chunkdata = gen.deltachunk(chain)
1856 1855 if not chunkdata:
1857 1856 break
1858 1857 node = chunkdata['node']
1859 1858 ui.write("%s\n" % hex(node))
1860 1859 chain = node
1861 1860 finally:
1862 1861 f.close()
1863 1862
1864 1863 @command('debugcheckstate', [], '')
1865 1864 def debugcheckstate(ui, repo):
1866 1865 """validate the correctness of the current dirstate"""
1867 1866 parent1, parent2 = repo.dirstate.parents()
1868 1867 m1 = repo[parent1].manifest()
1869 1868 m2 = repo[parent2].manifest()
1870 1869 errors = 0
1871 1870 for f in repo.dirstate:
1872 1871 state = repo.dirstate[f]
1873 1872 if state in "nr" and f not in m1:
1874 1873 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1875 1874 errors += 1
1876 1875 if state in "a" and f in m1:
1877 1876 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1878 1877 errors += 1
1879 1878 if state in "m" and f not in m1 and f not in m2:
1880 1879 ui.warn(_("%s in state %s, but not in either manifest\n") %
1881 1880 (f, state))
1882 1881 errors += 1
1883 1882 for f in m1:
1884 1883 state = repo.dirstate[f]
1885 1884 if state not in "nrm":
1886 1885 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1887 1886 errors += 1
1888 1887 if errors:
1889 1888 error = _(".hg/dirstate inconsistent with current parent's manifest")
1890 1889 raise util.Abort(error)
1891 1890
1892 1891 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1893 1892 def debugcommands(ui, cmd='', *args):
1894 1893 """list all available commands and options"""
1895 1894 for cmd, vals in sorted(table.iteritems()):
1896 1895 cmd = cmd.split('|')[0].strip('^')
1897 1896 opts = ', '.join([i[1] for i in vals[1]])
1898 1897 ui.write('%s: %s\n' % (cmd, opts))
1899 1898
1900 1899 @command('debugcomplete',
1901 1900 [('o', 'options', None, _('show the command options'))],
1902 1901 _('[-o] CMD'),
1903 1902 norepo=True)
1904 1903 def debugcomplete(ui, cmd='', **opts):
1905 1904 """returns the completion list associated with the given command"""
1906 1905
1907 1906 if opts.get('options'):
1908 1907 options = []
1909 1908 otables = [globalopts]
1910 1909 if cmd:
1911 1910 aliases, entry = cmdutil.findcmd(cmd, table, False)
1912 1911 otables.append(entry[1])
1913 1912 for t in otables:
1914 1913 for o in t:
1915 1914 if "(DEPRECATED)" in o[3]:
1916 1915 continue
1917 1916 if o[0]:
1918 1917 options.append('-%s' % o[0])
1919 1918 options.append('--%s' % o[1])
1920 1919 ui.write("%s\n" % "\n".join(options))
1921 1920 return
1922 1921
1923 1922 cmdlist = cmdutil.findpossible(cmd, table)
1924 1923 if ui.verbose:
1925 1924 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1926 1925 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1927 1926
1928 1927 @command('debugdag',
1929 1928 [('t', 'tags', None, _('use tags as labels')),
1930 1929 ('b', 'branches', None, _('annotate with branch names')),
1931 1930 ('', 'dots', None, _('use dots for runs')),
1932 1931 ('s', 'spaces', None, _('separate elements by spaces'))],
1933 1932 _('[OPTION]... [FILE [REV]...]'),
1934 1933 optionalrepo=True)
1935 1934 def debugdag(ui, repo, file_=None, *revs, **opts):
1936 1935 """format the changelog or an index DAG as a concise textual description
1937 1936
1938 1937 If you pass a revlog index, the revlog's DAG is emitted. If you list
1939 1938 revision numbers, they get labeled in the output as rN.
1940 1939
1941 1940 Otherwise, the changelog DAG of the current repo is emitted.
1942 1941 """
1943 1942 spaces = opts.get('spaces')
1944 1943 dots = opts.get('dots')
1945 1944 if file_:
1946 1945 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1947 1946 revs = set((int(r) for r in revs))
1948 1947 def events():
1949 1948 for r in rlog:
1950 1949 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1951 1950 if p != -1))
1952 1951 if r in revs:
1953 1952 yield 'l', (r, "r%i" % r)
1954 1953 elif repo:
1955 1954 cl = repo.changelog
1956 1955 tags = opts.get('tags')
1957 1956 branches = opts.get('branches')
1958 1957 if tags:
1959 1958 labels = {}
1960 1959 for l, n in repo.tags().items():
1961 1960 labels.setdefault(cl.rev(n), []).append(l)
1962 1961 def events():
1963 1962 b = "default"
1964 1963 for r in cl:
1965 1964 if branches:
1966 1965 newb = cl.read(cl.node(r))[5]['branch']
1967 1966 if newb != b:
1968 1967 yield 'a', newb
1969 1968 b = newb
1970 1969 yield 'n', (r, list(p for p in cl.parentrevs(r)
1971 1970 if p != -1))
1972 1971 if tags:
1973 1972 ls = labels.get(r)
1974 1973 if ls:
1975 1974 for l in ls:
1976 1975 yield 'l', (r, l)
1977 1976 else:
1978 1977 raise util.Abort(_('need repo for changelog dag'))
1979 1978
1980 1979 for line in dagparser.dagtextlines(events(),
1981 1980 addspaces=spaces,
1982 1981 wraplabels=True,
1983 1982 wrapannotations=True,
1984 1983 wrapnonlinear=dots,
1985 1984 usedots=dots,
1986 1985 maxlinewidth=70):
1987 1986 ui.write(line)
1988 1987 ui.write("\n")
1989 1988
1990 1989 @command('debugdata',
1991 1990 [('c', 'changelog', False, _('open changelog')),
1992 1991 ('m', 'manifest', False, _('open manifest'))],
1993 1992 _('-c|-m|FILE REV'))
1994 1993 def debugdata(ui, repo, file_, rev=None, **opts):
1995 1994 """dump the contents of a data file revision"""
1996 1995 if opts.get('changelog') or opts.get('manifest'):
1997 1996 file_, rev = None, file_
1998 1997 elif rev is None:
1999 1998 raise error.CommandError('debugdata', _('invalid arguments'))
2000 1999 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2001 2000 try:
2002 2001 ui.write(r.revision(r.lookup(rev)))
2003 2002 except KeyError:
2004 2003 raise util.Abort(_('invalid revision identifier %s') % rev)
2005 2004
2006 2005 @command('debugdate',
2007 2006 [('e', 'extended', None, _('try extended date formats'))],
2008 2007 _('[-e] DATE [RANGE]'),
2009 2008 norepo=True, optionalrepo=True)
2010 2009 def debugdate(ui, date, range=None, **opts):
2011 2010 """parse and display a date"""
2012 2011 if opts["extended"]:
2013 2012 d = util.parsedate(date, util.extendeddateformats)
2014 2013 else:
2015 2014 d = util.parsedate(date)
2016 2015 ui.write(("internal: %s %s\n") % d)
2017 2016 ui.write(("standard: %s\n") % util.datestr(d))
2018 2017 if range:
2019 2018 m = util.matchdate(range)
2020 2019 ui.write(("match: %s\n") % m(d[0]))
2021 2020
2022 2021 @command('debugdiscovery',
2023 2022 [('', 'old', None, _('use old-style discovery')),
2024 2023 ('', 'nonheads', None,
2025 2024 _('use old-style discovery with non-heads included')),
2026 2025 ] + remoteopts,
2027 2026 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2028 2027 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2029 2028 """runs the changeset discovery protocol in isolation"""
2030 2029 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2031 2030 opts.get('branch'))
2032 2031 remote = hg.peer(repo, opts, remoteurl)
2033 2032 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2034 2033
2035 2034 # make sure tests are repeatable
2036 2035 random.seed(12323)
2037 2036
2038 2037 def doit(localheads, remoteheads, remote=remote):
2039 2038 if opts.get('old'):
2040 2039 if localheads:
2041 2040 raise util.Abort('cannot use localheads with old style '
2042 2041 'discovery')
2043 2042 if not util.safehasattr(remote, 'branches'):
2044 2043 # enable in-client legacy support
2045 2044 remote = localrepo.locallegacypeer(remote.local())
2046 2045 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2047 2046 force=True)
2048 2047 common = set(common)
2049 2048 if not opts.get('nonheads'):
2050 2049 ui.write(("unpruned common: %s\n") %
2051 2050 " ".join(sorted(short(n) for n in common)))
2052 2051 dag = dagutil.revlogdag(repo.changelog)
2053 2052 all = dag.ancestorset(dag.internalizeall(common))
2054 2053 common = dag.externalizeall(dag.headsetofconnecteds(all))
2055 2054 else:
2056 2055 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2057 2056 common = set(common)
2058 2057 rheads = set(hds)
2059 2058 lheads = set(repo.heads())
2060 2059 ui.write(("common heads: %s\n") %
2061 2060 " ".join(sorted(short(n) for n in common)))
2062 2061 if lheads <= common:
2063 2062 ui.write(("local is subset\n"))
2064 2063 elif rheads <= common:
2065 2064 ui.write(("remote is subset\n"))
2066 2065
2067 2066 serverlogs = opts.get('serverlog')
2068 2067 if serverlogs:
2069 2068 for filename in serverlogs:
2070 2069 logfile = open(filename, 'r')
2071 2070 try:
2072 2071 line = logfile.readline()
2073 2072 while line:
2074 2073 parts = line.strip().split(';')
2075 2074 op = parts[1]
2076 2075 if op == 'cg':
2077 2076 pass
2078 2077 elif op == 'cgss':
2079 2078 doit(parts[2].split(' '), parts[3].split(' '))
2080 2079 elif op == 'unb':
2081 2080 doit(parts[3].split(' '), parts[2].split(' '))
2082 2081 line = logfile.readline()
2083 2082 finally:
2084 2083 logfile.close()
2085 2084
2086 2085 else:
2087 2086 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2088 2087 opts.get('remote_head'))
2089 2088 localrevs = opts.get('local_head')
2090 2089 doit(localrevs, remoterevs)
2091 2090
2092 2091 @command('debugfileset',
2093 2092 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2094 2093 _('[-r REV] FILESPEC'))
2095 2094 def debugfileset(ui, repo, expr, **opts):
2096 2095 '''parse and apply a fileset specification'''
2097 2096 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2098 2097 if ui.verbose:
2099 2098 tree = fileset.parse(expr)[0]
2100 2099 ui.note(tree, "\n")
2101 2100
2102 2101 for f in ctx.getfileset(expr):
2103 2102 ui.write("%s\n" % f)
2104 2103
2105 2104 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2106 2105 def debugfsinfo(ui, path="."):
2107 2106 """show information detected about current filesystem"""
2108 2107 util.writefile('.debugfsinfo', '')
2109 2108 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2110 2109 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2111 2110 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2112 2111 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2113 2112 and 'yes' or 'no'))
2114 2113 os.unlink('.debugfsinfo')
2115 2114
2116 2115 @command('debuggetbundle',
2117 2116 [('H', 'head', [], _('id of head node'), _('ID')),
2118 2117 ('C', 'common', [], _('id of common node'), _('ID')),
2119 2118 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2120 2119 _('REPO FILE [-H|-C ID]...'),
2121 2120 norepo=True)
2122 2121 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2123 2122 """retrieves a bundle from a repo
2124 2123
2125 2124 Every ID must be a full-length hex node id string. Saves the bundle to the
2126 2125 given file.
2127 2126 """
2128 2127 repo = hg.peer(ui, opts, repopath)
2129 2128 if not repo.capable('getbundle'):
2130 2129 raise util.Abort("getbundle() not supported by target repository")
2131 2130 args = {}
2132 2131 if common:
2133 2132 args['common'] = [bin(s) for s in common]
2134 2133 if head:
2135 2134 args['heads'] = [bin(s) for s in head]
2136 2135 # TODO: get desired bundlecaps from command line.
2137 2136 args['bundlecaps'] = None
2138 2137 bundle = repo.getbundle('debug', **args)
2139 2138
2140 2139 bundletype = opts.get('type', 'bzip2').lower()
2141 2140 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2142 2141 bundletype = btypes.get(bundletype)
2143 2142 if bundletype not in changegroup.bundletypes:
2144 2143 raise util.Abort(_('unknown bundle type specified with --type'))
2145 2144 changegroup.writebundle(bundle, bundlepath, bundletype)
2146 2145
2147 2146 @command('debugignore', [], '')
2148 2147 def debugignore(ui, repo, *values, **opts):
2149 2148 """display the combined ignore pattern"""
2150 2149 ignore = repo.dirstate._ignore
2151 2150 includepat = getattr(ignore, 'includepat', None)
2152 2151 if includepat is not None:
2153 2152 ui.write("%s\n" % includepat)
2154 2153 else:
2155 2154 raise util.Abort(_("no ignore patterns found"))
2156 2155
2157 2156 @command('debugindex',
2158 2157 [('c', 'changelog', False, _('open changelog')),
2159 2158 ('m', 'manifest', False, _('open manifest')),
2160 2159 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2161 2160 _('[-f FORMAT] -c|-m|FILE'),
2162 2161 optionalrepo=True)
2163 2162 def debugindex(ui, repo, file_=None, **opts):
2164 2163 """dump the contents of an index file"""
2165 2164 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2166 2165 format = opts.get('format', 0)
2167 2166 if format not in (0, 1):
2168 2167 raise util.Abort(_("unknown format %d") % format)
2169 2168
2170 2169 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2171 2170 if generaldelta:
2172 2171 basehdr = ' delta'
2173 2172 else:
2174 2173 basehdr = ' base'
2175 2174
2176 2175 if ui.debugflag:
2177 2176 shortfn = hex
2178 2177 else:
2179 2178 shortfn = short
2180 2179
2181 2180 # There might not be anything in r, so have a sane default
2182 2181 idlen = 12
2183 2182 for i in r:
2184 2183 idlen = len(shortfn(r.node(i)))
2185 2184 break
2186 2185
2187 2186 if format == 0:
2188 2187 ui.write(" rev offset length " + basehdr + " linkrev"
2189 2188 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2190 2189 elif format == 1:
2191 2190 ui.write(" rev flag offset length"
2192 2191 " size " + basehdr + " link p1 p2"
2193 2192 " %s\n" % "nodeid".rjust(idlen))
2194 2193
2195 2194 for i in r:
2196 2195 node = r.node(i)
2197 2196 if generaldelta:
2198 2197 base = r.deltaparent(i)
2199 2198 else:
2200 2199 base = r.chainbase(i)
2201 2200 if format == 0:
2202 2201 try:
2203 2202 pp = r.parents(node)
2204 2203 except Exception:
2205 2204 pp = [nullid, nullid]
2206 2205 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2207 2206 i, r.start(i), r.length(i), base, r.linkrev(i),
2208 2207 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2209 2208 elif format == 1:
2210 2209 pr = r.parentrevs(i)
2211 2210 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2212 2211 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2213 2212 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2214 2213
2215 2214 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2216 2215 def debugindexdot(ui, repo, file_):
2217 2216 """dump an index DAG as a graphviz dot file"""
2218 2217 r = None
2219 2218 if repo:
2220 2219 filelog = repo.file(file_)
2221 2220 if len(filelog):
2222 2221 r = filelog
2223 2222 if not r:
2224 2223 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2225 2224 ui.write(("digraph G {\n"))
2226 2225 for i in r:
2227 2226 node = r.node(i)
2228 2227 pp = r.parents(node)
2229 2228 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2230 2229 if pp[1] != nullid:
2231 2230 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2232 2231 ui.write("}\n")
2233 2232
2234 2233 @command('debuginstall', [], '', norepo=True)
2235 2234 def debuginstall(ui):
2236 2235 '''test Mercurial installation
2237 2236
2238 2237 Returns 0 on success.
2239 2238 '''
2240 2239
2241 2240 def writetemp(contents):
2242 2241 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2243 2242 f = os.fdopen(fd, "wb")
2244 2243 f.write(contents)
2245 2244 f.close()
2246 2245 return name
2247 2246
2248 2247 problems = 0
2249 2248
2250 2249 # encoding
2251 2250 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2252 2251 try:
2253 2252 encoding.fromlocal("test")
2254 2253 except util.Abort, inst:
2255 2254 ui.write(" %s\n" % inst)
2256 2255 ui.write(_(" (check that your locale is properly set)\n"))
2257 2256 problems += 1
2258 2257
2259 2258 # Python
2260 2259 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2261 2260 ui.status(_("checking Python version (%s)\n")
2262 2261 % ("%s.%s.%s" % sys.version_info[:3]))
2263 2262 ui.status(_("checking Python lib (%s)...\n")
2264 2263 % os.path.dirname(os.__file__))
2265 2264
2266 2265 # compiled modules
2267 2266 ui.status(_("checking installed modules (%s)...\n")
2268 2267 % os.path.dirname(__file__))
2269 2268 try:
2270 2269 import bdiff, mpatch, base85, osutil
2271 2270 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2272 2271 except Exception, inst:
2273 2272 ui.write(" %s\n" % inst)
2274 2273 ui.write(_(" One or more extensions could not be found"))
2275 2274 ui.write(_(" (check that you compiled the extensions)\n"))
2276 2275 problems += 1
2277 2276
2278 2277 # templates
2279 2278 import templater
2280 2279 p = templater.templatepaths()
2281 2280 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2282 2281 if p:
2283 2282 m = templater.templatepath("map-cmdline.default")
2284 2283 if m:
2285 2284 # template found, check if it is working
2286 2285 try:
2287 2286 templater.templater(m)
2288 2287 except Exception, inst:
2289 2288 ui.write(" %s\n" % inst)
2290 2289 p = None
2291 2290 else:
2292 2291 ui.write(_(" template 'default' not found\n"))
2293 2292 p = None
2294 2293 else:
2295 2294 ui.write(_(" no template directories found\n"))
2296 2295 if not p:
2297 2296 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2298 2297 problems += 1
2299 2298
2300 2299 # editor
2301 2300 ui.status(_("checking commit editor...\n"))
2302 2301 editor = ui.geteditor()
2303 2302 cmdpath = util.findexe(shlex.split(editor)[0])
2304 2303 if not cmdpath:
2305 2304 if editor == 'vi':
2306 2305 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2307 2306 ui.write(_(" (specify a commit editor in your configuration"
2308 2307 " file)\n"))
2309 2308 else:
2310 2309 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2311 2310 ui.write(_(" (specify a commit editor in your configuration"
2312 2311 " file)\n"))
2313 2312 problems += 1
2314 2313
2315 2314 # check username
2316 2315 ui.status(_("checking username...\n"))
2317 2316 try:
2318 2317 ui.username()
2319 2318 except util.Abort, e:
2320 2319 ui.write(" %s\n" % e)
2321 2320 ui.write(_(" (specify a username in your configuration file)\n"))
2322 2321 problems += 1
2323 2322
2324 2323 if not problems:
2325 2324 ui.status(_("no problems detected\n"))
2326 2325 else:
2327 2326 ui.write(_("%s problems detected,"
2328 2327 " please check your install!\n") % problems)
2329 2328
2330 2329 return problems
2331 2330
2332 2331 @command('debugknown', [], _('REPO ID...'), norepo=True)
2333 2332 def debugknown(ui, repopath, *ids, **opts):
2334 2333 """test whether node ids are known to a repo
2335 2334
2336 2335 Every ID must be a full-length hex node id string. Returns a list of 0s
2337 2336 and 1s indicating unknown/known.
2338 2337 """
2339 2338 repo = hg.peer(ui, opts, repopath)
2340 2339 if not repo.capable('known'):
2341 2340 raise util.Abort("known() not supported by target repository")
2342 2341 flags = repo.known([bin(s) for s in ids])
2343 2342 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2344 2343
2345 2344 @command('debuglabelcomplete', [], _('LABEL...'))
2346 2345 def debuglabelcomplete(ui, repo, *args):
2347 2346 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2348 2347 debugnamecomplete(ui, repo, *args)
2349 2348
2350 2349 @command('debugnamecomplete', [], _('NAME...'))
2351 2350 def debugnamecomplete(ui, repo, *args):
2352 2351 '''complete "names" - tags, open branch names, bookmark names'''
2353 2352
2354 2353 names = set()
2355 2354 # since we previously only listed open branches, we will handle that
2356 2355 # specially (after this for loop)
2357 2356 for name, ns in repo.names.iteritems():
2358 2357 if name != 'branches':
2359 2358 names.update(ns.listnames(repo))
2360 2359 names.update(tag for (tag, heads, tip, closed)
2361 2360 in repo.branchmap().iterbranches() if not closed)
2362 2361 completions = set()
2363 2362 if not args:
2364 2363 args = ['']
2365 2364 for a in args:
2366 2365 completions.update(n for n in names if n.startswith(a))
2367 2366 ui.write('\n'.join(sorted(completions)))
2368 2367 ui.write('\n')
2369 2368
2370 2369 @command('debuglocks',
2371 2370 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2372 2371 ('W', 'force-wlock', None,
2373 2372 _('free the working state lock (DANGEROUS)'))],
2374 2373 _('[OPTION]...'))
2375 2374 def debuglocks(ui, repo, **opts):
2376 2375 """show or modify state of locks
2377 2376
2378 2377 By default, this command will show which locks are held. This
2379 2378 includes the user and process holding the lock, the amount of time
2380 2379 the lock has been held, and the machine name where the process is
2381 2380 running if it's not local.
2382 2381
2383 2382 Locks protect the integrity of Mercurial's data, so should be
2384 2383 treated with care. System crashes or other interruptions may cause
2385 2384 locks to not be properly released, though Mercurial will usually
2386 2385 detect and remove such stale locks automatically.
2387 2386
2388 2387 However, detecting stale locks may not always be possible (for
2389 2388 instance, on a shared filesystem). Removing locks may also be
2390 2389 blocked by filesystem permissions.
2391 2390
2392 2391 Returns 0 if no locks are held.
2393 2392
2394 2393 """
2395 2394
2396 2395 if opts.get('force_lock'):
2397 2396 repo.svfs.unlink('lock')
2398 2397 if opts.get('force_wlock'):
2399 2398 repo.vfs.unlink('wlock')
2400 2399 if opts.get('force_lock') or opts.get('force_lock'):
2401 2400 return 0
2402 2401
2403 2402 now = time.time()
2404 2403 held = 0
2405 2404
2406 2405 def report(vfs, name, method):
2407 2406 # this causes stale locks to get reaped for more accurate reporting
2408 2407 try:
2409 2408 l = method(False)
2410 2409 except error.LockHeld:
2411 2410 l = None
2412 2411
2413 2412 if l:
2414 2413 l.release()
2415 2414 else:
2416 2415 try:
2417 2416 stat = repo.svfs.lstat(name)
2418 2417 age = now - stat.st_mtime
2419 2418 user = util.username(stat.st_uid)
2420 2419 locker = vfs.readlock(name)
2421 2420 if ":" in locker:
2422 2421 host, pid = locker.split(':')
2423 2422 if host == socket.gethostname():
2424 2423 locker = 'user %s, process %s' % (user, pid)
2425 2424 else:
2426 2425 locker = 'user %s, process %s, host %s' \
2427 2426 % (user, pid, host)
2428 2427 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2429 2428 return 1
2430 2429 except OSError, e:
2431 2430 if e.errno != errno.ENOENT:
2432 2431 raise
2433 2432
2434 2433 ui.write("%-6s free\n" % (name + ":"))
2435 2434 return 0
2436 2435
2437 2436 held += report(repo.svfs, "lock", repo.lock)
2438 2437 held += report(repo.vfs, "wlock", repo.wlock)
2439 2438
2440 2439 return held
2441 2440
2442 2441 @command('debugobsolete',
2443 2442 [('', 'flags', 0, _('markers flag')),
2444 2443 ('', 'record-parents', False,
2445 2444 _('record parent information for the precursor')),
2446 2445 ('r', 'rev', [], _('display markers relevant to REV')),
2447 2446 ] + commitopts2,
2448 2447 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2449 2448 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2450 2449 """create arbitrary obsolete marker
2451 2450
2452 2451 With no arguments, displays the list of obsolescence markers."""
2453 2452
2454 2453 def parsenodeid(s):
2455 2454 try:
2456 2455 # We do not use revsingle/revrange functions here to accept
2457 2456 # arbitrary node identifiers, possibly not present in the
2458 2457 # local repository.
2459 2458 n = bin(s)
2460 2459 if len(n) != len(nullid):
2461 2460 raise TypeError()
2462 2461 return n
2463 2462 except TypeError:
2464 2463 raise util.Abort('changeset references must be full hexadecimal '
2465 2464 'node identifiers')
2466 2465
2467 2466 if precursor is not None:
2468 2467 if opts['rev']:
2469 2468 raise util.Abort('cannot select revision when creating marker')
2470 2469 metadata = {}
2471 2470 metadata['user'] = opts['user'] or ui.username()
2472 2471 succs = tuple(parsenodeid(succ) for succ in successors)
2473 2472 l = repo.lock()
2474 2473 try:
2475 2474 tr = repo.transaction('debugobsolete')
2476 2475 try:
2477 2476 try:
2478 2477 date = opts.get('date')
2479 2478 if date:
2480 2479 date = util.parsedate(date)
2481 2480 else:
2482 2481 date = None
2483 2482 prec = parsenodeid(precursor)
2484 2483 parents = None
2485 2484 if opts['record_parents']:
2486 2485 if prec not in repo.unfiltered():
2487 2486 raise util.Abort('cannot used --record-parents on '
2488 2487 'unknown changesets')
2489 2488 parents = repo.unfiltered()[prec].parents()
2490 2489 parents = tuple(p.node() for p in parents)
2491 2490 repo.obsstore.create(tr, prec, succs, opts['flags'],
2492 2491 parents=parents, date=date,
2493 2492 metadata=metadata)
2494 2493 tr.close()
2495 2494 except ValueError, exc:
2496 2495 raise util.Abort(_('bad obsmarker input: %s') % exc)
2497 2496 finally:
2498 2497 tr.release()
2499 2498 finally:
2500 2499 l.release()
2501 2500 else:
2502 2501 if opts['rev']:
2503 2502 revs = scmutil.revrange(repo, opts['rev'])
2504 2503 nodes = [repo[r].node() for r in revs]
2505 2504 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2506 2505 markers.sort(key=lambda x: x._data)
2507 2506 else:
2508 2507 markers = obsolete.getmarkers(repo)
2509 2508
2510 2509 for m in markers:
2511 2510 cmdutil.showmarker(ui, m)
2512 2511
2513 2512 @command('debugpathcomplete',
2514 2513 [('f', 'full', None, _('complete an entire path')),
2515 2514 ('n', 'normal', None, _('show only normal files')),
2516 2515 ('a', 'added', None, _('show only added files')),
2517 2516 ('r', 'removed', None, _('show only removed files'))],
2518 2517 _('FILESPEC...'))
2519 2518 def debugpathcomplete(ui, repo, *specs, **opts):
2520 2519 '''complete part or all of a tracked path
2521 2520
2522 2521 This command supports shells that offer path name completion. It
2523 2522 currently completes only files already known to the dirstate.
2524 2523
2525 2524 Completion extends only to the next path segment unless
2526 2525 --full is specified, in which case entire paths are used.'''
2527 2526
2528 2527 def complete(path, acceptable):
2529 2528 dirstate = repo.dirstate
2530 2529 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2531 2530 rootdir = repo.root + os.sep
2532 2531 if spec != repo.root and not spec.startswith(rootdir):
2533 2532 return [], []
2534 2533 if os.path.isdir(spec):
2535 2534 spec += '/'
2536 2535 spec = spec[len(rootdir):]
2537 2536 fixpaths = os.sep != '/'
2538 2537 if fixpaths:
2539 2538 spec = spec.replace(os.sep, '/')
2540 2539 speclen = len(spec)
2541 2540 fullpaths = opts['full']
2542 2541 files, dirs = set(), set()
2543 2542 adddir, addfile = dirs.add, files.add
2544 2543 for f, st in dirstate.iteritems():
2545 2544 if f.startswith(spec) and st[0] in acceptable:
2546 2545 if fixpaths:
2547 2546 f = f.replace('/', os.sep)
2548 2547 if fullpaths:
2549 2548 addfile(f)
2550 2549 continue
2551 2550 s = f.find(os.sep, speclen)
2552 2551 if s >= 0:
2553 2552 adddir(f[:s])
2554 2553 else:
2555 2554 addfile(f)
2556 2555 return files, dirs
2557 2556
2558 2557 acceptable = ''
2559 2558 if opts['normal']:
2560 2559 acceptable += 'nm'
2561 2560 if opts['added']:
2562 2561 acceptable += 'a'
2563 2562 if opts['removed']:
2564 2563 acceptable += 'r'
2565 2564 cwd = repo.getcwd()
2566 2565 if not specs:
2567 2566 specs = ['.']
2568 2567
2569 2568 files, dirs = set(), set()
2570 2569 for spec in specs:
2571 2570 f, d = complete(spec, acceptable or 'nmar')
2572 2571 files.update(f)
2573 2572 dirs.update(d)
2574 2573 files.update(dirs)
2575 2574 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2576 2575 ui.write('\n')
2577 2576
2578 2577 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2579 2578 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2580 2579 '''access the pushkey key/value protocol
2581 2580
2582 2581 With two args, list the keys in the given namespace.
2583 2582
2584 2583 With five args, set a key to new if it currently is set to old.
2585 2584 Reports success or failure.
2586 2585 '''
2587 2586
2588 2587 target = hg.peer(ui, {}, repopath)
2589 2588 if keyinfo:
2590 2589 key, old, new = keyinfo
2591 2590 r = target.pushkey(namespace, key, old, new)
2592 2591 ui.status(str(r) + '\n')
2593 2592 return not r
2594 2593 else:
2595 2594 for k, v in sorted(target.listkeys(namespace).iteritems()):
2596 2595 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2597 2596 v.encode('string-escape')))
2598 2597
2599 2598 @command('debugpvec', [], _('A B'))
2600 2599 def debugpvec(ui, repo, a, b=None):
2601 2600 ca = scmutil.revsingle(repo, a)
2602 2601 cb = scmutil.revsingle(repo, b)
2603 2602 pa = pvec.ctxpvec(ca)
2604 2603 pb = pvec.ctxpvec(cb)
2605 2604 if pa == pb:
2606 2605 rel = "="
2607 2606 elif pa > pb:
2608 2607 rel = ">"
2609 2608 elif pa < pb:
2610 2609 rel = "<"
2611 2610 elif pa | pb:
2612 2611 rel = "|"
2613 2612 ui.write(_("a: %s\n") % pa)
2614 2613 ui.write(_("b: %s\n") % pb)
2615 2614 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2616 2615 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2617 2616 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2618 2617 pa.distance(pb), rel))
2619 2618
2620 2619 @command('debugrebuilddirstate|debugrebuildstate',
2621 2620 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2622 2621 _('[-r REV]'))
2623 2622 def debugrebuilddirstate(ui, repo, rev):
2624 2623 """rebuild the dirstate as it would look like for the given revision
2625 2624
2626 2625 If no revision is specified the first current parent will be used.
2627 2626
2628 2627 The dirstate will be set to the files of the given revision.
2629 2628 The actual working directory content or existing dirstate
2630 2629 information such as adds or removes is not considered.
2631 2630
2632 2631 One use of this command is to make the next :hg:`status` invocation
2633 2632 check the actual file content.
2634 2633 """
2635 2634 ctx = scmutil.revsingle(repo, rev)
2636 2635 wlock = repo.wlock()
2637 2636 try:
2638 2637 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2639 2638 finally:
2640 2639 wlock.release()
2641 2640
2642 2641 @command('debugrename',
2643 2642 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2644 2643 _('[-r REV] FILE'))
2645 2644 def debugrename(ui, repo, file1, *pats, **opts):
2646 2645 """dump rename information"""
2647 2646
2648 2647 ctx = scmutil.revsingle(repo, opts.get('rev'))
2649 2648 m = scmutil.match(ctx, (file1,) + pats, opts)
2650 2649 for abs in ctx.walk(m):
2651 2650 fctx = ctx[abs]
2652 2651 o = fctx.filelog().renamed(fctx.filenode())
2653 2652 rel = m.rel(abs)
2654 2653 if o:
2655 2654 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2656 2655 else:
2657 2656 ui.write(_("%s not renamed\n") % rel)
2658 2657
2659 2658 @command('debugrevlog',
2660 2659 [('c', 'changelog', False, _('open changelog')),
2661 2660 ('m', 'manifest', False, _('open manifest')),
2662 2661 ('d', 'dump', False, _('dump index data'))],
2663 2662 _('-c|-m|FILE'),
2664 2663 optionalrepo=True)
2665 2664 def debugrevlog(ui, repo, file_=None, **opts):
2666 2665 """show data and statistics about a revlog"""
2667 2666 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2668 2667
2669 2668 if opts.get("dump"):
2670 2669 numrevs = len(r)
2671 2670 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2672 2671 " rawsize totalsize compression heads chainlen\n")
2673 2672 ts = 0
2674 2673 heads = set()
2675 2674
2676 2675 for rev in xrange(numrevs):
2677 2676 dbase = r.deltaparent(rev)
2678 2677 if dbase == -1:
2679 2678 dbase = rev
2680 2679 cbase = r.chainbase(rev)
2681 2680 clen = r.chainlen(rev)
2682 2681 p1, p2 = r.parentrevs(rev)
2683 2682 rs = r.rawsize(rev)
2684 2683 ts = ts + rs
2685 2684 heads -= set(r.parentrevs(rev))
2686 2685 heads.add(rev)
2687 2686 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2688 2687 "%11d %5d %8d\n" %
2689 2688 (rev, p1, p2, r.start(rev), r.end(rev),
2690 2689 r.start(dbase), r.start(cbase),
2691 2690 r.start(p1), r.start(p2),
2692 2691 rs, ts, ts / r.end(rev), len(heads), clen))
2693 2692 return 0
2694 2693
2695 2694 v = r.version
2696 2695 format = v & 0xFFFF
2697 2696 flags = []
2698 2697 gdelta = False
2699 2698 if v & revlog.REVLOGNGINLINEDATA:
2700 2699 flags.append('inline')
2701 2700 if v & revlog.REVLOGGENERALDELTA:
2702 2701 gdelta = True
2703 2702 flags.append('generaldelta')
2704 2703 if not flags:
2705 2704 flags = ['(none)']
2706 2705
2707 2706 nummerges = 0
2708 2707 numfull = 0
2709 2708 numprev = 0
2710 2709 nump1 = 0
2711 2710 nump2 = 0
2712 2711 numother = 0
2713 2712 nump1prev = 0
2714 2713 nump2prev = 0
2715 2714 chainlengths = []
2716 2715
2717 2716 datasize = [None, 0, 0L]
2718 2717 fullsize = [None, 0, 0L]
2719 2718 deltasize = [None, 0, 0L]
2720 2719
2721 2720 def addsize(size, l):
2722 2721 if l[0] is None or size < l[0]:
2723 2722 l[0] = size
2724 2723 if size > l[1]:
2725 2724 l[1] = size
2726 2725 l[2] += size
2727 2726
2728 2727 numrevs = len(r)
2729 2728 for rev in xrange(numrevs):
2730 2729 p1, p2 = r.parentrevs(rev)
2731 2730 delta = r.deltaparent(rev)
2732 2731 if format > 0:
2733 2732 addsize(r.rawsize(rev), datasize)
2734 2733 if p2 != nullrev:
2735 2734 nummerges += 1
2736 2735 size = r.length(rev)
2737 2736 if delta == nullrev:
2738 2737 chainlengths.append(0)
2739 2738 numfull += 1
2740 2739 addsize(size, fullsize)
2741 2740 else:
2742 2741 chainlengths.append(chainlengths[delta] + 1)
2743 2742 addsize(size, deltasize)
2744 2743 if delta == rev - 1:
2745 2744 numprev += 1
2746 2745 if delta == p1:
2747 2746 nump1prev += 1
2748 2747 elif delta == p2:
2749 2748 nump2prev += 1
2750 2749 elif delta == p1:
2751 2750 nump1 += 1
2752 2751 elif delta == p2:
2753 2752 nump2 += 1
2754 2753 elif delta != nullrev:
2755 2754 numother += 1
2756 2755
2757 2756 # Adjust size min value for empty cases
2758 2757 for size in (datasize, fullsize, deltasize):
2759 2758 if size[0] is None:
2760 2759 size[0] = 0
2761 2760
2762 2761 numdeltas = numrevs - numfull
2763 2762 numoprev = numprev - nump1prev - nump2prev
2764 2763 totalrawsize = datasize[2]
2765 2764 datasize[2] /= numrevs
2766 2765 fulltotal = fullsize[2]
2767 2766 fullsize[2] /= numfull
2768 2767 deltatotal = deltasize[2]
2769 2768 if numrevs - numfull > 0:
2770 2769 deltasize[2] /= numrevs - numfull
2771 2770 totalsize = fulltotal + deltatotal
2772 2771 avgchainlen = sum(chainlengths) / numrevs
2773 2772 compratio = totalrawsize / totalsize
2774 2773
2775 2774 basedfmtstr = '%%%dd\n'
2776 2775 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2777 2776
2778 2777 def dfmtstr(max):
2779 2778 return basedfmtstr % len(str(max))
2780 2779 def pcfmtstr(max, padding=0):
2781 2780 return basepcfmtstr % (len(str(max)), ' ' * padding)
2782 2781
2783 2782 def pcfmt(value, total):
2784 2783 return (value, 100 * float(value) / total)
2785 2784
2786 2785 ui.write(('format : %d\n') % format)
2787 2786 ui.write(('flags : %s\n') % ', '.join(flags))
2788 2787
2789 2788 ui.write('\n')
2790 2789 fmt = pcfmtstr(totalsize)
2791 2790 fmt2 = dfmtstr(totalsize)
2792 2791 ui.write(('revisions : ') + fmt2 % numrevs)
2793 2792 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2794 2793 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2795 2794 ui.write(('revisions : ') + fmt2 % numrevs)
2796 2795 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2797 2796 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2798 2797 ui.write(('revision size : ') + fmt2 % totalsize)
2799 2798 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2800 2799 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2801 2800
2802 2801 ui.write('\n')
2803 2802 fmt = dfmtstr(max(avgchainlen, compratio))
2804 2803 ui.write(('avg chain length : ') + fmt % avgchainlen)
2805 2804 ui.write(('compression ratio : ') + fmt % compratio)
2806 2805
2807 2806 if format > 0:
2808 2807 ui.write('\n')
2809 2808 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2810 2809 % tuple(datasize))
2811 2810 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2812 2811 % tuple(fullsize))
2813 2812 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2814 2813 % tuple(deltasize))
2815 2814
2816 2815 if numdeltas > 0:
2817 2816 ui.write('\n')
2818 2817 fmt = pcfmtstr(numdeltas)
2819 2818 fmt2 = pcfmtstr(numdeltas, 4)
2820 2819 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2821 2820 if numprev > 0:
2822 2821 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2823 2822 numprev))
2824 2823 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2825 2824 numprev))
2826 2825 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2827 2826 numprev))
2828 2827 if gdelta:
2829 2828 ui.write(('deltas against p1 : ')
2830 2829 + fmt % pcfmt(nump1, numdeltas))
2831 2830 ui.write(('deltas against p2 : ')
2832 2831 + fmt % pcfmt(nump2, numdeltas))
2833 2832 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2834 2833 numdeltas))
2835 2834
2836 2835 @command('debugrevspec',
2837 2836 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2838 2837 ('REVSPEC'))
2839 2838 def debugrevspec(ui, repo, expr, **opts):
2840 2839 """parse and apply a revision specification
2841 2840
2842 2841 Use --verbose to print the parsed tree before and after aliases
2843 2842 expansion.
2844 2843 """
2845 2844 if ui.verbose:
2846 2845 tree = revset.parse(expr)[0]
2847 2846 ui.note(revset.prettyformat(tree), "\n")
2848 2847 newtree = revset.findaliases(ui, tree)
2849 2848 if newtree != tree:
2850 2849 ui.note(revset.prettyformat(newtree), "\n")
2851 2850 tree = newtree
2852 2851 newtree = revset.foldconcat(tree)
2853 2852 if newtree != tree:
2854 2853 ui.note(revset.prettyformat(newtree), "\n")
2855 2854 if opts["optimize"]:
2856 2855 weight, optimizedtree = revset.optimize(newtree, True)
2857 2856 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2858 2857 func = revset.match(ui, expr)
2859 2858 for c in func(repo, revset.spanset(repo)):
2860 2859 ui.write("%s\n" % c)
2861 2860
2862 2861 @command('debugsetparents', [], _('REV1 [REV2]'))
2863 2862 def debugsetparents(ui, repo, rev1, rev2=None):
2864 2863 """manually set the parents of the current working directory
2865 2864
2866 2865 This is useful for writing repository conversion tools, but should
2867 2866 be used with care.
2868 2867
2869 2868 Returns 0 on success.
2870 2869 """
2871 2870
2872 2871 r1 = scmutil.revsingle(repo, rev1).node()
2873 2872 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2874 2873
2875 2874 wlock = repo.wlock()
2876 2875 try:
2877 2876 repo.dirstate.beginparentchange()
2878 2877 repo.setparents(r1, r2)
2879 2878 repo.dirstate.endparentchange()
2880 2879 finally:
2881 2880 wlock.release()
2882 2881
2883 2882 @command('debugdirstate|debugstate',
2884 2883 [('', 'nodates', None, _('do not display the saved mtime')),
2885 2884 ('', 'datesort', None, _('sort by saved mtime'))],
2886 2885 _('[OPTION]...'))
2887 2886 def debugstate(ui, repo, nodates=None, datesort=None):
2888 2887 """show the contents of the current dirstate"""
2889 2888 timestr = ""
2890 2889 if datesort:
2891 2890 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2892 2891 else:
2893 2892 keyfunc = None # sort by filename
2894 2893 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2895 2894 if ent[3] == -1:
2896 2895 timestr = 'unset '
2897 2896 elif nodates:
2898 2897 timestr = 'set '
2899 2898 else:
2900 2899 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2901 2900 time.localtime(ent[3]))
2902 2901 if ent[1] & 020000:
2903 2902 mode = 'lnk'
2904 2903 else:
2905 2904 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2906 2905 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2907 2906 for f in repo.dirstate.copies():
2908 2907 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2909 2908
2910 2909 @command('debugsub',
2911 2910 [('r', 'rev', '',
2912 2911 _('revision to check'), _('REV'))],
2913 2912 _('[-r REV] [REV]'))
2914 2913 def debugsub(ui, repo, rev=None):
2915 2914 ctx = scmutil.revsingle(repo, rev, None)
2916 2915 for k, v in sorted(ctx.substate.items()):
2917 2916 ui.write(('path %s\n') % k)
2918 2917 ui.write((' source %s\n') % v[0])
2919 2918 ui.write((' revision %s\n') % v[1])
2920 2919
2921 2920 @command('debugsuccessorssets',
2922 2921 [],
2923 2922 _('[REV]'))
2924 2923 def debugsuccessorssets(ui, repo, *revs):
2925 2924 """show set of successors for revision
2926 2925
2927 2926 A successors set of changeset A is a consistent group of revisions that
2928 2927 succeed A. It contains non-obsolete changesets only.
2929 2928
2930 2929 In most cases a changeset A has a single successors set containing a single
2931 2930 successor (changeset A replaced by A').
2932 2931
2933 2932 A changeset that is made obsolete with no successors are called "pruned".
2934 2933 Such changesets have no successors sets at all.
2935 2934
2936 2935 A changeset that has been "split" will have a successors set containing
2937 2936 more than one successor.
2938 2937
2939 2938 A changeset that has been rewritten in multiple different ways is called
2940 2939 "divergent". Such changesets have multiple successor sets (each of which
2941 2940 may also be split, i.e. have multiple successors).
2942 2941
2943 2942 Results are displayed as follows::
2944 2943
2945 2944 <rev1>
2946 2945 <successors-1A>
2947 2946 <rev2>
2948 2947 <successors-2A>
2949 2948 <successors-2B1> <successors-2B2> <successors-2B3>
2950 2949
2951 2950 Here rev2 has two possible (i.e. divergent) successors sets. The first
2952 2951 holds one element, whereas the second holds three (i.e. the changeset has
2953 2952 been split).
2954 2953 """
2955 2954 # passed to successorssets caching computation from one call to another
2956 2955 cache = {}
2957 2956 ctx2str = str
2958 2957 node2str = short
2959 2958 if ui.debug():
2960 2959 def ctx2str(ctx):
2961 2960 return ctx.hex()
2962 2961 node2str = hex
2963 2962 for rev in scmutil.revrange(repo, revs):
2964 2963 ctx = repo[rev]
2965 2964 ui.write('%s\n'% ctx2str(ctx))
2966 2965 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2967 2966 if succsset:
2968 2967 ui.write(' ')
2969 2968 ui.write(node2str(succsset[0]))
2970 2969 for node in succsset[1:]:
2971 2970 ui.write(' ')
2972 2971 ui.write(node2str(node))
2973 2972 ui.write('\n')
2974 2973
2975 2974 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
2976 2975 def debugwalk(ui, repo, *pats, **opts):
2977 2976 """show how files match on given patterns"""
2978 2977 m = scmutil.match(repo[None], pats, opts)
2979 2978 items = list(repo.walk(m))
2980 2979 if not items:
2981 2980 return
2982 2981 f = lambda fn: fn
2983 2982 if ui.configbool('ui', 'slash') and os.sep != '/':
2984 2983 f = lambda fn: util.normpath(fn)
2985 2984 fmt = 'f %%-%ds %%-%ds %%s' % (
2986 2985 max([len(abs) for abs in items]),
2987 2986 max([len(m.rel(abs)) for abs in items]))
2988 2987 for abs in items:
2989 2988 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2990 2989 ui.write("%s\n" % line.rstrip())
2991 2990
2992 2991 @command('debugwireargs',
2993 2992 [('', 'three', '', 'three'),
2994 2993 ('', 'four', '', 'four'),
2995 2994 ('', 'five', '', 'five'),
2996 2995 ] + remoteopts,
2997 2996 _('REPO [OPTIONS]... [ONE [TWO]]'),
2998 2997 norepo=True)
2999 2998 def debugwireargs(ui, repopath, *vals, **opts):
3000 2999 repo = hg.peer(ui, opts, repopath)
3001 3000 for opt in remoteopts:
3002 3001 del opts[opt[1]]
3003 3002 args = {}
3004 3003 for k, v in opts.iteritems():
3005 3004 if v:
3006 3005 args[k] = v
3007 3006 # run twice to check that we don't mess up the stream for the next command
3008 3007 res1 = repo.debugwireargs(*vals, **args)
3009 3008 res2 = repo.debugwireargs(*vals, **args)
3010 3009 ui.write("%s\n" % res1)
3011 3010 if res1 != res2:
3012 3011 ui.warn("%s\n" % res2)
3013 3012
3014 3013 @command('^diff',
3015 3014 [('r', 'rev', [], _('revision'), _('REV')),
3016 3015 ('c', 'change', '', _('change made by revision'), _('REV'))
3017 3016 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3018 3017 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3019 3018 inferrepo=True)
3020 3019 def diff(ui, repo, *pats, **opts):
3021 3020 """diff repository (or selected files)
3022 3021
3023 3022 Show differences between revisions for the specified files.
3024 3023
3025 3024 Differences between files are shown using the unified diff format.
3026 3025
3027 3026 .. note::
3028 3027
3029 3028 diff may generate unexpected results for merges, as it will
3030 3029 default to comparing against the working directory's first
3031 3030 parent changeset if no revisions are specified.
3032 3031
3033 3032 When two revision arguments are given, then changes are shown
3034 3033 between those revisions. If only one revision is specified then
3035 3034 that revision is compared to the working directory, and, when no
3036 3035 revisions are specified, the working directory files are compared
3037 3036 to its parent.
3038 3037
3039 3038 Alternatively you can specify -c/--change with a revision to see
3040 3039 the changes in that changeset relative to its first parent.
3041 3040
3042 3041 Without the -a/--text option, diff will avoid generating diffs of
3043 3042 files it detects as binary. With -a, diff will generate a diff
3044 3043 anyway, probably with undesirable results.
3045 3044
3046 3045 Use the -g/--git option to generate diffs in the git extended diff
3047 3046 format. For more information, read :hg:`help diffs`.
3048 3047
3049 3048 .. container:: verbose
3050 3049
3051 3050 Examples:
3052 3051
3053 3052 - compare a file in the current working directory to its parent::
3054 3053
3055 3054 hg diff foo.c
3056 3055
3057 3056 - compare two historical versions of a directory, with rename info::
3058 3057
3059 3058 hg diff --git -r 1.0:1.2 lib/
3060 3059
3061 3060 - get change stats relative to the last change on some date::
3062 3061
3063 3062 hg diff --stat -r "date('may 2')"
3064 3063
3065 3064 - diff all newly-added files that contain a keyword::
3066 3065
3067 3066 hg diff "set:added() and grep(GNU)"
3068 3067
3069 3068 - compare a revision and its parents::
3070 3069
3071 3070 hg diff -c 9353 # compare against first parent
3072 3071 hg diff -r 9353^:9353 # same using revset syntax
3073 3072 hg diff -r 9353^2:9353 # compare against the second parent
3074 3073
3075 3074 Returns 0 on success.
3076 3075 """
3077 3076
3078 3077 revs = opts.get('rev')
3079 3078 change = opts.get('change')
3080 3079 stat = opts.get('stat')
3081 3080 reverse = opts.get('reverse')
3082 3081
3083 3082 if revs and change:
3084 3083 msg = _('cannot specify --rev and --change at the same time')
3085 3084 raise util.Abort(msg)
3086 3085 elif change:
3087 3086 node2 = scmutil.revsingle(repo, change, None).node()
3088 3087 node1 = repo[node2].p1().node()
3089 3088 else:
3090 3089 node1, node2 = scmutil.revpair(repo, revs)
3091 3090
3092 3091 if reverse:
3093 3092 node1, node2 = node2, node1
3094 3093
3095 3094 diffopts = patch.diffallopts(ui, opts)
3096 3095 m = scmutil.match(repo[node2], pats, opts)
3097 3096 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3098 3097 listsubrepos=opts.get('subrepos'))
3099 3098
3100 3099 @command('^export',
3101 3100 [('o', 'output', '',
3102 3101 _('print output to file with formatted name'), _('FORMAT')),
3103 3102 ('', 'switch-parent', None, _('diff against the second parent')),
3104 3103 ('r', 'rev', [], _('revisions to export'), _('REV')),
3105 3104 ] + diffopts,
3106 3105 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3107 3106 def export(ui, repo, *changesets, **opts):
3108 3107 """dump the header and diffs for one or more changesets
3109 3108
3110 3109 Print the changeset header and diffs for one or more revisions.
3111 3110 If no revision is given, the parent of the working directory is used.
3112 3111
3113 3112 The information shown in the changeset header is: author, date,
3114 3113 branch name (if non-default), changeset hash, parent(s) and commit
3115 3114 comment.
3116 3115
3117 3116 .. note::
3118 3117
3119 3118 export may generate unexpected diff output for merge
3120 3119 changesets, as it will compare the merge changeset against its
3121 3120 first parent only.
3122 3121
3123 3122 Output may be to a file, in which case the name of the file is
3124 3123 given using a format string. The formatting rules are as follows:
3125 3124
3126 3125 :``%%``: literal "%" character
3127 3126 :``%H``: changeset hash (40 hexadecimal digits)
3128 3127 :``%N``: number of patches being generated
3129 3128 :``%R``: changeset revision number
3130 3129 :``%b``: basename of the exporting repository
3131 3130 :``%h``: short-form changeset hash (12 hexadecimal digits)
3132 3131 :``%m``: first line of the commit message (only alphanumeric characters)
3133 3132 :``%n``: zero-padded sequence number, starting at 1
3134 3133 :``%r``: zero-padded changeset revision number
3135 3134
3136 3135 Without the -a/--text option, export will avoid generating diffs
3137 3136 of files it detects as binary. With -a, export will generate a
3138 3137 diff anyway, probably with undesirable results.
3139 3138
3140 3139 Use the -g/--git option to generate diffs in the git extended diff
3141 3140 format. See :hg:`help diffs` for more information.
3142 3141
3143 3142 With the --switch-parent option, the diff will be against the
3144 3143 second parent. It can be useful to review a merge.
3145 3144
3146 3145 .. container:: verbose
3147 3146
3148 3147 Examples:
3149 3148
3150 3149 - use export and import to transplant a bugfix to the current
3151 3150 branch::
3152 3151
3153 3152 hg export -r 9353 | hg import -
3154 3153
3155 3154 - export all the changesets between two revisions to a file with
3156 3155 rename information::
3157 3156
3158 3157 hg export --git -r 123:150 > changes.txt
3159 3158
3160 3159 - split outgoing changes into a series of patches with
3161 3160 descriptive names::
3162 3161
3163 3162 hg export -r "outgoing()" -o "%n-%m.patch"
3164 3163
3165 3164 Returns 0 on success.
3166 3165 """
3167 3166 changesets += tuple(opts.get('rev', []))
3168 3167 if not changesets:
3169 3168 changesets = ['.']
3170 3169 revs = scmutil.revrange(repo, changesets)
3171 3170 if not revs:
3172 3171 raise util.Abort(_("export requires at least one changeset"))
3173 3172 if len(revs) > 1:
3174 3173 ui.note(_('exporting patches:\n'))
3175 3174 else:
3176 3175 ui.note(_('exporting patch:\n'))
3177 3176 cmdutil.export(repo, revs, template=opts.get('output'),
3178 3177 switch_parent=opts.get('switch_parent'),
3179 3178 opts=patch.diffallopts(ui, opts))
3180 3179
3181 3180 @command('files',
3182 3181 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3183 3182 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3184 3183 ] + walkopts + formatteropts,
3185 3184 _('[OPTION]... [PATTERN]...'))
3186 3185 def files(ui, repo, *pats, **opts):
3187 3186 """list tracked files
3188 3187
3189 3188 Print files under Mercurial control in the working directory or
3190 3189 specified revision whose names match the given patterns (excluding
3191 3190 removed files).
3192 3191
3193 3192 If no patterns are given to match, this command prints the names
3194 3193 of all files under Mercurial control in the working copy.
3195 3194
3196 3195 .. container:: verbose
3197 3196
3198 3197 Examples:
3199 3198
3200 3199 - list all files under the current directory::
3201 3200
3202 3201 hg files .
3203 3202
3204 3203 - shows sizes and flags for current revision::
3205 3204
3206 3205 hg files -vr .
3207 3206
3208 3207 - list all files named README::
3209 3208
3210 3209 hg files -I "**/README"
3211 3210
3212 3211 - list all binary files::
3213 3212
3214 3213 hg files "set:binary()"
3215 3214
3216 3215 - find files containing a regular expression::
3217 3216
3218 3217 hg files "set:grep('bob')"
3219 3218
3220 3219 - search tracked file contents with xargs and grep::
3221 3220
3222 3221 hg files -0 | xargs -0 grep foo
3223 3222
3224 3223 See :hg:`help patterns` and :hg:`help filesets` for more information
3225 3224 on specifying file patterns.
3226 3225
3227 3226 Returns 0 if a match is found, 1 otherwise.
3228 3227
3229 3228 """
3230 3229 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3231 3230 rev = ctx.rev()
3232 3231 ret = 1
3233 3232
3234 3233 end = '\n'
3235 3234 if opts.get('print0'):
3236 3235 end = '\0'
3237 3236 fm = ui.formatter('files', opts)
3238 3237 fmt = '%s' + end
3239 3238
3240 3239 m = scmutil.match(ctx, pats, opts)
3241 3240 ds = repo.dirstate
3242 3241 for f in ctx.matches(m):
3243 3242 if rev is None and ds[f] == 'r':
3244 3243 continue
3245 3244 fm.startitem()
3246 3245 if ui.verbose:
3247 3246 fc = ctx[f]
3248 3247 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
3249 3248 fm.data(abspath=f)
3250 3249 fm.write('path', fmt, m.rel(f))
3251 3250 ret = 0
3252 3251
3253 3252 fm.end()
3254 3253
3255 3254 return ret
3256 3255
3257 3256 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3258 3257 def forget(ui, repo, *pats, **opts):
3259 3258 """forget the specified files on the next commit
3260 3259
3261 3260 Mark the specified files so they will no longer be tracked
3262 3261 after the next commit.
3263 3262
3264 3263 This only removes files from the current branch, not from the
3265 3264 entire project history, and it does not delete them from the
3266 3265 working directory.
3267 3266
3268 3267 To undo a forget before the next commit, see :hg:`add`.
3269 3268
3270 3269 .. container:: verbose
3271 3270
3272 3271 Examples:
3273 3272
3274 3273 - forget newly-added binary files::
3275 3274
3276 3275 hg forget "set:added() and binary()"
3277 3276
3278 3277 - forget files that would be excluded by .hgignore::
3279 3278
3280 3279 hg forget "set:hgignore()"
3281 3280
3282 3281 Returns 0 on success.
3283 3282 """
3284 3283
3285 3284 if not pats:
3286 3285 raise util.Abort(_('no files specified'))
3287 3286
3288 3287 m = scmutil.match(repo[None], pats, opts)
3289 3288 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3290 3289 return rejected and 1 or 0
3291 3290
3292 3291 @command(
3293 3292 'graft',
3294 3293 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3295 3294 ('c', 'continue', False, _('resume interrupted graft')),
3296 3295 ('e', 'edit', False, _('invoke editor on commit messages')),
3297 3296 ('', 'log', None, _('append graft info to log message')),
3298 3297 ('f', 'force', False, _('force graft')),
3299 3298 ('D', 'currentdate', False,
3300 3299 _('record the current date as commit date')),
3301 3300 ('U', 'currentuser', False,
3302 3301 _('record the current user as committer'), _('DATE'))]
3303 3302 + commitopts2 + mergetoolopts + dryrunopts,
3304 3303 _('[OPTION]... [-r] REV...'))
3305 3304 def graft(ui, repo, *revs, **opts):
3306 3305 '''copy changes from other branches onto the current branch
3307 3306
3308 3307 This command uses Mercurial's merge logic to copy individual
3309 3308 changes from other branches without merging branches in the
3310 3309 history graph. This is sometimes known as 'backporting' or
3311 3310 'cherry-picking'. By default, graft will copy user, date, and
3312 3311 description from the source changesets.
3313 3312
3314 3313 Changesets that are ancestors of the current revision, that have
3315 3314 already been grafted, or that are merges will be skipped.
3316 3315
3317 3316 If --log is specified, log messages will have a comment appended
3318 3317 of the form::
3319 3318
3320 3319 (grafted from CHANGESETHASH)
3321 3320
3322 3321 If --force is specified, revisions will be grafted even if they
3323 3322 are already ancestors of or have been grafted to the destination.
3324 3323 This is useful when the revisions have since been backed out.
3325 3324
3326 3325 If a graft merge results in conflicts, the graft process is
3327 3326 interrupted so that the current merge can be manually resolved.
3328 3327 Once all conflicts are addressed, the graft process can be
3329 3328 continued with the -c/--continue option.
3330 3329
3331 3330 .. note::
3332 3331
3333 3332 The -c/--continue option does not reapply earlier options, except
3334 3333 for --force.
3335 3334
3336 3335 .. container:: verbose
3337 3336
3338 3337 Examples:
3339 3338
3340 3339 - copy a single change to the stable branch and edit its description::
3341 3340
3342 3341 hg update stable
3343 3342 hg graft --edit 9393
3344 3343
3345 3344 - graft a range of changesets with one exception, updating dates::
3346 3345
3347 3346 hg graft -D "2085::2093 and not 2091"
3348 3347
3349 3348 - continue a graft after resolving conflicts::
3350 3349
3351 3350 hg graft -c
3352 3351
3353 3352 - show the source of a grafted changeset::
3354 3353
3355 3354 hg log --debug -r .
3356 3355
3357 3356 See :hg:`help revisions` and :hg:`help revsets` for more about
3358 3357 specifying revisions.
3359 3358
3360 3359 Returns 0 on successful completion.
3361 3360 '''
3362 3361
3363 3362 revs = list(revs)
3364 3363 revs.extend(opts['rev'])
3365 3364
3366 3365 if not opts.get('user') and opts.get('currentuser'):
3367 3366 opts['user'] = ui.username()
3368 3367 if not opts.get('date') and opts.get('currentdate'):
3369 3368 opts['date'] = "%d %d" % util.makedate()
3370 3369
3371 3370 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3372 3371
3373 3372 cont = False
3374 3373 if opts['continue']:
3375 3374 cont = True
3376 3375 if revs:
3377 3376 raise util.Abort(_("can't specify --continue and revisions"))
3378 3377 # read in unfinished revisions
3379 3378 try:
3380 3379 nodes = repo.vfs.read('graftstate').splitlines()
3381 3380 revs = [repo[node].rev() for node in nodes]
3382 3381 except IOError, inst:
3383 3382 if inst.errno != errno.ENOENT:
3384 3383 raise
3385 3384 raise util.Abort(_("no graft state found, can't continue"))
3386 3385 else:
3387 3386 cmdutil.checkunfinished(repo)
3388 3387 cmdutil.bailifchanged(repo)
3389 3388 if not revs:
3390 3389 raise util.Abort(_('no revisions specified'))
3391 3390 revs = scmutil.revrange(repo, revs)
3392 3391
3393 3392 skipped = set()
3394 3393 # check for merges
3395 3394 for rev in repo.revs('%ld and merge()', revs):
3396 3395 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3397 3396 skipped.add(rev)
3398 3397 revs = [r for r in revs if r not in skipped]
3399 3398 if not revs:
3400 3399 return -1
3401 3400
3402 3401 # Don't check in the --continue case, in effect retaining --force across
3403 3402 # --continues. That's because without --force, any revisions we decided to
3404 3403 # skip would have been filtered out here, so they wouldn't have made their
3405 3404 # way to the graftstate. With --force, any revisions we would have otherwise
3406 3405 # skipped would not have been filtered out, and if they hadn't been applied
3407 3406 # already, they'd have been in the graftstate.
3408 3407 if not (cont or opts.get('force')):
3409 3408 # check for ancestors of dest branch
3410 3409 crev = repo['.'].rev()
3411 3410 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3412 3411 # Cannot use x.remove(y) on smart set, this has to be a list.
3413 3412 # XXX make this lazy in the future
3414 3413 revs = list(revs)
3415 3414 # don't mutate while iterating, create a copy
3416 3415 for rev in list(revs):
3417 3416 if rev in ancestors:
3418 3417 ui.warn(_('skipping ancestor revision %d:%s\n') %
3419 3418 (rev, repo[rev]))
3420 3419 # XXX remove on list is slow
3421 3420 revs.remove(rev)
3422 3421 if not revs:
3423 3422 return -1
3424 3423
3425 3424 # analyze revs for earlier grafts
3426 3425 ids = {}
3427 3426 for ctx in repo.set("%ld", revs):
3428 3427 ids[ctx.hex()] = ctx.rev()
3429 3428 n = ctx.extra().get('source')
3430 3429 if n:
3431 3430 ids[n] = ctx.rev()
3432 3431
3433 3432 # check ancestors for earlier grafts
3434 3433 ui.debug('scanning for duplicate grafts\n')
3435 3434
3436 3435 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3437 3436 ctx = repo[rev]
3438 3437 n = ctx.extra().get('source')
3439 3438 if n in ids:
3440 3439 try:
3441 3440 r = repo[n].rev()
3442 3441 except error.RepoLookupError:
3443 3442 r = None
3444 3443 if r in revs:
3445 3444 ui.warn(_('skipping revision %d:%s '
3446 3445 '(already grafted to %d:%s)\n')
3447 3446 % (r, repo[r], rev, ctx))
3448 3447 revs.remove(r)
3449 3448 elif ids[n] in revs:
3450 3449 if r is None:
3451 3450 ui.warn(_('skipping already grafted revision %d:%s '
3452 3451 '(%d:%s also has unknown origin %s)\n')
3453 3452 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3454 3453 else:
3455 3454 ui.warn(_('skipping already grafted revision %d:%s '
3456 3455 '(%d:%s also has origin %d:%s)\n')
3457 3456 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3458 3457 revs.remove(ids[n])
3459 3458 elif ctx.hex() in ids:
3460 3459 r = ids[ctx.hex()]
3461 3460 ui.warn(_('skipping already grafted revision %d:%s '
3462 3461 '(was grafted from %d:%s)\n') %
3463 3462 (r, repo[r], rev, ctx))
3464 3463 revs.remove(r)
3465 3464 if not revs:
3466 3465 return -1
3467 3466
3468 3467 wlock = repo.wlock()
3469 3468 try:
3470 3469 for pos, ctx in enumerate(repo.set("%ld", revs)):
3471 3470 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3472 3471 ctx.description().split('\n', 1)[0])
3473 3472 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3474 3473 if names:
3475 3474 desc += ' (%s)' % ' '.join(names)
3476 3475 ui.status(_('grafting %s\n') % desc)
3477 3476 if opts.get('dry_run'):
3478 3477 continue
3479 3478
3480 3479 source = ctx.extra().get('source')
3481 3480 if not source:
3482 3481 source = ctx.hex()
3483 3482 extra = {'source': source}
3484 3483 user = ctx.user()
3485 3484 if opts.get('user'):
3486 3485 user = opts['user']
3487 3486 date = ctx.date()
3488 3487 if opts.get('date'):
3489 3488 date = opts['date']
3490 3489 message = ctx.description()
3491 3490 if opts.get('log'):
3492 3491 message += '\n(grafted from %s)' % ctx.hex()
3493 3492
3494 3493 # we don't merge the first commit when continuing
3495 3494 if not cont:
3496 3495 # perform the graft merge with p1(rev) as 'ancestor'
3497 3496 try:
3498 3497 # ui.forcemerge is an internal variable, do not document
3499 3498 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3500 3499 'graft')
3501 3500 stats = mergemod.graft(repo, ctx, ctx.p1(),
3502 3501 ['local', 'graft'])
3503 3502 finally:
3504 3503 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3505 3504 # report any conflicts
3506 3505 if stats and stats[3] > 0:
3507 3506 # write out state for --continue
3508 3507 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3509 3508 repo.vfs.write('graftstate', ''.join(nodelines))
3510 3509 raise util.Abort(
3511 3510 _("unresolved conflicts, can't continue"),
3512 3511 hint=_('use hg resolve and hg graft --continue'))
3513 3512 else:
3514 3513 cont = False
3515 3514
3516 3515 # commit
3517 3516 node = repo.commit(text=message, user=user,
3518 3517 date=date, extra=extra, editor=editor)
3519 3518 if node is None:
3520 3519 ui.warn(
3521 3520 _('note: graft of %d:%s created no changes to commit\n') %
3522 3521 (ctx.rev(), ctx))
3523 3522 finally:
3524 3523 wlock.release()
3525 3524
3526 3525 # remove state when we complete successfully
3527 3526 if not opts.get('dry_run'):
3528 3527 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3529 3528
3530 3529 return 0
3531 3530
3532 3531 @command('grep',
3533 3532 [('0', 'print0', None, _('end fields with NUL')),
3534 3533 ('', 'all', None, _('print all revisions that match')),
3535 3534 ('a', 'text', None, _('treat all files as text')),
3536 3535 ('f', 'follow', None,
3537 3536 _('follow changeset history,'
3538 3537 ' or file history across copies and renames')),
3539 3538 ('i', 'ignore-case', None, _('ignore case when matching')),
3540 3539 ('l', 'files-with-matches', None,
3541 3540 _('print only filenames and revisions that match')),
3542 3541 ('n', 'line-number', None, _('print matching line numbers')),
3543 3542 ('r', 'rev', [],
3544 3543 _('only search files changed within revision range'), _('REV')),
3545 3544 ('u', 'user', None, _('list the author (long with -v)')),
3546 3545 ('d', 'date', None, _('list the date (short with -q)')),
3547 3546 ] + walkopts,
3548 3547 _('[OPTION]... PATTERN [FILE]...'),
3549 3548 inferrepo=True)
3550 3549 def grep(ui, repo, pattern, *pats, **opts):
3551 3550 """search for a pattern in specified files and revisions
3552 3551
3553 3552 Search revisions of files for a regular expression.
3554 3553
3555 3554 This command behaves differently than Unix grep. It only accepts
3556 3555 Python/Perl regexps. It searches repository history, not the
3557 3556 working directory. It always prints the revision number in which a
3558 3557 match appears.
3559 3558
3560 3559 By default, grep only prints output for the first revision of a
3561 3560 file in which it finds a match. To get it to print every revision
3562 3561 that contains a change in match status ("-" for a match that
3563 3562 becomes a non-match, or "+" for a non-match that becomes a match),
3564 3563 use the --all flag.
3565 3564
3566 3565 Returns 0 if a match is found, 1 otherwise.
3567 3566 """
3568 3567 reflags = re.M
3569 3568 if opts.get('ignore_case'):
3570 3569 reflags |= re.I
3571 3570 try:
3572 3571 regexp = util.re.compile(pattern, reflags)
3573 3572 except re.error, inst:
3574 3573 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3575 3574 return 1
3576 3575 sep, eol = ':', '\n'
3577 3576 if opts.get('print0'):
3578 3577 sep = eol = '\0'
3579 3578
3580 3579 getfile = util.lrucachefunc(repo.file)
3581 3580
3582 3581 def matchlines(body):
3583 3582 begin = 0
3584 3583 linenum = 0
3585 3584 while begin < len(body):
3586 3585 match = regexp.search(body, begin)
3587 3586 if not match:
3588 3587 break
3589 3588 mstart, mend = match.span()
3590 3589 linenum += body.count('\n', begin, mstart) + 1
3591 3590 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3592 3591 begin = body.find('\n', mend) + 1 or len(body) + 1
3593 3592 lend = begin - 1
3594 3593 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3595 3594
3596 3595 class linestate(object):
3597 3596 def __init__(self, line, linenum, colstart, colend):
3598 3597 self.line = line
3599 3598 self.linenum = linenum
3600 3599 self.colstart = colstart
3601 3600 self.colend = colend
3602 3601
3603 3602 def __hash__(self):
3604 3603 return hash((self.linenum, self.line))
3605 3604
3606 3605 def __eq__(self, other):
3607 3606 return self.line == other.line
3608 3607
3609 3608 def __iter__(self):
3610 3609 yield (self.line[:self.colstart], '')
3611 3610 yield (self.line[self.colstart:self.colend], 'grep.match')
3612 3611 rest = self.line[self.colend:]
3613 3612 while rest != '':
3614 3613 match = regexp.search(rest)
3615 3614 if not match:
3616 3615 yield (rest, '')
3617 3616 break
3618 3617 mstart, mend = match.span()
3619 3618 yield (rest[:mstart], '')
3620 3619 yield (rest[mstart:mend], 'grep.match')
3621 3620 rest = rest[mend:]
3622 3621
3623 3622 matches = {}
3624 3623 copies = {}
3625 3624 def grepbody(fn, rev, body):
3626 3625 matches[rev].setdefault(fn, [])
3627 3626 m = matches[rev][fn]
3628 3627 for lnum, cstart, cend, line in matchlines(body):
3629 3628 s = linestate(line, lnum, cstart, cend)
3630 3629 m.append(s)
3631 3630
3632 3631 def difflinestates(a, b):
3633 3632 sm = difflib.SequenceMatcher(None, a, b)
3634 3633 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3635 3634 if tag == 'insert':
3636 3635 for i in xrange(blo, bhi):
3637 3636 yield ('+', b[i])
3638 3637 elif tag == 'delete':
3639 3638 for i in xrange(alo, ahi):
3640 3639 yield ('-', a[i])
3641 3640 elif tag == 'replace':
3642 3641 for i in xrange(alo, ahi):
3643 3642 yield ('-', a[i])
3644 3643 for i in xrange(blo, bhi):
3645 3644 yield ('+', b[i])
3646 3645
3647 3646 def display(fn, ctx, pstates, states):
3648 3647 rev = ctx.rev()
3649 3648 datefunc = ui.quiet and util.shortdate or util.datestr
3650 3649 found = False
3651 3650 @util.cachefunc
3652 3651 def binary():
3653 3652 flog = getfile(fn)
3654 3653 return util.binary(flog.read(ctx.filenode(fn)))
3655 3654
3656 3655 if opts.get('all'):
3657 3656 iter = difflinestates(pstates, states)
3658 3657 else:
3659 3658 iter = [('', l) for l in states]
3660 3659 for change, l in iter:
3661 3660 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3662 3661
3663 3662 if opts.get('line_number'):
3664 3663 cols.append((str(l.linenum), 'grep.linenumber'))
3665 3664 if opts.get('all'):
3666 3665 cols.append((change, 'grep.change'))
3667 3666 if opts.get('user'):
3668 3667 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3669 3668 if opts.get('date'):
3670 3669 cols.append((datefunc(ctx.date()), 'grep.date'))
3671 3670 for col, label in cols[:-1]:
3672 3671 ui.write(col, label=label)
3673 3672 ui.write(sep, label='grep.sep')
3674 3673 ui.write(cols[-1][0], label=cols[-1][1])
3675 3674 if not opts.get('files_with_matches'):
3676 3675 ui.write(sep, label='grep.sep')
3677 3676 if not opts.get('text') and binary():
3678 3677 ui.write(" Binary file matches")
3679 3678 else:
3680 3679 for s, label in l:
3681 3680 ui.write(s, label=label)
3682 3681 ui.write(eol)
3683 3682 found = True
3684 3683 if opts.get('files_with_matches'):
3685 3684 break
3686 3685 return found
3687 3686
3688 3687 skip = {}
3689 3688 revfiles = {}
3690 3689 matchfn = scmutil.match(repo[None], pats, opts)
3691 3690 found = False
3692 3691 follow = opts.get('follow')
3693 3692
3694 3693 def prep(ctx, fns):
3695 3694 rev = ctx.rev()
3696 3695 pctx = ctx.p1()
3697 3696 parent = pctx.rev()
3698 3697 matches.setdefault(rev, {})
3699 3698 matches.setdefault(parent, {})
3700 3699 files = revfiles.setdefault(rev, [])
3701 3700 for fn in fns:
3702 3701 flog = getfile(fn)
3703 3702 try:
3704 3703 fnode = ctx.filenode(fn)
3705 3704 except error.LookupError:
3706 3705 continue
3707 3706
3708 3707 copied = flog.renamed(fnode)
3709 3708 copy = follow and copied and copied[0]
3710 3709 if copy:
3711 3710 copies.setdefault(rev, {})[fn] = copy
3712 3711 if fn in skip:
3713 3712 if copy:
3714 3713 skip[copy] = True
3715 3714 continue
3716 3715 files.append(fn)
3717 3716
3718 3717 if fn not in matches[rev]:
3719 3718 grepbody(fn, rev, flog.read(fnode))
3720 3719
3721 3720 pfn = copy or fn
3722 3721 if pfn not in matches[parent]:
3723 3722 try:
3724 3723 fnode = pctx.filenode(pfn)
3725 3724 grepbody(pfn, parent, flog.read(fnode))
3726 3725 except error.LookupError:
3727 3726 pass
3728 3727
3729 3728 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3730 3729 rev = ctx.rev()
3731 3730 parent = ctx.p1().rev()
3732 3731 for fn in sorted(revfiles.get(rev, [])):
3733 3732 states = matches[rev][fn]
3734 3733 copy = copies.get(rev, {}).get(fn)
3735 3734 if fn in skip:
3736 3735 if copy:
3737 3736 skip[copy] = True
3738 3737 continue
3739 3738 pstates = matches.get(parent, {}).get(copy or fn, [])
3740 3739 if pstates or states:
3741 3740 r = display(fn, ctx, pstates, states)
3742 3741 found = found or r
3743 3742 if r and not opts.get('all'):
3744 3743 skip[fn] = True
3745 3744 if copy:
3746 3745 skip[copy] = True
3747 3746 del matches[rev]
3748 3747 del revfiles[rev]
3749 3748
3750 3749 return not found
3751 3750
3752 3751 @command('heads',
3753 3752 [('r', 'rev', '',
3754 3753 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3755 3754 ('t', 'topo', False, _('show topological heads only')),
3756 3755 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3757 3756 ('c', 'closed', False, _('show normal and closed branch heads')),
3758 3757 ] + templateopts,
3759 3758 _('[-ct] [-r STARTREV] [REV]...'))
3760 3759 def heads(ui, repo, *branchrevs, **opts):
3761 3760 """show branch heads
3762 3761
3763 3762 With no arguments, show all open branch heads in the repository.
3764 3763 Branch heads are changesets that have no descendants on the
3765 3764 same branch. They are where development generally takes place and
3766 3765 are the usual targets for update and merge operations.
3767 3766
3768 3767 If one or more REVs are given, only open branch heads on the
3769 3768 branches associated with the specified changesets are shown. This
3770 3769 means that you can use :hg:`heads .` to see the heads on the
3771 3770 currently checked-out branch.
3772 3771
3773 3772 If -c/--closed is specified, also show branch heads marked closed
3774 3773 (see :hg:`commit --close-branch`).
3775 3774
3776 3775 If STARTREV is specified, only those heads that are descendants of
3777 3776 STARTREV will be displayed.
3778 3777
3779 3778 If -t/--topo is specified, named branch mechanics will be ignored and only
3780 3779 topological heads (changesets with no children) will be shown.
3781 3780
3782 3781 Returns 0 if matching heads are found, 1 if not.
3783 3782 """
3784 3783
3785 3784 start = None
3786 3785 if 'rev' in opts:
3787 3786 start = scmutil.revsingle(repo, opts['rev'], None).node()
3788 3787
3789 3788 if opts.get('topo'):
3790 3789 heads = [repo[h] for h in repo.heads(start)]
3791 3790 else:
3792 3791 heads = []
3793 3792 for branch in repo.branchmap():
3794 3793 heads += repo.branchheads(branch, start, opts.get('closed'))
3795 3794 heads = [repo[h] for h in heads]
3796 3795
3797 3796 if branchrevs:
3798 3797 branches = set(repo[br].branch() for br in branchrevs)
3799 3798 heads = [h for h in heads if h.branch() in branches]
3800 3799
3801 3800 if opts.get('active') and branchrevs:
3802 3801 dagheads = repo.heads(start)
3803 3802 heads = [h for h in heads if h.node() in dagheads]
3804 3803
3805 3804 if branchrevs:
3806 3805 haveheads = set(h.branch() for h in heads)
3807 3806 if branches - haveheads:
3808 3807 headless = ', '.join(b for b in branches - haveheads)
3809 3808 msg = _('no open branch heads found on branches %s')
3810 3809 if opts.get('rev'):
3811 3810 msg += _(' (started at %s)') % opts['rev']
3812 3811 ui.warn((msg + '\n') % headless)
3813 3812
3814 3813 if not heads:
3815 3814 return 1
3816 3815
3817 3816 heads = sorted(heads, key=lambda x: -x.rev())
3818 3817 displayer = cmdutil.show_changeset(ui, repo, opts)
3819 3818 for ctx in heads:
3820 3819 displayer.show(ctx)
3821 3820 displayer.close()
3822 3821
3823 3822 @command('help',
3824 3823 [('e', 'extension', None, _('show only help for extensions')),
3825 3824 ('c', 'command', None, _('show only help for commands')),
3826 3825 ('k', 'keyword', '', _('show topics matching keyword')),
3827 3826 ],
3828 3827 _('[-ec] [TOPIC]'),
3829 3828 norepo=True)
3830 3829 def help_(ui, name=None, **opts):
3831 3830 """show help for a given topic or a help overview
3832 3831
3833 3832 With no arguments, print a list of commands with short help messages.
3834 3833
3835 3834 Given a topic, extension, or command name, print help for that
3836 3835 topic.
3837 3836
3838 3837 Returns 0 if successful.
3839 3838 """
3840 3839
3841 3840 textwidth = min(ui.termwidth(), 80) - 2
3842 3841
3843 3842 keep = []
3844 3843 if ui.verbose:
3845 3844 keep.append('verbose')
3846 3845 if sys.platform.startswith('win'):
3847 3846 keep.append('windows')
3848 3847 elif sys.platform == 'OpenVMS':
3849 3848 keep.append('vms')
3850 3849 elif sys.platform == 'plan9':
3851 3850 keep.append('plan9')
3852 3851 else:
3853 3852 keep.append('unix')
3854 3853 keep.append(sys.platform.lower())
3855 3854
3856 3855 section = None
3857 3856 if name and '.' in name:
3858 3857 name, section = name.split('.', 1)
3859 3858
3860 3859 text = help.help_(ui, name, **opts)
3861 3860
3862 3861 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3863 3862 section=section)
3864 3863 if section and not formatted:
3865 3864 raise util.Abort(_("help section not found"))
3866 3865
3867 3866 if 'verbose' in pruned:
3868 3867 keep.append('omitted')
3869 3868 else:
3870 3869 keep.append('notomitted')
3871 3870 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3872 3871 section=section)
3873 3872 ui.write(formatted)
3874 3873
3875 3874
3876 3875 @command('identify|id',
3877 3876 [('r', 'rev', '',
3878 3877 _('identify the specified revision'), _('REV')),
3879 3878 ('n', 'num', None, _('show local revision number')),
3880 3879 ('i', 'id', None, _('show global revision id')),
3881 3880 ('b', 'branch', None, _('show branch')),
3882 3881 ('t', 'tags', None, _('show tags')),
3883 3882 ('B', 'bookmarks', None, _('show bookmarks')),
3884 3883 ] + remoteopts,
3885 3884 _('[-nibtB] [-r REV] [SOURCE]'),
3886 3885 optionalrepo=True)
3887 3886 def identify(ui, repo, source=None, rev=None,
3888 3887 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3889 3888 """identify the working copy or specified revision
3890 3889
3891 3890 Print a summary identifying the repository state at REV using one or
3892 3891 two parent hash identifiers, followed by a "+" if the working
3893 3892 directory has uncommitted changes, the branch name (if not default),
3894 3893 a list of tags, and a list of bookmarks.
3895 3894
3896 3895 When REV is not given, print a summary of the current state of the
3897 3896 repository.
3898 3897
3899 3898 Specifying a path to a repository root or Mercurial bundle will
3900 3899 cause lookup to operate on that repository/bundle.
3901 3900
3902 3901 .. container:: verbose
3903 3902
3904 3903 Examples:
3905 3904
3906 3905 - generate a build identifier for the working directory::
3907 3906
3908 3907 hg id --id > build-id.dat
3909 3908
3910 3909 - find the revision corresponding to a tag::
3911 3910
3912 3911 hg id -n -r 1.3
3913 3912
3914 3913 - check the most recent revision of a remote repository::
3915 3914
3916 3915 hg id -r tip http://selenic.com/hg/
3917 3916
3918 3917 Returns 0 if successful.
3919 3918 """
3920 3919
3921 3920 if not repo and not source:
3922 3921 raise util.Abort(_("there is no Mercurial repository here "
3923 3922 "(.hg not found)"))
3924 3923
3925 3924 hexfunc = ui.debugflag and hex or short
3926 3925 default = not (num or id or branch or tags or bookmarks)
3927 3926 output = []
3928 3927 revs = []
3929 3928
3930 3929 if source:
3931 3930 source, branches = hg.parseurl(ui.expandpath(source))
3932 3931 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3933 3932 repo = peer.local()
3934 3933 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3935 3934
3936 3935 if not repo:
3937 3936 if num or branch or tags:
3938 3937 raise util.Abort(
3939 3938 _("can't query remote revision number, branch, or tags"))
3940 3939 if not rev and revs:
3941 3940 rev = revs[0]
3942 3941 if not rev:
3943 3942 rev = "tip"
3944 3943
3945 3944 remoterev = peer.lookup(rev)
3946 3945 if default or id:
3947 3946 output = [hexfunc(remoterev)]
3948 3947
3949 3948 def getbms():
3950 3949 bms = []
3951 3950
3952 3951 if 'bookmarks' in peer.listkeys('namespaces'):
3953 3952 hexremoterev = hex(remoterev)
3954 3953 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3955 3954 if bmr == hexremoterev]
3956 3955
3957 3956 return sorted(bms)
3958 3957
3959 3958 if bookmarks:
3960 3959 output.extend(getbms())
3961 3960 elif default and not ui.quiet:
3962 3961 # multiple bookmarks for a single parent separated by '/'
3963 3962 bm = '/'.join(getbms())
3964 3963 if bm:
3965 3964 output.append(bm)
3966 3965 else:
3967 3966 if not rev:
3968 3967 ctx = repo[None]
3969 3968 parents = ctx.parents()
3970 3969 changed = ""
3971 3970 if default or id or num:
3972 3971 if (util.any(repo.status())
3973 3972 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3974 3973 changed = '+'
3975 3974 if default or id:
3976 3975 output = ["%s%s" %
3977 3976 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3978 3977 if num:
3979 3978 output.append("%s%s" %
3980 3979 ('+'.join([str(p.rev()) for p in parents]), changed))
3981 3980 else:
3982 3981 ctx = scmutil.revsingle(repo, rev)
3983 3982 if default or id:
3984 3983 output = [hexfunc(ctx.node())]
3985 3984 if num:
3986 3985 output.append(str(ctx.rev()))
3987 3986
3988 3987 if default and not ui.quiet:
3989 3988 b = ctx.branch()
3990 3989 if b != 'default':
3991 3990 output.append("(%s)" % b)
3992 3991
3993 3992 # multiple tags for a single parent separated by '/'
3994 3993 t = '/'.join(ctx.tags())
3995 3994 if t:
3996 3995 output.append(t)
3997 3996
3998 3997 # multiple bookmarks for a single parent separated by '/'
3999 3998 bm = '/'.join(ctx.bookmarks())
4000 3999 if bm:
4001 4000 output.append(bm)
4002 4001 else:
4003 4002 if branch:
4004 4003 output.append(ctx.branch())
4005 4004
4006 4005 if tags:
4007 4006 output.extend(ctx.tags())
4008 4007
4009 4008 if bookmarks:
4010 4009 output.extend(ctx.bookmarks())
4011 4010
4012 4011 ui.write("%s\n" % ' '.join(output))
4013 4012
4014 4013 @command('import|patch',
4015 4014 [('p', 'strip', 1,
4016 4015 _('directory strip option for patch. This has the same '
4017 4016 'meaning as the corresponding patch option'), _('NUM')),
4018 4017 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4019 4018 ('e', 'edit', False, _('invoke editor on commit messages')),
4020 4019 ('f', 'force', None,
4021 4020 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4022 4021 ('', 'no-commit', None,
4023 4022 _("don't commit, just update the working directory")),
4024 4023 ('', 'bypass', None,
4025 4024 _("apply patch without touching the working directory")),
4026 4025 ('', 'partial', None,
4027 4026 _('commit even if some hunks fail')),
4028 4027 ('', 'exact', None,
4029 4028 _('apply patch to the nodes from which it was generated')),
4030 4029 ('', 'import-branch', None,
4031 4030 _('use any branch information in patch (implied by --exact)'))] +
4032 4031 commitopts + commitopts2 + similarityopts,
4033 4032 _('[OPTION]... PATCH...'))
4034 4033 def import_(ui, repo, patch1=None, *patches, **opts):
4035 4034 """import an ordered set of patches
4036 4035
4037 4036 Import a list of patches and commit them individually (unless
4038 4037 --no-commit is specified).
4039 4038
4040 4039 Because import first applies changes to the working directory,
4041 4040 import will abort if there are outstanding changes.
4042 4041
4043 4042 You can import a patch straight from a mail message. Even patches
4044 4043 as attachments work (to use the body part, it must have type
4045 4044 text/plain or text/x-patch). From and Subject headers of email
4046 4045 message are used as default committer and commit message. All
4047 4046 text/plain body parts before first diff are added to commit
4048 4047 message.
4049 4048
4050 4049 If the imported patch was generated by :hg:`export`, user and
4051 4050 description from patch override values from message headers and
4052 4051 body. Values given on command line with -m/--message and -u/--user
4053 4052 override these.
4054 4053
4055 4054 If --exact is specified, import will set the working directory to
4056 4055 the parent of each patch before applying it, and will abort if the
4057 4056 resulting changeset has a different ID than the one recorded in
4058 4057 the patch. This may happen due to character set problems or other
4059 4058 deficiencies in the text patch format.
4060 4059
4061 4060 Use --bypass to apply and commit patches directly to the
4062 4061 repository, not touching the working directory. Without --exact,
4063 4062 patches will be applied on top of the working directory parent
4064 4063 revision.
4065 4064
4066 4065 With -s/--similarity, hg will attempt to discover renames and
4067 4066 copies in the patch in the same way as :hg:`addremove`.
4068 4067
4069 4068 Use --partial to ensure a changeset will be created from the patch
4070 4069 even if some hunks fail to apply. Hunks that fail to apply will be
4071 4070 written to a <target-file>.rej file. Conflicts can then be resolved
4072 4071 by hand before :hg:`commit --amend` is run to update the created
4073 4072 changeset. This flag exists to let people import patches that
4074 4073 partially apply without losing the associated metadata (author,
4075 4074 date, description, ...). Note that when none of the hunk applies
4076 4075 cleanly, :hg:`import --partial` will create an empty changeset,
4077 4076 importing only the patch metadata.
4078 4077
4079 4078 To read a patch from standard input, use "-" as the patch name. If
4080 4079 a URL is specified, the patch will be downloaded from it.
4081 4080 See :hg:`help dates` for a list of formats valid for -d/--date.
4082 4081
4083 4082 .. container:: verbose
4084 4083
4085 4084 Examples:
4086 4085
4087 4086 - import a traditional patch from a website and detect renames::
4088 4087
4089 4088 hg import -s 80 http://example.com/bugfix.patch
4090 4089
4091 4090 - import a changeset from an hgweb server::
4092 4091
4093 4092 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4094 4093
4095 4094 - import all the patches in an Unix-style mbox::
4096 4095
4097 4096 hg import incoming-patches.mbox
4098 4097
4099 4098 - attempt to exactly restore an exported changeset (not always
4100 4099 possible)::
4101 4100
4102 4101 hg import --exact proposed-fix.patch
4103 4102
4104 4103 Returns 0 on success, 1 on partial success (see --partial).
4105 4104 """
4106 4105
4107 4106 if not patch1:
4108 4107 raise util.Abort(_('need at least one patch to import'))
4109 4108
4110 4109 patches = (patch1,) + patches
4111 4110
4112 4111 date = opts.get('date')
4113 4112 if date:
4114 4113 opts['date'] = util.parsedate(date)
4115 4114
4116 4115 update = not opts.get('bypass')
4117 4116 if not update and opts.get('no_commit'):
4118 4117 raise util.Abort(_('cannot use --no-commit with --bypass'))
4119 4118 try:
4120 4119 sim = float(opts.get('similarity') or 0)
4121 4120 except ValueError:
4122 4121 raise util.Abort(_('similarity must be a number'))
4123 4122 if sim < 0 or sim > 100:
4124 4123 raise util.Abort(_('similarity must be between 0 and 100'))
4125 4124 if sim and not update:
4126 4125 raise util.Abort(_('cannot use --similarity with --bypass'))
4127 4126 if opts.get('exact') and opts.get('edit'):
4128 4127 raise util.Abort(_('cannot use --exact with --edit'))
4129 4128
4130 4129 if update:
4131 4130 cmdutil.checkunfinished(repo)
4132 4131 if (opts.get('exact') or not opts.get('force')) and update:
4133 4132 cmdutil.bailifchanged(repo)
4134 4133
4135 4134 base = opts["base"]
4136 4135 wlock = lock = tr = None
4137 4136 msgs = []
4138 4137 ret = 0
4139 4138
4140 4139
4141 4140 try:
4142 4141 try:
4143 4142 wlock = repo.wlock()
4144 4143 repo.dirstate.beginparentchange()
4145 4144 if not opts.get('no_commit'):
4146 4145 lock = repo.lock()
4147 4146 tr = repo.transaction('import')
4148 4147 parents = repo.parents()
4149 4148 for patchurl in patches:
4150 4149 if patchurl == '-':
4151 4150 ui.status(_('applying patch from stdin\n'))
4152 4151 patchfile = ui.fin
4153 4152 patchurl = 'stdin' # for error message
4154 4153 else:
4155 4154 patchurl = os.path.join(base, patchurl)
4156 4155 ui.status(_('applying %s\n') % patchurl)
4157 4156 patchfile = hg.openpath(ui, patchurl)
4158 4157
4159 4158 haspatch = False
4160 4159 for hunk in patch.split(patchfile):
4161 4160 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4162 4161 parents, opts,
4163 4162 msgs, hg.clean)
4164 4163 if msg:
4165 4164 haspatch = True
4166 4165 ui.note(msg + '\n')
4167 4166 if update or opts.get('exact'):
4168 4167 parents = repo.parents()
4169 4168 else:
4170 4169 parents = [repo[node]]
4171 4170 if rej:
4172 4171 ui.write_err(_("patch applied partially\n"))
4173 4172 ui.write_err(_("(fix the .rej files and run "
4174 4173 "`hg commit --amend`)\n"))
4175 4174 ret = 1
4176 4175 break
4177 4176
4178 4177 if not haspatch:
4179 4178 raise util.Abort(_('%s: no diffs found') % patchurl)
4180 4179
4181 4180 if tr:
4182 4181 tr.close()
4183 4182 if msgs:
4184 4183 repo.savecommitmessage('\n* * *\n'.join(msgs))
4185 4184 repo.dirstate.endparentchange()
4186 4185 return ret
4187 4186 except: # re-raises
4188 4187 # wlock.release() indirectly calls dirstate.write(): since
4189 4188 # we're crashing, we do not want to change the working dir
4190 4189 # parent after all, so make sure it writes nothing
4191 4190 repo.dirstate.invalidate()
4192 4191 raise
4193 4192 finally:
4194 4193 if tr:
4195 4194 tr.release()
4196 4195 release(lock, wlock)
4197 4196
4198 4197 @command('incoming|in',
4199 4198 [('f', 'force', None,
4200 4199 _('run even if remote repository is unrelated')),
4201 4200 ('n', 'newest-first', None, _('show newest record first')),
4202 4201 ('', 'bundle', '',
4203 4202 _('file to store the bundles into'), _('FILE')),
4204 4203 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4205 4204 ('B', 'bookmarks', False, _("compare bookmarks")),
4206 4205 ('b', 'branch', [],
4207 4206 _('a specific branch you would like to pull'), _('BRANCH')),
4208 4207 ] + logopts + remoteopts + subrepoopts,
4209 4208 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4210 4209 def incoming(ui, repo, source="default", **opts):
4211 4210 """show new changesets found in source
4212 4211
4213 4212 Show new changesets found in the specified path/URL or the default
4214 4213 pull location. These are the changesets that would have been pulled
4215 4214 if a pull at the time you issued this command.
4216 4215
4217 4216 For remote repository, using --bundle avoids downloading the
4218 4217 changesets twice if the incoming is followed by a pull.
4219 4218
4220 4219 See pull for valid source format details.
4221 4220
4222 4221 .. container:: verbose
4223 4222
4224 4223 Examples:
4225 4224
4226 4225 - show incoming changes with patches and full description::
4227 4226
4228 4227 hg incoming -vp
4229 4228
4230 4229 - show incoming changes excluding merges, store a bundle::
4231 4230
4232 4231 hg in -vpM --bundle incoming.hg
4233 4232 hg pull incoming.hg
4234 4233
4235 4234 - briefly list changes inside a bundle::
4236 4235
4237 4236 hg in changes.hg -T "{desc|firstline}\\n"
4238 4237
4239 4238 Returns 0 if there are incoming changes, 1 otherwise.
4240 4239 """
4241 4240 if opts.get('graph'):
4242 4241 cmdutil.checkunsupportedgraphflags([], opts)
4243 4242 def display(other, chlist, displayer):
4244 4243 revdag = cmdutil.graphrevs(other, chlist, opts)
4245 4244 showparents = [ctx.node() for ctx in repo[None].parents()]
4246 4245 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4247 4246 graphmod.asciiedges)
4248 4247
4249 4248 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4250 4249 return 0
4251 4250
4252 4251 if opts.get('bundle') and opts.get('subrepos'):
4253 4252 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4254 4253
4255 4254 if opts.get('bookmarks'):
4256 4255 source, branches = hg.parseurl(ui.expandpath(source),
4257 4256 opts.get('branch'))
4258 4257 other = hg.peer(repo, opts, source)
4259 4258 if 'bookmarks' not in other.listkeys('namespaces'):
4260 4259 ui.warn(_("remote doesn't support bookmarks\n"))
4261 4260 return 0
4262 4261 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4263 4262 return bookmarks.diff(ui, repo, other)
4264 4263
4265 4264 repo._subtoppath = ui.expandpath(source)
4266 4265 try:
4267 4266 return hg.incoming(ui, repo, source, opts)
4268 4267 finally:
4269 4268 del repo._subtoppath
4270 4269
4271 4270
4272 4271 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4273 4272 norepo=True)
4274 4273 def init(ui, dest=".", **opts):
4275 4274 """create a new repository in the given directory
4276 4275
4277 4276 Initialize a new repository in the given directory. If the given
4278 4277 directory does not exist, it will be created.
4279 4278
4280 4279 If no directory is given, the current directory is used.
4281 4280
4282 4281 It is possible to specify an ``ssh://`` URL as the destination.
4283 4282 See :hg:`help urls` for more information.
4284 4283
4285 4284 Returns 0 on success.
4286 4285 """
4287 4286 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4288 4287
4289 4288 @command('locate',
4290 4289 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4291 4290 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4292 4291 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4293 4292 ] + walkopts,
4294 4293 _('[OPTION]... [PATTERN]...'))
4295 4294 def locate(ui, repo, *pats, **opts):
4296 4295 """locate files matching specific patterns (DEPRECATED)
4297 4296
4298 4297 Print files under Mercurial control in the working directory whose
4299 4298 names match the given patterns.
4300 4299
4301 4300 By default, this command searches all directories in the working
4302 4301 directory. To search just the current directory and its
4303 4302 subdirectories, use "--include .".
4304 4303
4305 4304 If no patterns are given to match, this command prints the names
4306 4305 of all files under Mercurial control in the working directory.
4307 4306
4308 4307 If you want to feed the output of this command into the "xargs"
4309 4308 command, use the -0 option to both this command and "xargs". This
4310 4309 will avoid the problem of "xargs" treating single filenames that
4311 4310 contain whitespace as multiple filenames.
4312 4311
4313 4312 See :hg:`help files` for a more versatile command.
4314 4313
4315 4314 Returns 0 if a match is found, 1 otherwise.
4316 4315 """
4317 4316 end = opts.get('print0') and '\0' or '\n'
4318 4317 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4319 4318
4320 4319 ret = 1
4321 4320 ctx = repo[rev]
4322 4321 m = scmutil.match(ctx, pats, opts, default='relglob')
4323 4322 m.bad = lambda x, y: False
4324 4323
4325 4324 for abs in ctx.matches(m):
4326 4325 if opts.get('fullpath'):
4327 4326 ui.write(repo.wjoin(abs), end)
4328 4327 else:
4329 4328 ui.write(((pats and m.rel(abs)) or abs), end)
4330 4329 ret = 0
4331 4330
4332 4331 return ret
4333 4332
4334 4333 @command('^log|history',
4335 4334 [('f', 'follow', None,
4336 4335 _('follow changeset history, or file history across copies and renames')),
4337 4336 ('', 'follow-first', None,
4338 4337 _('only follow the first parent of merge changesets (DEPRECATED)')),
4339 4338 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4340 4339 ('C', 'copies', None, _('show copied files')),
4341 4340 ('k', 'keyword', [],
4342 4341 _('do case-insensitive search for a given text'), _('TEXT')),
4343 4342 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4344 4343 ('', 'removed', None, _('include revisions where files were removed')),
4345 4344 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4346 4345 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4347 4346 ('', 'only-branch', [],
4348 4347 _('show only changesets within the given named branch (DEPRECATED)'),
4349 4348 _('BRANCH')),
4350 4349 ('b', 'branch', [],
4351 4350 _('show changesets within the given named branch'), _('BRANCH')),
4352 4351 ('P', 'prune', [],
4353 4352 _('do not display revision or any of its ancestors'), _('REV')),
4354 4353 ] + logopts + walkopts,
4355 4354 _('[OPTION]... [FILE]'),
4356 4355 inferrepo=True)
4357 4356 def log(ui, repo, *pats, **opts):
4358 4357 """show revision history of entire repository or files
4359 4358
4360 4359 Print the revision history of the specified files or the entire
4361 4360 project.
4362 4361
4363 4362 If no revision range is specified, the default is ``tip:0`` unless
4364 4363 --follow is set, in which case the working directory parent is
4365 4364 used as the starting revision.
4366 4365
4367 4366 File history is shown without following rename or copy history of
4368 4367 files. Use -f/--follow with a filename to follow history across
4369 4368 renames and copies. --follow without a filename will only show
4370 4369 ancestors or descendants of the starting revision.
4371 4370
4372 4371 By default this command prints revision number and changeset id,
4373 4372 tags, non-trivial parents, user, date and time, and a summary for
4374 4373 each commit. When the -v/--verbose switch is used, the list of
4375 4374 changed files and full commit message are shown.
4376 4375
4377 4376 With --graph the revisions are shown as an ASCII art DAG with the most
4378 4377 recent changeset at the top.
4379 4378 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4380 4379 and '+' represents a fork where the changeset from the lines below is a
4381 4380 parent of the 'o' merge on the same line.
4382 4381
4383 4382 .. note::
4384 4383
4385 4384 log -p/--patch may generate unexpected diff output for merge
4386 4385 changesets, as it will only compare the merge changeset against
4387 4386 its first parent. Also, only files different from BOTH parents
4388 4387 will appear in files:.
4389 4388
4390 4389 .. note::
4391 4390
4392 4391 for performance reasons, log FILE may omit duplicate changes
4393 4392 made on branches and will not show removals or mode changes. To
4394 4393 see all such changes, use the --removed switch.
4395 4394
4396 4395 .. container:: verbose
4397 4396
4398 4397 Some examples:
4399 4398
4400 4399 - changesets with full descriptions and file lists::
4401 4400
4402 4401 hg log -v
4403 4402
4404 4403 - changesets ancestral to the working directory::
4405 4404
4406 4405 hg log -f
4407 4406
4408 4407 - last 10 commits on the current branch::
4409 4408
4410 4409 hg log -l 10 -b .
4411 4410
4412 4411 - changesets showing all modifications of a file, including removals::
4413 4412
4414 4413 hg log --removed file.c
4415 4414
4416 4415 - all changesets that touch a directory, with diffs, excluding merges::
4417 4416
4418 4417 hg log -Mp lib/
4419 4418
4420 4419 - all revision numbers that match a keyword::
4421 4420
4422 4421 hg log -k bug --template "{rev}\\n"
4423 4422
4424 4423 - list available log templates::
4425 4424
4426 4425 hg log -T list
4427 4426
4428 4427 - check if a given changeset is included in a tagged release::
4429 4428
4430 4429 hg log -r "a21ccf and ancestor(1.9)"
4431 4430
4432 4431 - find all changesets by some user in a date range::
4433 4432
4434 4433 hg log -k alice -d "may 2008 to jul 2008"
4435 4434
4436 4435 - summary of all changesets after the last tag::
4437 4436
4438 4437 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4439 4438
4440 4439 See :hg:`help dates` for a list of formats valid for -d/--date.
4441 4440
4442 4441 See :hg:`help revisions` and :hg:`help revsets` for more about
4443 4442 specifying revisions.
4444 4443
4445 4444 See :hg:`help templates` for more about pre-packaged styles and
4446 4445 specifying custom templates.
4447 4446
4448 4447 Returns 0 on success.
4449 4448
4450 4449 """
4451 4450 if opts.get('graph'):
4452 4451 return cmdutil.graphlog(ui, repo, *pats, **opts)
4453 4452
4454 4453 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4455 4454 limit = cmdutil.loglimit(opts)
4456 4455 count = 0
4457 4456
4458 4457 getrenamed = None
4459 4458 if opts.get('copies'):
4460 4459 endrev = None
4461 4460 if opts.get('rev'):
4462 4461 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4463 4462 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4464 4463
4465 4464 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4466 4465 for rev in revs:
4467 4466 if count == limit:
4468 4467 break
4469 4468 ctx = repo[rev]
4470 4469 copies = None
4471 4470 if getrenamed is not None and rev:
4472 4471 copies = []
4473 4472 for fn in ctx.files():
4474 4473 rename = getrenamed(fn, rev)
4475 4474 if rename:
4476 4475 copies.append((fn, rename[0]))
4477 4476 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4478 4477 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4479 4478 if displayer.flush(rev):
4480 4479 count += 1
4481 4480
4482 4481 displayer.close()
4483 4482
4484 4483 @command('manifest',
4485 4484 [('r', 'rev', '', _('revision to display'), _('REV')),
4486 4485 ('', 'all', False, _("list files from all revisions"))]
4487 4486 + formatteropts,
4488 4487 _('[-r REV]'))
4489 4488 def manifest(ui, repo, node=None, rev=None, **opts):
4490 4489 """output the current or given revision of the project manifest
4491 4490
4492 4491 Print a list of version controlled files for the given revision.
4493 4492 If no revision is given, the first parent of the working directory
4494 4493 is used, or the null revision if no revision is checked out.
4495 4494
4496 4495 With -v, print file permissions, symlink and executable bits.
4497 4496 With --debug, print file revision hashes.
4498 4497
4499 4498 If option --all is specified, the list of all files from all revisions
4500 4499 is printed. This includes deleted and renamed files.
4501 4500
4502 4501 Returns 0 on success.
4503 4502 """
4504 4503
4505 4504 fm = ui.formatter('manifest', opts)
4506 4505
4507 4506 if opts.get('all'):
4508 4507 if rev or node:
4509 4508 raise util.Abort(_("can't specify a revision with --all"))
4510 4509
4511 4510 res = []
4512 4511 prefix = "data/"
4513 4512 suffix = ".i"
4514 4513 plen = len(prefix)
4515 4514 slen = len(suffix)
4516 4515 lock = repo.lock()
4517 4516 try:
4518 4517 for fn, b, size in repo.store.datafiles():
4519 4518 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4520 4519 res.append(fn[plen:-slen])
4521 4520 finally:
4522 4521 lock.release()
4523 4522 for f in res:
4524 4523 fm.startitem()
4525 4524 fm.write("path", '%s\n', f)
4526 4525 fm.end()
4527 4526 return
4528 4527
4529 4528 if rev and node:
4530 4529 raise util.Abort(_("please specify just one revision"))
4531 4530
4532 4531 if not node:
4533 4532 node = rev
4534 4533
4535 4534 char = {'l': '@', 'x': '*', '': ''}
4536 4535 mode = {'l': '644', 'x': '755', '': '644'}
4537 4536 ctx = scmutil.revsingle(repo, node)
4538 4537 mf = ctx.manifest()
4539 4538 for f in ctx:
4540 4539 fm.startitem()
4541 4540 fl = ctx[f].flags()
4542 4541 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4543 4542 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4544 4543 fm.write('path', '%s\n', f)
4545 4544 fm.end()
4546 4545
4547 4546 @command('^merge',
4548 4547 [('f', 'force', None,
4549 4548 _('force a merge including outstanding changes (DEPRECATED)')),
4550 4549 ('r', 'rev', '', _('revision to merge'), _('REV')),
4551 4550 ('P', 'preview', None,
4552 4551 _('review revisions to merge (no merge is performed)'))
4553 4552 ] + mergetoolopts,
4554 4553 _('[-P] [-f] [[-r] REV]'))
4555 4554 def merge(ui, repo, node=None, **opts):
4556 4555 """merge another revision into working directory
4557 4556
4558 4557 The current working directory is updated with all changes made in
4559 4558 the requested revision since the last common predecessor revision.
4560 4559
4561 4560 Files that changed between either parent are marked as changed for
4562 4561 the next commit and a commit must be performed before any further
4563 4562 updates to the repository are allowed. The next commit will have
4564 4563 two parents.
4565 4564
4566 4565 ``--tool`` can be used to specify the merge tool used for file
4567 4566 merges. It overrides the HGMERGE environment variable and your
4568 4567 configuration files. See :hg:`help merge-tools` for options.
4569 4568
4570 4569 If no revision is specified, the working directory's parent is a
4571 4570 head revision, and the current branch contains exactly one other
4572 4571 head, the other head is merged with by default. Otherwise, an
4573 4572 explicit revision with which to merge with must be provided.
4574 4573
4575 4574 :hg:`resolve` must be used to resolve unresolved files.
4576 4575
4577 4576 To undo an uncommitted merge, use :hg:`update --clean .` which
4578 4577 will check out a clean copy of the original merge parent, losing
4579 4578 all changes.
4580 4579
4581 4580 Returns 0 on success, 1 if there are unresolved files.
4582 4581 """
4583 4582
4584 4583 if opts.get('rev') and node:
4585 4584 raise util.Abort(_("please specify just one revision"))
4586 4585 if not node:
4587 4586 node = opts.get('rev')
4588 4587
4589 4588 if node:
4590 4589 node = scmutil.revsingle(repo, node).node()
4591 4590
4592 4591 if not node and repo._bookmarkcurrent:
4593 4592 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4594 4593 curhead = repo[repo._bookmarkcurrent].node()
4595 4594 if len(bmheads) == 2:
4596 4595 if curhead == bmheads[0]:
4597 4596 node = bmheads[1]
4598 4597 else:
4599 4598 node = bmheads[0]
4600 4599 elif len(bmheads) > 2:
4601 4600 raise util.Abort(_("multiple matching bookmarks to merge - "
4602 4601 "please merge with an explicit rev or bookmark"),
4603 4602 hint=_("run 'hg heads' to see all heads"))
4604 4603 elif len(bmheads) <= 1:
4605 4604 raise util.Abort(_("no matching bookmark to merge - "
4606 4605 "please merge with an explicit rev or bookmark"),
4607 4606 hint=_("run 'hg heads' to see all heads"))
4608 4607
4609 4608 if not node and not repo._bookmarkcurrent:
4610 4609 branch = repo[None].branch()
4611 4610 bheads = repo.branchheads(branch)
4612 4611 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4613 4612
4614 4613 if len(nbhs) > 2:
4615 4614 raise util.Abort(_("branch '%s' has %d heads - "
4616 4615 "please merge with an explicit rev")
4617 4616 % (branch, len(bheads)),
4618 4617 hint=_("run 'hg heads .' to see heads"))
4619 4618
4620 4619 parent = repo.dirstate.p1()
4621 4620 if len(nbhs) <= 1:
4622 4621 if len(bheads) > 1:
4623 4622 raise util.Abort(_("heads are bookmarked - "
4624 4623 "please merge with an explicit rev"),
4625 4624 hint=_("run 'hg heads' to see all heads"))
4626 4625 if len(repo.heads()) > 1:
4627 4626 raise util.Abort(_("branch '%s' has one head - "
4628 4627 "please merge with an explicit rev")
4629 4628 % branch,
4630 4629 hint=_("run 'hg heads' to see all heads"))
4631 4630 msg, hint = _('nothing to merge'), None
4632 4631 if parent != repo.lookup(branch):
4633 4632 hint = _("use 'hg update' instead")
4634 4633 raise util.Abort(msg, hint=hint)
4635 4634
4636 4635 if parent not in bheads:
4637 4636 raise util.Abort(_('working directory not at a head revision'),
4638 4637 hint=_("use 'hg update' or merge with an "
4639 4638 "explicit revision"))
4640 4639 if parent == nbhs[0]:
4641 4640 node = nbhs[-1]
4642 4641 else:
4643 4642 node = nbhs[0]
4644 4643
4645 4644 if opts.get('preview'):
4646 4645 # find nodes that are ancestors of p2 but not of p1
4647 4646 p1 = repo.lookup('.')
4648 4647 p2 = repo.lookup(node)
4649 4648 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4650 4649
4651 4650 displayer = cmdutil.show_changeset(ui, repo, opts)
4652 4651 for node in nodes:
4653 4652 displayer.show(repo[node])
4654 4653 displayer.close()
4655 4654 return 0
4656 4655
4657 4656 try:
4658 4657 # ui.forcemerge is an internal variable, do not document
4659 4658 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4660 4659 return hg.merge(repo, node, force=opts.get('force'))
4661 4660 finally:
4662 4661 ui.setconfig('ui', 'forcemerge', '', 'merge')
4663 4662
4664 4663 @command('outgoing|out',
4665 4664 [('f', 'force', None, _('run even when the destination is unrelated')),
4666 4665 ('r', 'rev', [],
4667 4666 _('a changeset intended to be included in the destination'), _('REV')),
4668 4667 ('n', 'newest-first', None, _('show newest record first')),
4669 4668 ('B', 'bookmarks', False, _('compare bookmarks')),
4670 4669 ('b', 'branch', [], _('a specific branch you would like to push'),
4671 4670 _('BRANCH')),
4672 4671 ] + logopts + remoteopts + subrepoopts,
4673 4672 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4674 4673 def outgoing(ui, repo, dest=None, **opts):
4675 4674 """show changesets not found in the destination
4676 4675
4677 4676 Show changesets not found in the specified destination repository
4678 4677 or the default push location. These are the changesets that would
4679 4678 be pushed if a push was requested.
4680 4679
4681 4680 See pull for details of valid destination formats.
4682 4681
4683 4682 Returns 0 if there are outgoing changes, 1 otherwise.
4684 4683 """
4685 4684 if opts.get('graph'):
4686 4685 cmdutil.checkunsupportedgraphflags([], opts)
4687 4686 o, other = hg._outgoing(ui, repo, dest, opts)
4688 4687 if not o:
4689 4688 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4690 4689 return
4691 4690
4692 4691 revdag = cmdutil.graphrevs(repo, o, opts)
4693 4692 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4694 4693 showparents = [ctx.node() for ctx in repo[None].parents()]
4695 4694 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4696 4695 graphmod.asciiedges)
4697 4696 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4698 4697 return 0
4699 4698
4700 4699 if opts.get('bookmarks'):
4701 4700 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4702 4701 dest, branches = hg.parseurl(dest, opts.get('branch'))
4703 4702 other = hg.peer(repo, opts, dest)
4704 4703 if 'bookmarks' not in other.listkeys('namespaces'):
4705 4704 ui.warn(_("remote doesn't support bookmarks\n"))
4706 4705 return 0
4707 4706 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4708 4707 return bookmarks.diff(ui, other, repo)
4709 4708
4710 4709 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4711 4710 try:
4712 4711 return hg.outgoing(ui, repo, dest, opts)
4713 4712 finally:
4714 4713 del repo._subtoppath
4715 4714
4716 4715 @command('parents',
4717 4716 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4718 4717 ] + templateopts,
4719 4718 _('[-r REV] [FILE]'),
4720 4719 inferrepo=True)
4721 4720 def parents(ui, repo, file_=None, **opts):
4722 4721 """show the parents of the working directory or revision (DEPRECATED)
4723 4722
4724 4723 Print the working directory's parent revisions. If a revision is
4725 4724 given via -r/--rev, the parent of that revision will be printed.
4726 4725 If a file argument is given, the revision in which the file was
4727 4726 last changed (before the working directory revision or the
4728 4727 argument to --rev if given) is printed.
4729 4728
4730 4729 See :hg:`summary` and :hg:`help revsets` for related information.
4731 4730
4732 4731 Returns 0 on success.
4733 4732 """
4734 4733
4735 4734 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4736 4735
4737 4736 if file_:
4738 4737 m = scmutil.match(ctx, (file_,), opts)
4739 4738 if m.anypats() or len(m.files()) != 1:
4740 4739 raise util.Abort(_('can only specify an explicit filename'))
4741 4740 file_ = m.files()[0]
4742 4741 filenodes = []
4743 4742 for cp in ctx.parents():
4744 4743 if not cp:
4745 4744 continue
4746 4745 try:
4747 4746 filenodes.append(cp.filenode(file_))
4748 4747 except error.LookupError:
4749 4748 pass
4750 4749 if not filenodes:
4751 4750 raise util.Abort(_("'%s' not found in manifest!") % file_)
4752 4751 p = []
4753 4752 for fn in filenodes:
4754 4753 fctx = repo.filectx(file_, fileid=fn)
4755 4754 p.append(fctx.node())
4756 4755 else:
4757 4756 p = [cp.node() for cp in ctx.parents()]
4758 4757
4759 4758 displayer = cmdutil.show_changeset(ui, repo, opts)
4760 4759 for n in p:
4761 4760 if n != nullid:
4762 4761 displayer.show(repo[n])
4763 4762 displayer.close()
4764 4763
4765 4764 @command('paths', [], _('[NAME]'), optionalrepo=True)
4766 4765 def paths(ui, repo, search=None):
4767 4766 """show aliases for remote repositories
4768 4767
4769 4768 Show definition of symbolic path name NAME. If no name is given,
4770 4769 show definition of all available names.
4771 4770
4772 4771 Option -q/--quiet suppresses all output when searching for NAME
4773 4772 and shows only the path names when listing all definitions.
4774 4773
4775 4774 Path names are defined in the [paths] section of your
4776 4775 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4777 4776 repository, ``.hg/hgrc`` is used, too.
4778 4777
4779 4778 The path names ``default`` and ``default-push`` have a special
4780 4779 meaning. When performing a push or pull operation, they are used
4781 4780 as fallbacks if no location is specified on the command-line.
4782 4781 When ``default-push`` is set, it will be used for push and
4783 4782 ``default`` will be used for pull; otherwise ``default`` is used
4784 4783 as the fallback for both. When cloning a repository, the clone
4785 4784 source is written as ``default`` in ``.hg/hgrc``. Note that
4786 4785 ``default`` and ``default-push`` apply to all inbound (e.g.
4787 4786 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4788 4787 :hg:`bundle`) operations.
4789 4788
4790 4789 See :hg:`help urls` for more information.
4791 4790
4792 4791 Returns 0 on success.
4793 4792 """
4794 4793 if search:
4795 4794 for name, path in ui.configitems("paths"):
4796 4795 if name == search:
4797 4796 ui.status("%s\n" % util.hidepassword(path))
4798 4797 return
4799 4798 if not ui.quiet:
4800 4799 ui.warn(_("not found!\n"))
4801 4800 return 1
4802 4801 else:
4803 4802 for name, path in ui.configitems("paths"):
4804 4803 if ui.quiet:
4805 4804 ui.write("%s\n" % name)
4806 4805 else:
4807 4806 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4808 4807
4809 4808 @command('phase',
4810 4809 [('p', 'public', False, _('set changeset phase to public')),
4811 4810 ('d', 'draft', False, _('set changeset phase to draft')),
4812 4811 ('s', 'secret', False, _('set changeset phase to secret')),
4813 4812 ('f', 'force', False, _('allow to move boundary backward')),
4814 4813 ('r', 'rev', [], _('target revision'), _('REV')),
4815 4814 ],
4816 4815 _('[-p|-d|-s] [-f] [-r] REV...'))
4817 4816 def phase(ui, repo, *revs, **opts):
4818 4817 """set or show the current phase name
4819 4818
4820 4819 With no argument, show the phase name of specified revisions.
4821 4820
4822 4821 With one of -p/--public, -d/--draft or -s/--secret, change the
4823 4822 phase value of the specified revisions.
4824 4823
4825 4824 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4826 4825 lower phase to an higher phase. Phases are ordered as follows::
4827 4826
4828 4827 public < draft < secret
4829 4828
4830 4829 Returns 0 on success, 1 if no phases were changed or some could not
4831 4830 be changed.
4832 4831 """
4833 4832 # search for a unique phase argument
4834 4833 targetphase = None
4835 4834 for idx, name in enumerate(phases.phasenames):
4836 4835 if opts[name]:
4837 4836 if targetphase is not None:
4838 4837 raise util.Abort(_('only one phase can be specified'))
4839 4838 targetphase = idx
4840 4839
4841 4840 # look for specified revision
4842 4841 revs = list(revs)
4843 4842 revs.extend(opts['rev'])
4844 4843 if not revs:
4845 4844 raise util.Abort(_('no revisions specified'))
4846 4845
4847 4846 revs = scmutil.revrange(repo, revs)
4848 4847
4849 4848 lock = None
4850 4849 ret = 0
4851 4850 if targetphase is None:
4852 4851 # display
4853 4852 for r in revs:
4854 4853 ctx = repo[r]
4855 4854 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4856 4855 else:
4857 4856 tr = None
4858 4857 lock = repo.lock()
4859 4858 try:
4860 4859 tr = repo.transaction("phase")
4861 4860 # set phase
4862 4861 if not revs:
4863 4862 raise util.Abort(_('empty revision set'))
4864 4863 nodes = [repo[r].node() for r in revs]
4865 4864 # moving revision from public to draft may hide them
4866 4865 # We have to check result on an unfiltered repository
4867 4866 unfi = repo.unfiltered()
4868 4867 getphase = unfi._phasecache.phase
4869 4868 olddata = [getphase(unfi, r) for r in unfi]
4870 4869 phases.advanceboundary(repo, tr, targetphase, nodes)
4871 4870 if opts['force']:
4872 4871 phases.retractboundary(repo, tr, targetphase, nodes)
4873 4872 tr.close()
4874 4873 finally:
4875 4874 if tr is not None:
4876 4875 tr.release()
4877 4876 lock.release()
4878 4877 getphase = unfi._phasecache.phase
4879 4878 newdata = [getphase(unfi, r) for r in unfi]
4880 4879 changes = sum(newdata[r] != olddata[r] for r in unfi)
4881 4880 cl = unfi.changelog
4882 4881 rejected = [n for n in nodes
4883 4882 if newdata[cl.rev(n)] < targetphase]
4884 4883 if rejected:
4885 4884 ui.warn(_('cannot move %i changesets to a higher '
4886 4885 'phase, use --force\n') % len(rejected))
4887 4886 ret = 1
4888 4887 if changes:
4889 4888 msg = _('phase changed for %i changesets\n') % changes
4890 4889 if ret:
4891 4890 ui.status(msg)
4892 4891 else:
4893 4892 ui.note(msg)
4894 4893 else:
4895 4894 ui.warn(_('no phases changed\n'))
4896 4895 ret = 1
4897 4896 return ret
4898 4897
4899 4898 def postincoming(ui, repo, modheads, optupdate, checkout):
4900 4899 if modheads == 0:
4901 4900 return
4902 4901 if optupdate:
4903 4902 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4904 4903 try:
4905 4904 ret = hg.update(repo, checkout)
4906 4905 except util.Abort, inst:
4907 4906 ui.warn(_("not updating: %s\n") % str(inst))
4908 4907 if inst.hint:
4909 4908 ui.warn(_("(%s)\n") % inst.hint)
4910 4909 return 0
4911 4910 if not ret and not checkout:
4912 4911 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4913 4912 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4914 4913 return ret
4915 4914 if modheads > 1:
4916 4915 currentbranchheads = len(repo.branchheads())
4917 4916 if currentbranchheads == modheads:
4918 4917 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4919 4918 elif currentbranchheads > 1:
4920 4919 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4921 4920 "merge)\n"))
4922 4921 else:
4923 4922 ui.status(_("(run 'hg heads' to see heads)\n"))
4924 4923 else:
4925 4924 ui.status(_("(run 'hg update' to get a working copy)\n"))
4926 4925
4927 4926 @command('^pull',
4928 4927 [('u', 'update', None,
4929 4928 _('update to new branch head if changesets were pulled')),
4930 4929 ('f', 'force', None, _('run even when remote repository is unrelated')),
4931 4930 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4932 4931 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4933 4932 ('b', 'branch', [], _('a specific branch you would like to pull'),
4934 4933 _('BRANCH')),
4935 4934 ] + remoteopts,
4936 4935 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4937 4936 def pull(ui, repo, source="default", **opts):
4938 4937 """pull changes from the specified source
4939 4938
4940 4939 Pull changes from a remote repository to a local one.
4941 4940
4942 4941 This finds all changes from the repository at the specified path
4943 4942 or URL and adds them to a local repository (the current one unless
4944 4943 -R is specified). By default, this does not update the copy of the
4945 4944 project in the working directory.
4946 4945
4947 4946 Use :hg:`incoming` if you want to see what would have been added
4948 4947 by a pull at the time you issued this command. If you then decide
4949 4948 to add those changes to the repository, you should use :hg:`pull
4950 4949 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4951 4950
4952 4951 If SOURCE is omitted, the 'default' path will be used.
4953 4952 See :hg:`help urls` for more information.
4954 4953
4955 4954 Returns 0 on success, 1 if an update had unresolved files.
4956 4955 """
4957 4956 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4958 4957 other = hg.peer(repo, opts, source)
4959 4958 try:
4960 4959 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4961 4960 revs, checkout = hg.addbranchrevs(repo, other, branches,
4962 4961 opts.get('rev'))
4963 4962
4964 4963 remotebookmarks = other.listkeys('bookmarks')
4965 4964
4966 4965 if opts.get('bookmark'):
4967 4966 if not revs:
4968 4967 revs = []
4969 4968 for b in opts['bookmark']:
4970 4969 if b not in remotebookmarks:
4971 4970 raise util.Abort(_('remote bookmark %s not found!') % b)
4972 4971 revs.append(remotebookmarks[b])
4973 4972
4974 4973 if revs:
4975 4974 try:
4976 4975 revs = [other.lookup(rev) for rev in revs]
4977 4976 except error.CapabilityError:
4978 4977 err = _("other repository doesn't support revision lookup, "
4979 4978 "so a rev cannot be specified.")
4980 4979 raise util.Abort(err)
4981 4980
4982 4981 modheads = exchange.pull(repo, other, heads=revs,
4983 4982 force=opts.get('force'),
4984 4983 bookmarks=opts.get('bookmark', ())).cgresult
4985 4984 if checkout:
4986 4985 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4987 4986 repo._subtoppath = source
4988 4987 try:
4989 4988 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4990 4989
4991 4990 finally:
4992 4991 del repo._subtoppath
4993 4992
4994 4993 finally:
4995 4994 other.close()
4996 4995 return ret
4997 4996
4998 4997 @command('^push',
4999 4998 [('f', 'force', None, _('force push')),
5000 4999 ('r', 'rev', [],
5001 5000 _('a changeset intended to be included in the destination'),
5002 5001 _('REV')),
5003 5002 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5004 5003 ('b', 'branch', [],
5005 5004 _('a specific branch you would like to push'), _('BRANCH')),
5006 5005 ('', 'new-branch', False, _('allow pushing a new branch')),
5007 5006 ] + remoteopts,
5008 5007 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5009 5008 def push(ui, repo, dest=None, **opts):
5010 5009 """push changes to the specified destination
5011 5010
5012 5011 Push changesets from the local repository to the specified
5013 5012 destination.
5014 5013
5015 5014 This operation is symmetrical to pull: it is identical to a pull
5016 5015 in the destination repository from the current one.
5017 5016
5018 5017 By default, push will not allow creation of new heads at the
5019 5018 destination, since multiple heads would make it unclear which head
5020 5019 to use. In this situation, it is recommended to pull and merge
5021 5020 before pushing.
5022 5021
5023 5022 Use --new-branch if you want to allow push to create a new named
5024 5023 branch that is not present at the destination. This allows you to
5025 5024 only create a new branch without forcing other changes.
5026 5025
5027 5026 .. note::
5028 5027
5029 5028 Extra care should be taken with the -f/--force option,
5030 5029 which will push all new heads on all branches, an action which will
5031 5030 almost always cause confusion for collaborators.
5032 5031
5033 5032 If -r/--rev is used, the specified revision and all its ancestors
5034 5033 will be pushed to the remote repository.
5035 5034
5036 5035 If -B/--bookmark is used, the specified bookmarked revision, its
5037 5036 ancestors, and the bookmark will be pushed to the remote
5038 5037 repository.
5039 5038
5040 5039 Please see :hg:`help urls` for important details about ``ssh://``
5041 5040 URLs. If DESTINATION is omitted, a default path will be used.
5042 5041
5043 5042 Returns 0 if push was successful, 1 if nothing to push.
5044 5043 """
5045 5044
5046 5045 if opts.get('bookmark'):
5047 5046 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5048 5047 for b in opts['bookmark']:
5049 5048 # translate -B options to -r so changesets get pushed
5050 5049 if b in repo._bookmarks:
5051 5050 opts.setdefault('rev', []).append(b)
5052 5051 else:
5053 5052 # if we try to push a deleted bookmark, translate it to null
5054 5053 # this lets simultaneous -r, -b options continue working
5055 5054 opts.setdefault('rev', []).append("null")
5056 5055
5057 5056 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5058 5057 dest, branches = hg.parseurl(dest, opts.get('branch'))
5059 5058 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5060 5059 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5061 5060 try:
5062 5061 other = hg.peer(repo, opts, dest)
5063 5062 except error.RepoError:
5064 5063 if dest == "default-push":
5065 5064 raise util.Abort(_("default repository not configured!"),
5066 5065 hint=_('see the "path" section in "hg help config"'))
5067 5066 else:
5068 5067 raise
5069 5068
5070 5069 if revs:
5071 5070 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5072 5071
5073 5072 repo._subtoppath = dest
5074 5073 try:
5075 5074 # push subrepos depth-first for coherent ordering
5076 5075 c = repo['']
5077 5076 subs = c.substate # only repos that are committed
5078 5077 for s in sorted(subs):
5079 5078 result = c.sub(s).push(opts)
5080 5079 if result == 0:
5081 5080 return not result
5082 5081 finally:
5083 5082 del repo._subtoppath
5084 5083 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5085 5084 newbranch=opts.get('new_branch'),
5086 5085 bookmarks=opts.get('bookmark', ()))
5087 5086
5088 5087 result = not pushop.cgresult
5089 5088
5090 5089 if pushop.bkresult is not None:
5091 5090 if pushop.bkresult == 2:
5092 5091 result = 2
5093 5092 elif not result and pushop.bkresult:
5094 5093 result = 2
5095 5094
5096 5095 return result
5097 5096
5098 5097 @command('recover', [])
5099 5098 def recover(ui, repo):
5100 5099 """roll back an interrupted transaction
5101 5100
5102 5101 Recover from an interrupted commit or pull.
5103 5102
5104 5103 This command tries to fix the repository status after an
5105 5104 interrupted operation. It should only be necessary when Mercurial
5106 5105 suggests it.
5107 5106
5108 5107 Returns 0 if successful, 1 if nothing to recover or verify fails.
5109 5108 """
5110 5109 if repo.recover():
5111 5110 return hg.verify(repo)
5112 5111 return 1
5113 5112
5114 5113 @command('^remove|rm',
5115 5114 [('A', 'after', None, _('record delete for missing files')),
5116 5115 ('f', 'force', None,
5117 5116 _('remove (and delete) file even if added or modified')),
5118 5117 ] + subrepoopts + walkopts,
5119 5118 _('[OPTION]... FILE...'),
5120 5119 inferrepo=True)
5121 5120 def remove(ui, repo, *pats, **opts):
5122 5121 """remove the specified files on the next commit
5123 5122
5124 5123 Schedule the indicated files for removal from the current branch.
5125 5124
5126 5125 This command schedules the files to be removed at the next commit.
5127 5126 To undo a remove before that, see :hg:`revert`. To undo added
5128 5127 files, see :hg:`forget`.
5129 5128
5130 5129 .. container:: verbose
5131 5130
5132 5131 -A/--after can be used to remove only files that have already
5133 5132 been deleted, -f/--force can be used to force deletion, and -Af
5134 5133 can be used to remove files from the next revision without
5135 5134 deleting them from the working directory.
5136 5135
5137 5136 The following table details the behavior of remove for different
5138 5137 file states (columns) and option combinations (rows). The file
5139 5138 states are Added [A], Clean [C], Modified [M] and Missing [!]
5140 5139 (as reported by :hg:`status`). The actions are Warn, Remove
5141 5140 (from branch) and Delete (from disk):
5142 5141
5143 5142 ========= == == == ==
5144 5143 opt/state A C M !
5145 5144 ========= == == == ==
5146 5145 none W RD W R
5147 5146 -f R RD RD R
5148 5147 -A W W W R
5149 5148 -Af R R R R
5150 5149 ========= == == == ==
5151 5150
5152 5151 Note that remove never deletes files in Added [A] state from the
5153 5152 working directory, not even if option --force is specified.
5154 5153
5155 5154 Returns 0 on success, 1 if any warnings encountered.
5156 5155 """
5157 5156
5158 5157 after, force = opts.get('after'), opts.get('force')
5159 5158 if not pats and not after:
5160 5159 raise util.Abort(_('no files specified'))
5161 5160
5162 5161 m = scmutil.match(repo[None], pats, opts)
5163 5162 subrepos = opts.get('subrepos')
5164 5163 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5165 5164
5166 5165 @command('rename|move|mv',
5167 5166 [('A', 'after', None, _('record a rename that has already occurred')),
5168 5167 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5169 5168 ] + walkopts + dryrunopts,
5170 5169 _('[OPTION]... SOURCE... DEST'))
5171 5170 def rename(ui, repo, *pats, **opts):
5172 5171 """rename files; equivalent of copy + remove
5173 5172
5174 5173 Mark dest as copies of sources; mark sources for deletion. If dest
5175 5174 is a directory, copies are put in that directory. If dest is a
5176 5175 file, there can only be one source.
5177 5176
5178 5177 By default, this command copies the contents of files as they
5179 5178 exist in the working directory. If invoked with -A/--after, the
5180 5179 operation is recorded, but no copying is performed.
5181 5180
5182 5181 This command takes effect at the next commit. To undo a rename
5183 5182 before that, see :hg:`revert`.
5184 5183
5185 5184 Returns 0 on success, 1 if errors are encountered.
5186 5185 """
5187 5186 wlock = repo.wlock(False)
5188 5187 try:
5189 5188 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5190 5189 finally:
5191 5190 wlock.release()
5192 5191
5193 5192 @command('resolve',
5194 5193 [('a', 'all', None, _('select all unresolved files')),
5195 5194 ('l', 'list', None, _('list state of files needing merge')),
5196 5195 ('m', 'mark', None, _('mark files as resolved')),
5197 5196 ('u', 'unmark', None, _('mark files as unresolved')),
5198 5197 ('n', 'no-status', None, _('hide status prefix'))]
5199 5198 + mergetoolopts + walkopts,
5200 5199 _('[OPTION]... [FILE]...'),
5201 5200 inferrepo=True)
5202 5201 def resolve(ui, repo, *pats, **opts):
5203 5202 """redo merges or set/view the merge status of files
5204 5203
5205 5204 Merges with unresolved conflicts are often the result of
5206 5205 non-interactive merging using the ``internal:merge`` configuration
5207 5206 setting, or a command-line merge tool like ``diff3``. The resolve
5208 5207 command is used to manage the files involved in a merge, after
5209 5208 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5210 5209 working directory must have two parents). See :hg:`help
5211 5210 merge-tools` for information on configuring merge tools.
5212 5211
5213 5212 The resolve command can be used in the following ways:
5214 5213
5215 5214 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5216 5215 files, discarding any previous merge attempts. Re-merging is not
5217 5216 performed for files already marked as resolved. Use ``--all/-a``
5218 5217 to select all unresolved files. ``--tool`` can be used to specify
5219 5218 the merge tool used for the given files. It overrides the HGMERGE
5220 5219 environment variable and your configuration files. Previous file
5221 5220 contents are saved with a ``.orig`` suffix.
5222 5221
5223 5222 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5224 5223 (e.g. after having manually fixed-up the files). The default is
5225 5224 to mark all unresolved files.
5226 5225
5227 5226 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5228 5227 default is to mark all resolved files.
5229 5228
5230 5229 - :hg:`resolve -l`: list files which had or still have conflicts.
5231 5230 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5232 5231
5233 5232 Note that Mercurial will not let you commit files with unresolved
5234 5233 merge conflicts. You must use :hg:`resolve -m ...` before you can
5235 5234 commit after a conflicting merge.
5236 5235
5237 5236 Returns 0 on success, 1 if any files fail a resolve attempt.
5238 5237 """
5239 5238
5240 5239 all, mark, unmark, show, nostatus = \
5241 5240 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5242 5241
5243 5242 if (show and (mark or unmark)) or (mark and unmark):
5244 5243 raise util.Abort(_("too many options specified"))
5245 5244 if pats and all:
5246 5245 raise util.Abort(_("can't specify --all and patterns"))
5247 5246 if not (all or pats or show or mark or unmark):
5248 5247 raise util.Abort(_('no files or directories specified'),
5249 5248 hint=('use --all to remerge all files'))
5250 5249
5251 5250 wlock = repo.wlock()
5252 5251 try:
5253 5252 ms = mergemod.mergestate(repo)
5254 5253
5255 5254 if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
5256 5255 raise util.Abort(
5257 5256 _('resolve command not applicable when not merging'))
5258 5257
5259 5258 m = scmutil.match(repo[None], pats, opts)
5260 5259 ret = 0
5261 5260 didwork = False
5262 5261
5263 5262 for f in ms:
5264 5263 if not m(f):
5265 5264 continue
5266 5265
5267 5266 didwork = True
5268 5267
5269 5268 if show:
5270 5269 if nostatus:
5271 5270 ui.write("%s\n" % f)
5272 5271 else:
5273 5272 ui.write("%s %s\n" % (ms[f].upper(), f),
5274 5273 label='resolve.' +
5275 5274 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5276 5275 elif mark:
5277 5276 ms.mark(f, "r")
5278 5277 elif unmark:
5279 5278 ms.mark(f, "u")
5280 5279 else:
5281 5280 wctx = repo[None]
5282 5281
5283 5282 # backup pre-resolve (merge uses .orig for its own purposes)
5284 5283 a = repo.wjoin(f)
5285 5284 util.copyfile(a, a + ".resolve")
5286 5285
5287 5286 try:
5288 5287 # resolve file
5289 5288 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5290 5289 'resolve')
5291 5290 if ms.resolve(f, wctx):
5292 5291 ret = 1
5293 5292 finally:
5294 5293 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5295 5294 ms.commit()
5296 5295
5297 5296 # replace filemerge's .orig file with our resolve file
5298 5297 util.rename(a + ".resolve", a + ".orig")
5299 5298
5300 5299 ms.commit()
5301 5300
5302 5301 if not didwork and pats:
5303 5302 ui.warn(_("arguments do not match paths that need resolving\n"))
5304 5303
5305 5304 finally:
5306 5305 wlock.release()
5307 5306
5308 5307 # Nudge users into finishing an unfinished operation. We don't print
5309 5308 # this with the list/show operation because we want list/show to remain
5310 5309 # machine readable.
5311 5310 if not list(ms.unresolved()) and not show:
5312 5311 ui.status(_('(no more unresolved files)\n'))
5313 5312
5314 5313 return ret
5315 5314
5316 5315 @command('revert',
5317 5316 [('a', 'all', None, _('revert all changes when no arguments given')),
5318 5317 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5319 5318 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5320 5319 ('C', 'no-backup', None, _('do not save backup copies of files')),
5321 5320 ] + walkopts + dryrunopts,
5322 5321 _('[OPTION]... [-r REV] [NAME]...'))
5323 5322 def revert(ui, repo, *pats, **opts):
5324 5323 """restore files to their checkout state
5325 5324
5326 5325 .. note::
5327 5326
5328 5327 To check out earlier revisions, you should use :hg:`update REV`.
5329 5328 To cancel an uncommitted merge (and lose your changes),
5330 5329 use :hg:`update --clean .`.
5331 5330
5332 5331 With no revision specified, revert the specified files or directories
5333 5332 to the contents they had in the parent of the working directory.
5334 5333 This restores the contents of files to an unmodified
5335 5334 state and unschedules adds, removes, copies, and renames. If the
5336 5335 working directory has two parents, you must explicitly specify a
5337 5336 revision.
5338 5337
5339 5338 Using the -r/--rev or -d/--date options, revert the given files or
5340 5339 directories to their states as of a specific revision. Because
5341 5340 revert does not change the working directory parents, this will
5342 5341 cause these files to appear modified. This can be helpful to "back
5343 5342 out" some or all of an earlier change. See :hg:`backout` for a
5344 5343 related method.
5345 5344
5346 5345 Modified files are saved with a .orig suffix before reverting.
5347 5346 To disable these backups, use --no-backup.
5348 5347
5349 5348 See :hg:`help dates` for a list of formats valid for -d/--date.
5350 5349
5351 5350 Returns 0 on success.
5352 5351 """
5353 5352
5354 5353 if opts.get("date"):
5355 5354 if opts.get("rev"):
5356 5355 raise util.Abort(_("you can't specify a revision and a date"))
5357 5356 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5358 5357
5359 5358 parent, p2 = repo.dirstate.parents()
5360 5359 if not opts.get('rev') and p2 != nullid:
5361 5360 # revert after merge is a trap for new users (issue2915)
5362 5361 raise util.Abort(_('uncommitted merge with no revision specified'),
5363 5362 hint=_('use "hg update" or see "hg help revert"'))
5364 5363
5365 5364 ctx = scmutil.revsingle(repo, opts.get('rev'))
5366 5365
5367 5366 if not pats and not opts.get('all'):
5368 5367 msg = _("no files or directories specified")
5369 5368 if p2 != nullid:
5370 5369 hint = _("uncommitted merge, use --all to discard all changes,"
5371 5370 " or 'hg update -C .' to abort the merge")
5372 5371 raise util.Abort(msg, hint=hint)
5373 5372 dirty = util.any(repo.status())
5374 5373 node = ctx.node()
5375 5374 if node != parent:
5376 5375 if dirty:
5377 5376 hint = _("uncommitted changes, use --all to discard all"
5378 5377 " changes, or 'hg update %s' to update") % ctx.rev()
5379 5378 else:
5380 5379 hint = _("use --all to revert all files,"
5381 5380 " or 'hg update %s' to update") % ctx.rev()
5382 5381 elif dirty:
5383 5382 hint = _("uncommitted changes, use --all to discard all changes")
5384 5383 else:
5385 5384 hint = _("use --all to revert all files")
5386 5385 raise util.Abort(msg, hint=hint)
5387 5386
5388 5387 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5389 5388
5390 5389 @command('rollback', dryrunopts +
5391 5390 [('f', 'force', False, _('ignore safety measures'))])
5392 5391 def rollback(ui, repo, **opts):
5393 5392 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5394 5393
5395 5394 Please use :hg:`commit --amend` instead of rollback to correct
5396 5395 mistakes in the last commit.
5397 5396
5398 5397 This command should be used with care. There is only one level of
5399 5398 rollback, and there is no way to undo a rollback. It will also
5400 5399 restore the dirstate at the time of the last transaction, losing
5401 5400 any dirstate changes since that time. This command does not alter
5402 5401 the working directory.
5403 5402
5404 5403 Transactions are used to encapsulate the effects of all commands
5405 5404 that create new changesets or propagate existing changesets into a
5406 5405 repository.
5407 5406
5408 5407 .. container:: verbose
5409 5408
5410 5409 For example, the following commands are transactional, and their
5411 5410 effects can be rolled back:
5412 5411
5413 5412 - commit
5414 5413 - import
5415 5414 - pull
5416 5415 - push (with this repository as the destination)
5417 5416 - unbundle
5418 5417
5419 5418 To avoid permanent data loss, rollback will refuse to rollback a
5420 5419 commit transaction if it isn't checked out. Use --force to
5421 5420 override this protection.
5422 5421
5423 5422 This command is not intended for use on public repositories. Once
5424 5423 changes are visible for pull by other users, rolling a transaction
5425 5424 back locally is ineffective (someone else may already have pulled
5426 5425 the changes). Furthermore, a race is possible with readers of the
5427 5426 repository; for example an in-progress pull from the repository
5428 5427 may fail if a rollback is performed.
5429 5428
5430 5429 Returns 0 on success, 1 if no rollback data is available.
5431 5430 """
5432 5431 return repo.rollback(dryrun=opts.get('dry_run'),
5433 5432 force=opts.get('force'))
5434 5433
5435 5434 @command('root', [])
5436 5435 def root(ui, repo):
5437 5436 """print the root (top) of the current working directory
5438 5437
5439 5438 Print the root directory of the current repository.
5440 5439
5441 5440 Returns 0 on success.
5442 5441 """
5443 5442 ui.write(repo.root + "\n")
5444 5443
5445 5444 @command('^serve',
5446 5445 [('A', 'accesslog', '', _('name of access log file to write to'),
5447 5446 _('FILE')),
5448 5447 ('d', 'daemon', None, _('run server in background')),
5449 5448 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('FILE')),
5450 5449 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5451 5450 # use string type, then we can check if something was passed
5452 5451 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5453 5452 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5454 5453 _('ADDR')),
5455 5454 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5456 5455 _('PREFIX')),
5457 5456 ('n', 'name', '',
5458 5457 _('name to show in web pages (default: working directory)'), _('NAME')),
5459 5458 ('', 'web-conf', '',
5460 5459 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5461 5460 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5462 5461 _('FILE')),
5463 5462 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5464 5463 ('', 'stdio', None, _('for remote clients')),
5465 5464 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5466 5465 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5467 5466 ('', 'style', '', _('template style to use'), _('STYLE')),
5468 5467 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5469 5468 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5470 5469 _('[OPTION]...'),
5471 5470 optionalrepo=True)
5472 5471 def serve(ui, repo, **opts):
5473 5472 """start stand-alone webserver
5474 5473
5475 5474 Start a local HTTP repository browser and pull server. You can use
5476 5475 this for ad-hoc sharing and browsing of repositories. It is
5477 5476 recommended to use a real web server to serve a repository for
5478 5477 longer periods of time.
5479 5478
5480 5479 Please note that the server does not implement access control.
5481 5480 This means that, by default, anybody can read from the server and
5482 5481 nobody can write to it by default. Set the ``web.allow_push``
5483 5482 option to ``*`` to allow everybody to push to the server. You
5484 5483 should use a real web server if you need to authenticate users.
5485 5484
5486 5485 By default, the server logs accesses to stdout and errors to
5487 5486 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5488 5487 files.
5489 5488
5490 5489 To have the server choose a free port number to listen on, specify
5491 5490 a port number of 0; in this case, the server will print the port
5492 5491 number it uses.
5493 5492
5494 5493 Returns 0 on success.
5495 5494 """
5496 5495
5497 5496 if opts["stdio"] and opts["cmdserver"]:
5498 5497 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5499 5498
5500 5499 if opts["stdio"]:
5501 5500 if repo is None:
5502 5501 raise error.RepoError(_("there is no Mercurial repository here"
5503 5502 " (.hg not found)"))
5504 5503 s = sshserver.sshserver(ui, repo)
5505 5504 s.serve_forever()
5506 5505
5507 5506 if opts["cmdserver"]:
5508 5507 service = commandserver.createservice(ui, repo, opts)
5509 5508 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
5510 5509
5511 5510 # this way we can check if something was given in the command-line
5512 5511 if opts.get('port'):
5513 5512 opts['port'] = util.getport(opts.get('port'))
5514 5513
5515 5514 baseui = repo and repo.baseui or ui
5516 5515 optlist = ("name templates style address port prefix ipv6"
5517 5516 " accesslog errorlog certificate encoding")
5518 5517 for o in optlist.split():
5519 5518 val = opts.get(o, '')
5520 5519 if val in (None, ''): # should check against default options instead
5521 5520 continue
5522 5521 baseui.setconfig("web", o, val, 'serve')
5523 5522 if repo and repo.ui != baseui:
5524 5523 repo.ui.setconfig("web", o, val, 'serve')
5525 5524
5526 5525 o = opts.get('web_conf') or opts.get('webdir_conf')
5527 5526 if not o:
5528 5527 if not repo:
5529 5528 raise error.RepoError(_("there is no Mercurial repository"
5530 5529 " here (.hg not found)"))
5531 5530 o = repo
5532 5531
5533 5532 app = hgweb.hgweb(o, baseui=baseui)
5534 5533 service = httpservice(ui, app, opts)
5535 5534 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5536 5535
5537 5536 class httpservice(object):
5538 5537 def __init__(self, ui, app, opts):
5539 5538 self.ui = ui
5540 5539 self.app = app
5541 5540 self.opts = opts
5542 5541
5543 5542 def init(self):
5544 5543 util.setsignalhandler()
5545 5544 self.httpd = hgweb_server.create_server(self.ui, self.app)
5546 5545
5547 5546 if self.opts['port'] and not self.ui.verbose:
5548 5547 return
5549 5548
5550 5549 if self.httpd.prefix:
5551 5550 prefix = self.httpd.prefix.strip('/') + '/'
5552 5551 else:
5553 5552 prefix = ''
5554 5553
5555 5554 port = ':%d' % self.httpd.port
5556 5555 if port == ':80':
5557 5556 port = ''
5558 5557
5559 5558 bindaddr = self.httpd.addr
5560 5559 if bindaddr == '0.0.0.0':
5561 5560 bindaddr = '*'
5562 5561 elif ':' in bindaddr: # IPv6
5563 5562 bindaddr = '[%s]' % bindaddr
5564 5563
5565 5564 fqaddr = self.httpd.fqaddr
5566 5565 if ':' in fqaddr:
5567 5566 fqaddr = '[%s]' % fqaddr
5568 5567 if self.opts['port']:
5569 5568 write = self.ui.status
5570 5569 else:
5571 5570 write = self.ui.write
5572 5571 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5573 5572 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5574 5573 self.ui.flush() # avoid buffering of status message
5575 5574
5576 5575 def run(self):
5577 5576 self.httpd.serve_forever()
5578 5577
5579 5578
5580 5579 @command('^status|st',
5581 5580 [('A', 'all', None, _('show status of all files')),
5582 5581 ('m', 'modified', None, _('show only modified files')),
5583 5582 ('a', 'added', None, _('show only added files')),
5584 5583 ('r', 'removed', None, _('show only removed files')),
5585 5584 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5586 5585 ('c', 'clean', None, _('show only files without changes')),
5587 5586 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5588 5587 ('i', 'ignored', None, _('show only ignored files')),
5589 5588 ('n', 'no-status', None, _('hide status prefix')),
5590 5589 ('C', 'copies', None, _('show source of copied files')),
5591 5590 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5592 5591 ('', 'rev', [], _('show difference from revision'), _('REV')),
5593 5592 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5594 5593 ] + walkopts + subrepoopts + formatteropts,
5595 5594 _('[OPTION]... [FILE]...'),
5596 5595 inferrepo=True)
5597 5596 def status(ui, repo, *pats, **opts):
5598 5597 """show changed files in the working directory
5599 5598
5600 5599 Show status of files in the repository. If names are given, only
5601 5600 files that match are shown. Files that are clean or ignored or
5602 5601 the source of a copy/move operation, are not listed unless
5603 5602 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5604 5603 Unless options described with "show only ..." are given, the
5605 5604 options -mardu are used.
5606 5605
5607 5606 Option -q/--quiet hides untracked (unknown and ignored) files
5608 5607 unless explicitly requested with -u/--unknown or -i/--ignored.
5609 5608
5610 5609 .. note::
5611 5610
5612 5611 status may appear to disagree with diff if permissions have
5613 5612 changed or a merge has occurred. The standard diff format does
5614 5613 not report permission changes and diff only reports changes
5615 5614 relative to one merge parent.
5616 5615
5617 5616 If one revision is given, it is used as the base revision.
5618 5617 If two revisions are given, the differences between them are
5619 5618 shown. The --change option can also be used as a shortcut to list
5620 5619 the changed files of a revision from its first parent.
5621 5620
5622 5621 The codes used to show the status of files are::
5623 5622
5624 5623 M = modified
5625 5624 A = added
5626 5625 R = removed
5627 5626 C = clean
5628 5627 ! = missing (deleted by non-hg command, but still tracked)
5629 5628 ? = not tracked
5630 5629 I = ignored
5631 5630 = origin of the previous file (with --copies)
5632 5631
5633 5632 .. container:: verbose
5634 5633
5635 5634 Examples:
5636 5635
5637 5636 - show changes in the working directory relative to a
5638 5637 changeset::
5639 5638
5640 5639 hg status --rev 9353
5641 5640
5642 5641 - show all changes including copies in an existing changeset::
5643 5642
5644 5643 hg status --copies --change 9353
5645 5644
5646 5645 - get a NUL separated list of added files, suitable for xargs::
5647 5646
5648 5647 hg status -an0
5649 5648
5650 5649 Returns 0 on success.
5651 5650 """
5652 5651
5653 5652 revs = opts.get('rev')
5654 5653 change = opts.get('change')
5655 5654
5656 5655 if revs and change:
5657 5656 msg = _('cannot specify --rev and --change at the same time')
5658 5657 raise util.Abort(msg)
5659 5658 elif change:
5660 5659 node2 = scmutil.revsingle(repo, change, None).node()
5661 5660 node1 = repo[node2].p1().node()
5662 5661 else:
5663 5662 node1, node2 = scmutil.revpair(repo, revs)
5664 5663
5665 5664 cwd = (pats and repo.getcwd()) or ''
5666 5665 end = opts.get('print0') and '\0' or '\n'
5667 5666 copy = {}
5668 5667 states = 'modified added removed deleted unknown ignored clean'.split()
5669 5668 show = [k for k in states if opts.get(k)]
5670 5669 if opts.get('all'):
5671 5670 show += ui.quiet and (states[:4] + ['clean']) or states
5672 5671 if not show:
5673 5672 show = ui.quiet and states[:4] or states[:5]
5674 5673
5675 5674 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5676 5675 'ignored' in show, 'clean' in show, 'unknown' in show,
5677 5676 opts.get('subrepos'))
5678 5677 changestates = zip(states, 'MAR!?IC', stat)
5679 5678
5680 5679 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5681 5680 copy = copies.pathcopies(repo[node1], repo[node2])
5682 5681
5683 5682 fm = ui.formatter('status', opts)
5684 5683 fmt = '%s' + end
5685 5684 showchar = not opts.get('no_status')
5686 5685
5687 5686 for state, char, files in changestates:
5688 5687 if state in show:
5689 5688 label = 'status.' + state
5690 5689 for f in files:
5691 5690 fm.startitem()
5692 5691 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5693 5692 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5694 5693 if f in copy:
5695 5694 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5696 5695 label='status.copied')
5697 5696 fm.end()
5698 5697
5699 5698 @command('^summary|sum',
5700 5699 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5701 5700 def summary(ui, repo, **opts):
5702 5701 """summarize working directory state
5703 5702
5704 5703 This generates a brief summary of the working directory state,
5705 5704 including parents, branch, commit status, and available updates.
5706 5705
5707 5706 With the --remote option, this will check the default paths for
5708 5707 incoming and outgoing changes. This can be time-consuming.
5709 5708
5710 5709 Returns 0 on success.
5711 5710 """
5712 5711
5713 5712 ctx = repo[None]
5714 5713 parents = ctx.parents()
5715 5714 pnode = parents[0].node()
5716 5715 marks = []
5717 5716
5718 5717 for p in parents:
5719 5718 # label with log.changeset (instead of log.parent) since this
5720 5719 # shows a working directory parent *changeset*:
5721 5720 # i18n: column positioning for "hg summary"
5722 5721 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5723 5722 label='log.changeset changeset.%s' % p.phasestr())
5724 5723 ui.write(' '.join(p.tags()), label='log.tag')
5725 5724 if p.bookmarks():
5726 5725 marks.extend(p.bookmarks())
5727 5726 if p.rev() == -1:
5728 5727 if not len(repo):
5729 5728 ui.write(_(' (empty repository)'))
5730 5729 else:
5731 5730 ui.write(_(' (no revision checked out)'))
5732 5731 ui.write('\n')
5733 5732 if p.description():
5734 5733 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5735 5734 label='log.summary')
5736 5735
5737 5736 branch = ctx.branch()
5738 5737 bheads = repo.branchheads(branch)
5739 5738 # i18n: column positioning for "hg summary"
5740 5739 m = _('branch: %s\n') % branch
5741 5740 if branch != 'default':
5742 5741 ui.write(m, label='log.branch')
5743 5742 else:
5744 5743 ui.status(m, label='log.branch')
5745 5744
5746 5745 if marks:
5747 5746 current = repo._bookmarkcurrent
5748 5747 # i18n: column positioning for "hg summary"
5749 5748 ui.write(_('bookmarks:'), label='log.bookmark')
5750 5749 if current is not None:
5751 5750 if current in marks:
5752 5751 ui.write(' *' + current, label='bookmarks.current')
5753 5752 marks.remove(current)
5754 5753 else:
5755 5754 ui.write(' [%s]' % current, label='bookmarks.current')
5756 5755 for m in marks:
5757 5756 ui.write(' ' + m, label='log.bookmark')
5758 5757 ui.write('\n', label='log.bookmark')
5759 5758
5760 5759 status = repo.status(unknown=True)
5761 5760
5762 5761 c = repo.dirstate.copies()
5763 5762 copied, renamed = [], []
5764 5763 for d, s in c.iteritems():
5765 5764 if s in status.removed:
5766 5765 status.removed.remove(s)
5767 5766 renamed.append(d)
5768 5767 else:
5769 5768 copied.append(d)
5770 5769 if d in status.added:
5771 5770 status.added.remove(d)
5772 5771
5773 5772 ms = mergemod.mergestate(repo)
5774 5773 unresolved = [f for f in ms if ms[f] == 'u']
5775 5774
5776 5775 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5777 5776
5778 5777 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5779 5778 (ui.label(_('%d added'), 'status.added'), status.added),
5780 5779 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5781 5780 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5782 5781 (ui.label(_('%d copied'), 'status.copied'), copied),
5783 5782 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5784 5783 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5785 5784 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5786 5785 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5787 5786 t = []
5788 5787 for l, s in labels:
5789 5788 if s:
5790 5789 t.append(l % len(s))
5791 5790
5792 5791 t = ', '.join(t)
5793 5792 cleanworkdir = False
5794 5793
5795 5794 if repo.vfs.exists('updatestate'):
5796 5795 t += _(' (interrupted update)')
5797 5796 elif len(parents) > 1:
5798 5797 t += _(' (merge)')
5799 5798 elif branch != parents[0].branch():
5800 5799 t += _(' (new branch)')
5801 5800 elif (parents[0].closesbranch() and
5802 5801 pnode in repo.branchheads(branch, closed=True)):
5803 5802 t += _(' (head closed)')
5804 5803 elif not (status.modified or status.added or status.removed or renamed or
5805 5804 copied or subs):
5806 5805 t += _(' (clean)')
5807 5806 cleanworkdir = True
5808 5807 elif pnode not in bheads:
5809 5808 t += _(' (new branch head)')
5810 5809
5811 5810 if cleanworkdir:
5812 5811 # i18n: column positioning for "hg summary"
5813 5812 ui.status(_('commit: %s\n') % t.strip())
5814 5813 else:
5815 5814 # i18n: column positioning for "hg summary"
5816 5815 ui.write(_('commit: %s\n') % t.strip())
5817 5816
5818 5817 # all ancestors of branch heads - all ancestors of parent = new csets
5819 5818 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5820 5819 bheads))
5821 5820
5822 5821 if new == 0:
5823 5822 # i18n: column positioning for "hg summary"
5824 5823 ui.status(_('update: (current)\n'))
5825 5824 elif pnode not in bheads:
5826 5825 # i18n: column positioning for "hg summary"
5827 5826 ui.write(_('update: %d new changesets (update)\n') % new)
5828 5827 else:
5829 5828 # i18n: column positioning for "hg summary"
5830 5829 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5831 5830 (new, len(bheads)))
5832 5831
5833 5832 cmdutil.summaryhooks(ui, repo)
5834 5833
5835 5834 if opts.get('remote'):
5836 5835 needsincoming, needsoutgoing = True, True
5837 5836 else:
5838 5837 needsincoming, needsoutgoing = False, False
5839 5838 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5840 5839 if i:
5841 5840 needsincoming = True
5842 5841 if o:
5843 5842 needsoutgoing = True
5844 5843 if not needsincoming and not needsoutgoing:
5845 5844 return
5846 5845
5847 5846 def getincoming():
5848 5847 source, branches = hg.parseurl(ui.expandpath('default'))
5849 5848 sbranch = branches[0]
5850 5849 try:
5851 5850 other = hg.peer(repo, {}, source)
5852 5851 except error.RepoError:
5853 5852 if opts.get('remote'):
5854 5853 raise
5855 5854 return source, sbranch, None, None, None
5856 5855 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5857 5856 if revs:
5858 5857 revs = [other.lookup(rev) for rev in revs]
5859 5858 ui.debug('comparing with %s\n' % util.hidepassword(source))
5860 5859 repo.ui.pushbuffer()
5861 5860 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5862 5861 repo.ui.popbuffer()
5863 5862 return source, sbranch, other, commoninc, commoninc[1]
5864 5863
5865 5864 if needsincoming:
5866 5865 source, sbranch, sother, commoninc, incoming = getincoming()
5867 5866 else:
5868 5867 source = sbranch = sother = commoninc = incoming = None
5869 5868
5870 5869 def getoutgoing():
5871 5870 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5872 5871 dbranch = branches[0]
5873 5872 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5874 5873 if source != dest:
5875 5874 try:
5876 5875 dother = hg.peer(repo, {}, dest)
5877 5876 except error.RepoError:
5878 5877 if opts.get('remote'):
5879 5878 raise
5880 5879 return dest, dbranch, None, None
5881 5880 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5882 5881 elif sother is None:
5883 5882 # there is no explicit destination peer, but source one is invalid
5884 5883 return dest, dbranch, None, None
5885 5884 else:
5886 5885 dother = sother
5887 5886 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5888 5887 common = None
5889 5888 else:
5890 5889 common = commoninc
5891 5890 if revs:
5892 5891 revs = [repo.lookup(rev) for rev in revs]
5893 5892 repo.ui.pushbuffer()
5894 5893 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5895 5894 commoninc=common)
5896 5895 repo.ui.popbuffer()
5897 5896 return dest, dbranch, dother, outgoing
5898 5897
5899 5898 if needsoutgoing:
5900 5899 dest, dbranch, dother, outgoing = getoutgoing()
5901 5900 else:
5902 5901 dest = dbranch = dother = outgoing = None
5903 5902
5904 5903 if opts.get('remote'):
5905 5904 t = []
5906 5905 if incoming:
5907 5906 t.append(_('1 or more incoming'))
5908 5907 o = outgoing.missing
5909 5908 if o:
5910 5909 t.append(_('%d outgoing') % len(o))
5911 5910 other = dother or sother
5912 5911 if 'bookmarks' in other.listkeys('namespaces'):
5913 5912 lmarks = repo.listkeys('bookmarks')
5914 5913 rmarks = other.listkeys('bookmarks')
5915 5914 diff = set(rmarks) - set(lmarks)
5916 5915 if len(diff) > 0:
5917 5916 t.append(_('%d incoming bookmarks') % len(diff))
5918 5917 diff = set(lmarks) - set(rmarks)
5919 5918 if len(diff) > 0:
5920 5919 t.append(_('%d outgoing bookmarks') % len(diff))
5921 5920
5922 5921 if t:
5923 5922 # i18n: column positioning for "hg summary"
5924 5923 ui.write(_('remote: %s\n') % (', '.join(t)))
5925 5924 else:
5926 5925 # i18n: column positioning for "hg summary"
5927 5926 ui.status(_('remote: (synced)\n'))
5928 5927
5929 5928 cmdutil.summaryremotehooks(ui, repo, opts,
5930 5929 ((source, sbranch, sother, commoninc),
5931 5930 (dest, dbranch, dother, outgoing)))
5932 5931
5933 5932 @command('tag',
5934 5933 [('f', 'force', None, _('force tag')),
5935 5934 ('l', 'local', None, _('make the tag local')),
5936 5935 ('r', 'rev', '', _('revision to tag'), _('REV')),
5937 5936 ('', 'remove', None, _('remove a tag')),
5938 5937 # -l/--local is already there, commitopts cannot be used
5939 5938 ('e', 'edit', None, _('invoke editor on commit messages')),
5940 5939 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5941 5940 ] + commitopts2,
5942 5941 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5943 5942 def tag(ui, repo, name1, *names, **opts):
5944 5943 """add one or more tags for the current or given revision
5945 5944
5946 5945 Name a particular revision using <name>.
5947 5946
5948 5947 Tags are used to name particular revisions of the repository and are
5949 5948 very useful to compare different revisions, to go back to significant
5950 5949 earlier versions or to mark branch points as releases, etc. Changing
5951 5950 an existing tag is normally disallowed; use -f/--force to override.
5952 5951
5953 5952 If no revision is given, the parent of the working directory is
5954 5953 used.
5955 5954
5956 5955 To facilitate version control, distribution, and merging of tags,
5957 5956 they are stored as a file named ".hgtags" which is managed similarly
5958 5957 to other project files and can be hand-edited if necessary. This
5959 5958 also means that tagging creates a new commit. The file
5960 5959 ".hg/localtags" is used for local tags (not shared among
5961 5960 repositories).
5962 5961
5963 5962 Tag commits are usually made at the head of a branch. If the parent
5964 5963 of the working directory is not a branch head, :hg:`tag` aborts; use
5965 5964 -f/--force to force the tag commit to be based on a non-head
5966 5965 changeset.
5967 5966
5968 5967 See :hg:`help dates` for a list of formats valid for -d/--date.
5969 5968
5970 5969 Since tag names have priority over branch names during revision
5971 5970 lookup, using an existing branch name as a tag name is discouraged.
5972 5971
5973 5972 Returns 0 on success.
5974 5973 """
5975 5974 wlock = lock = None
5976 5975 try:
5977 5976 wlock = repo.wlock()
5978 5977 lock = repo.lock()
5979 5978 rev_ = "."
5980 5979 names = [t.strip() for t in (name1,) + names]
5981 5980 if len(names) != len(set(names)):
5982 5981 raise util.Abort(_('tag names must be unique'))
5983 5982 for n in names:
5984 5983 scmutil.checknewlabel(repo, n, 'tag')
5985 5984 if not n:
5986 5985 raise util.Abort(_('tag names cannot consist entirely of '
5987 5986 'whitespace'))
5988 5987 if opts.get('rev') and opts.get('remove'):
5989 5988 raise util.Abort(_("--rev and --remove are incompatible"))
5990 5989 if opts.get('rev'):
5991 5990 rev_ = opts['rev']
5992 5991 message = opts.get('message')
5993 5992 if opts.get('remove'):
5994 5993 expectedtype = opts.get('local') and 'local' or 'global'
5995 5994 for n in names:
5996 5995 if not repo.tagtype(n):
5997 5996 raise util.Abort(_("tag '%s' does not exist") % n)
5998 5997 if repo.tagtype(n) != expectedtype:
5999 5998 if expectedtype == 'global':
6000 5999 raise util.Abort(_("tag '%s' is not a global tag") % n)
6001 6000 else:
6002 6001 raise util.Abort(_("tag '%s' is not a local tag") % n)
6003 6002 rev_ = nullid
6004 6003 if not message:
6005 6004 # we don't translate commit messages
6006 6005 message = 'Removed tag %s' % ', '.join(names)
6007 6006 elif not opts.get('force'):
6008 6007 for n in names:
6009 6008 if n in repo.tags():
6010 6009 raise util.Abort(_("tag '%s' already exists "
6011 6010 "(use -f to force)") % n)
6012 6011 if not opts.get('local'):
6013 6012 p1, p2 = repo.dirstate.parents()
6014 6013 if p2 != nullid:
6015 6014 raise util.Abort(_('uncommitted merge'))
6016 6015 bheads = repo.branchheads()
6017 6016 if not opts.get('force') and bheads and p1 not in bheads:
6018 6017 raise util.Abort(_('not at a branch head (use -f to force)'))
6019 6018 r = scmutil.revsingle(repo, rev_).node()
6020 6019
6021 6020 if not message:
6022 6021 # we don't translate commit messages
6023 6022 message = ('Added tag %s for changeset %s' %
6024 6023 (', '.join(names), short(r)))
6025 6024
6026 6025 date = opts.get('date')
6027 6026 if date:
6028 6027 date = util.parsedate(date)
6029 6028
6030 6029 if opts.get('remove'):
6031 6030 editform = 'tag.remove'
6032 6031 else:
6033 6032 editform = 'tag.add'
6034 6033 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6035 6034
6036 6035 # don't allow tagging the null rev
6037 6036 if (not opts.get('remove') and
6038 6037 scmutil.revsingle(repo, rev_).rev() == nullrev):
6039 6038 raise util.Abort(_("cannot tag null revision"))
6040 6039
6041 6040 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6042 6041 editor=editor)
6043 6042 finally:
6044 6043 release(lock, wlock)
6045 6044
6046 6045 @command('tags', formatteropts, '')
6047 6046 def tags(ui, repo, **opts):
6048 6047 """list repository tags
6049 6048
6050 6049 This lists both regular and local tags. When the -v/--verbose
6051 6050 switch is used, a third column "local" is printed for local tags.
6052 6051
6053 6052 Returns 0 on success.
6054 6053 """
6055 6054
6056 6055 fm = ui.formatter('tags', opts)
6057 6056 hexfunc = fm.hexfunc
6058 6057 tagtype = ""
6059 6058
6060 6059 for t, n in reversed(repo.tagslist()):
6061 6060 hn = hexfunc(n)
6062 6061 label = 'tags.normal'
6063 6062 tagtype = ''
6064 6063 if repo.tagtype(t) == 'local':
6065 6064 label = 'tags.local'
6066 6065 tagtype = 'local'
6067 6066
6068 6067 fm.startitem()
6069 6068 fm.write('tag', '%s', t, label=label)
6070 6069 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6071 6070 fm.condwrite(not ui.quiet, 'rev node', fmt,
6072 6071 repo.changelog.rev(n), hn, label=label)
6073 6072 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6074 6073 tagtype, label=label)
6075 6074 fm.plain('\n')
6076 6075 fm.end()
6077 6076
6078 6077 @command('tip',
6079 6078 [('p', 'patch', None, _('show patch')),
6080 6079 ('g', 'git', None, _('use git extended diff format')),
6081 6080 ] + templateopts,
6082 6081 _('[-p] [-g]'))
6083 6082 def tip(ui, repo, **opts):
6084 6083 """show the tip revision (DEPRECATED)
6085 6084
6086 6085 The tip revision (usually just called the tip) is the changeset
6087 6086 most recently added to the repository (and therefore the most
6088 6087 recently changed head).
6089 6088
6090 6089 If you have just made a commit, that commit will be the tip. If
6091 6090 you have just pulled changes from another repository, the tip of
6092 6091 that repository becomes the current tip. The "tip" tag is special
6093 6092 and cannot be renamed or assigned to a different changeset.
6094 6093
6095 6094 This command is deprecated, please use :hg:`heads` instead.
6096 6095
6097 6096 Returns 0 on success.
6098 6097 """
6099 6098 displayer = cmdutil.show_changeset(ui, repo, opts)
6100 6099 displayer.show(repo['tip'])
6101 6100 displayer.close()
6102 6101
6103 6102 @command('unbundle',
6104 6103 [('u', 'update', None,
6105 6104 _('update to new branch head if changesets were unbundled'))],
6106 6105 _('[-u] FILE...'))
6107 6106 def unbundle(ui, repo, fname1, *fnames, **opts):
6108 6107 """apply one or more changegroup files
6109 6108
6110 6109 Apply one or more compressed changegroup files generated by the
6111 6110 bundle command.
6112 6111
6113 6112 Returns 0 on success, 1 if an update has unresolved files.
6114 6113 """
6115 6114 fnames = (fname1,) + fnames
6116 6115
6117 6116 lock = repo.lock()
6118 6117 try:
6119 6118 for fname in fnames:
6120 6119 f = hg.openpath(ui, fname)
6121 6120 gen = exchange.readbundle(ui, f, fname)
6122 6121 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6123 6122 'bundle:' + fname)
6124 6123 finally:
6125 6124 lock.release()
6126 6125
6127 6126 return postincoming(ui, repo, modheads, opts.get('update'), None)
6128 6127
6129 6128 @command('^update|up|checkout|co',
6130 6129 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6131 6130 ('c', 'check', None,
6132 6131 _('update across branches if no uncommitted changes')),
6133 6132 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6134 6133 ('r', 'rev', '', _('revision'), _('REV'))
6135 6134 ] + mergetoolopts,
6136 6135 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6137 6136 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6138 6137 tool=None):
6139 6138 """update working directory (or switch revisions)
6140 6139
6141 6140 Update the repository's working directory to the specified
6142 6141 changeset. If no changeset is specified, update to the tip of the
6143 6142 current named branch and move the current bookmark (see :hg:`help
6144 6143 bookmarks`).
6145 6144
6146 6145 Update sets the working directory's parent revision to the specified
6147 6146 changeset (see :hg:`help parents`).
6148 6147
6149 6148 If the changeset is not a descendant or ancestor of the working
6150 6149 directory's parent, the update is aborted. With the -c/--check
6151 6150 option, the working directory is checked for uncommitted changes; if
6152 6151 none are found, the working directory is updated to the specified
6153 6152 changeset.
6154 6153
6155 6154 .. container:: verbose
6156 6155
6157 6156 The following rules apply when the working directory contains
6158 6157 uncommitted changes:
6159 6158
6160 6159 1. If neither -c/--check nor -C/--clean is specified, and if
6161 6160 the requested changeset is an ancestor or descendant of
6162 6161 the working directory's parent, the uncommitted changes
6163 6162 are merged into the requested changeset and the merged
6164 6163 result is left uncommitted. If the requested changeset is
6165 6164 not an ancestor or descendant (that is, it is on another
6166 6165 branch), the update is aborted and the uncommitted changes
6167 6166 are preserved.
6168 6167
6169 6168 2. With the -c/--check option, the update is aborted and the
6170 6169 uncommitted changes are preserved.
6171 6170
6172 6171 3. With the -C/--clean option, uncommitted changes are discarded and
6173 6172 the working directory is updated to the requested changeset.
6174 6173
6175 6174 To cancel an uncommitted merge (and lose your changes), use
6176 6175 :hg:`update --clean .`.
6177 6176
6178 6177 Use null as the changeset to remove the working directory (like
6179 6178 :hg:`clone -U`).
6180 6179
6181 6180 If you want to revert just one file to an older revision, use
6182 6181 :hg:`revert [-r REV] NAME`.
6183 6182
6184 6183 See :hg:`help dates` for a list of formats valid for -d/--date.
6185 6184
6186 6185 Returns 0 on success, 1 if there are unresolved files.
6187 6186 """
6188 6187 if rev and node:
6189 6188 raise util.Abort(_("please specify just one revision"))
6190 6189
6191 6190 if rev is None or rev == '':
6192 6191 rev = node
6193 6192
6194 6193 cmdutil.clearunfinished(repo)
6195 6194
6196 6195 # with no argument, we also move the current bookmark, if any
6197 6196 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6198 6197
6199 6198 # if we defined a bookmark, we have to remember the original bookmark name
6200 6199 brev = rev
6201 6200 rev = scmutil.revsingle(repo, rev, rev).rev()
6202 6201
6203 6202 if check and clean:
6204 6203 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6205 6204
6206 6205 if date:
6207 6206 if rev is not None:
6208 6207 raise util.Abort(_("you can't specify a revision and a date"))
6209 6208 rev = cmdutil.finddate(ui, repo, date)
6210 6209
6211 6210 if check:
6212 6211 c = repo[None]
6213 6212 if c.dirty(merge=False, branch=False, missing=True):
6214 6213 raise util.Abort(_("uncommitted changes"))
6215 6214 if rev is None:
6216 6215 rev = repo[repo[None].branch()].rev()
6217 6216
6218 6217 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6219 6218
6220 6219 if clean:
6221 6220 ret = hg.clean(repo, rev)
6222 6221 else:
6223 6222 ret = hg.update(repo, rev)
6224 6223
6225 6224 if not ret and movemarkfrom:
6226 6225 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6227 6226 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6228 6227 elif brev in repo._bookmarks:
6229 6228 bookmarks.setcurrent(repo, brev)
6230 6229 ui.status(_("(activating bookmark %s)\n") % brev)
6231 6230 elif brev:
6232 6231 if repo._bookmarkcurrent:
6233 6232 ui.status(_("(leaving bookmark %s)\n") %
6234 6233 repo._bookmarkcurrent)
6235 6234 bookmarks.unsetcurrent(repo)
6236 6235
6237 6236 return ret
6238 6237
6239 6238 @command('verify', [])
6240 6239 def verify(ui, repo):
6241 6240 """verify the integrity of the repository
6242 6241
6243 6242 Verify the integrity of the current repository.
6244 6243
6245 6244 This will perform an extensive check of the repository's
6246 6245 integrity, validating the hashes and checksums of each entry in
6247 6246 the changelog, manifest, and tracked files, as well as the
6248 6247 integrity of their crosslinks and indices.
6249 6248
6250 6249 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6251 6250 for more information about recovery from corruption of the
6252 6251 repository.
6253 6252
6254 6253 Returns 0 on success, 1 if errors are encountered.
6255 6254 """
6256 6255 return hg.verify(repo)
6257 6256
6258 6257 @command('version', [], norepo=True)
6259 6258 def version_(ui):
6260 6259 """output version and copyright information"""
6261 6260 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6262 6261 % util.version())
6263 6262 ui.status(_(
6264 6263 "(see http://mercurial.selenic.com for more information)\n"
6265 6264 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
6266 6265 "This is free software; see the source for copying conditions. "
6267 6266 "There is NO\nwarranty; "
6268 6267 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6269 6268 ))
6270 6269
6271 6270 ui.note(_("\nEnabled extensions:\n\n"))
6272 6271 if ui.verbose:
6273 6272 # format names and versions into columns
6274 6273 names = []
6275 6274 vers = []
6276 6275 for name, module in extensions.extensions():
6277 6276 names.append(name)
6278 6277 vers.append(extensions.moduleversion(module))
6279 6278 if names:
6280 6279 maxnamelen = max(len(n) for n in names)
6281 6280 for i, name in enumerate(names):
6282 6281 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
@@ -1,1681 +1,1682 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 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 import copy
9 9 import errno, os, re, shutil, posixpath, sys
10 10 import xml.dom.minidom
11 11 import stat, subprocess, tarfile
12 12 from i18n import _
13 13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 14 import phases
15 15 import pathutil
16 16 import exchange
17 17 hg = None
18 18 propertycache = util.propertycache
19 19
20 20 nullstate = ('', '', 'empty')
21 21
22 22 def _expandedabspath(path):
23 23 '''
24 24 get a path or url and if it is a path expand it and return an absolute path
25 25 '''
26 26 expandedpath = util.urllocalpath(util.expandpath(path))
27 27 u = util.url(expandedpath)
28 28 if not u.scheme:
29 29 path = util.normpath(os.path.abspath(u.path))
30 30 return path
31 31
32 32 def _getstorehashcachename(remotepath):
33 33 '''get a unique filename for the store hash cache of a remote repository'''
34 34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35 35
36 36 class SubrepoAbort(error.Abort):
37 37 """Exception class used to avoid handling a subrepo error more than once"""
38 38 def __init__(self, *args, **kw):
39 39 error.Abort.__init__(self, *args, **kw)
40 40 self.subrepo = kw.get('subrepo')
41 41 self.cause = kw.get('cause')
42 42
43 43 def annotatesubrepoerror(func):
44 44 def decoratedmethod(self, *args, **kargs):
45 45 try:
46 46 res = func(self, *args, **kargs)
47 47 except SubrepoAbort, ex:
48 48 # This exception has already been handled
49 49 raise ex
50 50 except error.Abort, ex:
51 51 subrepo = subrelpath(self)
52 52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 53 # avoid handling this exception by raising a SubrepoAbort exception
54 54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 55 cause=sys.exc_info())
56 56 return res
57 57 return decoratedmethod
58 58
59 59 def state(ctx, ui):
60 60 """return a state dict, mapping subrepo paths configured in .hgsub
61 61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 62 (key in types dict))
63 63 """
64 64 p = config.config()
65 65 def read(f, sections=None, remap=None):
66 66 if f in ctx:
67 67 try:
68 68 data = ctx[f].data()
69 69 except IOError, err:
70 70 if err.errno != errno.ENOENT:
71 71 raise
72 72 # handle missing subrepo spec files as removed
73 73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 74 return
75 75 p.parse(f, data, sections, remap, read)
76 76 else:
77 77 raise util.Abort(_("subrepo spec file %s not found") % f)
78 78
79 79 if '.hgsub' in ctx:
80 80 read('.hgsub')
81 81
82 82 for path, src in ui.configitems('subpaths'):
83 83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84 84
85 85 rev = {}
86 86 if '.hgsubstate' in ctx:
87 87 try:
88 88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 89 l = l.lstrip()
90 90 if not l:
91 91 continue
92 92 try:
93 93 revision, path = l.split(" ", 1)
94 94 except ValueError:
95 95 raise util.Abort(_("invalid subrepository revision "
96 96 "specifier in .hgsubstate line %d")
97 97 % (i + 1))
98 98 rev[path] = revision
99 99 except IOError, err:
100 100 if err.errno != errno.ENOENT:
101 101 raise
102 102
103 103 def remap(src):
104 104 for pattern, repl in p.items('subpaths'):
105 105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 106 # does a string decode.
107 107 repl = repl.encode('string-escape')
108 108 # However, we still want to allow back references to go
109 109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 110 # extra escapes are needed because re.sub string decodes.
111 111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 112 try:
113 113 src = re.sub(pattern, repl, src, 1)
114 114 except re.error, e:
115 115 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 116 % (p.source('subpaths', pattern), e))
117 117 return src
118 118
119 119 state = {}
120 120 for path, src in p[''].items():
121 121 kind = 'hg'
122 122 if src.startswith('['):
123 123 if ']' not in src:
124 124 raise util.Abort(_('missing ] in subrepo source'))
125 125 kind, src = src.split(']', 1)
126 126 kind = kind[1:]
127 127 src = src.lstrip() # strip any extra whitespace after ']'
128 128
129 129 if not util.url(src).isabs():
130 130 parent = _abssource(ctx._repo, abort=False)
131 131 if parent:
132 132 parent = util.url(parent)
133 133 parent.path = posixpath.join(parent.path or '', src)
134 134 parent.path = posixpath.normpath(parent.path)
135 135 joined = str(parent)
136 136 # Remap the full joined path and use it if it changes,
137 137 # else remap the original source.
138 138 remapped = remap(joined)
139 139 if remapped == joined:
140 140 src = remap(src)
141 141 else:
142 142 src = remapped
143 143
144 144 src = remap(src)
145 145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146 146
147 147 return state
148 148
149 149 def writestate(repo, state):
150 150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 152 repo.wwrite('.hgsubstate', ''.join(lines), '')
153 153
154 154 def submerge(repo, wctx, mctx, actx, overwrite):
155 155 """delegated from merge.applyupdates: merging of .hgsubstate file
156 156 in working context, merging context and ancestor context"""
157 157 if mctx == actx: # backwards?
158 158 actx = wctx.p1()
159 159 s1 = wctx.substate
160 160 s2 = mctx.substate
161 161 sa = actx.substate
162 162 sm = {}
163 163
164 164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165 165
166 166 def debug(s, msg, r=""):
167 167 if r:
168 168 r = "%s:%s:%s" % r
169 169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170 170
171 171 for s, l in sorted(s1.iteritems()):
172 172 a = sa.get(s, nullstate)
173 173 ld = l # local state with possible dirty flag for compares
174 174 if wctx.sub(s).dirty():
175 175 ld = (l[0], l[1] + "+")
176 176 if wctx == actx: # overwrite
177 177 a = ld
178 178
179 179 if s in s2:
180 180 r = s2[s]
181 181 if ld == r or r == a: # no change or local is newer
182 182 sm[s] = l
183 183 continue
184 184 elif ld == a: # other side changed
185 185 debug(s, "other changed, get", r)
186 186 wctx.sub(s).get(r, overwrite)
187 187 sm[s] = r
188 188 elif ld[0] != r[0]: # sources differ
189 189 if repo.ui.promptchoice(
190 190 _(' subrepository sources for %s differ\n'
191 191 'use (l)ocal source (%s) or (r)emote source (%s)?'
192 192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
193 193 debug(s, "prompt changed, get", r)
194 194 wctx.sub(s).get(r, overwrite)
195 195 sm[s] = r
196 196 elif ld[1] == a[1]: # local side is unchanged
197 197 debug(s, "other side changed, get", r)
198 198 wctx.sub(s).get(r, overwrite)
199 199 sm[s] = r
200 200 else:
201 201 debug(s, "both sides changed")
202 202 srepo = wctx.sub(s)
203 203 option = repo.ui.promptchoice(
204 204 _(' subrepository %s diverged (local revision: %s, '
205 205 'remote revision: %s)\n'
206 206 '(M)erge, keep (l)ocal or keep (r)emote?'
207 207 '$$ &Merge $$ &Local $$ &Remote')
208 208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
209 209 if option == 0:
210 210 wctx.sub(s).merge(r)
211 211 sm[s] = l
212 212 debug(s, "merge with", r)
213 213 elif option == 1:
214 214 sm[s] = l
215 215 debug(s, "keep local subrepo revision", l)
216 216 else:
217 217 wctx.sub(s).get(r, overwrite)
218 218 sm[s] = r
219 219 debug(s, "get remote subrepo revision", r)
220 220 elif ld == a: # remote removed, local unchanged
221 221 debug(s, "remote removed, remove")
222 222 wctx.sub(s).remove()
223 223 elif a == nullstate: # not present in remote or ancestor
224 224 debug(s, "local added, keep")
225 225 sm[s] = l
226 226 continue
227 227 else:
228 228 if repo.ui.promptchoice(
229 229 _(' local changed subrepository %s which remote removed\n'
230 230 'use (c)hanged version or (d)elete?'
231 231 '$$ &Changed $$ &Delete') % s, 0):
232 232 debug(s, "prompt remove")
233 233 wctx.sub(s).remove()
234 234
235 235 for s, r in sorted(s2.items()):
236 236 if s in s1:
237 237 continue
238 238 elif s not in sa:
239 239 debug(s, "remote added, get", r)
240 240 mctx.sub(s).get(r)
241 241 sm[s] = r
242 242 elif r != sa[s]:
243 243 if repo.ui.promptchoice(
244 244 _(' remote changed subrepository %s which local removed\n'
245 245 'use (c)hanged version or (d)elete?'
246 246 '$$ &Changed $$ &Delete') % s, 0) == 0:
247 247 debug(s, "prompt recreate", r)
248 248 wctx.sub(s).get(r)
249 249 sm[s] = r
250 250
251 251 # record merged .hgsubstate
252 252 writestate(repo, sm)
253 253 return sm
254 254
255 255 def _updateprompt(ui, sub, dirty, local, remote):
256 256 if dirty:
257 257 msg = (_(' subrepository sources for %s differ\n'
258 258 'use (l)ocal source (%s) or (r)emote source (%s)?'
259 259 '$$ &Local $$ &Remote')
260 260 % (subrelpath(sub), local, remote))
261 261 else:
262 262 msg = (_(' subrepository sources for %s differ (in checked out '
263 263 'version)\n'
264 264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 265 '$$ &Local $$ &Remote')
266 266 % (subrelpath(sub), local, remote))
267 267 return ui.promptchoice(msg, 0)
268 268
269 269 def reporelpath(repo):
270 270 """return path to this (sub)repo as seen from outermost repo"""
271 271 parent = repo
272 272 while util.safehasattr(parent, '_subparent'):
273 273 parent = parent._subparent
274 274 return repo.root[len(pathutil.normasprefix(parent.root)):]
275 275
276 276 def subrelpath(sub):
277 277 """return path to this subrepo as seen from outermost repo"""
278 278 if util.safehasattr(sub, '_relpath'):
279 279 return sub._relpath
280 280 if not util.safehasattr(sub, '_repo'):
281 281 return sub._path
282 282 return reporelpath(sub._repo)
283 283
284 284 def _abssource(repo, push=False, abort=True):
285 285 """return pull/push path of repo - either based on parent repo .hgsub info
286 286 or on the top repo config. Abort or return None if no source found."""
287 287 if util.safehasattr(repo, '_subparent'):
288 288 source = util.url(repo._subsource)
289 289 if source.isabs():
290 290 return str(source)
291 291 source.path = posixpath.normpath(source.path)
292 292 parent = _abssource(repo._subparent, push, abort=False)
293 293 if parent:
294 294 parent = util.url(util.pconvert(parent))
295 295 parent.path = posixpath.join(parent.path or '', source.path)
296 296 parent.path = posixpath.normpath(parent.path)
297 297 return str(parent)
298 298 else: # recursion reached top repo
299 299 if util.safehasattr(repo, '_subtoppath'):
300 300 return repo._subtoppath
301 301 if push and repo.ui.config('paths', 'default-push'):
302 302 return repo.ui.config('paths', 'default-push')
303 303 if repo.ui.config('paths', 'default'):
304 304 return repo.ui.config('paths', 'default')
305 305 if repo.shared():
306 306 # chop off the .hg component to get the default path form
307 307 return os.path.dirname(repo.sharedpath)
308 308 if abort:
309 309 raise util.Abort(_("default path for subrepository not found"))
310 310
311 311 def _sanitize(ui, path, ignore):
312 312 for dirname, dirs, names in os.walk(path):
313 313 for i, d in enumerate(dirs):
314 314 if d.lower() == ignore:
315 315 del dirs[i]
316 316 break
317 317 if os.path.basename(dirname).lower() != '.hg':
318 318 continue
319 319 for f in names:
320 320 if f.lower() == 'hgrc':
321 321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
322 322 "in '%s'\n") % dirname)
323 323 os.unlink(os.path.join(dirname, f))
324 324
325 325 def subrepo(ctx, path):
326 326 """return instance of the right subrepo class for subrepo in path"""
327 327 # subrepo inherently violates our import layering rules
328 328 # because it wants to make repo objects from deep inside the stack
329 329 # so we manually delay the circular imports to not break
330 330 # scripts that don't use our demand-loading
331 331 global hg
332 332 import hg as h
333 333 hg = h
334 334
335 335 pathutil.pathauditor(ctx._repo.root)(path)
336 336 state = ctx.substate[path]
337 337 if state[2] not in types:
338 338 raise util.Abort(_('unknown subrepo type %s') % state[2])
339 339 return types[state[2]](ctx, path, state[:2])
340 340
341 341 def newcommitphase(ui, ctx):
342 342 commitphase = phases.newcommitphase(ui)
343 343 substate = getattr(ctx, "substate", None)
344 344 if not substate:
345 345 return commitphase
346 346 check = ui.config('phases', 'checksubrepos', 'follow')
347 347 if check not in ('ignore', 'follow', 'abort'):
348 348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
349 349 % (check))
350 350 if check == 'ignore':
351 351 return commitphase
352 352 maxphase = phases.public
353 353 maxsub = None
354 354 for s in sorted(substate):
355 355 sub = ctx.sub(s)
356 356 subphase = sub.phase(substate[s][1])
357 357 if maxphase < subphase:
358 358 maxphase = subphase
359 359 maxsub = s
360 360 if commitphase < maxphase:
361 361 if check == 'abort':
362 362 raise util.Abort(_("can't commit in %s phase"
363 363 " conflicting %s from subrepository %s") %
364 364 (phases.phasenames[commitphase],
365 365 phases.phasenames[maxphase], maxsub))
366 366 ui.warn(_("warning: changes are committed in"
367 367 " %s phase from subrepository %s\n") %
368 368 (phases.phasenames[maxphase], maxsub))
369 369 return maxphase
370 370 return commitphase
371 371
372 372 # subrepo classes need to implement the following abstract class:
373 373
374 374 class abstractsubrepo(object):
375 375
376 376 def __init__(self, ui):
377 377 self.ui = ui
378 378
379 379 def storeclean(self, path):
380 380 """
381 381 returns true if the repository has not changed since it was last
382 382 cloned from or pushed to a given repository.
383 383 """
384 384 return False
385 385
386 386 def dirty(self, ignoreupdate=False):
387 387 """returns true if the dirstate of the subrepo is dirty or does not
388 388 match current stored state. If ignoreupdate is true, only check
389 389 whether the subrepo has uncommitted changes in its dirstate.
390 390 """
391 391 raise NotImplementedError
392 392
393 393 def basestate(self):
394 394 """current working directory base state, disregarding .hgsubstate
395 395 state and working directory modifications"""
396 396 raise NotImplementedError
397 397
398 398 def checknested(self, path):
399 399 """check if path is a subrepository within this repository"""
400 400 return False
401 401
402 402 def commit(self, text, user, date):
403 403 """commit the current changes to the subrepo with the given
404 404 log message. Use given user and date if possible. Return the
405 405 new state of the subrepo.
406 406 """
407 407 raise NotImplementedError
408 408
409 409 def phase(self, state):
410 410 """returns phase of specified state in the subrepository.
411 411 """
412 412 return phases.public
413 413
414 414 def remove(self):
415 415 """remove the subrepo
416 416
417 417 (should verify the dirstate is not dirty first)
418 418 """
419 419 raise NotImplementedError
420 420
421 421 def get(self, state, overwrite=False):
422 422 """run whatever commands are needed to put the subrepo into
423 423 this state
424 424 """
425 425 raise NotImplementedError
426 426
427 427 def merge(self, state):
428 428 """merge currently-saved state with the new state."""
429 429 raise NotImplementedError
430 430
431 431 def push(self, opts):
432 432 """perform whatever action is analogous to 'hg push'
433 433
434 434 This may be a no-op on some systems.
435 435 """
436 436 raise NotImplementedError
437 437
438 438 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
439 439 return []
440 440
441 441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
442 442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
443 443 return 1
444 444
445 445 def cat(self, match, prefix, **opts):
446 446 return 1
447 447
448 448 def status(self, rev2, **opts):
449 449 return scmutil.status([], [], [], [], [], [], [])
450 450
451 451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
452 452 pass
453 453
454 454 def outgoing(self, ui, dest, opts):
455 455 return 1
456 456
457 457 def incoming(self, ui, source, opts):
458 458 return 1
459 459
460 460 def files(self):
461 461 """return filename iterator"""
462 462 raise NotImplementedError
463 463
464 464 def filedata(self, name):
465 465 """return file data"""
466 466 raise NotImplementedError
467 467
468 468 def fileflags(self, name):
469 469 """return file flags"""
470 470 return ''
471 471
472 472 def archive(self, archiver, prefix, match=None):
473 473 if match is not None:
474 474 files = [f for f in self.files() if match(f)]
475 475 else:
476 476 files = self.files()
477 477 total = len(files)
478 478 relpath = subrelpath(self)
479 479 self.ui.progress(_('archiving (%s)') % relpath, 0,
480 480 unit=_('files'), total=total)
481 481 for i, name in enumerate(files):
482 482 flags = self.fileflags(name)
483 483 mode = 'x' in flags and 0755 or 0644
484 484 symlink = 'l' in flags
485 485 archiver.addfile(os.path.join(prefix, self._path, name),
486 486 mode, symlink, self.filedata(name))
487 487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
488 488 unit=_('files'), total=total)
489 489 self.ui.progress(_('archiving (%s)') % relpath, None)
490 490 return total
491 491
492 492 def walk(self, match):
493 493 '''
494 494 walk recursively through the directory tree, finding all files
495 495 matched by the match function
496 496 '''
497 497 pass
498 498
499 499 def forget(self, match, prefix):
500 500 return ([], [])
501 501
502 502 def removefiles(self, matcher, prefix, after, force, subrepos):
503 503 """remove the matched files from the subrepository and the filesystem,
504 504 possibly by force and/or after the file has been removed from the
505 505 filesystem. Return 0 on success, 1 on any warning.
506 506 """
507 507 return 1
508 508
509 509 def revert(self, substate, *pats, **opts):
510 510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
511 511 % (substate[0], substate[2]))
512 512 return []
513 513
514 514 def shortid(self, revid):
515 515 return revid
516 516
517 517 class hgsubrepo(abstractsubrepo):
518 518 def __init__(self, ctx, path, state):
519 519 super(hgsubrepo, self).__init__(ctx._repo.ui)
520 520 self._path = path
521 521 self._state = state
522 522 r = ctx._repo
523 523 root = r.wjoin(path)
524 524 create = not r.wvfs.exists('%s/.hg' % path)
525 525 self._repo = hg.repository(r.baseui, root, create=create)
526 526 self.ui = self._repo.ui
527 527 for s, k in [('ui', 'commitsubrepos')]:
528 528 v = r.ui.config(s, k)
529 529 if v:
530 530 self.ui.setconfig(s, k, v, 'subrepo')
531 531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
532 532 self._initrepo(r, state[0], create)
533 533
534 534 def storeclean(self, path):
535 535 lock = self._repo.lock()
536 536 try:
537 537 return self._storeclean(path)
538 538 finally:
539 539 lock.release()
540 540
541 541 def _storeclean(self, path):
542 542 clean = True
543 543 itercache = self._calcstorehash(path)
544 544 try:
545 545 for filehash in self._readstorehashcache(path):
546 546 if filehash != itercache.next():
547 547 clean = False
548 548 break
549 549 except StopIteration:
550 550 # the cached and current pull states have a different size
551 551 clean = False
552 552 if clean:
553 553 try:
554 554 itercache.next()
555 555 # the cached and current pull states have a different size
556 556 clean = False
557 557 except StopIteration:
558 558 pass
559 559 return clean
560 560
561 561 def _calcstorehash(self, remotepath):
562 562 '''calculate a unique "store hash"
563 563
564 564 This method is used to to detect when there are changes that may
565 565 require a push to a given remote path.'''
566 566 # sort the files that will be hashed in increasing (likely) file size
567 567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
568 568 yield '# %s\n' % _expandedabspath(remotepath)
569 569 vfs = self._repo.vfs
570 570 for relname in filelist:
571 571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
572 572 yield '%s = %s\n' % (relname, filehash)
573 573
574 574 @propertycache
575 575 def _cachestorehashvfs(self):
576 576 return scmutil.vfs(self._repo.join('cache/storehash'))
577 577
578 578 def _readstorehashcache(self, remotepath):
579 579 '''read the store hash cache for a given remote repository'''
580 580 cachefile = _getstorehashcachename(remotepath)
581 581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
582 582
583 583 def _cachestorehash(self, remotepath):
584 584 '''cache the current store hash
585 585
586 586 Each remote repo requires its own store hash cache, because a subrepo
587 587 store may be "clean" versus a given remote repo, but not versus another
588 588 '''
589 589 cachefile = _getstorehashcachename(remotepath)
590 590 lock = self._repo.lock()
591 591 try:
592 592 storehash = list(self._calcstorehash(remotepath))
593 593 vfs = self._cachestorehashvfs
594 594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
595 595 finally:
596 596 lock.release()
597 597
598 598 @annotatesubrepoerror
599 599 def _initrepo(self, parentrepo, source, create):
600 600 self._repo._subparent = parentrepo
601 601 self._repo._subsource = source
602 602
603 603 if create:
604 604 lines = ['[paths]\n']
605 605
606 606 def addpathconfig(key, value):
607 607 if value:
608 608 lines.append('%s = %s\n' % (key, value))
609 609 self.ui.setconfig('paths', key, value, 'subrepo')
610 610
611 611 defpath = _abssource(self._repo, abort=False)
612 612 defpushpath = _abssource(self._repo, True, abort=False)
613 613 addpathconfig('default', defpath)
614 614 if defpath != defpushpath:
615 615 addpathconfig('default-push', defpushpath)
616 616
617 617 fp = self._repo.vfs("hgrc", "w", text=True)
618 618 try:
619 619 fp.write(''.join(lines))
620 620 finally:
621 621 fp.close()
622 622
623 623 @annotatesubrepoerror
624 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
625 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
626 os.path.join(prefix, self._path), explicitonly)
624 def add(self, ui, match, prefix, explicitonly, **opts):
625 return cmdutil.add(ui, self._repo, match,
626 os.path.join(prefix, self._path), explicitonly,
627 **opts)
627 628
628 629 def addremove(self, m, prefix, opts, dry_run, similarity):
629 630 # In the same way as sub directories are processed, once in a subrepo,
630 631 # always entry any of its subrepos. Don't corrupt the options that will
631 632 # be used to process sibling subrepos however.
632 633 opts = copy.copy(opts)
633 634 opts['subrepos'] = True
634 635 return scmutil.addremove(self._repo, m,
635 636 os.path.join(prefix, self._path), opts,
636 637 dry_run, similarity)
637 638
638 639 @annotatesubrepoerror
639 640 def cat(self, match, prefix, **opts):
640 641 rev = self._state[1]
641 642 ctx = self._repo[rev]
642 643 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
643 644
644 645 @annotatesubrepoerror
645 646 def status(self, rev2, **opts):
646 647 try:
647 648 rev1 = self._state[1]
648 649 ctx1 = self._repo[rev1]
649 650 ctx2 = self._repo[rev2]
650 651 return self._repo.status(ctx1, ctx2, **opts)
651 652 except error.RepoLookupError, inst:
652 653 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
653 654 % (inst, subrelpath(self)))
654 655 return scmutil.status([], [], [], [], [], [], [])
655 656
656 657 @annotatesubrepoerror
657 658 def diff(self, ui, diffopts, node2, match, prefix, **opts):
658 659 try:
659 660 node1 = node.bin(self._state[1])
660 661 # We currently expect node2 to come from substate and be
661 662 # in hex format
662 663 if node2 is not None:
663 664 node2 = node.bin(node2)
664 665 cmdutil.diffordiffstat(ui, self._repo, diffopts,
665 666 node1, node2, match,
666 667 prefix=posixpath.join(prefix, self._path),
667 668 listsubrepos=True, **opts)
668 669 except error.RepoLookupError, inst:
669 670 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
670 671 % (inst, subrelpath(self)))
671 672
672 673 @annotatesubrepoerror
673 674 def archive(self, archiver, prefix, match=None):
674 675 self._get(self._state + ('hg',))
675 676 total = abstractsubrepo.archive(self, archiver, prefix, match)
676 677 rev = self._state[1]
677 678 ctx = self._repo[rev]
678 679 for subpath in ctx.substate:
679 680 s = subrepo(ctx, subpath)
680 681 submatch = matchmod.narrowmatcher(subpath, match)
681 682 total += s.archive(
682 683 archiver, os.path.join(prefix, self._path), submatch)
683 684 return total
684 685
685 686 @annotatesubrepoerror
686 687 def dirty(self, ignoreupdate=False):
687 688 r = self._state[1]
688 689 if r == '' and not ignoreupdate: # no state recorded
689 690 return True
690 691 w = self._repo[None]
691 692 if r != w.p1().hex() and not ignoreupdate:
692 693 # different version checked out
693 694 return True
694 695 return w.dirty() # working directory changed
695 696
696 697 def basestate(self):
697 698 return self._repo['.'].hex()
698 699
699 700 def checknested(self, path):
700 701 return self._repo._checknested(self._repo.wjoin(path))
701 702
702 703 @annotatesubrepoerror
703 704 def commit(self, text, user, date):
704 705 # don't bother committing in the subrepo if it's only been
705 706 # updated
706 707 if not self.dirty(True):
707 708 return self._repo['.'].hex()
708 709 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
709 710 n = self._repo.commit(text, user, date)
710 711 if not n:
711 712 return self._repo['.'].hex() # different version checked out
712 713 return node.hex(n)
713 714
714 715 @annotatesubrepoerror
715 716 def phase(self, state):
716 717 return self._repo[state].phase()
717 718
718 719 @annotatesubrepoerror
719 720 def remove(self):
720 721 # we can't fully delete the repository as it may contain
721 722 # local-only history
722 723 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
723 724 hg.clean(self._repo, node.nullid, False)
724 725
725 726 def _get(self, state):
726 727 source, revision, kind = state
727 728 if revision in self._repo.unfiltered():
728 729 return True
729 730 self._repo._subsource = source
730 731 srcurl = _abssource(self._repo)
731 732 other = hg.peer(self._repo, {}, srcurl)
732 733 if len(self._repo) == 0:
733 734 self.ui.status(_('cloning subrepo %s from %s\n')
734 735 % (subrelpath(self), srcurl))
735 736 parentrepo = self._repo._subparent
736 737 shutil.rmtree(self._repo.path)
737 738 other, cloned = hg.clone(self._repo._subparent.baseui, {},
738 739 other, self._repo.root,
739 740 update=False)
740 741 self._repo = cloned.local()
741 742 self._initrepo(parentrepo, source, create=True)
742 743 self._cachestorehash(srcurl)
743 744 else:
744 745 self.ui.status(_('pulling subrepo %s from %s\n')
745 746 % (subrelpath(self), srcurl))
746 747 cleansub = self.storeclean(srcurl)
747 748 exchange.pull(self._repo, other)
748 749 if cleansub:
749 750 # keep the repo clean after pull
750 751 self._cachestorehash(srcurl)
751 752 return False
752 753
753 754 @annotatesubrepoerror
754 755 def get(self, state, overwrite=False):
755 756 inrepo = self._get(state)
756 757 source, revision, kind = state
757 758 repo = self._repo
758 759 repo.ui.debug("getting subrepo %s\n" % self._path)
759 760 if inrepo:
760 761 urepo = repo.unfiltered()
761 762 ctx = urepo[revision]
762 763 if ctx.hidden():
763 764 urepo.ui.warn(
764 765 _('revision %s in subrepo %s is hidden\n') \
765 766 % (revision[0:12], self._path))
766 767 repo = urepo
767 768 hg.updaterepo(repo, revision, overwrite)
768 769
769 770 @annotatesubrepoerror
770 771 def merge(self, state):
771 772 self._get(state)
772 773 cur = self._repo['.']
773 774 dst = self._repo[state[1]]
774 775 anc = dst.ancestor(cur)
775 776
776 777 def mergefunc():
777 778 if anc == cur and dst.branch() == cur.branch():
778 779 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
779 780 hg.update(self._repo, state[1])
780 781 elif anc == dst:
781 782 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
782 783 else:
783 784 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
784 785 hg.merge(self._repo, state[1], remind=False)
785 786
786 787 wctx = self._repo[None]
787 788 if self.dirty():
788 789 if anc != dst:
789 790 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
790 791 mergefunc()
791 792 else:
792 793 mergefunc()
793 794 else:
794 795 mergefunc()
795 796
796 797 @annotatesubrepoerror
797 798 def push(self, opts):
798 799 force = opts.get('force')
799 800 newbranch = opts.get('new_branch')
800 801 ssh = opts.get('ssh')
801 802
802 803 # push subrepos depth-first for coherent ordering
803 804 c = self._repo['']
804 805 subs = c.substate # only repos that are committed
805 806 for s in sorted(subs):
806 807 if c.sub(s).push(opts) == 0:
807 808 return False
808 809
809 810 dsturl = _abssource(self._repo, True)
810 811 if not force:
811 812 if self.storeclean(dsturl):
812 813 self.ui.status(
813 814 _('no changes made to subrepo %s since last push to %s\n')
814 815 % (subrelpath(self), dsturl))
815 816 return None
816 817 self.ui.status(_('pushing subrepo %s to %s\n') %
817 818 (subrelpath(self), dsturl))
818 819 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
819 820 res = exchange.push(self._repo, other, force, newbranch=newbranch)
820 821
821 822 # the repo is now clean
822 823 self._cachestorehash(dsturl)
823 824 return res.cgresult
824 825
825 826 @annotatesubrepoerror
826 827 def outgoing(self, ui, dest, opts):
827 828 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
828 829
829 830 @annotatesubrepoerror
830 831 def incoming(self, ui, source, opts):
831 832 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
832 833
833 834 @annotatesubrepoerror
834 835 def files(self):
835 836 rev = self._state[1]
836 837 ctx = self._repo[rev]
837 838 return ctx.manifest()
838 839
839 840 def filedata(self, name):
840 841 rev = self._state[1]
841 842 return self._repo[rev][name].data()
842 843
843 844 def fileflags(self, name):
844 845 rev = self._state[1]
845 846 ctx = self._repo[rev]
846 847 return ctx.flags(name)
847 848
848 849 def walk(self, match):
849 850 ctx = self._repo[None]
850 851 return ctx.walk(match)
851 852
852 853 @annotatesubrepoerror
853 854 def forget(self, match, prefix):
854 855 return cmdutil.forget(self.ui, self._repo, match,
855 856 os.path.join(prefix, self._path), True)
856 857
857 858 @annotatesubrepoerror
858 859 def removefiles(self, matcher, prefix, after, force, subrepos):
859 860 return cmdutil.remove(self.ui, self._repo, matcher,
860 861 os.path.join(prefix, self._path), after, force,
861 862 subrepos)
862 863
863 864 @annotatesubrepoerror
864 865 def revert(self, substate, *pats, **opts):
865 866 # reverting a subrepo is a 2 step process:
866 867 # 1. if the no_backup is not set, revert all modified
867 868 # files inside the subrepo
868 869 # 2. update the subrepo to the revision specified in
869 870 # the corresponding substate dictionary
870 871 self.ui.status(_('reverting subrepo %s\n') % substate[0])
871 872 if not opts.get('no_backup'):
872 873 # Revert all files on the subrepo, creating backups
873 874 # Note that this will not recursively revert subrepos
874 875 # We could do it if there was a set:subrepos() predicate
875 876 opts = opts.copy()
876 877 opts['date'] = None
877 878 opts['rev'] = substate[1]
878 879
879 880 pats = []
880 881 if not opts.get('all'):
881 882 pats = ['set:modified()']
882 883 self.filerevert(*pats, **opts)
883 884
884 885 # Update the repo to the revision specified in the given substate
885 886 self.get(substate, overwrite=True)
886 887
887 888 def filerevert(self, *pats, **opts):
888 889 ctx = self._repo[opts['rev']]
889 890 parents = self._repo.dirstate.parents()
890 891 if opts.get('all'):
891 892 pats = ['set:modified()']
892 893 else:
893 894 pats = []
894 895 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
895 896
896 897 def shortid(self, revid):
897 898 return revid[:12]
898 899
899 900 class svnsubrepo(abstractsubrepo):
900 901 def __init__(self, ctx, path, state):
901 902 super(svnsubrepo, self).__init__(ctx._repo.ui)
902 903 self._path = path
903 904 self._state = state
904 905 self._ctx = ctx
905 906 self._exe = util.findexe('svn')
906 907 if not self._exe:
907 908 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
908 909 % self._path)
909 910
910 911 def _svncommand(self, commands, filename='', failok=False):
911 912 cmd = [self._exe]
912 913 extrakw = {}
913 914 if not self.ui.interactive():
914 915 # Making stdin be a pipe should prevent svn from behaving
915 916 # interactively even if we can't pass --non-interactive.
916 917 extrakw['stdin'] = subprocess.PIPE
917 918 # Starting in svn 1.5 --non-interactive is a global flag
918 919 # instead of being per-command, but we need to support 1.4 so
919 920 # we have to be intelligent about what commands take
920 921 # --non-interactive.
921 922 if commands[0] in ('update', 'checkout', 'commit'):
922 923 cmd.append('--non-interactive')
923 924 cmd.extend(commands)
924 925 if filename is not None:
925 926 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
926 927 cmd.append(path)
927 928 env = dict(os.environ)
928 929 # Avoid localized output, preserve current locale for everything else.
929 930 lc_all = env.get('LC_ALL')
930 931 if lc_all:
931 932 env['LANG'] = lc_all
932 933 del env['LC_ALL']
933 934 env['LC_MESSAGES'] = 'C'
934 935 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
935 936 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
936 937 universal_newlines=True, env=env, **extrakw)
937 938 stdout, stderr = p.communicate()
938 939 stderr = stderr.strip()
939 940 if not failok:
940 941 if p.returncode:
941 942 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
942 943 if stderr:
943 944 self.ui.warn(stderr + '\n')
944 945 return stdout, stderr
945 946
946 947 @propertycache
947 948 def _svnversion(self):
948 949 output, err = self._svncommand(['--version', '--quiet'], filename=None)
949 950 m = re.search(r'^(\d+)\.(\d+)', output)
950 951 if not m:
951 952 raise util.Abort(_('cannot retrieve svn tool version'))
952 953 return (int(m.group(1)), int(m.group(2)))
953 954
954 955 def _wcrevs(self):
955 956 # Get the working directory revision as well as the last
956 957 # commit revision so we can compare the subrepo state with
957 958 # both. We used to store the working directory one.
958 959 output, err = self._svncommand(['info', '--xml'])
959 960 doc = xml.dom.minidom.parseString(output)
960 961 entries = doc.getElementsByTagName('entry')
961 962 lastrev, rev = '0', '0'
962 963 if entries:
963 964 rev = str(entries[0].getAttribute('revision')) or '0'
964 965 commits = entries[0].getElementsByTagName('commit')
965 966 if commits:
966 967 lastrev = str(commits[0].getAttribute('revision')) or '0'
967 968 return (lastrev, rev)
968 969
969 970 def _wcrev(self):
970 971 return self._wcrevs()[0]
971 972
972 973 def _wcchanged(self):
973 974 """Return (changes, extchanges, missing) where changes is True
974 975 if the working directory was changed, extchanges is
975 976 True if any of these changes concern an external entry and missing
976 977 is True if any change is a missing entry.
977 978 """
978 979 output, err = self._svncommand(['status', '--xml'])
979 980 externals, changes, missing = [], [], []
980 981 doc = xml.dom.minidom.parseString(output)
981 982 for e in doc.getElementsByTagName('entry'):
982 983 s = e.getElementsByTagName('wc-status')
983 984 if not s:
984 985 continue
985 986 item = s[0].getAttribute('item')
986 987 props = s[0].getAttribute('props')
987 988 path = e.getAttribute('path')
988 989 if item == 'external':
989 990 externals.append(path)
990 991 elif item == 'missing':
991 992 missing.append(path)
992 993 if (item not in ('', 'normal', 'unversioned', 'external')
993 994 or props not in ('', 'none', 'normal')):
994 995 changes.append(path)
995 996 for path in changes:
996 997 for ext in externals:
997 998 if path == ext or path.startswith(ext + os.sep):
998 999 return True, True, bool(missing)
999 1000 return bool(changes), False, bool(missing)
1000 1001
1001 1002 def dirty(self, ignoreupdate=False):
1002 1003 if not self._wcchanged()[0]:
1003 1004 if self._state[1] in self._wcrevs() or ignoreupdate:
1004 1005 return False
1005 1006 return True
1006 1007
1007 1008 def basestate(self):
1008 1009 lastrev, rev = self._wcrevs()
1009 1010 if lastrev != rev:
1010 1011 # Last committed rev is not the same than rev. We would
1011 1012 # like to take lastrev but we do not know if the subrepo
1012 1013 # URL exists at lastrev. Test it and fallback to rev it
1013 1014 # is not there.
1014 1015 try:
1015 1016 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1016 1017 return lastrev
1017 1018 except error.Abort:
1018 1019 pass
1019 1020 return rev
1020 1021
1021 1022 @annotatesubrepoerror
1022 1023 def commit(self, text, user, date):
1023 1024 # user and date are out of our hands since svn is centralized
1024 1025 changed, extchanged, missing = self._wcchanged()
1025 1026 if not changed:
1026 1027 return self.basestate()
1027 1028 if extchanged:
1028 1029 # Do not try to commit externals
1029 1030 raise util.Abort(_('cannot commit svn externals'))
1030 1031 if missing:
1031 1032 # svn can commit with missing entries but aborting like hg
1032 1033 # seems a better approach.
1033 1034 raise util.Abort(_('cannot commit missing svn entries'))
1034 1035 commitinfo, err = self._svncommand(['commit', '-m', text])
1035 1036 self.ui.status(commitinfo)
1036 1037 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1037 1038 if not newrev:
1038 1039 if not commitinfo.strip():
1039 1040 # Sometimes, our definition of "changed" differs from
1040 1041 # svn one. For instance, svn ignores missing files
1041 1042 # when committing. If there are only missing files, no
1042 1043 # commit is made, no output and no error code.
1043 1044 raise util.Abort(_('failed to commit svn changes'))
1044 1045 raise util.Abort(commitinfo.splitlines()[-1])
1045 1046 newrev = newrev.groups()[0]
1046 1047 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1047 1048 return newrev
1048 1049
1049 1050 @annotatesubrepoerror
1050 1051 def remove(self):
1051 1052 if self.dirty():
1052 1053 self.ui.warn(_('not removing repo %s because '
1053 1054 'it has changes.\n') % self._path)
1054 1055 return
1055 1056 self.ui.note(_('removing subrepo %s\n') % self._path)
1056 1057
1057 1058 def onerror(function, path, excinfo):
1058 1059 if function is not os.remove:
1059 1060 raise
1060 1061 # read-only files cannot be unlinked under Windows
1061 1062 s = os.stat(path)
1062 1063 if (s.st_mode & stat.S_IWRITE) != 0:
1063 1064 raise
1064 1065 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1065 1066 os.remove(path)
1066 1067
1067 1068 path = self._ctx._repo.wjoin(self._path)
1068 1069 shutil.rmtree(path, onerror=onerror)
1069 1070 try:
1070 1071 os.removedirs(os.path.dirname(path))
1071 1072 except OSError:
1072 1073 pass
1073 1074
1074 1075 @annotatesubrepoerror
1075 1076 def get(self, state, overwrite=False):
1076 1077 if overwrite:
1077 1078 self._svncommand(['revert', '--recursive'])
1078 1079 args = ['checkout']
1079 1080 if self._svnversion >= (1, 5):
1080 1081 args.append('--force')
1081 1082 # The revision must be specified at the end of the URL to properly
1082 1083 # update to a directory which has since been deleted and recreated.
1083 1084 args.append('%s@%s' % (state[0], state[1]))
1084 1085 status, err = self._svncommand(args, failok=True)
1085 1086 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1086 1087 if not re.search('Checked out revision [0-9]+.', status):
1087 1088 if ('is already a working copy for a different URL' in err
1088 1089 and (self._wcchanged()[:2] == (False, False))):
1089 1090 # obstructed but clean working copy, so just blow it away.
1090 1091 self.remove()
1091 1092 self.get(state, overwrite=False)
1092 1093 return
1093 1094 raise util.Abort((status or err).splitlines()[-1])
1094 1095 self.ui.status(status)
1095 1096
1096 1097 @annotatesubrepoerror
1097 1098 def merge(self, state):
1098 1099 old = self._state[1]
1099 1100 new = state[1]
1100 1101 wcrev = self._wcrev()
1101 1102 if new != wcrev:
1102 1103 dirty = old == wcrev or self._wcchanged()[0]
1103 1104 if _updateprompt(self.ui, self, dirty, wcrev, new):
1104 1105 self.get(state, False)
1105 1106
1106 1107 def push(self, opts):
1107 1108 # push is a no-op for SVN
1108 1109 return True
1109 1110
1110 1111 @annotatesubrepoerror
1111 1112 def files(self):
1112 1113 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1113 1114 doc = xml.dom.minidom.parseString(output)
1114 1115 paths = []
1115 1116 for e in doc.getElementsByTagName('entry'):
1116 1117 kind = str(e.getAttribute('kind'))
1117 1118 if kind != 'file':
1118 1119 continue
1119 1120 name = ''.join(c.data for c
1120 1121 in e.getElementsByTagName('name')[0].childNodes
1121 1122 if c.nodeType == c.TEXT_NODE)
1122 1123 paths.append(name.encode('utf-8'))
1123 1124 return paths
1124 1125
1125 1126 def filedata(self, name):
1126 1127 return self._svncommand(['cat'], name)[0]
1127 1128
1128 1129
1129 1130 class gitsubrepo(abstractsubrepo):
1130 1131 def __init__(self, ctx, path, state):
1131 1132 super(gitsubrepo, self).__init__(ctx._repo.ui)
1132 1133 self._state = state
1133 1134 self._ctx = ctx
1134 1135 self._path = path
1135 1136 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1136 1137 self._abspath = ctx._repo.wjoin(path)
1137 1138 self._subparent = ctx._repo
1138 1139 self._ensuregit()
1139 1140
1140 1141 def _ensuregit(self):
1141 1142 try:
1142 1143 self._gitexecutable = 'git'
1143 1144 out, err = self._gitnodir(['--version'])
1144 1145 except OSError, e:
1145 1146 if e.errno != 2 or os.name != 'nt':
1146 1147 raise
1147 1148 self._gitexecutable = 'git.cmd'
1148 1149 out, err = self._gitnodir(['--version'])
1149 1150 versionstatus = self._checkversion(out)
1150 1151 if versionstatus == 'unknown':
1151 1152 self.ui.warn(_('cannot retrieve git version\n'))
1152 1153 elif versionstatus == 'abort':
1153 1154 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1154 1155 elif versionstatus == 'warning':
1155 1156 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1156 1157
1157 1158 @staticmethod
1158 1159 def _gitversion(out):
1159 1160 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1160 1161 if m:
1161 1162 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1162 1163
1163 1164 m = re.search(r'^git version (\d+)\.(\d+)', out)
1164 1165 if m:
1165 1166 return (int(m.group(1)), int(m.group(2)), 0)
1166 1167
1167 1168 return -1
1168 1169
1169 1170 @staticmethod
1170 1171 def _checkversion(out):
1171 1172 '''ensure git version is new enough
1172 1173
1173 1174 >>> _checkversion = gitsubrepo._checkversion
1174 1175 >>> _checkversion('git version 1.6.0')
1175 1176 'ok'
1176 1177 >>> _checkversion('git version 1.8.5')
1177 1178 'ok'
1178 1179 >>> _checkversion('git version 1.4.0')
1179 1180 'abort'
1180 1181 >>> _checkversion('git version 1.5.0')
1181 1182 'warning'
1182 1183 >>> _checkversion('git version 1.9-rc0')
1183 1184 'ok'
1184 1185 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1185 1186 'ok'
1186 1187 >>> _checkversion('git version 1.9.0.GIT')
1187 1188 'ok'
1188 1189 >>> _checkversion('git version 12345')
1189 1190 'unknown'
1190 1191 >>> _checkversion('no')
1191 1192 'unknown'
1192 1193 '''
1193 1194 version = gitsubrepo._gitversion(out)
1194 1195 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1195 1196 # despite the docstring comment. For now, error on 1.4.0, warn on
1196 1197 # 1.5.0 but attempt to continue.
1197 1198 if version == -1:
1198 1199 return 'unknown'
1199 1200 if version < (1, 5, 0):
1200 1201 return 'abort'
1201 1202 elif version < (1, 6, 0):
1202 1203 return 'warning'
1203 1204 return 'ok'
1204 1205
1205 1206 def _gitcommand(self, commands, env=None, stream=False):
1206 1207 return self._gitdir(commands, env=env, stream=stream)[0]
1207 1208
1208 1209 def _gitdir(self, commands, env=None, stream=False):
1209 1210 return self._gitnodir(commands, env=env, stream=stream,
1210 1211 cwd=self._abspath)
1211 1212
1212 1213 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1213 1214 """Calls the git command
1214 1215
1215 1216 The methods tries to call the git command. versions prior to 1.6.0
1216 1217 are not supported and very probably fail.
1217 1218 """
1218 1219 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1219 1220 # unless ui.quiet is set, print git's stderr,
1220 1221 # which is mostly progress and useful info
1221 1222 errpipe = None
1222 1223 if self.ui.quiet:
1223 1224 errpipe = open(os.devnull, 'w')
1224 1225 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1225 1226 cwd=cwd, env=env, close_fds=util.closefds,
1226 1227 stdout=subprocess.PIPE, stderr=errpipe)
1227 1228 if stream:
1228 1229 return p.stdout, None
1229 1230
1230 1231 retdata = p.stdout.read().strip()
1231 1232 # wait for the child to exit to avoid race condition.
1232 1233 p.wait()
1233 1234
1234 1235 if p.returncode != 0 and p.returncode != 1:
1235 1236 # there are certain error codes that are ok
1236 1237 command = commands[0]
1237 1238 if command in ('cat-file', 'symbolic-ref'):
1238 1239 return retdata, p.returncode
1239 1240 # for all others, abort
1240 1241 raise util.Abort('git %s error %d in %s' %
1241 1242 (command, p.returncode, self._relpath))
1242 1243
1243 1244 return retdata, p.returncode
1244 1245
1245 1246 def _gitmissing(self):
1246 1247 return not os.path.exists(os.path.join(self._abspath, '.git'))
1247 1248
1248 1249 def _gitstate(self):
1249 1250 return self._gitcommand(['rev-parse', 'HEAD'])
1250 1251
1251 1252 def _gitcurrentbranch(self):
1252 1253 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1253 1254 if err:
1254 1255 current = None
1255 1256 return current
1256 1257
1257 1258 def _gitremote(self, remote):
1258 1259 out = self._gitcommand(['remote', 'show', '-n', remote])
1259 1260 line = out.split('\n')[1]
1260 1261 i = line.index('URL: ') + len('URL: ')
1261 1262 return line[i:]
1262 1263
1263 1264 def _githavelocally(self, revision):
1264 1265 out, code = self._gitdir(['cat-file', '-e', revision])
1265 1266 return code == 0
1266 1267
1267 1268 def _gitisancestor(self, r1, r2):
1268 1269 base = self._gitcommand(['merge-base', r1, r2])
1269 1270 return base == r1
1270 1271
1271 1272 def _gitisbare(self):
1272 1273 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1273 1274
1274 1275 def _gitupdatestat(self):
1275 1276 """This must be run before git diff-index.
1276 1277 diff-index only looks at changes to file stat;
1277 1278 this command looks at file contents and updates the stat."""
1278 1279 self._gitcommand(['update-index', '-q', '--refresh'])
1279 1280
1280 1281 def _gitbranchmap(self):
1281 1282 '''returns 2 things:
1282 1283 a map from git branch to revision
1283 1284 a map from revision to branches'''
1284 1285 branch2rev = {}
1285 1286 rev2branch = {}
1286 1287
1287 1288 out = self._gitcommand(['for-each-ref', '--format',
1288 1289 '%(objectname) %(refname)'])
1289 1290 for line in out.split('\n'):
1290 1291 revision, ref = line.split(' ')
1291 1292 if (not ref.startswith('refs/heads/') and
1292 1293 not ref.startswith('refs/remotes/')):
1293 1294 continue
1294 1295 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1295 1296 continue # ignore remote/HEAD redirects
1296 1297 branch2rev[ref] = revision
1297 1298 rev2branch.setdefault(revision, []).append(ref)
1298 1299 return branch2rev, rev2branch
1299 1300
1300 1301 def _gittracking(self, branches):
1301 1302 'return map of remote branch to local tracking branch'
1302 1303 # assumes no more than one local tracking branch for each remote
1303 1304 tracking = {}
1304 1305 for b in branches:
1305 1306 if b.startswith('refs/remotes/'):
1306 1307 continue
1307 1308 bname = b.split('/', 2)[2]
1308 1309 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1309 1310 if remote:
1310 1311 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1311 1312 tracking['refs/remotes/%s/%s' %
1312 1313 (remote, ref.split('/', 2)[2])] = b
1313 1314 return tracking
1314 1315
1315 1316 def _abssource(self, source):
1316 1317 if '://' not in source:
1317 1318 # recognize the scp syntax as an absolute source
1318 1319 colon = source.find(':')
1319 1320 if colon != -1 and '/' not in source[:colon]:
1320 1321 return source
1321 1322 self._subsource = source
1322 1323 return _abssource(self)
1323 1324
1324 1325 def _fetch(self, source, revision):
1325 1326 if self._gitmissing():
1326 1327 source = self._abssource(source)
1327 1328 self.ui.status(_('cloning subrepo %s from %s\n') %
1328 1329 (self._relpath, source))
1329 1330 self._gitnodir(['clone', source, self._abspath])
1330 1331 if self._githavelocally(revision):
1331 1332 return
1332 1333 self.ui.status(_('pulling subrepo %s from %s\n') %
1333 1334 (self._relpath, self._gitremote('origin')))
1334 1335 # try only origin: the originally cloned repo
1335 1336 self._gitcommand(['fetch'])
1336 1337 if not self._githavelocally(revision):
1337 1338 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1338 1339 (revision, self._relpath))
1339 1340
1340 1341 @annotatesubrepoerror
1341 1342 def dirty(self, ignoreupdate=False):
1342 1343 if self._gitmissing():
1343 1344 return self._state[1] != ''
1344 1345 if self._gitisbare():
1345 1346 return True
1346 1347 if not ignoreupdate and self._state[1] != self._gitstate():
1347 1348 # different version checked out
1348 1349 return True
1349 1350 # check for staged changes or modified files; ignore untracked files
1350 1351 self._gitupdatestat()
1351 1352 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1352 1353 return code == 1
1353 1354
1354 1355 def basestate(self):
1355 1356 return self._gitstate()
1356 1357
1357 1358 @annotatesubrepoerror
1358 1359 def get(self, state, overwrite=False):
1359 1360 source, revision, kind = state
1360 1361 if not revision:
1361 1362 self.remove()
1362 1363 return
1363 1364 self._fetch(source, revision)
1364 1365 # if the repo was set to be bare, unbare it
1365 1366 if self._gitisbare():
1366 1367 self._gitcommand(['config', 'core.bare', 'false'])
1367 1368 if self._gitstate() == revision:
1368 1369 self._gitcommand(['reset', '--hard', 'HEAD'])
1369 1370 return
1370 1371 elif self._gitstate() == revision:
1371 1372 if overwrite:
1372 1373 # first reset the index to unmark new files for commit, because
1373 1374 # reset --hard will otherwise throw away files added for commit,
1374 1375 # not just unmark them.
1375 1376 self._gitcommand(['reset', 'HEAD'])
1376 1377 self._gitcommand(['reset', '--hard', 'HEAD'])
1377 1378 return
1378 1379 branch2rev, rev2branch = self._gitbranchmap()
1379 1380
1380 1381 def checkout(args):
1381 1382 cmd = ['checkout']
1382 1383 if overwrite:
1383 1384 # first reset the index to unmark new files for commit, because
1384 1385 # the -f option will otherwise throw away files added for
1385 1386 # commit, not just unmark them.
1386 1387 self._gitcommand(['reset', 'HEAD'])
1387 1388 cmd.append('-f')
1388 1389 self._gitcommand(cmd + args)
1389 1390 _sanitize(self.ui, self._abspath, '.git')
1390 1391
1391 1392 def rawcheckout():
1392 1393 # no branch to checkout, check it out with no branch
1393 1394 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1394 1395 self._relpath)
1395 1396 self.ui.warn(_('check out a git branch if you intend '
1396 1397 'to make changes\n'))
1397 1398 checkout(['-q', revision])
1398 1399
1399 1400 if revision not in rev2branch:
1400 1401 rawcheckout()
1401 1402 return
1402 1403 branches = rev2branch[revision]
1403 1404 firstlocalbranch = None
1404 1405 for b in branches:
1405 1406 if b == 'refs/heads/master':
1406 1407 # master trumps all other branches
1407 1408 checkout(['refs/heads/master'])
1408 1409 return
1409 1410 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1410 1411 firstlocalbranch = b
1411 1412 if firstlocalbranch:
1412 1413 checkout([firstlocalbranch])
1413 1414 return
1414 1415
1415 1416 tracking = self._gittracking(branch2rev.keys())
1416 1417 # choose a remote branch already tracked if possible
1417 1418 remote = branches[0]
1418 1419 if remote not in tracking:
1419 1420 for b in branches:
1420 1421 if b in tracking:
1421 1422 remote = b
1422 1423 break
1423 1424
1424 1425 if remote not in tracking:
1425 1426 # create a new local tracking branch
1426 1427 local = remote.split('/', 3)[3]
1427 1428 checkout(['-b', local, remote])
1428 1429 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1429 1430 # When updating to a tracked remote branch,
1430 1431 # if the local tracking branch is downstream of it,
1431 1432 # a normal `git pull` would have performed a "fast-forward merge"
1432 1433 # which is equivalent to updating the local branch to the remote.
1433 1434 # Since we are only looking at branching at update, we need to
1434 1435 # detect this situation and perform this action lazily.
1435 1436 if tracking[remote] != self._gitcurrentbranch():
1436 1437 checkout([tracking[remote]])
1437 1438 self._gitcommand(['merge', '--ff', remote])
1438 1439 _sanitize(self.ui, self._abspath, '.git')
1439 1440 else:
1440 1441 # a real merge would be required, just checkout the revision
1441 1442 rawcheckout()
1442 1443
1443 1444 @annotatesubrepoerror
1444 1445 def commit(self, text, user, date):
1445 1446 if self._gitmissing():
1446 1447 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1447 1448 cmd = ['commit', '-a', '-m', text]
1448 1449 env = os.environ.copy()
1449 1450 if user:
1450 1451 cmd += ['--author', user]
1451 1452 if date:
1452 1453 # git's date parser silently ignores when seconds < 1e9
1453 1454 # convert to ISO8601
1454 1455 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1455 1456 '%Y-%m-%dT%H:%M:%S %1%2')
1456 1457 self._gitcommand(cmd, env=env)
1457 1458 # make sure commit works otherwise HEAD might not exist under certain
1458 1459 # circumstances
1459 1460 return self._gitstate()
1460 1461
1461 1462 @annotatesubrepoerror
1462 1463 def merge(self, state):
1463 1464 source, revision, kind = state
1464 1465 self._fetch(source, revision)
1465 1466 base = self._gitcommand(['merge-base', revision, self._state[1]])
1466 1467 self._gitupdatestat()
1467 1468 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1468 1469
1469 1470 def mergefunc():
1470 1471 if base == revision:
1471 1472 self.get(state) # fast forward merge
1472 1473 elif base != self._state[1]:
1473 1474 self._gitcommand(['merge', '--no-commit', revision])
1474 1475 _sanitize(self.ui, self._abspath, '.git')
1475 1476
1476 1477 if self.dirty():
1477 1478 if self._gitstate() != revision:
1478 1479 dirty = self._gitstate() == self._state[1] or code != 0
1479 1480 if _updateprompt(self.ui, self, dirty,
1480 1481 self._state[1][:7], revision[:7]):
1481 1482 mergefunc()
1482 1483 else:
1483 1484 mergefunc()
1484 1485
1485 1486 @annotatesubrepoerror
1486 1487 def push(self, opts):
1487 1488 force = opts.get('force')
1488 1489
1489 1490 if not self._state[1]:
1490 1491 return True
1491 1492 if self._gitmissing():
1492 1493 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1493 1494 # if a branch in origin contains the revision, nothing to do
1494 1495 branch2rev, rev2branch = self._gitbranchmap()
1495 1496 if self._state[1] in rev2branch:
1496 1497 for b in rev2branch[self._state[1]]:
1497 1498 if b.startswith('refs/remotes/origin/'):
1498 1499 return True
1499 1500 for b, revision in branch2rev.iteritems():
1500 1501 if b.startswith('refs/remotes/origin/'):
1501 1502 if self._gitisancestor(self._state[1], revision):
1502 1503 return True
1503 1504 # otherwise, try to push the currently checked out branch
1504 1505 cmd = ['push']
1505 1506 if force:
1506 1507 cmd.append('--force')
1507 1508
1508 1509 current = self._gitcurrentbranch()
1509 1510 if current:
1510 1511 # determine if the current branch is even useful
1511 1512 if not self._gitisancestor(self._state[1], current):
1512 1513 self.ui.warn(_('unrelated git branch checked out '
1513 1514 'in subrepo %s\n') % self._relpath)
1514 1515 return False
1515 1516 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1516 1517 (current.split('/', 2)[2], self._relpath))
1517 1518 ret = self._gitdir(cmd + ['origin', current])
1518 1519 return ret[1] == 0
1519 1520 else:
1520 1521 self.ui.warn(_('no branch checked out in subrepo %s\n'
1521 1522 'cannot push revision %s\n') %
1522 1523 (self._relpath, self._state[1]))
1523 1524 return False
1524 1525
1525 1526 @annotatesubrepoerror
1526 1527 def remove(self):
1527 1528 if self._gitmissing():
1528 1529 return
1529 1530 if self.dirty():
1530 1531 self.ui.warn(_('not removing repo %s because '
1531 1532 'it has changes.\n') % self._relpath)
1532 1533 return
1533 1534 # we can't fully delete the repository as it may contain
1534 1535 # local-only history
1535 1536 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1536 1537 self._gitcommand(['config', 'core.bare', 'true'])
1537 1538 for f in os.listdir(self._abspath):
1538 1539 if f == '.git':
1539 1540 continue
1540 1541 path = os.path.join(self._abspath, f)
1541 1542 if os.path.isdir(path) and not os.path.islink(path):
1542 1543 shutil.rmtree(path)
1543 1544 else:
1544 1545 os.remove(path)
1545 1546
1546 1547 def archive(self, archiver, prefix, match=None):
1547 1548 total = 0
1548 1549 source, revision = self._state
1549 1550 if not revision:
1550 1551 return total
1551 1552 self._fetch(source, revision)
1552 1553
1553 1554 # Parse git's native archive command.
1554 1555 # This should be much faster than manually traversing the trees
1555 1556 # and objects with many subprocess calls.
1556 1557 tarstream = self._gitcommand(['archive', revision], stream=True)
1557 1558 tar = tarfile.open(fileobj=tarstream, mode='r|')
1558 1559 relpath = subrelpath(self)
1559 1560 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1560 1561 for i, info in enumerate(tar):
1561 1562 if info.isdir():
1562 1563 continue
1563 1564 if match and not match(info.name):
1564 1565 continue
1565 1566 if info.issym():
1566 1567 data = info.linkname
1567 1568 else:
1568 1569 data = tar.extractfile(info).read()
1569 1570 archiver.addfile(os.path.join(prefix, self._path, info.name),
1570 1571 info.mode, info.issym(), data)
1571 1572 total += 1
1572 1573 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1573 1574 unit=_('files'))
1574 1575 self.ui.progress(_('archiving (%s)') % relpath, None)
1575 1576 return total
1576 1577
1577 1578
1578 1579 @annotatesubrepoerror
1579 1580 def status(self, rev2, **opts):
1580 1581 rev1 = self._state[1]
1581 1582 if self._gitmissing() or not rev1:
1582 1583 # if the repo is missing, return no results
1583 1584 return [], [], [], [], [], [], []
1584 1585 modified, added, removed = [], [], []
1585 1586 self._gitupdatestat()
1586 1587 if rev2:
1587 1588 command = ['diff-tree', rev1, rev2]
1588 1589 else:
1589 1590 command = ['diff-index', rev1]
1590 1591 out = self._gitcommand(command)
1591 1592 for line in out.split('\n'):
1592 1593 tab = line.find('\t')
1593 1594 if tab == -1:
1594 1595 continue
1595 1596 status, f = line[tab - 1], line[tab + 1:]
1596 1597 if status == 'M':
1597 1598 modified.append(f)
1598 1599 elif status == 'A':
1599 1600 added.append(f)
1600 1601 elif status == 'D':
1601 1602 removed.append(f)
1602 1603
1603 1604 deleted, unknown, ignored, clean = [], [], [], []
1604 1605
1605 1606 if not rev2:
1606 1607 command = ['ls-files', '--others', '--exclude-standard']
1607 1608 out = self._gitcommand(command)
1608 1609 for line in out.split('\n'):
1609 1610 if len(line) == 0:
1610 1611 continue
1611 1612 unknown.append(line)
1612 1613
1613 1614 return scmutil.status(modified, added, removed, deleted,
1614 1615 unknown, ignored, clean)
1615 1616
1616 1617 @annotatesubrepoerror
1617 1618 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1618 1619 node1 = self._state[1]
1619 1620 cmd = ['diff']
1620 1621 if opts['stat']:
1621 1622 cmd.append('--stat')
1622 1623 else:
1623 1624 # for Git, this also implies '-p'
1624 1625 cmd.append('-U%d' % diffopts.context)
1625 1626
1626 1627 gitprefix = os.path.join(prefix, self._path)
1627 1628
1628 1629 if diffopts.noprefix:
1629 1630 cmd.extend(['--src-prefix=%s/' % gitprefix,
1630 1631 '--dst-prefix=%s/' % gitprefix])
1631 1632 else:
1632 1633 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1633 1634 '--dst-prefix=b/%s/' % gitprefix])
1634 1635
1635 1636 if diffopts.ignorews:
1636 1637 cmd.append('--ignore-all-space')
1637 1638 if diffopts.ignorewsamount:
1638 1639 cmd.append('--ignore-space-change')
1639 1640 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1640 1641 and diffopts.ignoreblanklines:
1641 1642 cmd.append('--ignore-blank-lines')
1642 1643
1643 1644 cmd.append(node1)
1644 1645 if node2:
1645 1646 cmd.append(node2)
1646 1647
1647 1648 if match.anypats():
1648 1649 return #No support for include/exclude yet
1649 1650
1650 1651 if match.always():
1651 1652 ui.write(self._gitcommand(cmd))
1652 1653 elif match.files():
1653 1654 for f in match.files():
1654 1655 ui.write(self._gitcommand(cmd + [f]))
1655 1656 elif match(gitprefix): #Subrepo is matched
1656 1657 ui.write(self._gitcommand(cmd))
1657 1658
1658 1659 @annotatesubrepoerror
1659 1660 def revert(self, substate, *pats, **opts):
1660 1661 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1661 1662 if not opts.get('no_backup'):
1662 1663 status = self.status(None)
1663 1664 names = status.modified
1664 1665 for name in names:
1665 1666 bakname = "%s.orig" % name
1666 1667 self.ui.note(_('saving current version of %s as %s\n') %
1667 1668 (name, bakname))
1668 1669 util.rename(os.path.join(self._abspath, name),
1669 1670 os.path.join(self._abspath, bakname))
1670 1671
1671 1672 self.get(substate, overwrite=True)
1672 1673 return []
1673 1674
1674 1675 def shortid(self, revid):
1675 1676 return revid[:7]
1676 1677
1677 1678 types = {
1678 1679 'hg': hgsubrepo,
1679 1680 'svn': svnsubrepo,
1680 1681 'git': gitsubrepo,
1681 1682 }
General Comments 0
You need to be logged in to leave comments. Login now