##// END OF EJS Templates
server: move cmdutil.service() to new module (API)...
Yuya Nishihara -
r30506:d9d8d78e default
parent child Browse files
Show More
@@ -0,0 +1,107 b''
1 # server.py - utility and factory of server
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 from __future__ import absolute_import
9
10 import errno
11 import os
12 import sys
13 import tempfile
14
15 from .i18n import _
16
17 from . import (
18 error,
19 util,
20 )
21
22 def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
23 runargs=None, appendpid=False):
24 '''Run a command as a service.'''
25
26 def writepid(pid):
27 if opts['pid_file']:
28 if appendpid:
29 mode = 'a'
30 else:
31 mode = 'w'
32 fp = open(opts['pid_file'], mode)
33 fp.write(str(pid) + '\n')
34 fp.close()
35
36 if opts['daemon'] and not opts['daemon_postexec']:
37 # Signal child process startup with file removal
38 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
39 os.close(lockfd)
40 try:
41 if not runargs:
42 runargs = util.hgcmd() + sys.argv[1:]
43 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
44 # Don't pass --cwd to the child process, because we've already
45 # changed directory.
46 for i in xrange(1, len(runargs)):
47 if runargs[i].startswith('--cwd='):
48 del runargs[i]
49 break
50 elif runargs[i].startswith('--cwd'):
51 del runargs[i:i + 2]
52 break
53 def condfn():
54 return not os.path.exists(lockpath)
55 pid = util.rundetached(runargs, condfn)
56 if pid < 0:
57 raise error.Abort(_('child process failed to start'))
58 writepid(pid)
59 finally:
60 try:
61 os.unlink(lockpath)
62 except OSError as e:
63 if e.errno != errno.ENOENT:
64 raise
65 if parentfn:
66 return parentfn(pid)
67 else:
68 return
69
70 if initfn:
71 initfn()
72
73 if not opts['daemon']:
74 writepid(util.getpid())
75
76 if opts['daemon_postexec']:
77 try:
78 os.setsid()
79 except AttributeError:
80 pass
81 for inst in opts['daemon_postexec']:
82 if inst.startswith('unlink:'):
83 lockpath = inst[7:]
84 os.unlink(lockpath)
85 elif inst.startswith('chdir:'):
86 os.chdir(inst[6:])
87 elif inst != 'none':
88 raise error.Abort(_('invalid value for --daemon-postexec: %s')
89 % inst)
90 util.hidewindow()
91 util.stdout.flush()
92 util.stderr.flush()
93
94 nullfd = os.open(os.devnull, os.O_RDWR)
95 logfilefd = nullfd
96 if logfile:
97 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
98 os.dup2(nullfd, 0)
99 os.dup2(logfilefd, 1)
100 os.dup2(logfilefd, 2)
101 if nullfd not in (0, 1, 2):
102 os.close(nullfd)
103 if logfile and logfilefd not in (0, 1, 2):
104 os.close(logfilefd)
105
106 if runfn:
107 return runfn()
@@ -1,3528 +1,3440 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import re
13 import sys
14 13 import tempfile
15 14
16 15 from .i18n import _
17 16 from .node import (
18 17 bin,
19 18 hex,
20 19 nullid,
21 20 nullrev,
22 21 short,
23 22 )
24 23
25 24 from . import (
26 25 bookmarks,
27 26 changelog,
28 27 copies,
29 28 crecord as crecordmod,
30 29 dirstateguard as dirstateguardmod,
31 30 encoding,
32 31 error,
33 32 formatter,
34 33 graphmod,
35 34 lock as lockmod,
36 35 match as matchmod,
37 36 mergeutil,
38 37 obsolete,
39 38 patch,
40 39 pathutil,
41 40 phases,
42 41 repair,
43 42 revlog,
44 43 revset,
45 44 scmutil,
46 45 templatekw,
47 46 templater,
48 47 util,
49 48 )
50 49 stringio = util.stringio
51 50
52 51 def ishunk(x):
53 52 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
54 53 return isinstance(x, hunkclasses)
55 54
56 55 def newandmodified(chunks, originalchunks):
57 56 newlyaddedandmodifiedfiles = set()
58 57 for chunk in chunks:
59 58 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
60 59 originalchunks:
61 60 newlyaddedandmodifiedfiles.add(chunk.header.filename())
62 61 return newlyaddedandmodifiedfiles
63 62
64 63 def parsealiases(cmd):
65 64 return cmd.lstrip("^").split("|")
66 65
67 66 def setupwrapcolorwrite(ui):
68 67 # wrap ui.write so diff output can be labeled/colorized
69 68 def wrapwrite(orig, *args, **kw):
70 69 label = kw.pop('label', '')
71 70 for chunk, l in patch.difflabel(lambda: args):
72 71 orig(chunk, label=label + l)
73 72
74 73 oldwrite = ui.write
75 74 def wrap(*args, **kwargs):
76 75 return wrapwrite(oldwrite, *args, **kwargs)
77 76 setattr(ui, 'write', wrap)
78 77 return oldwrite
79 78
80 79 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
81 80 if usecurses:
82 81 if testfile:
83 82 recordfn = crecordmod.testdecorator(testfile,
84 83 crecordmod.testchunkselector)
85 84 else:
86 85 recordfn = crecordmod.chunkselector
87 86
88 87 return crecordmod.filterpatch(ui, originalhunks, recordfn)
89 88
90 89 else:
91 90 return patch.filterpatch(ui, originalhunks, operation)
92 91
93 92 def recordfilter(ui, originalhunks, operation=None):
94 93 """ Prompts the user to filter the originalhunks and return a list of
95 94 selected hunks.
96 95 *operation* is used for to build ui messages to indicate the user what
97 96 kind of filtering they are doing: reverting, committing, shelving, etc.
98 97 (see patch.filterpatch).
99 98 """
100 99 usecurses = crecordmod.checkcurses(ui)
101 100 testfile = ui.config('experimental', 'crecordtest', None)
102 101 oldwrite = setupwrapcolorwrite(ui)
103 102 try:
104 103 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
105 104 testfile, operation)
106 105 finally:
107 106 ui.write = oldwrite
108 107 return newchunks, newopts
109 108
110 109 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
111 110 filterfn, *pats, **opts):
112 111 from . import merge as mergemod
113 112 if not ui.interactive():
114 113 if cmdsuggest:
115 114 msg = _('running non-interactively, use %s instead') % cmdsuggest
116 115 else:
117 116 msg = _('running non-interactively')
118 117 raise error.Abort(msg)
119 118
120 119 # make sure username is set before going interactive
121 120 if not opts.get('user'):
122 121 ui.username() # raise exception, username not provided
123 122
124 123 def recordfunc(ui, repo, message, match, opts):
125 124 """This is generic record driver.
126 125
127 126 Its job is to interactively filter local changes, and
128 127 accordingly prepare working directory into a state in which the
129 128 job can be delegated to a non-interactive commit command such as
130 129 'commit' or 'qrefresh'.
131 130
132 131 After the actual job is done by non-interactive command, the
133 132 working directory is restored to its original state.
134 133
135 134 In the end we'll record interesting changes, and everything else
136 135 will be left in place, so the user can continue working.
137 136 """
138 137
139 138 checkunfinished(repo, commit=True)
140 139 wctx = repo[None]
141 140 merge = len(wctx.parents()) > 1
142 141 if merge:
143 142 raise error.Abort(_('cannot partially commit a merge '
144 143 '(use "hg commit" instead)'))
145 144
146 145 def fail(f, msg):
147 146 raise error.Abort('%s: %s' % (f, msg))
148 147
149 148 force = opts.get('force')
150 149 if not force:
151 150 vdirs = []
152 151 match.explicitdir = vdirs.append
153 152 match.bad = fail
154 153
155 154 status = repo.status(match=match)
156 155 if not force:
157 156 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
158 157 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
159 158 diffopts.nodates = True
160 159 diffopts.git = True
161 160 diffopts.showfunc = True
162 161 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
163 162 originalchunks = patch.parsepatch(originaldiff)
164 163
165 164 # 1. filter patch, since we are intending to apply subset of it
166 165 try:
167 166 chunks, newopts = filterfn(ui, originalchunks)
168 167 except patch.PatchError as err:
169 168 raise error.Abort(_('error parsing patch: %s') % err)
170 169 opts.update(newopts)
171 170
172 171 # We need to keep a backup of files that have been newly added and
173 172 # modified during the recording process because there is a previous
174 173 # version without the edit in the workdir
175 174 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
176 175 contenders = set()
177 176 for h in chunks:
178 177 try:
179 178 contenders.update(set(h.files()))
180 179 except AttributeError:
181 180 pass
182 181
183 182 changed = status.modified + status.added + status.removed
184 183 newfiles = [f for f in changed if f in contenders]
185 184 if not newfiles:
186 185 ui.status(_('no changes to record\n'))
187 186 return 0
188 187
189 188 modified = set(status.modified)
190 189
191 190 # 2. backup changed files, so we can restore them in the end
192 191
193 192 if backupall:
194 193 tobackup = changed
195 194 else:
196 195 tobackup = [f for f in newfiles if f in modified or f in \
197 196 newlyaddedandmodifiedfiles]
198 197 backups = {}
199 198 if tobackup:
200 199 backupdir = repo.join('record-backups')
201 200 try:
202 201 os.mkdir(backupdir)
203 202 except OSError as err:
204 203 if err.errno != errno.EEXIST:
205 204 raise
206 205 try:
207 206 # backup continues
208 207 for f in tobackup:
209 208 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
210 209 dir=backupdir)
211 210 os.close(fd)
212 211 ui.debug('backup %r as %r\n' % (f, tmpname))
213 212 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
214 213 backups[f] = tmpname
215 214
216 215 fp = stringio()
217 216 for c in chunks:
218 217 fname = c.filename()
219 218 if fname in backups:
220 219 c.write(fp)
221 220 dopatch = fp.tell()
222 221 fp.seek(0)
223 222
224 223 # 2.5 optionally review / modify patch in text editor
225 224 if opts.get('review', False):
226 225 patchtext = (crecordmod.diffhelptext
227 226 + crecordmod.patchhelptext
228 227 + fp.read())
229 228 reviewedpatch = ui.edit(patchtext, "",
230 229 extra={"suffix": ".diff"})
231 230 fp.truncate(0)
232 231 fp.write(reviewedpatch)
233 232 fp.seek(0)
234 233
235 234 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
236 235 # 3a. apply filtered patch to clean repo (clean)
237 236 if backups:
238 237 # Equivalent to hg.revert
239 238 m = scmutil.matchfiles(repo, backups.keys())
240 239 mergemod.update(repo, repo.dirstate.p1(),
241 240 False, True, matcher=m)
242 241
243 242 # 3b. (apply)
244 243 if dopatch:
245 244 try:
246 245 ui.debug('applying patch\n')
247 246 ui.debug(fp.getvalue())
248 247 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
249 248 except patch.PatchError as err:
250 249 raise error.Abort(str(err))
251 250 del fp
252 251
253 252 # 4. We prepared working directory according to filtered
254 253 # patch. Now is the time to delegate the job to
255 254 # commit/qrefresh or the like!
256 255
257 256 # Make all of the pathnames absolute.
258 257 newfiles = [repo.wjoin(nf) for nf in newfiles]
259 258 return commitfunc(ui, repo, *newfiles, **opts)
260 259 finally:
261 260 # 5. finally restore backed-up files
262 261 try:
263 262 dirstate = repo.dirstate
264 263 for realname, tmpname in backups.iteritems():
265 264 ui.debug('restoring %r to %r\n' % (tmpname, realname))
266 265
267 266 if dirstate[realname] == 'n':
268 267 # without normallookup, restoring timestamp
269 268 # may cause partially committed files
270 269 # to be treated as unmodified
271 270 dirstate.normallookup(realname)
272 271
273 272 # copystat=True here and above are a hack to trick any
274 273 # editors that have f open that we haven't modified them.
275 274 #
276 275 # Also note that this racy as an editor could notice the
277 276 # file's mtime before we've finished writing it.
278 277 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
279 278 os.unlink(tmpname)
280 279 if tobackup:
281 280 os.rmdir(backupdir)
282 281 except OSError:
283 282 pass
284 283
285 284 def recordinwlock(ui, repo, message, match, opts):
286 285 with repo.wlock():
287 286 return recordfunc(ui, repo, message, match, opts)
288 287
289 288 return commit(ui, repo, recordinwlock, pats, opts)
290 289
291 290 def findpossible(cmd, table, strict=False):
292 291 """
293 292 Return cmd -> (aliases, command table entry)
294 293 for each matching command.
295 294 Return debug commands (or their aliases) only if no normal command matches.
296 295 """
297 296 choice = {}
298 297 debugchoice = {}
299 298
300 299 if cmd in table:
301 300 # short-circuit exact matches, "log" alias beats "^log|history"
302 301 keys = [cmd]
303 302 else:
304 303 keys = table.keys()
305 304
306 305 allcmds = []
307 306 for e in keys:
308 307 aliases = parsealiases(e)
309 308 allcmds.extend(aliases)
310 309 found = None
311 310 if cmd in aliases:
312 311 found = cmd
313 312 elif not strict:
314 313 for a in aliases:
315 314 if a.startswith(cmd):
316 315 found = a
317 316 break
318 317 if found is not None:
319 318 if aliases[0].startswith("debug") or found.startswith("debug"):
320 319 debugchoice[found] = (aliases, table[e])
321 320 else:
322 321 choice[found] = (aliases, table[e])
323 322
324 323 if not choice and debugchoice:
325 324 choice = debugchoice
326 325
327 326 return choice, allcmds
328 327
329 328 def findcmd(cmd, table, strict=True):
330 329 """Return (aliases, command table entry) for command string."""
331 330 choice, allcmds = findpossible(cmd, table, strict)
332 331
333 332 if cmd in choice:
334 333 return choice[cmd]
335 334
336 335 if len(choice) > 1:
337 336 clist = choice.keys()
338 337 clist.sort()
339 338 raise error.AmbiguousCommand(cmd, clist)
340 339
341 340 if choice:
342 341 return choice.values()[0]
343 342
344 343 raise error.UnknownCommand(cmd, allcmds)
345 344
346 345 def findrepo(p):
347 346 while not os.path.isdir(os.path.join(p, ".hg")):
348 347 oldp, p = p, os.path.dirname(p)
349 348 if p == oldp:
350 349 return None
351 350
352 351 return p
353 352
354 353 def bailifchanged(repo, merge=True):
355 354 if merge and repo.dirstate.p2() != nullid:
356 355 raise error.Abort(_('outstanding uncommitted merge'))
357 356 modified, added, removed, deleted = repo.status()[:4]
358 357 if modified or added or removed or deleted:
359 358 raise error.Abort(_('uncommitted changes'))
360 359 ctx = repo[None]
361 360 for s in sorted(ctx.substate):
362 361 ctx.sub(s).bailifchanged()
363 362
364 363 def logmessage(ui, opts):
365 364 """ get the log message according to -m and -l option """
366 365 message = opts.get('message')
367 366 logfile = opts.get('logfile')
368 367
369 368 if message and logfile:
370 369 raise error.Abort(_('options --message and --logfile are mutually '
371 370 'exclusive'))
372 371 if not message and logfile:
373 372 try:
374 373 if logfile == '-':
375 374 message = ui.fin.read()
376 375 else:
377 376 message = '\n'.join(util.readfile(logfile).splitlines())
378 377 except IOError as inst:
379 378 raise error.Abort(_("can't read commit message '%s': %s") %
380 379 (logfile, inst.strerror))
381 380 return message
382 381
383 382 def mergeeditform(ctxorbool, baseformname):
384 383 """return appropriate editform name (referencing a committemplate)
385 384
386 385 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
387 386 merging is committed.
388 387
389 388 This returns baseformname with '.merge' appended if it is a merge,
390 389 otherwise '.normal' is appended.
391 390 """
392 391 if isinstance(ctxorbool, bool):
393 392 if ctxorbool:
394 393 return baseformname + ".merge"
395 394 elif 1 < len(ctxorbool.parents()):
396 395 return baseformname + ".merge"
397 396
398 397 return baseformname + ".normal"
399 398
400 399 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
401 400 editform='', **opts):
402 401 """get appropriate commit message editor according to '--edit' option
403 402
404 403 'finishdesc' is a function to be called with edited commit message
405 404 (= 'description' of the new changeset) just after editing, but
406 405 before checking empty-ness. It should return actual text to be
407 406 stored into history. This allows to change description before
408 407 storing.
409 408
410 409 'extramsg' is a extra message to be shown in the editor instead of
411 410 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
412 411 is automatically added.
413 412
414 413 'editform' is a dot-separated list of names, to distinguish
415 414 the purpose of commit text editing.
416 415
417 416 'getcommiteditor' returns 'commitforceeditor' regardless of
418 417 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
419 418 they are specific for usage in MQ.
420 419 """
421 420 if edit or finishdesc or extramsg:
422 421 return lambda r, c, s: commitforceeditor(r, c, s,
423 422 finishdesc=finishdesc,
424 423 extramsg=extramsg,
425 424 editform=editform)
426 425 elif editform:
427 426 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
428 427 else:
429 428 return commiteditor
430 429
431 430 def loglimit(opts):
432 431 """get the log limit according to option -l/--limit"""
433 432 limit = opts.get('limit')
434 433 if limit:
435 434 try:
436 435 limit = int(limit)
437 436 except ValueError:
438 437 raise error.Abort(_('limit must be a positive integer'))
439 438 if limit <= 0:
440 439 raise error.Abort(_('limit must be positive'))
441 440 else:
442 441 limit = None
443 442 return limit
444 443
445 444 def makefilename(repo, pat, node, desc=None,
446 445 total=None, seqno=None, revwidth=None, pathname=None):
447 446 node_expander = {
448 447 'H': lambda: hex(node),
449 448 'R': lambda: str(repo.changelog.rev(node)),
450 449 'h': lambda: short(node),
451 450 'm': lambda: re.sub('[^\w]', '_', str(desc))
452 451 }
453 452 expander = {
454 453 '%': lambda: '%',
455 454 'b': lambda: os.path.basename(repo.root),
456 455 }
457 456
458 457 try:
459 458 if node:
460 459 expander.update(node_expander)
461 460 if node:
462 461 expander['r'] = (lambda:
463 462 str(repo.changelog.rev(node)).zfill(revwidth or 0))
464 463 if total is not None:
465 464 expander['N'] = lambda: str(total)
466 465 if seqno is not None:
467 466 expander['n'] = lambda: str(seqno)
468 467 if total is not None and seqno is not None:
469 468 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
470 469 if pathname is not None:
471 470 expander['s'] = lambda: os.path.basename(pathname)
472 471 expander['d'] = lambda: os.path.dirname(pathname) or '.'
473 472 expander['p'] = lambda: pathname
474 473
475 474 newname = []
476 475 patlen = len(pat)
477 476 i = 0
478 477 while i < patlen:
479 478 c = pat[i]
480 479 if c == '%':
481 480 i += 1
482 481 c = pat[i]
483 482 c = expander[c]()
484 483 newname.append(c)
485 484 i += 1
486 485 return ''.join(newname)
487 486 except KeyError as inst:
488 487 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
489 488 inst.args[0])
490 489
491 490 class _unclosablefile(object):
492 491 def __init__(self, fp):
493 492 self._fp = fp
494 493
495 494 def close(self):
496 495 pass
497 496
498 497 def __iter__(self):
499 498 return iter(self._fp)
500 499
501 500 def __getattr__(self, attr):
502 501 return getattr(self._fp, attr)
503 502
504 503 def __enter__(self):
505 504 return self
506 505
507 506 def __exit__(self, exc_type, exc_value, exc_tb):
508 507 pass
509 508
510 509 def makefileobj(repo, pat, node=None, desc=None, total=None,
511 510 seqno=None, revwidth=None, mode='wb', modemap=None,
512 511 pathname=None):
513 512
514 513 writable = mode not in ('r', 'rb')
515 514
516 515 if not pat or pat == '-':
517 516 if writable:
518 517 fp = repo.ui.fout
519 518 else:
520 519 fp = repo.ui.fin
521 520 return _unclosablefile(fp)
522 521 if util.safehasattr(pat, 'write') and writable:
523 522 return pat
524 523 if util.safehasattr(pat, 'read') and 'r' in mode:
525 524 return pat
526 525 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
527 526 if modemap is not None:
528 527 mode = modemap.get(fn, mode)
529 528 if mode == 'wb':
530 529 modemap[fn] = 'ab'
531 530 return open(fn, mode)
532 531
533 532 def openrevlog(repo, cmd, file_, opts):
534 533 """opens the changelog, manifest, a filelog or a given revlog"""
535 534 cl = opts['changelog']
536 535 mf = opts['manifest']
537 536 dir = opts['dir']
538 537 msg = None
539 538 if cl and mf:
540 539 msg = _('cannot specify --changelog and --manifest at the same time')
541 540 elif cl and dir:
542 541 msg = _('cannot specify --changelog and --dir at the same time')
543 542 elif cl or mf or dir:
544 543 if file_:
545 544 msg = _('cannot specify filename with --changelog or --manifest')
546 545 elif not repo:
547 546 msg = _('cannot specify --changelog or --manifest or --dir '
548 547 'without a repository')
549 548 if msg:
550 549 raise error.Abort(msg)
551 550
552 551 r = None
553 552 if repo:
554 553 if cl:
555 554 r = repo.unfiltered().changelog
556 555 elif dir:
557 556 if 'treemanifest' not in repo.requirements:
558 557 raise error.Abort(_("--dir can only be used on repos with "
559 558 "treemanifest enabled"))
560 559 dirlog = repo.manifestlog._revlog.dirlog(dir)
561 560 if len(dirlog):
562 561 r = dirlog
563 562 elif mf:
564 563 r = repo.manifestlog._revlog
565 564 elif file_:
566 565 filelog = repo.file(file_)
567 566 if len(filelog):
568 567 r = filelog
569 568 if not r:
570 569 if not file_:
571 570 raise error.CommandError(cmd, _('invalid arguments'))
572 571 if not os.path.isfile(file_):
573 572 raise error.Abort(_("revlog '%s' not found") % file_)
574 573 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
575 574 file_[:-2] + ".i")
576 575 return r
577 576
578 577 def copy(ui, repo, pats, opts, rename=False):
579 578 # called with the repo lock held
580 579 #
581 580 # hgsep => pathname that uses "/" to separate directories
582 581 # ossep => pathname that uses os.sep to separate directories
583 582 cwd = repo.getcwd()
584 583 targets = {}
585 584 after = opts.get("after")
586 585 dryrun = opts.get("dry_run")
587 586 wctx = repo[None]
588 587
589 588 def walkpat(pat):
590 589 srcs = []
591 590 if after:
592 591 badstates = '?'
593 592 else:
594 593 badstates = '?r'
595 594 m = scmutil.match(repo[None], [pat], opts, globbed=True)
596 595 for abs in repo.walk(m):
597 596 state = repo.dirstate[abs]
598 597 rel = m.rel(abs)
599 598 exact = m.exact(abs)
600 599 if state in badstates:
601 600 if exact and state == '?':
602 601 ui.warn(_('%s: not copying - file is not managed\n') % rel)
603 602 if exact and state == 'r':
604 603 ui.warn(_('%s: not copying - file has been marked for'
605 604 ' remove\n') % rel)
606 605 continue
607 606 # abs: hgsep
608 607 # rel: ossep
609 608 srcs.append((abs, rel, exact))
610 609 return srcs
611 610
612 611 # abssrc: hgsep
613 612 # relsrc: ossep
614 613 # otarget: ossep
615 614 def copyfile(abssrc, relsrc, otarget, exact):
616 615 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
617 616 if '/' in abstarget:
618 617 # We cannot normalize abstarget itself, this would prevent
619 618 # case only renames, like a => A.
620 619 abspath, absname = abstarget.rsplit('/', 1)
621 620 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
622 621 reltarget = repo.pathto(abstarget, cwd)
623 622 target = repo.wjoin(abstarget)
624 623 src = repo.wjoin(abssrc)
625 624 state = repo.dirstate[abstarget]
626 625
627 626 scmutil.checkportable(ui, abstarget)
628 627
629 628 # check for collisions
630 629 prevsrc = targets.get(abstarget)
631 630 if prevsrc is not None:
632 631 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
633 632 (reltarget, repo.pathto(abssrc, cwd),
634 633 repo.pathto(prevsrc, cwd)))
635 634 return
636 635
637 636 # check for overwrites
638 637 exists = os.path.lexists(target)
639 638 samefile = False
640 639 if exists and abssrc != abstarget:
641 640 if (repo.dirstate.normalize(abssrc) ==
642 641 repo.dirstate.normalize(abstarget)):
643 642 if not rename:
644 643 ui.warn(_("%s: can't copy - same file\n") % reltarget)
645 644 return
646 645 exists = False
647 646 samefile = True
648 647
649 648 if not after and exists or after and state in 'mn':
650 649 if not opts['force']:
651 650 if state in 'mn':
652 651 msg = _('%s: not overwriting - file already committed\n')
653 652 if after:
654 653 flags = '--after --force'
655 654 else:
656 655 flags = '--force'
657 656 if rename:
658 657 hint = _('(hg rename %s to replace the file by '
659 658 'recording a rename)\n') % flags
660 659 else:
661 660 hint = _('(hg copy %s to replace the file by '
662 661 'recording a copy)\n') % flags
663 662 else:
664 663 msg = _('%s: not overwriting - file exists\n')
665 664 if rename:
666 665 hint = _('(hg rename --after to record the rename)\n')
667 666 else:
668 667 hint = _('(hg copy --after to record the copy)\n')
669 668 ui.warn(msg % reltarget)
670 669 ui.warn(hint)
671 670 return
672 671
673 672 if after:
674 673 if not exists:
675 674 if rename:
676 675 ui.warn(_('%s: not recording move - %s does not exist\n') %
677 676 (relsrc, reltarget))
678 677 else:
679 678 ui.warn(_('%s: not recording copy - %s does not exist\n') %
680 679 (relsrc, reltarget))
681 680 return
682 681 elif not dryrun:
683 682 try:
684 683 if exists:
685 684 os.unlink(target)
686 685 targetdir = os.path.dirname(target) or '.'
687 686 if not os.path.isdir(targetdir):
688 687 os.makedirs(targetdir)
689 688 if samefile:
690 689 tmp = target + "~hgrename"
691 690 os.rename(src, tmp)
692 691 os.rename(tmp, target)
693 692 else:
694 693 util.copyfile(src, target)
695 694 srcexists = True
696 695 except IOError as inst:
697 696 if inst.errno == errno.ENOENT:
698 697 ui.warn(_('%s: deleted in working directory\n') % relsrc)
699 698 srcexists = False
700 699 else:
701 700 ui.warn(_('%s: cannot copy - %s\n') %
702 701 (relsrc, inst.strerror))
703 702 return True # report a failure
704 703
705 704 if ui.verbose or not exact:
706 705 if rename:
707 706 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
708 707 else:
709 708 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
710 709
711 710 targets[abstarget] = abssrc
712 711
713 712 # fix up dirstate
714 713 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
715 714 dryrun=dryrun, cwd=cwd)
716 715 if rename and not dryrun:
717 716 if not after and srcexists and not samefile:
718 717 util.unlinkpath(repo.wjoin(abssrc))
719 718 wctx.forget([abssrc])
720 719
721 720 # pat: ossep
722 721 # dest ossep
723 722 # srcs: list of (hgsep, hgsep, ossep, bool)
724 723 # return: function that takes hgsep and returns ossep
725 724 def targetpathfn(pat, dest, srcs):
726 725 if os.path.isdir(pat):
727 726 abspfx = pathutil.canonpath(repo.root, cwd, pat)
728 727 abspfx = util.localpath(abspfx)
729 728 if destdirexists:
730 729 striplen = len(os.path.split(abspfx)[0])
731 730 else:
732 731 striplen = len(abspfx)
733 732 if striplen:
734 733 striplen += len(os.sep)
735 734 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
736 735 elif destdirexists:
737 736 res = lambda p: os.path.join(dest,
738 737 os.path.basename(util.localpath(p)))
739 738 else:
740 739 res = lambda p: dest
741 740 return res
742 741
743 742 # pat: ossep
744 743 # dest ossep
745 744 # srcs: list of (hgsep, hgsep, ossep, bool)
746 745 # return: function that takes hgsep and returns ossep
747 746 def targetpathafterfn(pat, dest, srcs):
748 747 if matchmod.patkind(pat):
749 748 # a mercurial pattern
750 749 res = lambda p: os.path.join(dest,
751 750 os.path.basename(util.localpath(p)))
752 751 else:
753 752 abspfx = pathutil.canonpath(repo.root, cwd, pat)
754 753 if len(abspfx) < len(srcs[0][0]):
755 754 # A directory. Either the target path contains the last
756 755 # component of the source path or it does not.
757 756 def evalpath(striplen):
758 757 score = 0
759 758 for s in srcs:
760 759 t = os.path.join(dest, util.localpath(s[0])[striplen:])
761 760 if os.path.lexists(t):
762 761 score += 1
763 762 return score
764 763
765 764 abspfx = util.localpath(abspfx)
766 765 striplen = len(abspfx)
767 766 if striplen:
768 767 striplen += len(os.sep)
769 768 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
770 769 score = evalpath(striplen)
771 770 striplen1 = len(os.path.split(abspfx)[0])
772 771 if striplen1:
773 772 striplen1 += len(os.sep)
774 773 if evalpath(striplen1) > score:
775 774 striplen = striplen1
776 775 res = lambda p: os.path.join(dest,
777 776 util.localpath(p)[striplen:])
778 777 else:
779 778 # a file
780 779 if destdirexists:
781 780 res = lambda p: os.path.join(dest,
782 781 os.path.basename(util.localpath(p)))
783 782 else:
784 783 res = lambda p: dest
785 784 return res
786 785
787 786 pats = scmutil.expandpats(pats)
788 787 if not pats:
789 788 raise error.Abort(_('no source or destination specified'))
790 789 if len(pats) == 1:
791 790 raise error.Abort(_('no destination specified'))
792 791 dest = pats.pop()
793 792 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
794 793 if not destdirexists:
795 794 if len(pats) > 1 or matchmod.patkind(pats[0]):
796 795 raise error.Abort(_('with multiple sources, destination must be an '
797 796 'existing directory'))
798 797 if util.endswithsep(dest):
799 798 raise error.Abort(_('destination %s is not a directory') % dest)
800 799
801 800 tfn = targetpathfn
802 801 if after:
803 802 tfn = targetpathafterfn
804 803 copylist = []
805 804 for pat in pats:
806 805 srcs = walkpat(pat)
807 806 if not srcs:
808 807 continue
809 808 copylist.append((tfn(pat, dest, srcs), srcs))
810 809 if not copylist:
811 810 raise error.Abort(_('no files to copy'))
812 811
813 812 errors = 0
814 813 for targetpath, srcs in copylist:
815 814 for abssrc, relsrc, exact in srcs:
816 815 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
817 816 errors += 1
818 817
819 818 if errors:
820 819 ui.warn(_('(consider using --after)\n'))
821 820
822 821 return errors != 0
823 822
824 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
825 runargs=None, appendpid=False):
826 '''Run a command as a service.'''
827
828 def writepid(pid):
829 if opts['pid_file']:
830 if appendpid:
831 mode = 'a'
832 else:
833 mode = 'w'
834 fp = open(opts['pid_file'], mode)
835 fp.write(str(pid) + '\n')
836 fp.close()
837
838 if opts['daemon'] and not opts['daemon_postexec']:
839 # Signal child process startup with file removal
840 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
841 os.close(lockfd)
842 try:
843 if not runargs:
844 runargs = util.hgcmd() + sys.argv[1:]
845 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
846 # Don't pass --cwd to the child process, because we've already
847 # changed directory.
848 for i in xrange(1, len(runargs)):
849 if runargs[i].startswith('--cwd='):
850 del runargs[i]
851 break
852 elif runargs[i].startswith('--cwd'):
853 del runargs[i:i + 2]
854 break
855 def condfn():
856 return not os.path.exists(lockpath)
857 pid = util.rundetached(runargs, condfn)
858 if pid < 0:
859 raise error.Abort(_('child process failed to start'))
860 writepid(pid)
861 finally:
862 try:
863 os.unlink(lockpath)
864 except OSError as e:
865 if e.errno != errno.ENOENT:
866 raise
867 if parentfn:
868 return parentfn(pid)
869 else:
870 return
871
872 if initfn:
873 initfn()
874
875 if not opts['daemon']:
876 writepid(util.getpid())
877
878 if opts['daemon_postexec']:
879 try:
880 os.setsid()
881 except AttributeError:
882 pass
883 for inst in opts['daemon_postexec']:
884 if inst.startswith('unlink:'):
885 lockpath = inst[7:]
886 os.unlink(lockpath)
887 elif inst.startswith('chdir:'):
888 os.chdir(inst[6:])
889 elif inst != 'none':
890 raise error.Abort(_('invalid value for --daemon-postexec: %s')
891 % inst)
892 util.hidewindow()
893 util.stdout.flush()
894 util.stderr.flush()
895
896 nullfd = os.open(os.devnull, os.O_RDWR)
897 logfilefd = nullfd
898 if logfile:
899 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
900 os.dup2(nullfd, 0)
901 os.dup2(logfilefd, 1)
902 os.dup2(logfilefd, 2)
903 if nullfd not in (0, 1, 2):
904 os.close(nullfd)
905 if logfile and logfilefd not in (0, 1, 2):
906 os.close(logfilefd)
907
908 if runfn:
909 return runfn()
910
911 823 ## facility to let extension process additional data into an import patch
912 824 # list of identifier to be executed in order
913 825 extrapreimport = [] # run before commit
914 826 extrapostimport = [] # run after commit
915 827 # mapping from identifier to actual import function
916 828 #
917 829 # 'preimport' are run before the commit is made and are provided the following
918 830 # arguments:
919 831 # - repo: the localrepository instance,
920 832 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
921 833 # - extra: the future extra dictionary of the changeset, please mutate it,
922 834 # - opts: the import options.
923 835 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
924 836 # mutation of in memory commit and more. Feel free to rework the code to get
925 837 # there.
926 838 extrapreimportmap = {}
927 839 # 'postimport' are run after the commit is made and are provided the following
928 840 # argument:
929 841 # - ctx: the changectx created by import.
930 842 extrapostimportmap = {}
931 843
932 844 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
933 845 """Utility function used by commands.import to import a single patch
934 846
935 847 This function is explicitly defined here to help the evolve extension to
936 848 wrap this part of the import logic.
937 849
938 850 The API is currently a bit ugly because it a simple code translation from
939 851 the import command. Feel free to make it better.
940 852
941 853 :hunk: a patch (as a binary string)
942 854 :parents: nodes that will be parent of the created commit
943 855 :opts: the full dict of option passed to the import command
944 856 :msgs: list to save commit message to.
945 857 (used in case we need to save it when failing)
946 858 :updatefunc: a function that update a repo to a given node
947 859 updatefunc(<repo>, <node>)
948 860 """
949 861 # avoid cycle context -> subrepo -> cmdutil
950 862 from . import context
951 863 extractdata = patch.extract(ui, hunk)
952 864 tmpname = extractdata.get('filename')
953 865 message = extractdata.get('message')
954 866 user = opts.get('user') or extractdata.get('user')
955 867 date = opts.get('date') or extractdata.get('date')
956 868 branch = extractdata.get('branch')
957 869 nodeid = extractdata.get('nodeid')
958 870 p1 = extractdata.get('p1')
959 871 p2 = extractdata.get('p2')
960 872
961 873 nocommit = opts.get('no_commit')
962 874 importbranch = opts.get('import_branch')
963 875 update = not opts.get('bypass')
964 876 strip = opts["strip"]
965 877 prefix = opts["prefix"]
966 878 sim = float(opts.get('similarity') or 0)
967 879 if not tmpname:
968 880 return (None, None, False)
969 881
970 882 rejects = False
971 883
972 884 try:
973 885 cmdline_message = logmessage(ui, opts)
974 886 if cmdline_message:
975 887 # pickup the cmdline msg
976 888 message = cmdline_message
977 889 elif message:
978 890 # pickup the patch msg
979 891 message = message.strip()
980 892 else:
981 893 # launch the editor
982 894 message = None
983 895 ui.debug('message:\n%s\n' % message)
984 896
985 897 if len(parents) == 1:
986 898 parents.append(repo[nullid])
987 899 if opts.get('exact'):
988 900 if not nodeid or not p1:
989 901 raise error.Abort(_('not a Mercurial patch'))
990 902 p1 = repo[p1]
991 903 p2 = repo[p2 or nullid]
992 904 elif p2:
993 905 try:
994 906 p1 = repo[p1]
995 907 p2 = repo[p2]
996 908 # Without any options, consider p2 only if the
997 909 # patch is being applied on top of the recorded
998 910 # first parent.
999 911 if p1 != parents[0]:
1000 912 p1 = parents[0]
1001 913 p2 = repo[nullid]
1002 914 except error.RepoError:
1003 915 p1, p2 = parents
1004 916 if p2.node() == nullid:
1005 917 ui.warn(_("warning: import the patch as a normal revision\n"
1006 918 "(use --exact to import the patch as a merge)\n"))
1007 919 else:
1008 920 p1, p2 = parents
1009 921
1010 922 n = None
1011 923 if update:
1012 924 if p1 != parents[0]:
1013 925 updatefunc(repo, p1.node())
1014 926 if p2 != parents[1]:
1015 927 repo.setparents(p1.node(), p2.node())
1016 928
1017 929 if opts.get('exact') or importbranch:
1018 930 repo.dirstate.setbranch(branch or 'default')
1019 931
1020 932 partial = opts.get('partial', False)
1021 933 files = set()
1022 934 try:
1023 935 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1024 936 files=files, eolmode=None, similarity=sim / 100.0)
1025 937 except patch.PatchError as e:
1026 938 if not partial:
1027 939 raise error.Abort(str(e))
1028 940 if partial:
1029 941 rejects = True
1030 942
1031 943 files = list(files)
1032 944 if nocommit:
1033 945 if message:
1034 946 msgs.append(message)
1035 947 else:
1036 948 if opts.get('exact') or p2:
1037 949 # If you got here, you either use --force and know what
1038 950 # you are doing or used --exact or a merge patch while
1039 951 # being updated to its first parent.
1040 952 m = None
1041 953 else:
1042 954 m = scmutil.matchfiles(repo, files or [])
1043 955 editform = mergeeditform(repo[None], 'import.normal')
1044 956 if opts.get('exact'):
1045 957 editor = None
1046 958 else:
1047 959 editor = getcommiteditor(editform=editform, **opts)
1048 960 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
1049 961 extra = {}
1050 962 for idfunc in extrapreimport:
1051 963 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1052 964 try:
1053 965 if partial:
1054 966 repo.ui.setconfig('ui', 'allowemptycommit', True)
1055 967 n = repo.commit(message, user,
1056 968 date, match=m,
1057 969 editor=editor, extra=extra)
1058 970 for idfunc in extrapostimport:
1059 971 extrapostimportmap[idfunc](repo[n])
1060 972 finally:
1061 973 repo.ui.restoreconfig(allowemptyback)
1062 974 else:
1063 975 if opts.get('exact') or importbranch:
1064 976 branch = branch or 'default'
1065 977 else:
1066 978 branch = p1.branch()
1067 979 store = patch.filestore()
1068 980 try:
1069 981 files = set()
1070 982 try:
1071 983 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1072 984 files, eolmode=None)
1073 985 except patch.PatchError as e:
1074 986 raise error.Abort(str(e))
1075 987 if opts.get('exact'):
1076 988 editor = None
1077 989 else:
1078 990 editor = getcommiteditor(editform='import.bypass')
1079 991 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1080 992 message,
1081 993 user,
1082 994 date,
1083 995 branch, files, store,
1084 996 editor=editor)
1085 997 n = memctx.commit()
1086 998 finally:
1087 999 store.close()
1088 1000 if opts.get('exact') and nocommit:
1089 1001 # --exact with --no-commit is still useful in that it does merge
1090 1002 # and branch bits
1091 1003 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1092 1004 elif opts.get('exact') and hex(n) != nodeid:
1093 1005 raise error.Abort(_('patch is damaged or loses information'))
1094 1006 msg = _('applied to working directory')
1095 1007 if n:
1096 1008 # i18n: refers to a short changeset id
1097 1009 msg = _('created %s') % short(n)
1098 1010 return (msg, n, rejects)
1099 1011 finally:
1100 1012 os.unlink(tmpname)
1101 1013
1102 1014 # facility to let extensions include additional data in an exported patch
1103 1015 # list of identifiers to be executed in order
1104 1016 extraexport = []
1105 1017 # mapping from identifier to actual export function
1106 1018 # function as to return a string to be added to the header or None
1107 1019 # it is given two arguments (sequencenumber, changectx)
1108 1020 extraexportmap = {}
1109 1021
1110 1022 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1111 1023 opts=None, match=None):
1112 1024 '''export changesets as hg patches.'''
1113 1025
1114 1026 total = len(revs)
1115 1027 revwidth = max([len(str(rev)) for rev in revs])
1116 1028 filemode = {}
1117 1029
1118 1030 def single(rev, seqno, fp):
1119 1031 ctx = repo[rev]
1120 1032 node = ctx.node()
1121 1033 parents = [p.node() for p in ctx.parents() if p]
1122 1034 branch = ctx.branch()
1123 1035 if switch_parent:
1124 1036 parents.reverse()
1125 1037
1126 1038 if parents:
1127 1039 prev = parents[0]
1128 1040 else:
1129 1041 prev = nullid
1130 1042
1131 1043 shouldclose = False
1132 1044 if not fp and len(template) > 0:
1133 1045 desc_lines = ctx.description().rstrip().split('\n')
1134 1046 desc = desc_lines[0] #Commit always has a first line.
1135 1047 fp = makefileobj(repo, template, node, desc=desc, total=total,
1136 1048 seqno=seqno, revwidth=revwidth, mode='wb',
1137 1049 modemap=filemode)
1138 1050 shouldclose = True
1139 1051 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1140 1052 repo.ui.note("%s\n" % fp.name)
1141 1053
1142 1054 if not fp:
1143 1055 write = repo.ui.write
1144 1056 else:
1145 1057 def write(s, **kw):
1146 1058 fp.write(s)
1147 1059
1148 1060 write("# HG changeset patch\n")
1149 1061 write("# User %s\n" % ctx.user())
1150 1062 write("# Date %d %d\n" % ctx.date())
1151 1063 write("# %s\n" % util.datestr(ctx.date()))
1152 1064 if branch and branch != 'default':
1153 1065 write("# Branch %s\n" % branch)
1154 1066 write("# Node ID %s\n" % hex(node))
1155 1067 write("# Parent %s\n" % hex(prev))
1156 1068 if len(parents) > 1:
1157 1069 write("# Parent %s\n" % hex(parents[1]))
1158 1070
1159 1071 for headerid in extraexport:
1160 1072 header = extraexportmap[headerid](seqno, ctx)
1161 1073 if header is not None:
1162 1074 write('# %s\n' % header)
1163 1075 write(ctx.description().rstrip())
1164 1076 write("\n\n")
1165 1077
1166 1078 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1167 1079 write(chunk, label=label)
1168 1080
1169 1081 if shouldclose:
1170 1082 fp.close()
1171 1083
1172 1084 for seqno, rev in enumerate(revs):
1173 1085 single(rev, seqno + 1, fp)
1174 1086
1175 1087 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1176 1088 changes=None, stat=False, fp=None, prefix='',
1177 1089 root='', listsubrepos=False):
1178 1090 '''show diff or diffstat.'''
1179 1091 if fp is None:
1180 1092 write = ui.write
1181 1093 else:
1182 1094 def write(s, **kw):
1183 1095 fp.write(s)
1184 1096
1185 1097 if root:
1186 1098 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1187 1099 else:
1188 1100 relroot = ''
1189 1101 if relroot != '':
1190 1102 # XXX relative roots currently don't work if the root is within a
1191 1103 # subrepo
1192 1104 uirelroot = match.uipath(relroot)
1193 1105 relroot += '/'
1194 1106 for matchroot in match.files():
1195 1107 if not matchroot.startswith(relroot):
1196 1108 ui.warn(_('warning: %s not inside relative root %s\n') % (
1197 1109 match.uipath(matchroot), uirelroot))
1198 1110
1199 1111 if stat:
1200 1112 diffopts = diffopts.copy(context=0)
1201 1113 width = 80
1202 1114 if not ui.plain():
1203 1115 width = ui.termwidth()
1204 1116 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1205 1117 prefix=prefix, relroot=relroot)
1206 1118 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1207 1119 width=width):
1208 1120 write(chunk, label=label)
1209 1121 else:
1210 1122 for chunk, label in patch.diffui(repo, node1, node2, match,
1211 1123 changes, diffopts, prefix=prefix,
1212 1124 relroot=relroot):
1213 1125 write(chunk, label=label)
1214 1126
1215 1127 if listsubrepos:
1216 1128 ctx1 = repo[node1]
1217 1129 ctx2 = repo[node2]
1218 1130 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1219 1131 tempnode2 = node2
1220 1132 try:
1221 1133 if node2 is not None:
1222 1134 tempnode2 = ctx2.substate[subpath][1]
1223 1135 except KeyError:
1224 1136 # A subrepo that existed in node1 was deleted between node1 and
1225 1137 # node2 (inclusive). Thus, ctx2's substate won't contain that
1226 1138 # subpath. The best we can do is to ignore it.
1227 1139 tempnode2 = None
1228 1140 submatch = matchmod.subdirmatcher(subpath, match)
1229 1141 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1230 1142 stat=stat, fp=fp, prefix=prefix)
1231 1143
1232 1144 class changeset_printer(object):
1233 1145 '''show changeset information when templating not requested.'''
1234 1146
1235 1147 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1236 1148 self.ui = ui
1237 1149 self.repo = repo
1238 1150 self.buffered = buffered
1239 1151 self.matchfn = matchfn
1240 1152 self.diffopts = diffopts
1241 1153 self.header = {}
1242 1154 self.hunk = {}
1243 1155 self.lastheader = None
1244 1156 self.footer = None
1245 1157
1246 1158 def flush(self, ctx):
1247 1159 rev = ctx.rev()
1248 1160 if rev in self.header:
1249 1161 h = self.header[rev]
1250 1162 if h != self.lastheader:
1251 1163 self.lastheader = h
1252 1164 self.ui.write(h)
1253 1165 del self.header[rev]
1254 1166 if rev in self.hunk:
1255 1167 self.ui.write(self.hunk[rev])
1256 1168 del self.hunk[rev]
1257 1169 return 1
1258 1170 return 0
1259 1171
1260 1172 def close(self):
1261 1173 if self.footer:
1262 1174 self.ui.write(self.footer)
1263 1175
1264 1176 def show(self, ctx, copies=None, matchfn=None, **props):
1265 1177 if self.buffered:
1266 1178 self.ui.pushbuffer(labeled=True)
1267 1179 self._show(ctx, copies, matchfn, props)
1268 1180 self.hunk[ctx.rev()] = self.ui.popbuffer()
1269 1181 else:
1270 1182 self._show(ctx, copies, matchfn, props)
1271 1183
1272 1184 def _show(self, ctx, copies, matchfn, props):
1273 1185 '''show a single changeset or file revision'''
1274 1186 changenode = ctx.node()
1275 1187 rev = ctx.rev()
1276 1188 if self.ui.debugflag:
1277 1189 hexfunc = hex
1278 1190 else:
1279 1191 hexfunc = short
1280 1192 # as of now, wctx.node() and wctx.rev() return None, but we want to
1281 1193 # show the same values as {node} and {rev} templatekw
1282 1194 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1283 1195
1284 1196 if self.ui.quiet:
1285 1197 self.ui.write("%d:%s\n" % revnode, label='log.node')
1286 1198 return
1287 1199
1288 1200 date = util.datestr(ctx.date())
1289 1201
1290 1202 # i18n: column positioning for "hg log"
1291 1203 self.ui.write(_("changeset: %d:%s\n") % revnode,
1292 1204 label='log.changeset changeset.%s' % ctx.phasestr())
1293 1205
1294 1206 # branches are shown first before any other names due to backwards
1295 1207 # compatibility
1296 1208 branch = ctx.branch()
1297 1209 # don't show the default branch name
1298 1210 if branch != 'default':
1299 1211 # i18n: column positioning for "hg log"
1300 1212 self.ui.write(_("branch: %s\n") % branch,
1301 1213 label='log.branch')
1302 1214
1303 1215 for nsname, ns in self.repo.names.iteritems():
1304 1216 # branches has special logic already handled above, so here we just
1305 1217 # skip it
1306 1218 if nsname == 'branches':
1307 1219 continue
1308 1220 # we will use the templatename as the color name since those two
1309 1221 # should be the same
1310 1222 for name in ns.names(self.repo, changenode):
1311 1223 self.ui.write(ns.logfmt % name,
1312 1224 label='log.%s' % ns.colorname)
1313 1225 if self.ui.debugflag:
1314 1226 # i18n: column positioning for "hg log"
1315 1227 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1316 1228 label='log.phase')
1317 1229 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1318 1230 label = 'log.parent changeset.%s' % pctx.phasestr()
1319 1231 # i18n: column positioning for "hg log"
1320 1232 self.ui.write(_("parent: %d:%s\n")
1321 1233 % (pctx.rev(), hexfunc(pctx.node())),
1322 1234 label=label)
1323 1235
1324 1236 if self.ui.debugflag and rev is not None:
1325 1237 mnode = ctx.manifestnode()
1326 1238 # i18n: column positioning for "hg log"
1327 1239 self.ui.write(_("manifest: %d:%s\n") %
1328 1240 (self.repo.manifestlog._revlog.rev(mnode),
1329 1241 hex(mnode)),
1330 1242 label='ui.debug log.manifest')
1331 1243 # i18n: column positioning for "hg log"
1332 1244 self.ui.write(_("user: %s\n") % ctx.user(),
1333 1245 label='log.user')
1334 1246 # i18n: column positioning for "hg log"
1335 1247 self.ui.write(_("date: %s\n") % date,
1336 1248 label='log.date')
1337 1249
1338 1250 if self.ui.debugflag:
1339 1251 files = ctx.p1().status(ctx)[:3]
1340 1252 for key, value in zip([# i18n: column positioning for "hg log"
1341 1253 _("files:"),
1342 1254 # i18n: column positioning for "hg log"
1343 1255 _("files+:"),
1344 1256 # i18n: column positioning for "hg log"
1345 1257 _("files-:")], files):
1346 1258 if value:
1347 1259 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1348 1260 label='ui.debug log.files')
1349 1261 elif ctx.files() and self.ui.verbose:
1350 1262 # i18n: column positioning for "hg log"
1351 1263 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1352 1264 label='ui.note log.files')
1353 1265 if copies and self.ui.verbose:
1354 1266 copies = ['%s (%s)' % c for c in copies]
1355 1267 # i18n: column positioning for "hg log"
1356 1268 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1357 1269 label='ui.note log.copies')
1358 1270
1359 1271 extra = ctx.extra()
1360 1272 if extra and self.ui.debugflag:
1361 1273 for key, value in sorted(extra.items()):
1362 1274 # i18n: column positioning for "hg log"
1363 1275 self.ui.write(_("extra: %s=%s\n")
1364 1276 % (key, value.encode('string_escape')),
1365 1277 label='ui.debug log.extra')
1366 1278
1367 1279 description = ctx.description().strip()
1368 1280 if description:
1369 1281 if self.ui.verbose:
1370 1282 self.ui.write(_("description:\n"),
1371 1283 label='ui.note log.description')
1372 1284 self.ui.write(description,
1373 1285 label='ui.note log.description')
1374 1286 self.ui.write("\n\n")
1375 1287 else:
1376 1288 # i18n: column positioning for "hg log"
1377 1289 self.ui.write(_("summary: %s\n") %
1378 1290 description.splitlines()[0],
1379 1291 label='log.summary')
1380 1292 self.ui.write("\n")
1381 1293
1382 1294 self.showpatch(ctx, matchfn)
1383 1295
1384 1296 def showpatch(self, ctx, matchfn):
1385 1297 if not matchfn:
1386 1298 matchfn = self.matchfn
1387 1299 if matchfn:
1388 1300 stat = self.diffopts.get('stat')
1389 1301 diff = self.diffopts.get('patch')
1390 1302 diffopts = patch.diffallopts(self.ui, self.diffopts)
1391 1303 node = ctx.node()
1392 1304 prev = ctx.p1().node()
1393 1305 if stat:
1394 1306 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1395 1307 match=matchfn, stat=True)
1396 1308 if diff:
1397 1309 if stat:
1398 1310 self.ui.write("\n")
1399 1311 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1400 1312 match=matchfn, stat=False)
1401 1313 self.ui.write("\n")
1402 1314
1403 1315 class jsonchangeset(changeset_printer):
1404 1316 '''format changeset information.'''
1405 1317
1406 1318 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1407 1319 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1408 1320 self.cache = {}
1409 1321 self._first = True
1410 1322
1411 1323 def close(self):
1412 1324 if not self._first:
1413 1325 self.ui.write("\n]\n")
1414 1326 else:
1415 1327 self.ui.write("[]\n")
1416 1328
1417 1329 def _show(self, ctx, copies, matchfn, props):
1418 1330 '''show a single changeset or file revision'''
1419 1331 rev = ctx.rev()
1420 1332 if rev is None:
1421 1333 jrev = jnode = 'null'
1422 1334 else:
1423 1335 jrev = str(rev)
1424 1336 jnode = '"%s"' % hex(ctx.node())
1425 1337 j = encoding.jsonescape
1426 1338
1427 1339 if self._first:
1428 1340 self.ui.write("[\n {")
1429 1341 self._first = False
1430 1342 else:
1431 1343 self.ui.write(",\n {")
1432 1344
1433 1345 if self.ui.quiet:
1434 1346 self.ui.write(('\n "rev": %s') % jrev)
1435 1347 self.ui.write((',\n "node": %s') % jnode)
1436 1348 self.ui.write('\n }')
1437 1349 return
1438 1350
1439 1351 self.ui.write(('\n "rev": %s') % jrev)
1440 1352 self.ui.write((',\n "node": %s') % jnode)
1441 1353 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1442 1354 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1443 1355 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1444 1356 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1445 1357 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1446 1358
1447 1359 self.ui.write((',\n "bookmarks": [%s]') %
1448 1360 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1449 1361 self.ui.write((',\n "tags": [%s]') %
1450 1362 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1451 1363 self.ui.write((',\n "parents": [%s]') %
1452 1364 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1453 1365
1454 1366 if self.ui.debugflag:
1455 1367 if rev is None:
1456 1368 jmanifestnode = 'null'
1457 1369 else:
1458 1370 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1459 1371 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1460 1372
1461 1373 self.ui.write((',\n "extra": {%s}') %
1462 1374 ", ".join('"%s": "%s"' % (j(k), j(v))
1463 1375 for k, v in ctx.extra().items()))
1464 1376
1465 1377 files = ctx.p1().status(ctx)
1466 1378 self.ui.write((',\n "modified": [%s]') %
1467 1379 ", ".join('"%s"' % j(f) for f in files[0]))
1468 1380 self.ui.write((',\n "added": [%s]') %
1469 1381 ", ".join('"%s"' % j(f) for f in files[1]))
1470 1382 self.ui.write((',\n "removed": [%s]') %
1471 1383 ", ".join('"%s"' % j(f) for f in files[2]))
1472 1384
1473 1385 elif self.ui.verbose:
1474 1386 self.ui.write((',\n "files": [%s]') %
1475 1387 ", ".join('"%s"' % j(f) for f in ctx.files()))
1476 1388
1477 1389 if copies:
1478 1390 self.ui.write((',\n "copies": {%s}') %
1479 1391 ", ".join('"%s": "%s"' % (j(k), j(v))
1480 1392 for k, v in copies))
1481 1393
1482 1394 matchfn = self.matchfn
1483 1395 if matchfn:
1484 1396 stat = self.diffopts.get('stat')
1485 1397 diff = self.diffopts.get('patch')
1486 1398 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1487 1399 node, prev = ctx.node(), ctx.p1().node()
1488 1400 if stat:
1489 1401 self.ui.pushbuffer()
1490 1402 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1491 1403 match=matchfn, stat=True)
1492 1404 self.ui.write((',\n "diffstat": "%s"')
1493 1405 % j(self.ui.popbuffer()))
1494 1406 if diff:
1495 1407 self.ui.pushbuffer()
1496 1408 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1497 1409 match=matchfn, stat=False)
1498 1410 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1499 1411
1500 1412 self.ui.write("\n }")
1501 1413
1502 1414 class changeset_templater(changeset_printer):
1503 1415 '''format changeset information.'''
1504 1416
1505 1417 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1506 1418 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1507 1419 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1508 1420 filters = {'formatnode': formatnode}
1509 1421 defaulttempl = {
1510 1422 'parent': '{rev}:{node|formatnode} ',
1511 1423 'manifest': '{rev}:{node|formatnode}',
1512 1424 'file_copy': '{name} ({source})',
1513 1425 'extra': '{key}={value|stringescape}'
1514 1426 }
1515 1427 # filecopy is preserved for compatibility reasons
1516 1428 defaulttempl['filecopy'] = defaulttempl['file_copy']
1517 1429 assert not (tmpl and mapfile)
1518 1430 if mapfile:
1519 1431 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1520 1432 cache=defaulttempl)
1521 1433 else:
1522 1434 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1523 1435 filters=filters,
1524 1436 cache=defaulttempl)
1525 1437
1526 1438 self.cache = {}
1527 1439
1528 1440 # find correct templates for current mode
1529 1441 tmplmodes = [
1530 1442 (True, None),
1531 1443 (self.ui.verbose, 'verbose'),
1532 1444 (self.ui.quiet, 'quiet'),
1533 1445 (self.ui.debugflag, 'debug'),
1534 1446 ]
1535 1447
1536 1448 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1537 1449 'docheader': '', 'docfooter': ''}
1538 1450 for mode, postfix in tmplmodes:
1539 1451 for t in self._parts:
1540 1452 cur = t
1541 1453 if postfix:
1542 1454 cur += "_" + postfix
1543 1455 if mode and cur in self.t:
1544 1456 self._parts[t] = cur
1545 1457
1546 1458 if self._parts['docheader']:
1547 1459 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1548 1460
1549 1461 def close(self):
1550 1462 if self._parts['docfooter']:
1551 1463 if not self.footer:
1552 1464 self.footer = ""
1553 1465 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1554 1466 return super(changeset_templater, self).close()
1555 1467
1556 1468 def _show(self, ctx, copies, matchfn, props):
1557 1469 '''show a single changeset or file revision'''
1558 1470 props = props.copy()
1559 1471 props.update(templatekw.keywords)
1560 1472 props['templ'] = self.t
1561 1473 props['ctx'] = ctx
1562 1474 props['repo'] = self.repo
1563 1475 props['ui'] = self.repo.ui
1564 1476 props['revcache'] = {'copies': copies}
1565 1477 props['cache'] = self.cache
1566 1478
1567 1479 # write header
1568 1480 if self._parts['header']:
1569 1481 h = templater.stringify(self.t(self._parts['header'], **props))
1570 1482 if self.buffered:
1571 1483 self.header[ctx.rev()] = h
1572 1484 else:
1573 1485 if self.lastheader != h:
1574 1486 self.lastheader = h
1575 1487 self.ui.write(h)
1576 1488
1577 1489 # write changeset metadata, then patch if requested
1578 1490 key = self._parts['changeset']
1579 1491 self.ui.write(templater.stringify(self.t(key, **props)))
1580 1492 self.showpatch(ctx, matchfn)
1581 1493
1582 1494 if self._parts['footer']:
1583 1495 if not self.footer:
1584 1496 self.footer = templater.stringify(
1585 1497 self.t(self._parts['footer'], **props))
1586 1498
1587 1499 def gettemplate(ui, tmpl, style):
1588 1500 """
1589 1501 Find the template matching the given template spec or style.
1590 1502 """
1591 1503
1592 1504 # ui settings
1593 1505 if not tmpl and not style: # template are stronger than style
1594 1506 tmpl = ui.config('ui', 'logtemplate')
1595 1507 if tmpl:
1596 1508 return templater.unquotestring(tmpl), None
1597 1509 else:
1598 1510 style = util.expandpath(ui.config('ui', 'style', ''))
1599 1511
1600 1512 if not tmpl and style:
1601 1513 mapfile = style
1602 1514 if not os.path.split(mapfile)[0]:
1603 1515 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1604 1516 or templater.templatepath(mapfile))
1605 1517 if mapname:
1606 1518 mapfile = mapname
1607 1519 return None, mapfile
1608 1520
1609 1521 if not tmpl:
1610 1522 return None, None
1611 1523
1612 1524 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1613 1525
1614 1526 def show_changeset(ui, repo, opts, buffered=False):
1615 1527 """show one changeset using template or regular display.
1616 1528
1617 1529 Display format will be the first non-empty hit of:
1618 1530 1. option 'template'
1619 1531 2. option 'style'
1620 1532 3. [ui] setting 'logtemplate'
1621 1533 4. [ui] setting 'style'
1622 1534 If all of these values are either the unset or the empty string,
1623 1535 regular display via changeset_printer() is done.
1624 1536 """
1625 1537 # options
1626 1538 matchfn = None
1627 1539 if opts.get('patch') or opts.get('stat'):
1628 1540 matchfn = scmutil.matchall(repo)
1629 1541
1630 1542 if opts.get('template') == 'json':
1631 1543 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1632 1544
1633 1545 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1634 1546
1635 1547 if not tmpl and not mapfile:
1636 1548 return changeset_printer(ui, repo, matchfn, opts, buffered)
1637 1549
1638 1550 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1639 1551
1640 1552 def showmarker(fm, marker, index=None):
1641 1553 """utility function to display obsolescence marker in a readable way
1642 1554
1643 1555 To be used by debug function."""
1644 1556 if index is not None:
1645 1557 fm.write('index', '%i ', index)
1646 1558 fm.write('precnode', '%s ', hex(marker.precnode()))
1647 1559 succs = marker.succnodes()
1648 1560 fm.condwrite(succs, 'succnodes', '%s ',
1649 1561 fm.formatlist(map(hex, succs), name='node'))
1650 1562 fm.write('flag', '%X ', marker.flags())
1651 1563 parents = marker.parentnodes()
1652 1564 if parents is not None:
1653 1565 fm.write('parentnodes', '{%s} ',
1654 1566 fm.formatlist(map(hex, parents), name='node', sep=', '))
1655 1567 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1656 1568 meta = marker.metadata().copy()
1657 1569 meta.pop('date', None)
1658 1570 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1659 1571 fm.plain('\n')
1660 1572
1661 1573 def finddate(ui, repo, date):
1662 1574 """Find the tipmost changeset that matches the given date spec"""
1663 1575
1664 1576 df = util.matchdate(date)
1665 1577 m = scmutil.matchall(repo)
1666 1578 results = {}
1667 1579
1668 1580 def prep(ctx, fns):
1669 1581 d = ctx.date()
1670 1582 if df(d[0]):
1671 1583 results[ctx.rev()] = d
1672 1584
1673 1585 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1674 1586 rev = ctx.rev()
1675 1587 if rev in results:
1676 1588 ui.status(_("found revision %s from %s\n") %
1677 1589 (rev, util.datestr(results[rev])))
1678 1590 return str(rev)
1679 1591
1680 1592 raise error.Abort(_("revision matching date not found"))
1681 1593
1682 1594 def increasingwindows(windowsize=8, sizelimit=512):
1683 1595 while True:
1684 1596 yield windowsize
1685 1597 if windowsize < sizelimit:
1686 1598 windowsize *= 2
1687 1599
1688 1600 class FileWalkError(Exception):
1689 1601 pass
1690 1602
1691 1603 def walkfilerevs(repo, match, follow, revs, fncache):
1692 1604 '''Walks the file history for the matched files.
1693 1605
1694 1606 Returns the changeset revs that are involved in the file history.
1695 1607
1696 1608 Throws FileWalkError if the file history can't be walked using
1697 1609 filelogs alone.
1698 1610 '''
1699 1611 wanted = set()
1700 1612 copies = []
1701 1613 minrev, maxrev = min(revs), max(revs)
1702 1614 def filerevgen(filelog, last):
1703 1615 """
1704 1616 Only files, no patterns. Check the history of each file.
1705 1617
1706 1618 Examines filelog entries within minrev, maxrev linkrev range
1707 1619 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1708 1620 tuples in backwards order
1709 1621 """
1710 1622 cl_count = len(repo)
1711 1623 revs = []
1712 1624 for j in xrange(0, last + 1):
1713 1625 linkrev = filelog.linkrev(j)
1714 1626 if linkrev < minrev:
1715 1627 continue
1716 1628 # only yield rev for which we have the changelog, it can
1717 1629 # happen while doing "hg log" during a pull or commit
1718 1630 if linkrev >= cl_count:
1719 1631 break
1720 1632
1721 1633 parentlinkrevs = []
1722 1634 for p in filelog.parentrevs(j):
1723 1635 if p != nullrev:
1724 1636 parentlinkrevs.append(filelog.linkrev(p))
1725 1637 n = filelog.node(j)
1726 1638 revs.append((linkrev, parentlinkrevs,
1727 1639 follow and filelog.renamed(n)))
1728 1640
1729 1641 return reversed(revs)
1730 1642 def iterfiles():
1731 1643 pctx = repo['.']
1732 1644 for filename in match.files():
1733 1645 if follow:
1734 1646 if filename not in pctx:
1735 1647 raise error.Abort(_('cannot follow file not in parent '
1736 1648 'revision: "%s"') % filename)
1737 1649 yield filename, pctx[filename].filenode()
1738 1650 else:
1739 1651 yield filename, None
1740 1652 for filename_node in copies:
1741 1653 yield filename_node
1742 1654
1743 1655 for file_, node in iterfiles():
1744 1656 filelog = repo.file(file_)
1745 1657 if not len(filelog):
1746 1658 if node is None:
1747 1659 # A zero count may be a directory or deleted file, so
1748 1660 # try to find matching entries on the slow path.
1749 1661 if follow:
1750 1662 raise error.Abort(
1751 1663 _('cannot follow nonexistent file: "%s"') % file_)
1752 1664 raise FileWalkError("Cannot walk via filelog")
1753 1665 else:
1754 1666 continue
1755 1667
1756 1668 if node is None:
1757 1669 last = len(filelog) - 1
1758 1670 else:
1759 1671 last = filelog.rev(node)
1760 1672
1761 1673 # keep track of all ancestors of the file
1762 1674 ancestors = set([filelog.linkrev(last)])
1763 1675
1764 1676 # iterate from latest to oldest revision
1765 1677 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1766 1678 if not follow:
1767 1679 if rev > maxrev:
1768 1680 continue
1769 1681 else:
1770 1682 # Note that last might not be the first interesting
1771 1683 # rev to us:
1772 1684 # if the file has been changed after maxrev, we'll
1773 1685 # have linkrev(last) > maxrev, and we still need
1774 1686 # to explore the file graph
1775 1687 if rev not in ancestors:
1776 1688 continue
1777 1689 # XXX insert 1327 fix here
1778 1690 if flparentlinkrevs:
1779 1691 ancestors.update(flparentlinkrevs)
1780 1692
1781 1693 fncache.setdefault(rev, []).append(file_)
1782 1694 wanted.add(rev)
1783 1695 if copied:
1784 1696 copies.append(copied)
1785 1697
1786 1698 return wanted
1787 1699
1788 1700 class _followfilter(object):
1789 1701 def __init__(self, repo, onlyfirst=False):
1790 1702 self.repo = repo
1791 1703 self.startrev = nullrev
1792 1704 self.roots = set()
1793 1705 self.onlyfirst = onlyfirst
1794 1706
1795 1707 def match(self, rev):
1796 1708 def realparents(rev):
1797 1709 if self.onlyfirst:
1798 1710 return self.repo.changelog.parentrevs(rev)[0:1]
1799 1711 else:
1800 1712 return filter(lambda x: x != nullrev,
1801 1713 self.repo.changelog.parentrevs(rev))
1802 1714
1803 1715 if self.startrev == nullrev:
1804 1716 self.startrev = rev
1805 1717 return True
1806 1718
1807 1719 if rev > self.startrev:
1808 1720 # forward: all descendants
1809 1721 if not self.roots:
1810 1722 self.roots.add(self.startrev)
1811 1723 for parent in realparents(rev):
1812 1724 if parent in self.roots:
1813 1725 self.roots.add(rev)
1814 1726 return True
1815 1727 else:
1816 1728 # backwards: all parents
1817 1729 if not self.roots:
1818 1730 self.roots.update(realparents(self.startrev))
1819 1731 if rev in self.roots:
1820 1732 self.roots.remove(rev)
1821 1733 self.roots.update(realparents(rev))
1822 1734 return True
1823 1735
1824 1736 return False
1825 1737
1826 1738 def walkchangerevs(repo, match, opts, prepare):
1827 1739 '''Iterate over files and the revs in which they changed.
1828 1740
1829 1741 Callers most commonly need to iterate backwards over the history
1830 1742 in which they are interested. Doing so has awful (quadratic-looking)
1831 1743 performance, so we use iterators in a "windowed" way.
1832 1744
1833 1745 We walk a window of revisions in the desired order. Within the
1834 1746 window, we first walk forwards to gather data, then in the desired
1835 1747 order (usually backwards) to display it.
1836 1748
1837 1749 This function returns an iterator yielding contexts. Before
1838 1750 yielding each context, the iterator will first call the prepare
1839 1751 function on each context in the window in forward order.'''
1840 1752
1841 1753 follow = opts.get('follow') or opts.get('follow_first')
1842 1754 revs = _logrevs(repo, opts)
1843 1755 if not revs:
1844 1756 return []
1845 1757 wanted = set()
1846 1758 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1847 1759 opts.get('removed'))
1848 1760 fncache = {}
1849 1761 change = repo.changectx
1850 1762
1851 1763 # First step is to fill wanted, the set of revisions that we want to yield.
1852 1764 # When it does not induce extra cost, we also fill fncache for revisions in
1853 1765 # wanted: a cache of filenames that were changed (ctx.files()) and that
1854 1766 # match the file filtering conditions.
1855 1767
1856 1768 if match.always():
1857 1769 # No files, no patterns. Display all revs.
1858 1770 wanted = revs
1859 1771 elif not slowpath:
1860 1772 # We only have to read through the filelog to find wanted revisions
1861 1773
1862 1774 try:
1863 1775 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1864 1776 except FileWalkError:
1865 1777 slowpath = True
1866 1778
1867 1779 # We decided to fall back to the slowpath because at least one
1868 1780 # of the paths was not a file. Check to see if at least one of them
1869 1781 # existed in history, otherwise simply return
1870 1782 for path in match.files():
1871 1783 if path == '.' or path in repo.store:
1872 1784 break
1873 1785 else:
1874 1786 return []
1875 1787
1876 1788 if slowpath:
1877 1789 # We have to read the changelog to match filenames against
1878 1790 # changed files
1879 1791
1880 1792 if follow:
1881 1793 raise error.Abort(_('can only follow copies/renames for explicit '
1882 1794 'filenames'))
1883 1795
1884 1796 # The slow path checks files modified in every changeset.
1885 1797 # This is really slow on large repos, so compute the set lazily.
1886 1798 class lazywantedset(object):
1887 1799 def __init__(self):
1888 1800 self.set = set()
1889 1801 self.revs = set(revs)
1890 1802
1891 1803 # No need to worry about locality here because it will be accessed
1892 1804 # in the same order as the increasing window below.
1893 1805 def __contains__(self, value):
1894 1806 if value in self.set:
1895 1807 return True
1896 1808 elif not value in self.revs:
1897 1809 return False
1898 1810 else:
1899 1811 self.revs.discard(value)
1900 1812 ctx = change(value)
1901 1813 matches = filter(match, ctx.files())
1902 1814 if matches:
1903 1815 fncache[value] = matches
1904 1816 self.set.add(value)
1905 1817 return True
1906 1818 return False
1907 1819
1908 1820 def discard(self, value):
1909 1821 self.revs.discard(value)
1910 1822 self.set.discard(value)
1911 1823
1912 1824 wanted = lazywantedset()
1913 1825
1914 1826 # it might be worthwhile to do this in the iterator if the rev range
1915 1827 # is descending and the prune args are all within that range
1916 1828 for rev in opts.get('prune', ()):
1917 1829 rev = repo[rev].rev()
1918 1830 ff = _followfilter(repo)
1919 1831 stop = min(revs[0], revs[-1])
1920 1832 for x in xrange(rev, stop - 1, -1):
1921 1833 if ff.match(x):
1922 1834 wanted = wanted - [x]
1923 1835
1924 1836 # Now that wanted is correctly initialized, we can iterate over the
1925 1837 # revision range, yielding only revisions in wanted.
1926 1838 def iterate():
1927 1839 if follow and match.always():
1928 1840 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1929 1841 def want(rev):
1930 1842 return ff.match(rev) and rev in wanted
1931 1843 else:
1932 1844 def want(rev):
1933 1845 return rev in wanted
1934 1846
1935 1847 it = iter(revs)
1936 1848 stopiteration = False
1937 1849 for windowsize in increasingwindows():
1938 1850 nrevs = []
1939 1851 for i in xrange(windowsize):
1940 1852 rev = next(it, None)
1941 1853 if rev is None:
1942 1854 stopiteration = True
1943 1855 break
1944 1856 elif want(rev):
1945 1857 nrevs.append(rev)
1946 1858 for rev in sorted(nrevs):
1947 1859 fns = fncache.get(rev)
1948 1860 ctx = change(rev)
1949 1861 if not fns:
1950 1862 def fns_generator():
1951 1863 for f in ctx.files():
1952 1864 if match(f):
1953 1865 yield f
1954 1866 fns = fns_generator()
1955 1867 prepare(ctx, fns)
1956 1868 for rev in nrevs:
1957 1869 yield change(rev)
1958 1870
1959 1871 if stopiteration:
1960 1872 break
1961 1873
1962 1874 return iterate()
1963 1875
1964 1876 def _makefollowlogfilematcher(repo, files, followfirst):
1965 1877 # When displaying a revision with --patch --follow FILE, we have
1966 1878 # to know which file of the revision must be diffed. With
1967 1879 # --follow, we want the names of the ancestors of FILE in the
1968 1880 # revision, stored in "fcache". "fcache" is populated by
1969 1881 # reproducing the graph traversal already done by --follow revset
1970 1882 # and relating revs to file names (which is not "correct" but
1971 1883 # good enough).
1972 1884 fcache = {}
1973 1885 fcacheready = [False]
1974 1886 pctx = repo['.']
1975 1887
1976 1888 def populate():
1977 1889 for fn in files:
1978 1890 fctx = pctx[fn]
1979 1891 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1980 1892 for c in fctx.ancestors(followfirst=followfirst):
1981 1893 fcache.setdefault(c.rev(), set()).add(c.path())
1982 1894
1983 1895 def filematcher(rev):
1984 1896 if not fcacheready[0]:
1985 1897 # Lazy initialization
1986 1898 fcacheready[0] = True
1987 1899 populate()
1988 1900 return scmutil.matchfiles(repo, fcache.get(rev, []))
1989 1901
1990 1902 return filematcher
1991 1903
1992 1904 def _makenofollowlogfilematcher(repo, pats, opts):
1993 1905 '''hook for extensions to override the filematcher for non-follow cases'''
1994 1906 return None
1995 1907
1996 1908 def _makelogrevset(repo, pats, opts, revs):
1997 1909 """Return (expr, filematcher) where expr is a revset string built
1998 1910 from log options and file patterns or None. If --stat or --patch
1999 1911 are not passed filematcher is None. Otherwise it is a callable
2000 1912 taking a revision number and returning a match objects filtering
2001 1913 the files to be detailed when displaying the revision.
2002 1914 """
2003 1915 opt2revset = {
2004 1916 'no_merges': ('not merge()', None),
2005 1917 'only_merges': ('merge()', None),
2006 1918 '_ancestors': ('ancestors(%(val)s)', None),
2007 1919 '_fancestors': ('_firstancestors(%(val)s)', None),
2008 1920 '_descendants': ('descendants(%(val)s)', None),
2009 1921 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2010 1922 '_matchfiles': ('_matchfiles(%(val)s)', None),
2011 1923 'date': ('date(%(val)r)', None),
2012 1924 'branch': ('branch(%(val)r)', ' or '),
2013 1925 '_patslog': ('filelog(%(val)r)', ' or '),
2014 1926 '_patsfollow': ('follow(%(val)r)', ' or '),
2015 1927 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2016 1928 'keyword': ('keyword(%(val)r)', ' or '),
2017 1929 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2018 1930 'user': ('user(%(val)r)', ' or '),
2019 1931 }
2020 1932
2021 1933 opts = dict(opts)
2022 1934 # follow or not follow?
2023 1935 follow = opts.get('follow') or opts.get('follow_first')
2024 1936 if opts.get('follow_first'):
2025 1937 followfirst = 1
2026 1938 else:
2027 1939 followfirst = 0
2028 1940 # --follow with FILE behavior depends on revs...
2029 1941 it = iter(revs)
2030 1942 startrev = next(it)
2031 1943 followdescendants = startrev < next(it, startrev)
2032 1944
2033 1945 # branch and only_branch are really aliases and must be handled at
2034 1946 # the same time
2035 1947 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2036 1948 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2037 1949 # pats/include/exclude are passed to match.match() directly in
2038 1950 # _matchfiles() revset but walkchangerevs() builds its matcher with
2039 1951 # scmutil.match(). The difference is input pats are globbed on
2040 1952 # platforms without shell expansion (windows).
2041 1953 wctx = repo[None]
2042 1954 match, pats = scmutil.matchandpats(wctx, pats, opts)
2043 1955 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2044 1956 opts.get('removed'))
2045 1957 if not slowpath:
2046 1958 for f in match.files():
2047 1959 if follow and f not in wctx:
2048 1960 # If the file exists, it may be a directory, so let it
2049 1961 # take the slow path.
2050 1962 if os.path.exists(repo.wjoin(f)):
2051 1963 slowpath = True
2052 1964 continue
2053 1965 else:
2054 1966 raise error.Abort(_('cannot follow file not in parent '
2055 1967 'revision: "%s"') % f)
2056 1968 filelog = repo.file(f)
2057 1969 if not filelog:
2058 1970 # A zero count may be a directory or deleted file, so
2059 1971 # try to find matching entries on the slow path.
2060 1972 if follow:
2061 1973 raise error.Abort(
2062 1974 _('cannot follow nonexistent file: "%s"') % f)
2063 1975 slowpath = True
2064 1976
2065 1977 # We decided to fall back to the slowpath because at least one
2066 1978 # of the paths was not a file. Check to see if at least one of them
2067 1979 # existed in history - in that case, we'll continue down the
2068 1980 # slowpath; otherwise, we can turn off the slowpath
2069 1981 if slowpath:
2070 1982 for path in match.files():
2071 1983 if path == '.' or path in repo.store:
2072 1984 break
2073 1985 else:
2074 1986 slowpath = False
2075 1987
2076 1988 fpats = ('_patsfollow', '_patsfollowfirst')
2077 1989 fnopats = (('_ancestors', '_fancestors'),
2078 1990 ('_descendants', '_fdescendants'))
2079 1991 if slowpath:
2080 1992 # See walkchangerevs() slow path.
2081 1993 #
2082 1994 # pats/include/exclude cannot be represented as separate
2083 1995 # revset expressions as their filtering logic applies at file
2084 1996 # level. For instance "-I a -X a" matches a revision touching
2085 1997 # "a" and "b" while "file(a) and not file(b)" does
2086 1998 # not. Besides, filesets are evaluated against the working
2087 1999 # directory.
2088 2000 matchargs = ['r:', 'd:relpath']
2089 2001 for p in pats:
2090 2002 matchargs.append('p:' + p)
2091 2003 for p in opts.get('include', []):
2092 2004 matchargs.append('i:' + p)
2093 2005 for p in opts.get('exclude', []):
2094 2006 matchargs.append('x:' + p)
2095 2007 matchargs = ','.join(('%r' % p) for p in matchargs)
2096 2008 opts['_matchfiles'] = matchargs
2097 2009 if follow:
2098 2010 opts[fnopats[0][followfirst]] = '.'
2099 2011 else:
2100 2012 if follow:
2101 2013 if pats:
2102 2014 # follow() revset interprets its file argument as a
2103 2015 # manifest entry, so use match.files(), not pats.
2104 2016 opts[fpats[followfirst]] = list(match.files())
2105 2017 else:
2106 2018 op = fnopats[followdescendants][followfirst]
2107 2019 opts[op] = 'rev(%d)' % startrev
2108 2020 else:
2109 2021 opts['_patslog'] = list(pats)
2110 2022
2111 2023 filematcher = None
2112 2024 if opts.get('patch') or opts.get('stat'):
2113 2025 # When following files, track renames via a special matcher.
2114 2026 # If we're forced to take the slowpath it means we're following
2115 2027 # at least one pattern/directory, so don't bother with rename tracking.
2116 2028 if follow and not match.always() and not slowpath:
2117 2029 # _makefollowlogfilematcher expects its files argument to be
2118 2030 # relative to the repo root, so use match.files(), not pats.
2119 2031 filematcher = _makefollowlogfilematcher(repo, match.files(),
2120 2032 followfirst)
2121 2033 else:
2122 2034 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2123 2035 if filematcher is None:
2124 2036 filematcher = lambda rev: match
2125 2037
2126 2038 expr = []
2127 2039 for op, val in sorted(opts.iteritems()):
2128 2040 if not val:
2129 2041 continue
2130 2042 if op not in opt2revset:
2131 2043 continue
2132 2044 revop, andor = opt2revset[op]
2133 2045 if '%(val)' not in revop:
2134 2046 expr.append(revop)
2135 2047 else:
2136 2048 if not isinstance(val, list):
2137 2049 e = revop % {'val': val}
2138 2050 else:
2139 2051 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2140 2052 expr.append(e)
2141 2053
2142 2054 if expr:
2143 2055 expr = '(' + ' and '.join(expr) + ')'
2144 2056 else:
2145 2057 expr = None
2146 2058 return expr, filematcher
2147 2059
2148 2060 def _logrevs(repo, opts):
2149 2061 # Default --rev value depends on --follow but --follow behavior
2150 2062 # depends on revisions resolved from --rev...
2151 2063 follow = opts.get('follow') or opts.get('follow_first')
2152 2064 if opts.get('rev'):
2153 2065 revs = scmutil.revrange(repo, opts['rev'])
2154 2066 elif follow and repo.dirstate.p1() == nullid:
2155 2067 revs = revset.baseset()
2156 2068 elif follow:
2157 2069 revs = repo.revs('reverse(:.)')
2158 2070 else:
2159 2071 revs = revset.spanset(repo)
2160 2072 revs.reverse()
2161 2073 return revs
2162 2074
2163 2075 def getgraphlogrevs(repo, pats, opts):
2164 2076 """Return (revs, expr, filematcher) where revs is an iterable of
2165 2077 revision numbers, expr is a revset string built from log options
2166 2078 and file patterns or None, and used to filter 'revs'. If --stat or
2167 2079 --patch are not passed filematcher is None. Otherwise it is a
2168 2080 callable taking a revision number and returning a match objects
2169 2081 filtering the files to be detailed when displaying the revision.
2170 2082 """
2171 2083 limit = loglimit(opts)
2172 2084 revs = _logrevs(repo, opts)
2173 2085 if not revs:
2174 2086 return revset.baseset(), None, None
2175 2087 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2176 2088 if opts.get('rev'):
2177 2089 # User-specified revs might be unsorted, but don't sort before
2178 2090 # _makelogrevset because it might depend on the order of revs
2179 2091 if not (revs.isdescending() or revs.istopo()):
2180 2092 revs.sort(reverse=True)
2181 2093 if expr:
2182 2094 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2183 2095 revs = matcher(repo, revs)
2184 2096 if limit is not None:
2185 2097 limitedrevs = []
2186 2098 for idx, rev in enumerate(revs):
2187 2099 if idx >= limit:
2188 2100 break
2189 2101 limitedrevs.append(rev)
2190 2102 revs = revset.baseset(limitedrevs)
2191 2103
2192 2104 return revs, expr, filematcher
2193 2105
2194 2106 def getlogrevs(repo, pats, opts):
2195 2107 """Return (revs, expr, filematcher) where revs is an iterable of
2196 2108 revision numbers, expr is a revset string built from log options
2197 2109 and file patterns or None, and used to filter 'revs'. If --stat or
2198 2110 --patch are not passed filematcher is None. Otherwise it is a
2199 2111 callable taking a revision number and returning a match objects
2200 2112 filtering the files to be detailed when displaying the revision.
2201 2113 """
2202 2114 limit = loglimit(opts)
2203 2115 revs = _logrevs(repo, opts)
2204 2116 if not revs:
2205 2117 return revset.baseset([]), None, None
2206 2118 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2207 2119 if expr:
2208 2120 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2209 2121 revs = matcher(repo, revs)
2210 2122 if limit is not None:
2211 2123 limitedrevs = []
2212 2124 for idx, r in enumerate(revs):
2213 2125 if limit <= idx:
2214 2126 break
2215 2127 limitedrevs.append(r)
2216 2128 revs = revset.baseset(limitedrevs)
2217 2129
2218 2130 return revs, expr, filematcher
2219 2131
2220 2132 def _graphnodeformatter(ui, displayer):
2221 2133 spec = ui.config('ui', 'graphnodetemplate')
2222 2134 if not spec:
2223 2135 return templatekw.showgraphnode # fast path for "{graphnode}"
2224 2136
2225 2137 templ = formatter.gettemplater(ui, 'graphnode', spec)
2226 2138 cache = {}
2227 2139 if isinstance(displayer, changeset_templater):
2228 2140 cache = displayer.cache # reuse cache of slow templates
2229 2141 props = templatekw.keywords.copy()
2230 2142 props['templ'] = templ
2231 2143 props['cache'] = cache
2232 2144 def formatnode(repo, ctx):
2233 2145 props['ctx'] = ctx
2234 2146 props['repo'] = repo
2235 2147 props['ui'] = repo.ui
2236 2148 props['revcache'] = {}
2237 2149 return templater.stringify(templ('graphnode', **props))
2238 2150 return formatnode
2239 2151
2240 2152 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2241 2153 filematcher=None):
2242 2154 formatnode = _graphnodeformatter(ui, displayer)
2243 2155 state = graphmod.asciistate()
2244 2156 styles = state['styles']
2245 2157
2246 2158 # only set graph styling if HGPLAIN is not set.
2247 2159 if ui.plain('graph'):
2248 2160 # set all edge styles to |, the default pre-3.8 behaviour
2249 2161 styles.update(dict.fromkeys(styles, '|'))
2250 2162 else:
2251 2163 edgetypes = {
2252 2164 'parent': graphmod.PARENT,
2253 2165 'grandparent': graphmod.GRANDPARENT,
2254 2166 'missing': graphmod.MISSINGPARENT
2255 2167 }
2256 2168 for name, key in edgetypes.items():
2257 2169 # experimental config: experimental.graphstyle.*
2258 2170 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2259 2171 styles[key])
2260 2172 if not styles[key]:
2261 2173 styles[key] = None
2262 2174
2263 2175 # experimental config: experimental.graphshorten
2264 2176 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2265 2177
2266 2178 for rev, type, ctx, parents in dag:
2267 2179 char = formatnode(repo, ctx)
2268 2180 copies = None
2269 2181 if getrenamed and ctx.rev():
2270 2182 copies = []
2271 2183 for fn in ctx.files():
2272 2184 rename = getrenamed(fn, ctx.rev())
2273 2185 if rename:
2274 2186 copies.append((fn, rename[0]))
2275 2187 revmatchfn = None
2276 2188 if filematcher is not None:
2277 2189 revmatchfn = filematcher(ctx.rev())
2278 2190 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2279 2191 lines = displayer.hunk.pop(rev).split('\n')
2280 2192 if not lines[-1]:
2281 2193 del lines[-1]
2282 2194 displayer.flush(ctx)
2283 2195 edges = edgefn(type, char, lines, state, rev, parents)
2284 2196 for type, char, lines, coldata in edges:
2285 2197 graphmod.ascii(ui, state, type, char, lines, coldata)
2286 2198 displayer.close()
2287 2199
2288 2200 def graphlog(ui, repo, *pats, **opts):
2289 2201 # Parameters are identical to log command ones
2290 2202 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2291 2203 revdag = graphmod.dagwalker(repo, revs)
2292 2204
2293 2205 getrenamed = None
2294 2206 if opts.get('copies'):
2295 2207 endrev = None
2296 2208 if opts.get('rev'):
2297 2209 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2298 2210 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2299 2211 displayer = show_changeset(ui, repo, opts, buffered=True)
2300 2212 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2301 2213 filematcher)
2302 2214
2303 2215 def checkunsupportedgraphflags(pats, opts):
2304 2216 for op in ["newest_first"]:
2305 2217 if op in opts and opts[op]:
2306 2218 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2307 2219 % op.replace("_", "-"))
2308 2220
2309 2221 def graphrevs(repo, nodes, opts):
2310 2222 limit = loglimit(opts)
2311 2223 nodes.reverse()
2312 2224 if limit is not None:
2313 2225 nodes = nodes[:limit]
2314 2226 return graphmod.nodes(repo, nodes)
2315 2227
2316 2228 def add(ui, repo, match, prefix, explicitonly, **opts):
2317 2229 join = lambda f: os.path.join(prefix, f)
2318 2230 bad = []
2319 2231
2320 2232 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2321 2233 names = []
2322 2234 wctx = repo[None]
2323 2235 cca = None
2324 2236 abort, warn = scmutil.checkportabilityalert(ui)
2325 2237 if abort or warn:
2326 2238 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2327 2239
2328 2240 badmatch = matchmod.badmatch(match, badfn)
2329 2241 dirstate = repo.dirstate
2330 2242 # We don't want to just call wctx.walk here, since it would return a lot of
2331 2243 # clean files, which we aren't interested in and takes time.
2332 2244 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2333 2245 True, False, full=False)):
2334 2246 exact = match.exact(f)
2335 2247 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2336 2248 if cca:
2337 2249 cca(f)
2338 2250 names.append(f)
2339 2251 if ui.verbose or not exact:
2340 2252 ui.status(_('adding %s\n') % match.rel(f))
2341 2253
2342 2254 for subpath in sorted(wctx.substate):
2343 2255 sub = wctx.sub(subpath)
2344 2256 try:
2345 2257 submatch = matchmod.subdirmatcher(subpath, match)
2346 2258 if opts.get('subrepos'):
2347 2259 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2348 2260 else:
2349 2261 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2350 2262 except error.LookupError:
2351 2263 ui.status(_("skipping missing subrepository: %s\n")
2352 2264 % join(subpath))
2353 2265
2354 2266 if not opts.get('dry_run'):
2355 2267 rejected = wctx.add(names, prefix)
2356 2268 bad.extend(f for f in rejected if f in match.files())
2357 2269 return bad
2358 2270
2359 2271 def forget(ui, repo, match, prefix, explicitonly):
2360 2272 join = lambda f: os.path.join(prefix, f)
2361 2273 bad = []
2362 2274 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2363 2275 wctx = repo[None]
2364 2276 forgot = []
2365 2277
2366 2278 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2367 2279 forget = sorted(s[0] + s[1] + s[3] + s[6])
2368 2280 if explicitonly:
2369 2281 forget = [f for f in forget if match.exact(f)]
2370 2282
2371 2283 for subpath in sorted(wctx.substate):
2372 2284 sub = wctx.sub(subpath)
2373 2285 try:
2374 2286 submatch = matchmod.subdirmatcher(subpath, match)
2375 2287 subbad, subforgot = sub.forget(submatch, prefix)
2376 2288 bad.extend([subpath + '/' + f for f in subbad])
2377 2289 forgot.extend([subpath + '/' + f for f in subforgot])
2378 2290 except error.LookupError:
2379 2291 ui.status(_("skipping missing subrepository: %s\n")
2380 2292 % join(subpath))
2381 2293
2382 2294 if not explicitonly:
2383 2295 for f in match.files():
2384 2296 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2385 2297 if f not in forgot:
2386 2298 if repo.wvfs.exists(f):
2387 2299 # Don't complain if the exact case match wasn't given.
2388 2300 # But don't do this until after checking 'forgot', so
2389 2301 # that subrepo files aren't normalized, and this op is
2390 2302 # purely from data cached by the status walk above.
2391 2303 if repo.dirstate.normalize(f) in repo.dirstate:
2392 2304 continue
2393 2305 ui.warn(_('not removing %s: '
2394 2306 'file is already untracked\n')
2395 2307 % match.rel(f))
2396 2308 bad.append(f)
2397 2309
2398 2310 for f in forget:
2399 2311 if ui.verbose or not match.exact(f):
2400 2312 ui.status(_('removing %s\n') % match.rel(f))
2401 2313
2402 2314 rejected = wctx.forget(forget, prefix)
2403 2315 bad.extend(f for f in rejected if f in match.files())
2404 2316 forgot.extend(f for f in forget if f not in rejected)
2405 2317 return bad, forgot
2406 2318
2407 2319 def files(ui, ctx, m, fm, fmt, subrepos):
2408 2320 rev = ctx.rev()
2409 2321 ret = 1
2410 2322 ds = ctx.repo().dirstate
2411 2323
2412 2324 for f in ctx.matches(m):
2413 2325 if rev is None and ds[f] == 'r':
2414 2326 continue
2415 2327 fm.startitem()
2416 2328 if ui.verbose:
2417 2329 fc = ctx[f]
2418 2330 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2419 2331 fm.data(abspath=f)
2420 2332 fm.write('path', fmt, m.rel(f))
2421 2333 ret = 0
2422 2334
2423 2335 for subpath in sorted(ctx.substate):
2424 2336 submatch = matchmod.subdirmatcher(subpath, m)
2425 2337 if (subrepos or m.exact(subpath) or any(submatch.files())):
2426 2338 sub = ctx.sub(subpath)
2427 2339 try:
2428 2340 recurse = m.exact(subpath) or subrepos
2429 2341 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2430 2342 ret = 0
2431 2343 except error.LookupError:
2432 2344 ui.status(_("skipping missing subrepository: %s\n")
2433 2345 % m.abs(subpath))
2434 2346
2435 2347 return ret
2436 2348
2437 2349 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2438 2350 join = lambda f: os.path.join(prefix, f)
2439 2351 ret = 0
2440 2352 s = repo.status(match=m, clean=True)
2441 2353 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2442 2354
2443 2355 wctx = repo[None]
2444 2356
2445 2357 if warnings is None:
2446 2358 warnings = []
2447 2359 warn = True
2448 2360 else:
2449 2361 warn = False
2450 2362
2451 2363 subs = sorted(wctx.substate)
2452 2364 total = len(subs)
2453 2365 count = 0
2454 2366 for subpath in subs:
2455 2367 count += 1
2456 2368 submatch = matchmod.subdirmatcher(subpath, m)
2457 2369 if subrepos or m.exact(subpath) or any(submatch.files()):
2458 2370 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2459 2371 sub = wctx.sub(subpath)
2460 2372 try:
2461 2373 if sub.removefiles(submatch, prefix, after, force, subrepos,
2462 2374 warnings):
2463 2375 ret = 1
2464 2376 except error.LookupError:
2465 2377 warnings.append(_("skipping missing subrepository: %s\n")
2466 2378 % join(subpath))
2467 2379 ui.progress(_('searching'), None)
2468 2380
2469 2381 # warn about failure to delete explicit files/dirs
2470 2382 deleteddirs = util.dirs(deleted)
2471 2383 files = m.files()
2472 2384 total = len(files)
2473 2385 count = 0
2474 2386 for f in files:
2475 2387 def insubrepo():
2476 2388 for subpath in wctx.substate:
2477 2389 if f.startswith(subpath + '/'):
2478 2390 return True
2479 2391 return False
2480 2392
2481 2393 count += 1
2482 2394 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2483 2395 isdir = f in deleteddirs or wctx.hasdir(f)
2484 2396 if (f in repo.dirstate or isdir or f == '.'
2485 2397 or insubrepo() or f in subs):
2486 2398 continue
2487 2399
2488 2400 if repo.wvfs.exists(f):
2489 2401 if repo.wvfs.isdir(f):
2490 2402 warnings.append(_('not removing %s: no tracked files\n')
2491 2403 % m.rel(f))
2492 2404 else:
2493 2405 warnings.append(_('not removing %s: file is untracked\n')
2494 2406 % m.rel(f))
2495 2407 # missing files will generate a warning elsewhere
2496 2408 ret = 1
2497 2409 ui.progress(_('deleting'), None)
2498 2410
2499 2411 if force:
2500 2412 list = modified + deleted + clean + added
2501 2413 elif after:
2502 2414 list = deleted
2503 2415 remaining = modified + added + clean
2504 2416 total = len(remaining)
2505 2417 count = 0
2506 2418 for f in remaining:
2507 2419 count += 1
2508 2420 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2509 2421 warnings.append(_('not removing %s: file still exists\n')
2510 2422 % m.rel(f))
2511 2423 ret = 1
2512 2424 ui.progress(_('skipping'), None)
2513 2425 else:
2514 2426 list = deleted + clean
2515 2427 total = len(modified) + len(added)
2516 2428 count = 0
2517 2429 for f in modified:
2518 2430 count += 1
2519 2431 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2520 2432 warnings.append(_('not removing %s: file is modified (use -f'
2521 2433 ' to force removal)\n') % m.rel(f))
2522 2434 ret = 1
2523 2435 for f in added:
2524 2436 count += 1
2525 2437 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2526 2438 warnings.append(_("not removing %s: file has been marked for add"
2527 2439 " (use 'hg forget' to undo add)\n") % m.rel(f))
2528 2440 ret = 1
2529 2441 ui.progress(_('skipping'), None)
2530 2442
2531 2443 list = sorted(list)
2532 2444 total = len(list)
2533 2445 count = 0
2534 2446 for f in list:
2535 2447 count += 1
2536 2448 if ui.verbose or not m.exact(f):
2537 2449 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2538 2450 ui.status(_('removing %s\n') % m.rel(f))
2539 2451 ui.progress(_('deleting'), None)
2540 2452
2541 2453 with repo.wlock():
2542 2454 if not after:
2543 2455 for f in list:
2544 2456 if f in added:
2545 2457 continue # we never unlink added files on remove
2546 2458 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2547 2459 repo[None].forget(list)
2548 2460
2549 2461 if warn:
2550 2462 for warning in warnings:
2551 2463 ui.warn(warning)
2552 2464
2553 2465 return ret
2554 2466
2555 2467 def cat(ui, repo, ctx, matcher, prefix, **opts):
2556 2468 err = 1
2557 2469
2558 2470 def write(path):
2559 2471 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2560 2472 pathname=os.path.join(prefix, path))
2561 2473 data = ctx[path].data()
2562 2474 if opts.get('decode'):
2563 2475 data = repo.wwritedata(path, data)
2564 2476 fp.write(data)
2565 2477 fp.close()
2566 2478
2567 2479 # Automation often uses hg cat on single files, so special case it
2568 2480 # for performance to avoid the cost of parsing the manifest.
2569 2481 if len(matcher.files()) == 1 and not matcher.anypats():
2570 2482 file = matcher.files()[0]
2571 2483 mfl = repo.manifestlog
2572 2484 mfnode = ctx.manifestnode()
2573 2485 try:
2574 2486 if mfnode and mfl[mfnode].find(file)[0]:
2575 2487 write(file)
2576 2488 return 0
2577 2489 except KeyError:
2578 2490 pass
2579 2491
2580 2492 for abs in ctx.walk(matcher):
2581 2493 write(abs)
2582 2494 err = 0
2583 2495
2584 2496 for subpath in sorted(ctx.substate):
2585 2497 sub = ctx.sub(subpath)
2586 2498 try:
2587 2499 submatch = matchmod.subdirmatcher(subpath, matcher)
2588 2500
2589 2501 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2590 2502 **opts):
2591 2503 err = 0
2592 2504 except error.RepoLookupError:
2593 2505 ui.status(_("skipping missing subrepository: %s\n")
2594 2506 % os.path.join(prefix, subpath))
2595 2507
2596 2508 return err
2597 2509
2598 2510 def commit(ui, repo, commitfunc, pats, opts):
2599 2511 '''commit the specified files or all outstanding changes'''
2600 2512 date = opts.get('date')
2601 2513 if date:
2602 2514 opts['date'] = util.parsedate(date)
2603 2515 message = logmessage(ui, opts)
2604 2516 matcher = scmutil.match(repo[None], pats, opts)
2605 2517
2606 2518 # extract addremove carefully -- this function can be called from a command
2607 2519 # that doesn't support addremove
2608 2520 if opts.get('addremove'):
2609 2521 if scmutil.addremove(repo, matcher, "", opts) != 0:
2610 2522 raise error.Abort(
2611 2523 _("failed to mark all new/missing files as added/removed"))
2612 2524
2613 2525 return commitfunc(ui, repo, message, matcher, opts)
2614 2526
2615 2527 def samefile(f, ctx1, ctx2):
2616 2528 if f in ctx1.manifest():
2617 2529 a = ctx1.filectx(f)
2618 2530 if f in ctx2.manifest():
2619 2531 b = ctx2.filectx(f)
2620 2532 return (not a.cmp(b)
2621 2533 and a.flags() == b.flags())
2622 2534 else:
2623 2535 return False
2624 2536 else:
2625 2537 return f not in ctx2.manifest()
2626 2538
2627 2539 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2628 2540 # avoid cycle context -> subrepo -> cmdutil
2629 2541 from . import context
2630 2542
2631 2543 # amend will reuse the existing user if not specified, but the obsolete
2632 2544 # marker creation requires that the current user's name is specified.
2633 2545 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2634 2546 ui.username() # raise exception if username not set
2635 2547
2636 2548 ui.note(_('amending changeset %s\n') % old)
2637 2549 base = old.p1()
2638 2550 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2639 2551
2640 2552 wlock = lock = newid = None
2641 2553 try:
2642 2554 wlock = repo.wlock()
2643 2555 lock = repo.lock()
2644 2556 with repo.transaction('amend') as tr:
2645 2557 # See if we got a message from -m or -l, if not, open the editor
2646 2558 # with the message of the changeset to amend
2647 2559 message = logmessage(ui, opts)
2648 2560 # ensure logfile does not conflict with later enforcement of the
2649 2561 # message. potential logfile content has been processed by
2650 2562 # `logmessage` anyway.
2651 2563 opts.pop('logfile')
2652 2564 # First, do a regular commit to record all changes in the working
2653 2565 # directory (if there are any)
2654 2566 ui.callhooks = False
2655 2567 activebookmark = repo._bookmarks.active
2656 2568 try:
2657 2569 repo._bookmarks.active = None
2658 2570 opts['message'] = 'temporary amend commit for %s' % old
2659 2571 node = commit(ui, repo, commitfunc, pats, opts)
2660 2572 finally:
2661 2573 repo._bookmarks.active = activebookmark
2662 2574 repo._bookmarks.recordchange(tr)
2663 2575 ui.callhooks = True
2664 2576 ctx = repo[node]
2665 2577
2666 2578 # Participating changesets:
2667 2579 #
2668 2580 # node/ctx o - new (intermediate) commit that contains changes
2669 2581 # | from working dir to go into amending commit
2670 2582 # | (or a workingctx if there were no changes)
2671 2583 # |
2672 2584 # old o - changeset to amend
2673 2585 # |
2674 2586 # base o - parent of amending changeset
2675 2587
2676 2588 # Update extra dict from amended commit (e.g. to preserve graft
2677 2589 # source)
2678 2590 extra.update(old.extra())
2679 2591
2680 2592 # Also update it from the intermediate commit or from the wctx
2681 2593 extra.update(ctx.extra())
2682 2594
2683 2595 if len(old.parents()) > 1:
2684 2596 # ctx.files() isn't reliable for merges, so fall back to the
2685 2597 # slower repo.status() method
2686 2598 files = set([fn for st in repo.status(base, old)[:3]
2687 2599 for fn in st])
2688 2600 else:
2689 2601 files = set(old.files())
2690 2602
2691 2603 # Second, we use either the commit we just did, or if there were no
2692 2604 # changes the parent of the working directory as the version of the
2693 2605 # files in the final amend commit
2694 2606 if node:
2695 2607 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2696 2608
2697 2609 user = ctx.user()
2698 2610 date = ctx.date()
2699 2611 # Recompute copies (avoid recording a -> b -> a)
2700 2612 copied = copies.pathcopies(base, ctx)
2701 2613 if old.p2:
2702 2614 copied.update(copies.pathcopies(old.p2(), ctx))
2703 2615
2704 2616 # Prune files which were reverted by the updates: if old
2705 2617 # introduced file X and our intermediate commit, node,
2706 2618 # renamed that file, then those two files are the same and
2707 2619 # we can discard X from our list of files. Likewise if X
2708 2620 # was deleted, it's no longer relevant
2709 2621 files.update(ctx.files())
2710 2622 files = [f for f in files if not samefile(f, ctx, base)]
2711 2623
2712 2624 def filectxfn(repo, ctx_, path):
2713 2625 try:
2714 2626 fctx = ctx[path]
2715 2627 flags = fctx.flags()
2716 2628 mctx = context.memfilectx(repo,
2717 2629 fctx.path(), fctx.data(),
2718 2630 islink='l' in flags,
2719 2631 isexec='x' in flags,
2720 2632 copied=copied.get(path))
2721 2633 return mctx
2722 2634 except KeyError:
2723 2635 return None
2724 2636 else:
2725 2637 ui.note(_('copying changeset %s to %s\n') % (old, base))
2726 2638
2727 2639 # Use version of files as in the old cset
2728 2640 def filectxfn(repo, ctx_, path):
2729 2641 try:
2730 2642 return old.filectx(path)
2731 2643 except KeyError:
2732 2644 return None
2733 2645
2734 2646 user = opts.get('user') or old.user()
2735 2647 date = opts.get('date') or old.date()
2736 2648 editform = mergeeditform(old, 'commit.amend')
2737 2649 editor = getcommiteditor(editform=editform, **opts)
2738 2650 if not message:
2739 2651 editor = getcommiteditor(edit=True, editform=editform)
2740 2652 message = old.description()
2741 2653
2742 2654 pureextra = extra.copy()
2743 2655 extra['amend_source'] = old.hex()
2744 2656
2745 2657 new = context.memctx(repo,
2746 2658 parents=[base.node(), old.p2().node()],
2747 2659 text=message,
2748 2660 files=files,
2749 2661 filectxfn=filectxfn,
2750 2662 user=user,
2751 2663 date=date,
2752 2664 extra=extra,
2753 2665 editor=editor)
2754 2666
2755 2667 newdesc = changelog.stripdesc(new.description())
2756 2668 if ((not node)
2757 2669 and newdesc == old.description()
2758 2670 and user == old.user()
2759 2671 and date == old.date()
2760 2672 and pureextra == old.extra()):
2761 2673 # nothing changed. continuing here would create a new node
2762 2674 # anyway because of the amend_source noise.
2763 2675 #
2764 2676 # This not what we expect from amend.
2765 2677 return old.node()
2766 2678
2767 2679 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2768 2680 try:
2769 2681 if opts.get('secret'):
2770 2682 commitphase = 'secret'
2771 2683 else:
2772 2684 commitphase = old.phase()
2773 2685 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2774 2686 newid = repo.commitctx(new)
2775 2687 finally:
2776 2688 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2777 2689 if newid != old.node():
2778 2690 # Reroute the working copy parent to the new changeset
2779 2691 repo.setparents(newid, nullid)
2780 2692
2781 2693 # Move bookmarks from old parent to amend commit
2782 2694 bms = repo.nodebookmarks(old.node())
2783 2695 if bms:
2784 2696 marks = repo._bookmarks
2785 2697 for bm in bms:
2786 2698 ui.debug('moving bookmarks %r from %s to %s\n' %
2787 2699 (marks, old.hex(), hex(newid)))
2788 2700 marks[bm] = newid
2789 2701 marks.recordchange(tr)
2790 2702 #commit the whole amend process
2791 2703 if createmarkers:
2792 2704 # mark the new changeset as successor of the rewritten one
2793 2705 new = repo[newid]
2794 2706 obs = [(old, (new,))]
2795 2707 if node:
2796 2708 obs.append((ctx, ()))
2797 2709
2798 2710 obsolete.createmarkers(repo, obs)
2799 2711 if not createmarkers and newid != old.node():
2800 2712 # Strip the intermediate commit (if there was one) and the amended
2801 2713 # commit
2802 2714 if node:
2803 2715 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2804 2716 ui.note(_('stripping amended changeset %s\n') % old)
2805 2717 repair.strip(ui, repo, old.node(), topic='amend-backup')
2806 2718 finally:
2807 2719 lockmod.release(lock, wlock)
2808 2720 return newid
2809 2721
2810 2722 def commiteditor(repo, ctx, subs, editform=''):
2811 2723 if ctx.description():
2812 2724 return ctx.description()
2813 2725 return commitforceeditor(repo, ctx, subs, editform=editform,
2814 2726 unchangedmessagedetection=True)
2815 2727
2816 2728 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2817 2729 editform='', unchangedmessagedetection=False):
2818 2730 if not extramsg:
2819 2731 extramsg = _("Leave message empty to abort commit.")
2820 2732
2821 2733 forms = [e for e in editform.split('.') if e]
2822 2734 forms.insert(0, 'changeset')
2823 2735 templatetext = None
2824 2736 while forms:
2825 2737 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2826 2738 if tmpl:
2827 2739 templatetext = committext = buildcommittemplate(
2828 2740 repo, ctx, subs, extramsg, tmpl)
2829 2741 break
2830 2742 forms.pop()
2831 2743 else:
2832 2744 committext = buildcommittext(repo, ctx, subs, extramsg)
2833 2745
2834 2746 # run editor in the repository root
2835 2747 olddir = os.getcwd()
2836 2748 os.chdir(repo.root)
2837 2749
2838 2750 # make in-memory changes visible to external process
2839 2751 tr = repo.currenttransaction()
2840 2752 repo.dirstate.write(tr)
2841 2753 pending = tr and tr.writepending() and repo.root
2842 2754
2843 2755 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2844 2756 editform=editform, pending=pending)
2845 2757 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2846 2758 os.chdir(olddir)
2847 2759
2848 2760 if finishdesc:
2849 2761 text = finishdesc(text)
2850 2762 if not text.strip():
2851 2763 raise error.Abort(_("empty commit message"))
2852 2764 if unchangedmessagedetection and editortext == templatetext:
2853 2765 raise error.Abort(_("commit message unchanged"))
2854 2766
2855 2767 return text
2856 2768
2857 2769 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2858 2770 ui = repo.ui
2859 2771 tmpl, mapfile = gettemplate(ui, tmpl, None)
2860 2772
2861 2773 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2862 2774
2863 2775 for k, v in repo.ui.configitems('committemplate'):
2864 2776 if k != 'changeset':
2865 2777 t.t.cache[k] = v
2866 2778
2867 2779 if not extramsg:
2868 2780 extramsg = '' # ensure that extramsg is string
2869 2781
2870 2782 ui.pushbuffer()
2871 2783 t.show(ctx, extramsg=extramsg)
2872 2784 return ui.popbuffer()
2873 2785
2874 2786 def hgprefix(msg):
2875 2787 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2876 2788
2877 2789 def buildcommittext(repo, ctx, subs, extramsg):
2878 2790 edittext = []
2879 2791 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2880 2792 if ctx.description():
2881 2793 edittext.append(ctx.description())
2882 2794 edittext.append("")
2883 2795 edittext.append("") # Empty line between message and comments.
2884 2796 edittext.append(hgprefix(_("Enter commit message."
2885 2797 " Lines beginning with 'HG:' are removed.")))
2886 2798 edittext.append(hgprefix(extramsg))
2887 2799 edittext.append("HG: --")
2888 2800 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2889 2801 if ctx.p2():
2890 2802 edittext.append(hgprefix(_("branch merge")))
2891 2803 if ctx.branch():
2892 2804 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2893 2805 if bookmarks.isactivewdirparent(repo):
2894 2806 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2895 2807 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2896 2808 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2897 2809 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2898 2810 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2899 2811 if not added and not modified and not removed:
2900 2812 edittext.append(hgprefix(_("no files changed")))
2901 2813 edittext.append("")
2902 2814
2903 2815 return "\n".join(edittext)
2904 2816
2905 2817 def commitstatus(repo, node, branch, bheads=None, opts=None):
2906 2818 if opts is None:
2907 2819 opts = {}
2908 2820 ctx = repo[node]
2909 2821 parents = ctx.parents()
2910 2822
2911 2823 if (not opts.get('amend') and bheads and node not in bheads and not
2912 2824 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2913 2825 repo.ui.status(_('created new head\n'))
2914 2826 # The message is not printed for initial roots. For the other
2915 2827 # changesets, it is printed in the following situations:
2916 2828 #
2917 2829 # Par column: for the 2 parents with ...
2918 2830 # N: null or no parent
2919 2831 # B: parent is on another named branch
2920 2832 # C: parent is a regular non head changeset
2921 2833 # H: parent was a branch head of the current branch
2922 2834 # Msg column: whether we print "created new head" message
2923 2835 # In the following, it is assumed that there already exists some
2924 2836 # initial branch heads of the current branch, otherwise nothing is
2925 2837 # printed anyway.
2926 2838 #
2927 2839 # Par Msg Comment
2928 2840 # N N y additional topo root
2929 2841 #
2930 2842 # B N y additional branch root
2931 2843 # C N y additional topo head
2932 2844 # H N n usual case
2933 2845 #
2934 2846 # B B y weird additional branch root
2935 2847 # C B y branch merge
2936 2848 # H B n merge with named branch
2937 2849 #
2938 2850 # C C y additional head from merge
2939 2851 # C H n merge with a head
2940 2852 #
2941 2853 # H H n head merge: head count decreases
2942 2854
2943 2855 if not opts.get('close_branch'):
2944 2856 for r in parents:
2945 2857 if r.closesbranch() and r.branch() == branch:
2946 2858 repo.ui.status(_('reopening closed branch head %d\n') % r)
2947 2859
2948 2860 if repo.ui.debugflag:
2949 2861 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2950 2862 elif repo.ui.verbose:
2951 2863 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2952 2864
2953 2865 def postcommitstatus(repo, pats, opts):
2954 2866 return repo.status(match=scmutil.match(repo[None], pats, opts))
2955 2867
2956 2868 def revert(ui, repo, ctx, parents, *pats, **opts):
2957 2869 parent, p2 = parents
2958 2870 node = ctx.node()
2959 2871
2960 2872 mf = ctx.manifest()
2961 2873 if node == p2:
2962 2874 parent = p2
2963 2875
2964 2876 # need all matching names in dirstate and manifest of target rev,
2965 2877 # so have to walk both. do not print errors if files exist in one
2966 2878 # but not other. in both cases, filesets should be evaluated against
2967 2879 # workingctx to get consistent result (issue4497). this means 'set:**'
2968 2880 # cannot be used to select missing files from target rev.
2969 2881
2970 2882 # `names` is a mapping for all elements in working copy and target revision
2971 2883 # The mapping is in the form:
2972 2884 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2973 2885 names = {}
2974 2886
2975 2887 with repo.wlock():
2976 2888 ## filling of the `names` mapping
2977 2889 # walk dirstate to fill `names`
2978 2890
2979 2891 interactive = opts.get('interactive', False)
2980 2892 wctx = repo[None]
2981 2893 m = scmutil.match(wctx, pats, opts)
2982 2894
2983 2895 # we'll need this later
2984 2896 targetsubs = sorted(s for s in wctx.substate if m(s))
2985 2897
2986 2898 if not m.always():
2987 2899 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2988 2900 names[abs] = m.rel(abs), m.exact(abs)
2989 2901
2990 2902 # walk target manifest to fill `names`
2991 2903
2992 2904 def badfn(path, msg):
2993 2905 if path in names:
2994 2906 return
2995 2907 if path in ctx.substate:
2996 2908 return
2997 2909 path_ = path + '/'
2998 2910 for f in names:
2999 2911 if f.startswith(path_):
3000 2912 return
3001 2913 ui.warn("%s: %s\n" % (m.rel(path), msg))
3002 2914
3003 2915 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3004 2916 if abs not in names:
3005 2917 names[abs] = m.rel(abs), m.exact(abs)
3006 2918
3007 2919 # Find status of all file in `names`.
3008 2920 m = scmutil.matchfiles(repo, names)
3009 2921
3010 2922 changes = repo.status(node1=node, match=m,
3011 2923 unknown=True, ignored=True, clean=True)
3012 2924 else:
3013 2925 changes = repo.status(node1=node, match=m)
3014 2926 for kind in changes:
3015 2927 for abs in kind:
3016 2928 names[abs] = m.rel(abs), m.exact(abs)
3017 2929
3018 2930 m = scmutil.matchfiles(repo, names)
3019 2931
3020 2932 modified = set(changes.modified)
3021 2933 added = set(changes.added)
3022 2934 removed = set(changes.removed)
3023 2935 _deleted = set(changes.deleted)
3024 2936 unknown = set(changes.unknown)
3025 2937 unknown.update(changes.ignored)
3026 2938 clean = set(changes.clean)
3027 2939 modadded = set()
3028 2940
3029 2941 # split between files known in target manifest and the others
3030 2942 smf = set(mf)
3031 2943
3032 2944 # determine the exact nature of the deleted changesets
3033 2945 deladded = _deleted - smf
3034 2946 deleted = _deleted - deladded
3035 2947
3036 2948 # We need to account for the state of the file in the dirstate,
3037 2949 # even when we revert against something else than parent. This will
3038 2950 # slightly alter the behavior of revert (doing back up or not, delete
3039 2951 # or just forget etc).
3040 2952 if parent == node:
3041 2953 dsmodified = modified
3042 2954 dsadded = added
3043 2955 dsremoved = removed
3044 2956 # store all local modifications, useful later for rename detection
3045 2957 localchanges = dsmodified | dsadded
3046 2958 modified, added, removed = set(), set(), set()
3047 2959 else:
3048 2960 changes = repo.status(node1=parent, match=m)
3049 2961 dsmodified = set(changes.modified)
3050 2962 dsadded = set(changes.added)
3051 2963 dsremoved = set(changes.removed)
3052 2964 # store all local modifications, useful later for rename detection
3053 2965 localchanges = dsmodified | dsadded
3054 2966
3055 2967 # only take into account for removes between wc and target
3056 2968 clean |= dsremoved - removed
3057 2969 dsremoved &= removed
3058 2970 # distinct between dirstate remove and other
3059 2971 removed -= dsremoved
3060 2972
3061 2973 modadded = added & dsmodified
3062 2974 added -= modadded
3063 2975
3064 2976 # tell newly modified apart.
3065 2977 dsmodified &= modified
3066 2978 dsmodified |= modified & dsadded # dirstate added may need backup
3067 2979 modified -= dsmodified
3068 2980
3069 2981 # We need to wait for some post-processing to update this set
3070 2982 # before making the distinction. The dirstate will be used for
3071 2983 # that purpose.
3072 2984 dsadded = added
3073 2985
3074 2986 # in case of merge, files that are actually added can be reported as
3075 2987 # modified, we need to post process the result
3076 2988 if p2 != nullid:
3077 2989 mergeadd = dsmodified - smf
3078 2990 dsadded |= mergeadd
3079 2991 dsmodified -= mergeadd
3080 2992
3081 2993 # if f is a rename, update `names` to also revert the source
3082 2994 cwd = repo.getcwd()
3083 2995 for f in localchanges:
3084 2996 src = repo.dirstate.copied(f)
3085 2997 # XXX should we check for rename down to target node?
3086 2998 if src and src not in names and repo.dirstate[src] == 'r':
3087 2999 dsremoved.add(src)
3088 3000 names[src] = (repo.pathto(src, cwd), True)
3089 3001
3090 3002 # distinguish between file to forget and the other
3091 3003 added = set()
3092 3004 for abs in dsadded:
3093 3005 if repo.dirstate[abs] != 'a':
3094 3006 added.add(abs)
3095 3007 dsadded -= added
3096 3008
3097 3009 for abs in deladded:
3098 3010 if repo.dirstate[abs] == 'a':
3099 3011 dsadded.add(abs)
3100 3012 deladded -= dsadded
3101 3013
3102 3014 # For files marked as removed, we check if an unknown file is present at
3103 3015 # the same path. If a such file exists it may need to be backed up.
3104 3016 # Making the distinction at this stage helps have simpler backup
3105 3017 # logic.
3106 3018 removunk = set()
3107 3019 for abs in removed:
3108 3020 target = repo.wjoin(abs)
3109 3021 if os.path.lexists(target):
3110 3022 removunk.add(abs)
3111 3023 removed -= removunk
3112 3024
3113 3025 dsremovunk = set()
3114 3026 for abs in dsremoved:
3115 3027 target = repo.wjoin(abs)
3116 3028 if os.path.lexists(target):
3117 3029 dsremovunk.add(abs)
3118 3030 dsremoved -= dsremovunk
3119 3031
3120 3032 # action to be actually performed by revert
3121 3033 # (<list of file>, message>) tuple
3122 3034 actions = {'revert': ([], _('reverting %s\n')),
3123 3035 'add': ([], _('adding %s\n')),
3124 3036 'remove': ([], _('removing %s\n')),
3125 3037 'drop': ([], _('removing %s\n')),
3126 3038 'forget': ([], _('forgetting %s\n')),
3127 3039 'undelete': ([], _('undeleting %s\n')),
3128 3040 'noop': (None, _('no changes needed to %s\n')),
3129 3041 'unknown': (None, _('file not managed: %s\n')),
3130 3042 }
3131 3043
3132 3044 # "constant" that convey the backup strategy.
3133 3045 # All set to `discard` if `no-backup` is set do avoid checking
3134 3046 # no_backup lower in the code.
3135 3047 # These values are ordered for comparison purposes
3136 3048 backupinteractive = 3 # do backup if interactively modified
3137 3049 backup = 2 # unconditionally do backup
3138 3050 check = 1 # check if the existing file differs from target
3139 3051 discard = 0 # never do backup
3140 3052 if opts.get('no_backup'):
3141 3053 backupinteractive = backup = check = discard
3142 3054 if interactive:
3143 3055 dsmodifiedbackup = backupinteractive
3144 3056 else:
3145 3057 dsmodifiedbackup = backup
3146 3058 tobackup = set()
3147 3059
3148 3060 backupanddel = actions['remove']
3149 3061 if not opts.get('no_backup'):
3150 3062 backupanddel = actions['drop']
3151 3063
3152 3064 disptable = (
3153 3065 # dispatch table:
3154 3066 # file state
3155 3067 # action
3156 3068 # make backup
3157 3069
3158 3070 ## Sets that results that will change file on disk
3159 3071 # Modified compared to target, no local change
3160 3072 (modified, actions['revert'], discard),
3161 3073 # Modified compared to target, but local file is deleted
3162 3074 (deleted, actions['revert'], discard),
3163 3075 # Modified compared to target, local change
3164 3076 (dsmodified, actions['revert'], dsmodifiedbackup),
3165 3077 # Added since target
3166 3078 (added, actions['remove'], discard),
3167 3079 # Added in working directory
3168 3080 (dsadded, actions['forget'], discard),
3169 3081 # Added since target, have local modification
3170 3082 (modadded, backupanddel, backup),
3171 3083 # Added since target but file is missing in working directory
3172 3084 (deladded, actions['drop'], discard),
3173 3085 # Removed since target, before working copy parent
3174 3086 (removed, actions['add'], discard),
3175 3087 # Same as `removed` but an unknown file exists at the same path
3176 3088 (removunk, actions['add'], check),
3177 3089 # Removed since targe, marked as such in working copy parent
3178 3090 (dsremoved, actions['undelete'], discard),
3179 3091 # Same as `dsremoved` but an unknown file exists at the same path
3180 3092 (dsremovunk, actions['undelete'], check),
3181 3093 ## the following sets does not result in any file changes
3182 3094 # File with no modification
3183 3095 (clean, actions['noop'], discard),
3184 3096 # Existing file, not tracked anywhere
3185 3097 (unknown, actions['unknown'], discard),
3186 3098 )
3187 3099
3188 3100 for abs, (rel, exact) in sorted(names.items()):
3189 3101 # target file to be touch on disk (relative to cwd)
3190 3102 target = repo.wjoin(abs)
3191 3103 # search the entry in the dispatch table.
3192 3104 # if the file is in any of these sets, it was touched in the working
3193 3105 # directory parent and we are sure it needs to be reverted.
3194 3106 for table, (xlist, msg), dobackup in disptable:
3195 3107 if abs not in table:
3196 3108 continue
3197 3109 if xlist is not None:
3198 3110 xlist.append(abs)
3199 3111 if dobackup:
3200 3112 # If in interactive mode, don't automatically create
3201 3113 # .orig files (issue4793)
3202 3114 if dobackup == backupinteractive:
3203 3115 tobackup.add(abs)
3204 3116 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3205 3117 bakname = scmutil.origpath(ui, repo, rel)
3206 3118 ui.note(_('saving current version of %s as %s\n') %
3207 3119 (rel, bakname))
3208 3120 if not opts.get('dry_run'):
3209 3121 if interactive:
3210 3122 util.copyfile(target, bakname)
3211 3123 else:
3212 3124 util.rename(target, bakname)
3213 3125 if ui.verbose or not exact:
3214 3126 if not isinstance(msg, basestring):
3215 3127 msg = msg(abs)
3216 3128 ui.status(msg % rel)
3217 3129 elif exact:
3218 3130 ui.warn(msg % rel)
3219 3131 break
3220 3132
3221 3133 if not opts.get('dry_run'):
3222 3134 needdata = ('revert', 'add', 'undelete')
3223 3135 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3224 3136 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3225 3137
3226 3138 if targetsubs:
3227 3139 # Revert the subrepos on the revert list
3228 3140 for sub in targetsubs:
3229 3141 try:
3230 3142 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3231 3143 except KeyError:
3232 3144 raise error.Abort("subrepository '%s' does not exist in %s!"
3233 3145 % (sub, short(ctx.node())))
3234 3146
3235 3147 def _revertprefetch(repo, ctx, *files):
3236 3148 """Let extension changing the storage layer prefetch content"""
3237 3149 pass
3238 3150
3239 3151 def _performrevert(repo, parents, ctx, actions, interactive=False,
3240 3152 tobackup=None):
3241 3153 """function that actually perform all the actions computed for revert
3242 3154
3243 3155 This is an independent function to let extension to plug in and react to
3244 3156 the imminent revert.
3245 3157
3246 3158 Make sure you have the working directory locked when calling this function.
3247 3159 """
3248 3160 parent, p2 = parents
3249 3161 node = ctx.node()
3250 3162 excluded_files = []
3251 3163 matcher_opts = {"exclude": excluded_files}
3252 3164
3253 3165 def checkout(f):
3254 3166 fc = ctx[f]
3255 3167 repo.wwrite(f, fc.data(), fc.flags())
3256 3168
3257 3169 audit_path = pathutil.pathauditor(repo.root)
3258 3170 for f in actions['forget'][0]:
3259 3171 if interactive:
3260 3172 choice = \
3261 3173 repo.ui.promptchoice(
3262 3174 _("forget added file %s (yn)?$$ &Yes $$ &No")
3263 3175 % f)
3264 3176 if choice == 0:
3265 3177 repo.dirstate.drop(f)
3266 3178 else:
3267 3179 excluded_files.append(repo.wjoin(f))
3268 3180 else:
3269 3181 repo.dirstate.drop(f)
3270 3182 for f in actions['remove'][0]:
3271 3183 audit_path(f)
3272 3184 try:
3273 3185 util.unlinkpath(repo.wjoin(f))
3274 3186 except OSError:
3275 3187 pass
3276 3188 repo.dirstate.remove(f)
3277 3189 for f in actions['drop'][0]:
3278 3190 audit_path(f)
3279 3191 repo.dirstate.remove(f)
3280 3192
3281 3193 normal = None
3282 3194 if node == parent:
3283 3195 # We're reverting to our parent. If possible, we'd like status
3284 3196 # to report the file as clean. We have to use normallookup for
3285 3197 # merges to avoid losing information about merged/dirty files.
3286 3198 if p2 != nullid:
3287 3199 normal = repo.dirstate.normallookup
3288 3200 else:
3289 3201 normal = repo.dirstate.normal
3290 3202
3291 3203 newlyaddedandmodifiedfiles = set()
3292 3204 if interactive:
3293 3205 # Prompt the user for changes to revert
3294 3206 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3295 3207 m = scmutil.match(ctx, torevert, matcher_opts)
3296 3208 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3297 3209 diffopts.nodates = True
3298 3210 diffopts.git = True
3299 3211 reversehunks = repo.ui.configbool('experimental',
3300 3212 'revertalternateinteractivemode',
3301 3213 True)
3302 3214 if reversehunks:
3303 3215 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3304 3216 else:
3305 3217 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3306 3218 originalchunks = patch.parsepatch(diff)
3307 3219 operation = 'discard' if node == parent else 'revert'
3308 3220
3309 3221 try:
3310 3222
3311 3223 chunks, opts = recordfilter(repo.ui, originalchunks,
3312 3224 operation=operation)
3313 3225 if reversehunks:
3314 3226 chunks = patch.reversehunks(chunks)
3315 3227
3316 3228 except patch.PatchError as err:
3317 3229 raise error.Abort(_('error parsing patch: %s') % err)
3318 3230
3319 3231 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3320 3232 if tobackup is None:
3321 3233 tobackup = set()
3322 3234 # Apply changes
3323 3235 fp = stringio()
3324 3236 for c in chunks:
3325 3237 # Create a backup file only if this hunk should be backed up
3326 3238 if ishunk(c) and c.header.filename() in tobackup:
3327 3239 abs = c.header.filename()
3328 3240 target = repo.wjoin(abs)
3329 3241 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3330 3242 util.copyfile(target, bakname)
3331 3243 tobackup.remove(abs)
3332 3244 c.write(fp)
3333 3245 dopatch = fp.tell()
3334 3246 fp.seek(0)
3335 3247 if dopatch:
3336 3248 try:
3337 3249 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3338 3250 except patch.PatchError as err:
3339 3251 raise error.Abort(str(err))
3340 3252 del fp
3341 3253 else:
3342 3254 for f in actions['revert'][0]:
3343 3255 checkout(f)
3344 3256 if normal:
3345 3257 normal(f)
3346 3258
3347 3259 for f in actions['add'][0]:
3348 3260 # Don't checkout modified files, they are already created by the diff
3349 3261 if f not in newlyaddedandmodifiedfiles:
3350 3262 checkout(f)
3351 3263 repo.dirstate.add(f)
3352 3264
3353 3265 normal = repo.dirstate.normallookup
3354 3266 if node == parent and p2 == nullid:
3355 3267 normal = repo.dirstate.normal
3356 3268 for f in actions['undelete'][0]:
3357 3269 checkout(f)
3358 3270 normal(f)
3359 3271
3360 3272 copied = copies.pathcopies(repo[parent], ctx)
3361 3273
3362 3274 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3363 3275 if f in copied:
3364 3276 repo.dirstate.copy(copied[f], f)
3365 3277
3366 3278 def command(table):
3367 3279 """Returns a function object to be used as a decorator for making commands.
3368 3280
3369 3281 This function receives a command table as its argument. The table should
3370 3282 be a dict.
3371 3283
3372 3284 The returned function can be used as a decorator for adding commands
3373 3285 to that command table. This function accepts multiple arguments to define
3374 3286 a command.
3375 3287
3376 3288 The first argument is the command name.
3377 3289
3378 3290 The options argument is an iterable of tuples defining command arguments.
3379 3291 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3380 3292
3381 3293 The synopsis argument defines a short, one line summary of how to use the
3382 3294 command. This shows up in the help output.
3383 3295
3384 3296 The norepo argument defines whether the command does not require a
3385 3297 local repository. Most commands operate against a repository, thus the
3386 3298 default is False.
3387 3299
3388 3300 The optionalrepo argument defines whether the command optionally requires
3389 3301 a local repository.
3390 3302
3391 3303 The inferrepo argument defines whether to try to find a repository from the
3392 3304 command line arguments. If True, arguments will be examined for potential
3393 3305 repository locations. See ``findrepo()``. If a repository is found, it
3394 3306 will be used.
3395 3307 """
3396 3308 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3397 3309 inferrepo=False):
3398 3310 def decorator(func):
3399 3311 func.norepo = norepo
3400 3312 func.optionalrepo = optionalrepo
3401 3313 func.inferrepo = inferrepo
3402 3314 if synopsis:
3403 3315 table[name] = func, list(options), synopsis
3404 3316 else:
3405 3317 table[name] = func, list(options)
3406 3318 return func
3407 3319 return decorator
3408 3320
3409 3321 return cmd
3410 3322
3411 3323 def checkunresolved(ms):
3412 3324 ms._repo.ui.deprecwarn('checkunresolved moved from cmdutil to mergeutil',
3413 3325 '4.1')
3414 3326 return mergeutil.checkunresolved(ms)
3415 3327
3416 3328 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3417 3329 # commands.outgoing. "missing" is "missing" of the result of
3418 3330 # "findcommonoutgoing()"
3419 3331 outgoinghooks = util.hooks()
3420 3332
3421 3333 # a list of (ui, repo) functions called by commands.summary
3422 3334 summaryhooks = util.hooks()
3423 3335
3424 3336 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3425 3337 #
3426 3338 # functions should return tuple of booleans below, if 'changes' is None:
3427 3339 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3428 3340 #
3429 3341 # otherwise, 'changes' is a tuple of tuples below:
3430 3342 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3431 3343 # - (desturl, destbranch, destpeer, outgoing)
3432 3344 summaryremotehooks = util.hooks()
3433 3345
3434 3346 # A list of state files kept by multistep operations like graft.
3435 3347 # Since graft cannot be aborted, it is considered 'clearable' by update.
3436 3348 # note: bisect is intentionally excluded
3437 3349 # (state file, clearable, allowcommit, error, hint)
3438 3350 unfinishedstates = [
3439 3351 ('graftstate', True, False, _('graft in progress'),
3440 3352 _("use 'hg graft --continue' or 'hg update' to abort")),
3441 3353 ('updatestate', True, False, _('last update was interrupted'),
3442 3354 _("use 'hg update' to get a consistent checkout"))
3443 3355 ]
3444 3356
3445 3357 def checkunfinished(repo, commit=False):
3446 3358 '''Look for an unfinished multistep operation, like graft, and abort
3447 3359 if found. It's probably good to check this right before
3448 3360 bailifchanged().
3449 3361 '''
3450 3362 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3451 3363 if commit and allowcommit:
3452 3364 continue
3453 3365 if repo.vfs.exists(f):
3454 3366 raise error.Abort(msg, hint=hint)
3455 3367
3456 3368 def clearunfinished(repo):
3457 3369 '''Check for unfinished operations (as above), and clear the ones
3458 3370 that are clearable.
3459 3371 '''
3460 3372 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3461 3373 if not clearable and repo.vfs.exists(f):
3462 3374 raise error.Abort(msg, hint=hint)
3463 3375 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3464 3376 if clearable and repo.vfs.exists(f):
3465 3377 util.unlink(repo.join(f))
3466 3378
3467 3379 afterresolvedstates = [
3468 3380 ('graftstate',
3469 3381 _('hg graft --continue')),
3470 3382 ]
3471 3383
3472 3384 def howtocontinue(repo):
3473 3385 '''Check for an unfinished operation and return the command to finish
3474 3386 it.
3475 3387
3476 3388 afterresolvedstates tuples define a .hg/{file} and the corresponding
3477 3389 command needed to finish it.
3478 3390
3479 3391 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3480 3392 a boolean.
3481 3393 '''
3482 3394 contmsg = _("continue: %s")
3483 3395 for f, msg in afterresolvedstates:
3484 3396 if repo.vfs.exists(f):
3485 3397 return contmsg % msg, True
3486 3398 workingctx = repo[None]
3487 3399 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3488 3400 for s in workingctx.substate)
3489 3401 if dirty:
3490 3402 return contmsg % _("hg commit"), False
3491 3403 return None, None
3492 3404
3493 3405 def checkafterresolved(repo):
3494 3406 '''Inform the user about the next action after completing hg resolve
3495 3407
3496 3408 If there's a matching afterresolvedstates, howtocontinue will yield
3497 3409 repo.ui.warn as the reporter.
3498 3410
3499 3411 Otherwise, it will yield repo.ui.note.
3500 3412 '''
3501 3413 msg, warning = howtocontinue(repo)
3502 3414 if msg is not None:
3503 3415 if warning:
3504 3416 repo.ui.warn("%s\n" % msg)
3505 3417 else:
3506 3418 repo.ui.note("%s\n" % msg)
3507 3419
3508 3420 def wrongtooltocontinue(repo, task):
3509 3421 '''Raise an abort suggesting how to properly continue if there is an
3510 3422 active task.
3511 3423
3512 3424 Uses howtocontinue() to find the active task.
3513 3425
3514 3426 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3515 3427 a hint.
3516 3428 '''
3517 3429 after = howtocontinue(repo)
3518 3430 hint = None
3519 3431 if after[1]:
3520 3432 hint = after[0]
3521 3433 raise error.Abort(_('no %s in progress') % task, hint=hint)
3522 3434
3523 3435 class dirstateguard(dirstateguardmod.dirstateguard):
3524 3436 def __init__(self, repo, name):
3525 3437 dirstateguardmod.dirstateguard.__init__(self, repo, name)
3526 3438 repo.ui.deprecwarn(
3527 3439 'dirstateguard has moved from cmdutil to dirstateguard',
3528 3440 '4.1')
@@ -1,7092 +1,7093 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 __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import operator
13 13 import os
14 14 import random
15 15 import re
16 16 import shlex
17 17 import socket
18 18 import string
19 19 import sys
20 20 import tempfile
21 21 import time
22 22
23 23 from .i18n import _
24 24 from .node import (
25 25 bin,
26 26 hex,
27 27 nullhex,
28 28 nullid,
29 29 nullrev,
30 30 short,
31 31 )
32 32 from . import (
33 33 archival,
34 34 bookmarks,
35 35 bundle2,
36 36 changegroup,
37 37 cmdutil,
38 38 commandserver,
39 39 copies,
40 40 dagparser,
41 41 dagutil,
42 42 destutil,
43 43 dirstateguard,
44 44 discovery,
45 45 encoding,
46 46 error,
47 47 exchange,
48 48 extensions,
49 49 fileset,
50 50 formatter,
51 51 graphmod,
52 52 hbisect,
53 53 help,
54 54 hg,
55 55 hgweb,
56 56 localrepo,
57 57 lock as lockmod,
58 58 merge as mergemod,
59 59 minirst,
60 60 obsolete,
61 61 patch,
62 62 phases,
63 63 policy,
64 64 pvec,
65 65 pycompat,
66 66 repair,
67 67 revlog,
68 68 revset,
69 69 scmutil,
70 server,
70 71 setdiscovery,
71 72 sshserver,
72 73 sslutil,
73 74 streamclone,
74 75 templatekw,
75 76 templater,
76 77 treediscovery,
77 78 ui as uimod,
78 79 util,
79 80 )
80 81
81 82 release = lockmod.release
82 83
83 84 table = {}
84 85
85 86 command = cmdutil.command(table)
86 87
87 88 # label constants
88 89 # until 3.5, bookmarks.current was the advertised name, not
89 90 # bookmarks.active, so we must use both to avoid breaking old
90 91 # custom styles
91 92 activebookmarklabel = 'bookmarks.active bookmarks.current'
92 93
93 94 # common command options
94 95
95 96 globalopts = [
96 97 ('R', 'repository', '',
97 98 _('repository root directory or name of overlay bundle file'),
98 99 _('REPO')),
99 100 ('', 'cwd', '',
100 101 _('change working directory'), _('DIR')),
101 102 ('y', 'noninteractive', None,
102 103 _('do not prompt, automatically pick the first choice for all prompts')),
103 104 ('q', 'quiet', None, _('suppress output')),
104 105 ('v', 'verbose', None, _('enable additional output')),
105 106 ('', 'config', [],
106 107 _('set/override config option (use \'section.name=value\')'),
107 108 _('CONFIG')),
108 109 ('', 'debug', None, _('enable debugging output')),
109 110 ('', 'debugger', None, _('start debugger')),
110 111 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
111 112 _('ENCODE')),
112 113 ('', 'encodingmode', encoding.encodingmode,
113 114 _('set the charset encoding mode'), _('MODE')),
114 115 ('', 'traceback', None, _('always print a traceback on exception')),
115 116 ('', 'time', None, _('time how long the command takes')),
116 117 ('', 'profile', None, _('print command execution profile')),
117 118 ('', 'version', None, _('output version information and exit')),
118 119 ('h', 'help', None, _('display help and exit')),
119 120 ('', 'hidden', False, _('consider hidden changesets')),
120 121 ]
121 122
122 123 dryrunopts = [('n', 'dry-run', None,
123 124 _('do not perform actions, just print output'))]
124 125
125 126 remoteopts = [
126 127 ('e', 'ssh', '',
127 128 _('specify ssh command to use'), _('CMD')),
128 129 ('', 'remotecmd', '',
129 130 _('specify hg command to run on the remote side'), _('CMD')),
130 131 ('', 'insecure', None,
131 132 _('do not verify server certificate (ignoring web.cacerts config)')),
132 133 ]
133 134
134 135 walkopts = [
135 136 ('I', 'include', [],
136 137 _('include names matching the given patterns'), _('PATTERN')),
137 138 ('X', 'exclude', [],
138 139 _('exclude names matching the given patterns'), _('PATTERN')),
139 140 ]
140 141
141 142 commitopts = [
142 143 ('m', 'message', '',
143 144 _('use text as commit message'), _('TEXT')),
144 145 ('l', 'logfile', '',
145 146 _('read commit message from file'), _('FILE')),
146 147 ]
147 148
148 149 commitopts2 = [
149 150 ('d', 'date', '',
150 151 _('record the specified date as commit date'), _('DATE')),
151 152 ('u', 'user', '',
152 153 _('record the specified user as committer'), _('USER')),
153 154 ]
154 155
155 156 # hidden for now
156 157 formatteropts = [
157 158 ('T', 'template', '',
158 159 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
159 160 ]
160 161
161 162 templateopts = [
162 163 ('', 'style', '',
163 164 _('display using template map file (DEPRECATED)'), _('STYLE')),
164 165 ('T', 'template', '',
165 166 _('display with template'), _('TEMPLATE')),
166 167 ]
167 168
168 169 logopts = [
169 170 ('p', 'patch', None, _('show patch')),
170 171 ('g', 'git', None, _('use git extended diff format')),
171 172 ('l', 'limit', '',
172 173 _('limit number of changes displayed'), _('NUM')),
173 174 ('M', 'no-merges', None, _('do not show merges')),
174 175 ('', 'stat', None, _('output diffstat-style summary of changes')),
175 176 ('G', 'graph', None, _("show the revision DAG")),
176 177 ] + templateopts
177 178
178 179 diffopts = [
179 180 ('a', 'text', None, _('treat all files as text')),
180 181 ('g', 'git', None, _('use git extended diff format')),
181 182 ('', 'nodates', None, _('omit dates from diff headers'))
182 183 ]
183 184
184 185 diffwsopts = [
185 186 ('w', 'ignore-all-space', None,
186 187 _('ignore white space when comparing lines')),
187 188 ('b', 'ignore-space-change', None,
188 189 _('ignore changes in the amount of white space')),
189 190 ('B', 'ignore-blank-lines', None,
190 191 _('ignore changes whose lines are all blank')),
191 192 ]
192 193
193 194 diffopts2 = [
194 195 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
195 196 ('p', 'show-function', None, _('show which function each change is in')),
196 197 ('', 'reverse', None, _('produce a diff that undoes the changes')),
197 198 ] + diffwsopts + [
198 199 ('U', 'unified', '',
199 200 _('number of lines of context to show'), _('NUM')),
200 201 ('', 'stat', None, _('output diffstat-style summary of changes')),
201 202 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
202 203 ]
203 204
204 205 mergetoolopts = [
205 206 ('t', 'tool', '', _('specify merge tool')),
206 207 ]
207 208
208 209 similarityopts = [
209 210 ('s', 'similarity', '',
210 211 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
211 212 ]
212 213
213 214 subrepoopts = [
214 215 ('S', 'subrepos', None,
215 216 _('recurse into subrepositories'))
216 217 ]
217 218
218 219 debugrevlogopts = [
219 220 ('c', 'changelog', False, _('open changelog')),
220 221 ('m', 'manifest', False, _('open manifest')),
221 222 ('', 'dir', '', _('open directory manifest')),
222 223 ]
223 224
224 225 # Commands start here, listed alphabetically
225 226
226 227 @command('^add',
227 228 walkopts + subrepoopts + dryrunopts,
228 229 _('[OPTION]... [FILE]...'),
229 230 inferrepo=True)
230 231 def add(ui, repo, *pats, **opts):
231 232 """add the specified files on the next commit
232 233
233 234 Schedule files to be version controlled and added to the
234 235 repository.
235 236
236 237 The files will be added to the repository at the next commit. To
237 238 undo an add before that, see :hg:`forget`.
238 239
239 240 If no names are given, add all files to the repository (except
240 241 files matching ``.hgignore``).
241 242
242 243 .. container:: verbose
243 244
244 245 Examples:
245 246
246 247 - New (unknown) files are added
247 248 automatically by :hg:`add`::
248 249
249 250 $ ls
250 251 foo.c
251 252 $ hg status
252 253 ? foo.c
253 254 $ hg add
254 255 adding foo.c
255 256 $ hg status
256 257 A foo.c
257 258
258 259 - Specific files to be added can be specified::
259 260
260 261 $ ls
261 262 bar.c foo.c
262 263 $ hg status
263 264 ? bar.c
264 265 ? foo.c
265 266 $ hg add bar.c
266 267 $ hg status
267 268 A bar.c
268 269 ? foo.c
269 270
270 271 Returns 0 if all files are successfully added.
271 272 """
272 273
273 274 m = scmutil.match(repo[None], pats, opts)
274 275 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
275 276 return rejected and 1 or 0
276 277
277 278 @command('addremove',
278 279 similarityopts + subrepoopts + walkopts + dryrunopts,
279 280 _('[OPTION]... [FILE]...'),
280 281 inferrepo=True)
281 282 def addremove(ui, repo, *pats, **opts):
282 283 """add all new files, delete all missing files
283 284
284 285 Add all new files and remove all missing files from the
285 286 repository.
286 287
287 288 Unless names are given, new files are ignored if they match any of
288 289 the patterns in ``.hgignore``. As with add, these changes take
289 290 effect at the next commit.
290 291
291 292 Use the -s/--similarity option to detect renamed files. This
292 293 option takes a percentage between 0 (disabled) and 100 (files must
293 294 be identical) as its parameter. With a parameter greater than 0,
294 295 this compares every removed file with every added file and records
295 296 those similar enough as renames. Detecting renamed files this way
296 297 can be expensive. After using this option, :hg:`status -C` can be
297 298 used to check which files were identified as moved or renamed. If
298 299 not specified, -s/--similarity defaults to 100 and only renames of
299 300 identical files are detected.
300 301
301 302 .. container:: verbose
302 303
303 304 Examples:
304 305
305 306 - A number of files (bar.c and foo.c) are new,
306 307 while foobar.c has been removed (without using :hg:`remove`)
307 308 from the repository::
308 309
309 310 $ ls
310 311 bar.c foo.c
311 312 $ hg status
312 313 ! foobar.c
313 314 ? bar.c
314 315 ? foo.c
315 316 $ hg addremove
316 317 adding bar.c
317 318 adding foo.c
318 319 removing foobar.c
319 320 $ hg status
320 321 A bar.c
321 322 A foo.c
322 323 R foobar.c
323 324
324 325 - A file foobar.c was moved to foo.c without using :hg:`rename`.
325 326 Afterwards, it was edited slightly::
326 327
327 328 $ ls
328 329 foo.c
329 330 $ hg status
330 331 ! foobar.c
331 332 ? foo.c
332 333 $ hg addremove --similarity 90
333 334 removing foobar.c
334 335 adding foo.c
335 336 recording removal of foobar.c as rename to foo.c (94% similar)
336 337 $ hg status -C
337 338 A foo.c
338 339 foobar.c
339 340 R foobar.c
340 341
341 342 Returns 0 if all files are successfully added.
342 343 """
343 344 try:
344 345 sim = float(opts.get('similarity') or 100)
345 346 except ValueError:
346 347 raise error.Abort(_('similarity must be a number'))
347 348 if sim < 0 or sim > 100:
348 349 raise error.Abort(_('similarity must be between 0 and 100'))
349 350 matcher = scmutil.match(repo[None], pats, opts)
350 351 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
351 352
352 353 @command('^annotate|blame',
353 354 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
354 355 ('', 'follow', None,
355 356 _('follow copies/renames and list the filename (DEPRECATED)')),
356 357 ('', 'no-follow', None, _("don't follow copies and renames")),
357 358 ('a', 'text', None, _('treat all files as text')),
358 359 ('u', 'user', None, _('list the author (long with -v)')),
359 360 ('f', 'file', None, _('list the filename')),
360 361 ('d', 'date', None, _('list the date (short with -q)')),
361 362 ('n', 'number', None, _('list the revision number (default)')),
362 363 ('c', 'changeset', None, _('list the changeset')),
363 364 ('l', 'line-number', None, _('show line number at the first appearance'))
364 365 ] + diffwsopts + walkopts + formatteropts,
365 366 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
366 367 inferrepo=True)
367 368 def annotate(ui, repo, *pats, **opts):
368 369 """show changeset information by line for each file
369 370
370 371 List changes in files, showing the revision id responsible for
371 372 each line.
372 373
373 374 This command is useful for discovering when a change was made and
374 375 by whom.
375 376
376 377 If you include --file, --user, or --date, the revision number is
377 378 suppressed unless you also include --number.
378 379
379 380 Without the -a/--text option, annotate will avoid processing files
380 381 it detects as binary. With -a, annotate will annotate the file
381 382 anyway, although the results will probably be neither useful
382 383 nor desirable.
383 384
384 385 Returns 0 on success.
385 386 """
386 387 if not pats:
387 388 raise error.Abort(_('at least one filename or pattern is required'))
388 389
389 390 if opts.get('follow'):
390 391 # --follow is deprecated and now just an alias for -f/--file
391 392 # to mimic the behavior of Mercurial before version 1.5
392 393 opts['file'] = True
393 394
394 395 ctx = scmutil.revsingle(repo, opts.get('rev'))
395 396
396 397 fm = ui.formatter('annotate', opts)
397 398 if ui.quiet:
398 399 datefunc = util.shortdate
399 400 else:
400 401 datefunc = util.datestr
401 402 if ctx.rev() is None:
402 403 def hexfn(node):
403 404 if node is None:
404 405 return None
405 406 else:
406 407 return fm.hexfunc(node)
407 408 if opts.get('changeset'):
408 409 # omit "+" suffix which is appended to node hex
409 410 def formatrev(rev):
410 411 if rev is None:
411 412 return '%d' % ctx.p1().rev()
412 413 else:
413 414 return '%d' % rev
414 415 else:
415 416 def formatrev(rev):
416 417 if rev is None:
417 418 return '%d+' % ctx.p1().rev()
418 419 else:
419 420 return '%d ' % rev
420 421 def formathex(hex):
421 422 if hex is None:
422 423 return '%s+' % fm.hexfunc(ctx.p1().node())
423 424 else:
424 425 return '%s ' % hex
425 426 else:
426 427 hexfn = fm.hexfunc
427 428 formatrev = formathex = str
428 429
429 430 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
430 431 ('number', ' ', lambda x: x[0].rev(), formatrev),
431 432 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
432 433 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
433 434 ('file', ' ', lambda x: x[0].path(), str),
434 435 ('line_number', ':', lambda x: x[1], str),
435 436 ]
436 437 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
437 438
438 439 if (not opts.get('user') and not opts.get('changeset')
439 440 and not opts.get('date') and not opts.get('file')):
440 441 opts['number'] = True
441 442
442 443 linenumber = opts.get('line_number') is not None
443 444 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
444 445 raise error.Abort(_('at least one of -n/-c is required for -l'))
445 446
446 447 if fm.isplain():
447 448 def makefunc(get, fmt):
448 449 return lambda x: fmt(get(x))
449 450 else:
450 451 def makefunc(get, fmt):
451 452 return get
452 453 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
453 454 if opts.get(op)]
454 455 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
455 456 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
456 457 if opts.get(op))
457 458
458 459 def bad(x, y):
459 460 raise error.Abort("%s: %s" % (x, y))
460 461
461 462 m = scmutil.match(ctx, pats, opts, badfn=bad)
462 463
463 464 follow = not opts.get('no_follow')
464 465 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
465 466 whitespace=True)
466 467 for abs in ctx.walk(m):
467 468 fctx = ctx[abs]
468 469 if not opts.get('text') and util.binary(fctx.data()):
469 470 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
470 471 continue
471 472
472 473 lines = fctx.annotate(follow=follow, linenumber=linenumber,
473 474 diffopts=diffopts)
474 475 if not lines:
475 476 continue
476 477 formats = []
477 478 pieces = []
478 479
479 480 for f, sep in funcmap:
480 481 l = [f(n) for n, dummy in lines]
481 482 if fm.isplain():
482 483 sizes = [encoding.colwidth(x) for x in l]
483 484 ml = max(sizes)
484 485 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
485 486 else:
486 487 formats.append(['%s' for x in l])
487 488 pieces.append(l)
488 489
489 490 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
490 491 fm.startitem()
491 492 fm.write(fields, "".join(f), *p)
492 493 fm.write('line', ": %s", l[1])
493 494
494 495 if not lines[-1][1].endswith('\n'):
495 496 fm.plain('\n')
496 497
497 498 fm.end()
498 499
499 500 @command('archive',
500 501 [('', 'no-decode', None, _('do not pass files through decoders')),
501 502 ('p', 'prefix', '', _('directory prefix for files in archive'),
502 503 _('PREFIX')),
503 504 ('r', 'rev', '', _('revision to distribute'), _('REV')),
504 505 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
505 506 ] + subrepoopts + walkopts,
506 507 _('[OPTION]... DEST'))
507 508 def archive(ui, repo, dest, **opts):
508 509 '''create an unversioned archive of a repository revision
509 510
510 511 By default, the revision used is the parent of the working
511 512 directory; use -r/--rev to specify a different revision.
512 513
513 514 The archive type is automatically detected based on file
514 515 extension (to override, use -t/--type).
515 516
516 517 .. container:: verbose
517 518
518 519 Examples:
519 520
520 521 - create a zip file containing the 1.0 release::
521 522
522 523 hg archive -r 1.0 project-1.0.zip
523 524
524 525 - create a tarball excluding .hg files::
525 526
526 527 hg archive project.tar.gz -X ".hg*"
527 528
528 529 Valid types are:
529 530
530 531 :``files``: a directory full of files (default)
531 532 :``tar``: tar archive, uncompressed
532 533 :``tbz2``: tar archive, compressed using bzip2
533 534 :``tgz``: tar archive, compressed using gzip
534 535 :``uzip``: zip archive, uncompressed
535 536 :``zip``: zip archive, compressed using deflate
536 537
537 538 The exact name of the destination archive or directory is given
538 539 using a format string; see :hg:`help export` for details.
539 540
540 541 Each member added to an archive file has a directory prefix
541 542 prepended. Use -p/--prefix to specify a format string for the
542 543 prefix. The default is the basename of the archive, with suffixes
543 544 removed.
544 545
545 546 Returns 0 on success.
546 547 '''
547 548
548 549 ctx = scmutil.revsingle(repo, opts.get('rev'))
549 550 if not ctx:
550 551 raise error.Abort(_('no working directory: please specify a revision'))
551 552 node = ctx.node()
552 553 dest = cmdutil.makefilename(repo, dest, node)
553 554 if os.path.realpath(dest) == repo.root:
554 555 raise error.Abort(_('repository root cannot be destination'))
555 556
556 557 kind = opts.get('type') or archival.guesskind(dest) or 'files'
557 558 prefix = opts.get('prefix')
558 559
559 560 if dest == '-':
560 561 if kind == 'files':
561 562 raise error.Abort(_('cannot archive plain files to stdout'))
562 563 dest = cmdutil.makefileobj(repo, dest)
563 564 if not prefix:
564 565 prefix = os.path.basename(repo.root) + '-%h'
565 566
566 567 prefix = cmdutil.makefilename(repo, prefix, node)
567 568 matchfn = scmutil.match(ctx, [], opts)
568 569 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
569 570 matchfn, prefix, subrepos=opts.get('subrepos'))
570 571
571 572 @command('backout',
572 573 [('', 'merge', None, _('merge with old dirstate parent after backout')),
573 574 ('', 'commit', None,
574 575 _('commit if no conflicts were encountered (DEPRECATED)')),
575 576 ('', 'no-commit', None, _('do not commit')),
576 577 ('', 'parent', '',
577 578 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
578 579 ('r', 'rev', '', _('revision to backout'), _('REV')),
579 580 ('e', 'edit', False, _('invoke editor on commit messages')),
580 581 ] + mergetoolopts + walkopts + commitopts + commitopts2,
581 582 _('[OPTION]... [-r] REV'))
582 583 def backout(ui, repo, node=None, rev=None, **opts):
583 584 '''reverse effect of earlier changeset
584 585
585 586 Prepare a new changeset with the effect of REV undone in the
586 587 current working directory. If no conflicts were encountered,
587 588 it will be committed immediately.
588 589
589 590 If REV is the parent of the working directory, then this new changeset
590 591 is committed automatically (unless --no-commit is specified).
591 592
592 593 .. note::
593 594
594 595 :hg:`backout` cannot be used to fix either an unwanted or
595 596 incorrect merge.
596 597
597 598 .. container:: verbose
598 599
599 600 Examples:
600 601
601 602 - Reverse the effect of the parent of the working directory.
602 603 This backout will be committed immediately::
603 604
604 605 hg backout -r .
605 606
606 607 - Reverse the effect of previous bad revision 23::
607 608
608 609 hg backout -r 23
609 610
610 611 - Reverse the effect of previous bad revision 23 and
611 612 leave changes uncommitted::
612 613
613 614 hg backout -r 23 --no-commit
614 615 hg commit -m "Backout revision 23"
615 616
616 617 By default, the pending changeset will have one parent,
617 618 maintaining a linear history. With --merge, the pending
618 619 changeset will instead have two parents: the old parent of the
619 620 working directory and a new child of REV that simply undoes REV.
620 621
621 622 Before version 1.7, the behavior without --merge was equivalent
622 623 to specifying --merge followed by :hg:`update --clean .` to
623 624 cancel the merge and leave the child of REV as a head to be
624 625 merged separately.
625 626
626 627 See :hg:`help dates` for a list of formats valid for -d/--date.
627 628
628 629 See :hg:`help revert` for a way to restore files to the state
629 630 of another revision.
630 631
631 632 Returns 0 on success, 1 if nothing to backout or there are unresolved
632 633 files.
633 634 '''
634 635 wlock = lock = None
635 636 try:
636 637 wlock = repo.wlock()
637 638 lock = repo.lock()
638 639 return _dobackout(ui, repo, node, rev, **opts)
639 640 finally:
640 641 release(lock, wlock)
641 642
642 643 def _dobackout(ui, repo, node=None, rev=None, **opts):
643 644 if opts.get('commit') and opts.get('no_commit'):
644 645 raise error.Abort(_("cannot use --commit with --no-commit"))
645 646 if opts.get('merge') and opts.get('no_commit'):
646 647 raise error.Abort(_("cannot use --merge with --no-commit"))
647 648
648 649 if rev and node:
649 650 raise error.Abort(_("please specify just one revision"))
650 651
651 652 if not rev:
652 653 rev = node
653 654
654 655 if not rev:
655 656 raise error.Abort(_("please specify a revision to backout"))
656 657
657 658 date = opts.get('date')
658 659 if date:
659 660 opts['date'] = util.parsedate(date)
660 661
661 662 cmdutil.checkunfinished(repo)
662 663 cmdutil.bailifchanged(repo)
663 664 node = scmutil.revsingle(repo, rev).node()
664 665
665 666 op1, op2 = repo.dirstate.parents()
666 667 if not repo.changelog.isancestor(node, op1):
667 668 raise error.Abort(_('cannot backout change that is not an ancestor'))
668 669
669 670 p1, p2 = repo.changelog.parents(node)
670 671 if p1 == nullid:
671 672 raise error.Abort(_('cannot backout a change with no parents'))
672 673 if p2 != nullid:
673 674 if not opts.get('parent'):
674 675 raise error.Abort(_('cannot backout a merge changeset'))
675 676 p = repo.lookup(opts['parent'])
676 677 if p not in (p1, p2):
677 678 raise error.Abort(_('%s is not a parent of %s') %
678 679 (short(p), short(node)))
679 680 parent = p
680 681 else:
681 682 if opts.get('parent'):
682 683 raise error.Abort(_('cannot use --parent on non-merge changeset'))
683 684 parent = p1
684 685
685 686 # the backout should appear on the same branch
686 687 branch = repo.dirstate.branch()
687 688 bheads = repo.branchheads(branch)
688 689 rctx = scmutil.revsingle(repo, hex(parent))
689 690 if not opts.get('merge') and op1 != node:
690 691 dsguard = dirstateguard.dirstateguard(repo, 'backout')
691 692 try:
692 693 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
693 694 'backout')
694 695 stats = mergemod.update(repo, parent, True, True, node, False)
695 696 repo.setparents(op1, op2)
696 697 dsguard.close()
697 698 hg._showstats(repo, stats)
698 699 if stats[3]:
699 700 repo.ui.status(_("use 'hg resolve' to retry unresolved "
700 701 "file merges\n"))
701 702 return 1
702 703 finally:
703 704 ui.setconfig('ui', 'forcemerge', '', '')
704 705 lockmod.release(dsguard)
705 706 else:
706 707 hg.clean(repo, node, show_stats=False)
707 708 repo.dirstate.setbranch(branch)
708 709 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
709 710
710 711 if opts.get('no_commit'):
711 712 msg = _("changeset %s backed out, "
712 713 "don't forget to commit.\n")
713 714 ui.status(msg % short(node))
714 715 return 0
715 716
716 717 def commitfunc(ui, repo, message, match, opts):
717 718 editform = 'backout'
718 719 e = cmdutil.getcommiteditor(editform=editform, **opts)
719 720 if not message:
720 721 # we don't translate commit messages
721 722 message = "Backed out changeset %s" % short(node)
722 723 e = cmdutil.getcommiteditor(edit=True, editform=editform)
723 724 return repo.commit(message, opts.get('user'), opts.get('date'),
724 725 match, editor=e)
725 726 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
726 727 if not newnode:
727 728 ui.status(_("nothing changed\n"))
728 729 return 1
729 730 cmdutil.commitstatus(repo, newnode, branch, bheads)
730 731
731 732 def nice(node):
732 733 return '%d:%s' % (repo.changelog.rev(node), short(node))
733 734 ui.status(_('changeset %s backs out changeset %s\n') %
734 735 (nice(repo.changelog.tip()), nice(node)))
735 736 if opts.get('merge') and op1 != node:
736 737 hg.clean(repo, op1, show_stats=False)
737 738 ui.status(_('merging with changeset %s\n')
738 739 % nice(repo.changelog.tip()))
739 740 try:
740 741 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
741 742 'backout')
742 743 return hg.merge(repo, hex(repo.changelog.tip()))
743 744 finally:
744 745 ui.setconfig('ui', 'forcemerge', '', '')
745 746 return 0
746 747
747 748 @command('bisect',
748 749 [('r', 'reset', False, _('reset bisect state')),
749 750 ('g', 'good', False, _('mark changeset good')),
750 751 ('b', 'bad', False, _('mark changeset bad')),
751 752 ('s', 'skip', False, _('skip testing changeset')),
752 753 ('e', 'extend', False, _('extend the bisect range')),
753 754 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
754 755 ('U', 'noupdate', False, _('do not update to target'))],
755 756 _("[-gbsr] [-U] [-c CMD] [REV]"))
756 757 def bisect(ui, repo, rev=None, extra=None, command=None,
757 758 reset=None, good=None, bad=None, skip=None, extend=None,
758 759 noupdate=None):
759 760 """subdivision search of changesets
760 761
761 762 This command helps to find changesets which introduce problems. To
762 763 use, mark the earliest changeset you know exhibits the problem as
763 764 bad, then mark the latest changeset which is free from the problem
764 765 as good. Bisect will update your working directory to a revision
765 766 for testing (unless the -U/--noupdate option is specified). Once
766 767 you have performed tests, mark the working directory as good or
767 768 bad, and bisect will either update to another candidate changeset
768 769 or announce that it has found the bad revision.
769 770
770 771 As a shortcut, you can also use the revision argument to mark a
771 772 revision as good or bad without checking it out first.
772 773
773 774 If you supply a command, it will be used for automatic bisection.
774 775 The environment variable HG_NODE will contain the ID of the
775 776 changeset being tested. The exit status of the command will be
776 777 used to mark revisions as good or bad: status 0 means good, 125
777 778 means to skip the revision, 127 (command not found) will abort the
778 779 bisection, and any other non-zero exit status means the revision
779 780 is bad.
780 781
781 782 .. container:: verbose
782 783
783 784 Some examples:
784 785
785 786 - start a bisection with known bad revision 34, and good revision 12::
786 787
787 788 hg bisect --bad 34
788 789 hg bisect --good 12
789 790
790 791 - advance the current bisection by marking current revision as good or
791 792 bad::
792 793
793 794 hg bisect --good
794 795 hg bisect --bad
795 796
796 797 - mark the current revision, or a known revision, to be skipped (e.g. if
797 798 that revision is not usable because of another issue)::
798 799
799 800 hg bisect --skip
800 801 hg bisect --skip 23
801 802
802 803 - skip all revisions that do not touch directories ``foo`` or ``bar``::
803 804
804 805 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
805 806
806 807 - forget the current bisection::
807 808
808 809 hg bisect --reset
809 810
810 811 - use 'make && make tests' to automatically find the first broken
811 812 revision::
812 813
813 814 hg bisect --reset
814 815 hg bisect --bad 34
815 816 hg bisect --good 12
816 817 hg bisect --command "make && make tests"
817 818
818 819 - see all changesets whose states are already known in the current
819 820 bisection::
820 821
821 822 hg log -r "bisect(pruned)"
822 823
823 824 - see the changeset currently being bisected (especially useful
824 825 if running with -U/--noupdate)::
825 826
826 827 hg log -r "bisect(current)"
827 828
828 829 - see all changesets that took part in the current bisection::
829 830
830 831 hg log -r "bisect(range)"
831 832
832 833 - you can even get a nice graph::
833 834
834 835 hg log --graph -r "bisect(range)"
835 836
836 837 See :hg:`help revsets` for more about the `bisect()` keyword.
837 838
838 839 Returns 0 on success.
839 840 """
840 841 # backward compatibility
841 842 if rev in "good bad reset init".split():
842 843 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
843 844 cmd, rev, extra = rev, extra, None
844 845 if cmd == "good":
845 846 good = True
846 847 elif cmd == "bad":
847 848 bad = True
848 849 else:
849 850 reset = True
850 851 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
851 852 raise error.Abort(_('incompatible arguments'))
852 853
853 854 cmdutil.checkunfinished(repo)
854 855
855 856 if reset:
856 857 hbisect.resetstate(repo)
857 858 return
858 859
859 860 state = hbisect.load_state(repo)
860 861
861 862 # update state
862 863 if good or bad or skip:
863 864 if rev:
864 865 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
865 866 else:
866 867 nodes = [repo.lookup('.')]
867 868 if good:
868 869 state['good'] += nodes
869 870 elif bad:
870 871 state['bad'] += nodes
871 872 elif skip:
872 873 state['skip'] += nodes
873 874 hbisect.save_state(repo, state)
874 875 if not (state['good'] and state['bad']):
875 876 return
876 877
877 878 def mayupdate(repo, node, show_stats=True):
878 879 """common used update sequence"""
879 880 if noupdate:
880 881 return
881 882 cmdutil.bailifchanged(repo)
882 883 return hg.clean(repo, node, show_stats=show_stats)
883 884
884 885 displayer = cmdutil.show_changeset(ui, repo, {})
885 886
886 887 if command:
887 888 changesets = 1
888 889 if noupdate:
889 890 try:
890 891 node = state['current'][0]
891 892 except LookupError:
892 893 raise error.Abort(_('current bisect revision is unknown - '
893 894 'start a new bisect to fix'))
894 895 else:
895 896 node, p2 = repo.dirstate.parents()
896 897 if p2 != nullid:
897 898 raise error.Abort(_('current bisect revision is a merge'))
898 899 if rev:
899 900 node = repo[scmutil.revsingle(repo, rev, node)].node()
900 901 try:
901 902 while changesets:
902 903 # update state
903 904 state['current'] = [node]
904 905 hbisect.save_state(repo, state)
905 906 status = ui.system(command, environ={'HG_NODE': hex(node)})
906 907 if status == 125:
907 908 transition = "skip"
908 909 elif status == 0:
909 910 transition = "good"
910 911 # status < 0 means process was killed
911 912 elif status == 127:
912 913 raise error.Abort(_("failed to execute %s") % command)
913 914 elif status < 0:
914 915 raise error.Abort(_("%s killed") % command)
915 916 else:
916 917 transition = "bad"
917 918 state[transition].append(node)
918 919 ctx = repo[node]
919 920 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
920 921 hbisect.checkstate(state)
921 922 # bisect
922 923 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
923 924 # update to next check
924 925 node = nodes[0]
925 926 mayupdate(repo, node, show_stats=False)
926 927 finally:
927 928 state['current'] = [node]
928 929 hbisect.save_state(repo, state)
929 930 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
930 931 return
931 932
932 933 hbisect.checkstate(state)
933 934
934 935 # actually bisect
935 936 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
936 937 if extend:
937 938 if not changesets:
938 939 extendnode = hbisect.extendrange(repo, state, nodes, good)
939 940 if extendnode is not None:
940 941 ui.write(_("Extending search to changeset %d:%s\n")
941 942 % (extendnode.rev(), extendnode))
942 943 state['current'] = [extendnode.node()]
943 944 hbisect.save_state(repo, state)
944 945 return mayupdate(repo, extendnode.node())
945 946 raise error.Abort(_("nothing to extend"))
946 947
947 948 if changesets == 0:
948 949 hbisect.printresult(ui, repo, state, displayer, nodes, good)
949 950 else:
950 951 assert len(nodes) == 1 # only a single node can be tested next
951 952 node = nodes[0]
952 953 # compute the approximate number of remaining tests
953 954 tests, size = 0, 2
954 955 while size <= changesets:
955 956 tests, size = tests + 1, size * 2
956 957 rev = repo.changelog.rev(node)
957 958 ui.write(_("Testing changeset %d:%s "
958 959 "(%d changesets remaining, ~%d tests)\n")
959 960 % (rev, short(node), changesets, tests))
960 961 state['current'] = [node]
961 962 hbisect.save_state(repo, state)
962 963 return mayupdate(repo, node)
963 964
964 965 @command('bookmarks|bookmark',
965 966 [('f', 'force', False, _('force')),
966 967 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
967 968 ('d', 'delete', False, _('delete a given bookmark')),
968 969 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
969 970 ('i', 'inactive', False, _('mark a bookmark inactive')),
970 971 ] + formatteropts,
971 972 _('hg bookmarks [OPTIONS]... [NAME]...'))
972 973 def bookmark(ui, repo, *names, **opts):
973 974 '''create a new bookmark or list existing bookmarks
974 975
975 976 Bookmarks are labels on changesets to help track lines of development.
976 977 Bookmarks are unversioned and can be moved, renamed and deleted.
977 978 Deleting or moving a bookmark has no effect on the associated changesets.
978 979
979 980 Creating or updating to a bookmark causes it to be marked as 'active'.
980 981 The active bookmark is indicated with a '*'.
981 982 When a commit is made, the active bookmark will advance to the new commit.
982 983 A plain :hg:`update` will also advance an active bookmark, if possible.
983 984 Updating away from a bookmark will cause it to be deactivated.
984 985
985 986 Bookmarks can be pushed and pulled between repositories (see
986 987 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
987 988 diverged, a new 'divergent bookmark' of the form 'name@path' will
988 989 be created. Using :hg:`merge` will resolve the divergence.
989 990
990 991 A bookmark named '@' has the special property that :hg:`clone` will
991 992 check it out by default if it exists.
992 993
993 994 .. container:: verbose
994 995
995 996 Examples:
996 997
997 998 - create an active bookmark for a new line of development::
998 999
999 1000 hg book new-feature
1000 1001
1001 1002 - create an inactive bookmark as a place marker::
1002 1003
1003 1004 hg book -i reviewed
1004 1005
1005 1006 - create an inactive bookmark on another changeset::
1006 1007
1007 1008 hg book -r .^ tested
1008 1009
1009 1010 - rename bookmark turkey to dinner::
1010 1011
1011 1012 hg book -m turkey dinner
1012 1013
1013 1014 - move the '@' bookmark from another branch::
1014 1015
1015 1016 hg book -f @
1016 1017 '''
1017 1018 force = opts.get('force')
1018 1019 rev = opts.get('rev')
1019 1020 delete = opts.get('delete')
1020 1021 rename = opts.get('rename')
1021 1022 inactive = opts.get('inactive')
1022 1023
1023 1024 def checkformat(mark):
1024 1025 mark = mark.strip()
1025 1026 if not mark:
1026 1027 raise error.Abort(_("bookmark names cannot consist entirely of "
1027 1028 "whitespace"))
1028 1029 scmutil.checknewlabel(repo, mark, 'bookmark')
1029 1030 return mark
1030 1031
1031 1032 def checkconflict(repo, mark, cur, force=False, target=None):
1032 1033 if mark in marks and not force:
1033 1034 if target:
1034 1035 if marks[mark] == target and target == cur:
1035 1036 # re-activating a bookmark
1036 1037 return
1037 1038 anc = repo.changelog.ancestors([repo[target].rev()])
1038 1039 bmctx = repo[marks[mark]]
1039 1040 divs = [repo[b].node() for b in marks
1040 1041 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1041 1042
1042 1043 # allow resolving a single divergent bookmark even if moving
1043 1044 # the bookmark across branches when a revision is specified
1044 1045 # that contains a divergent bookmark
1045 1046 if bmctx.rev() not in anc and target in divs:
1046 1047 bookmarks.deletedivergent(repo, [target], mark)
1047 1048 return
1048 1049
1049 1050 deletefrom = [b for b in divs
1050 1051 if repo[b].rev() in anc or b == target]
1051 1052 bookmarks.deletedivergent(repo, deletefrom, mark)
1052 1053 if bookmarks.validdest(repo, bmctx, repo[target]):
1053 1054 ui.status(_("moving bookmark '%s' forward from %s\n") %
1054 1055 (mark, short(bmctx.node())))
1055 1056 return
1056 1057 raise error.Abort(_("bookmark '%s' already exists "
1057 1058 "(use -f to force)") % mark)
1058 1059 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1059 1060 and not force):
1060 1061 raise error.Abort(
1061 1062 _("a bookmark cannot have the name of an existing branch"))
1062 1063
1063 1064 if delete and rename:
1064 1065 raise error.Abort(_("--delete and --rename are incompatible"))
1065 1066 if delete and rev:
1066 1067 raise error.Abort(_("--rev is incompatible with --delete"))
1067 1068 if rename and rev:
1068 1069 raise error.Abort(_("--rev is incompatible with --rename"))
1069 1070 if not names and (delete or rev):
1070 1071 raise error.Abort(_("bookmark name required"))
1071 1072
1072 1073 if delete or rename or names or inactive:
1073 1074 wlock = lock = tr = None
1074 1075 try:
1075 1076 wlock = repo.wlock()
1076 1077 lock = repo.lock()
1077 1078 cur = repo.changectx('.').node()
1078 1079 marks = repo._bookmarks
1079 1080 if delete:
1080 1081 tr = repo.transaction('bookmark')
1081 1082 for mark in names:
1082 1083 if mark not in marks:
1083 1084 raise error.Abort(_("bookmark '%s' does not exist") %
1084 1085 mark)
1085 1086 if mark == repo._activebookmark:
1086 1087 bookmarks.deactivate(repo)
1087 1088 del marks[mark]
1088 1089
1089 1090 elif rename:
1090 1091 tr = repo.transaction('bookmark')
1091 1092 if not names:
1092 1093 raise error.Abort(_("new bookmark name required"))
1093 1094 elif len(names) > 1:
1094 1095 raise error.Abort(_("only one new bookmark name allowed"))
1095 1096 mark = checkformat(names[0])
1096 1097 if rename not in marks:
1097 1098 raise error.Abort(_("bookmark '%s' does not exist")
1098 1099 % rename)
1099 1100 checkconflict(repo, mark, cur, force)
1100 1101 marks[mark] = marks[rename]
1101 1102 if repo._activebookmark == rename and not inactive:
1102 1103 bookmarks.activate(repo, mark)
1103 1104 del marks[rename]
1104 1105 elif names:
1105 1106 tr = repo.transaction('bookmark')
1106 1107 newact = None
1107 1108 for mark in names:
1108 1109 mark = checkformat(mark)
1109 1110 if newact is None:
1110 1111 newact = mark
1111 1112 if inactive and mark == repo._activebookmark:
1112 1113 bookmarks.deactivate(repo)
1113 1114 return
1114 1115 tgt = cur
1115 1116 if rev:
1116 1117 tgt = scmutil.revsingle(repo, rev).node()
1117 1118 checkconflict(repo, mark, cur, force, tgt)
1118 1119 marks[mark] = tgt
1119 1120 if not inactive and cur == marks[newact] and not rev:
1120 1121 bookmarks.activate(repo, newact)
1121 1122 elif cur != tgt and newact == repo._activebookmark:
1122 1123 bookmarks.deactivate(repo)
1123 1124 elif inactive:
1124 1125 if len(marks) == 0:
1125 1126 ui.status(_("no bookmarks set\n"))
1126 1127 elif not repo._activebookmark:
1127 1128 ui.status(_("no active bookmark\n"))
1128 1129 else:
1129 1130 bookmarks.deactivate(repo)
1130 1131 if tr is not None:
1131 1132 marks.recordchange(tr)
1132 1133 tr.close()
1133 1134 finally:
1134 1135 lockmod.release(tr, lock, wlock)
1135 1136 else: # show bookmarks
1136 1137 fm = ui.formatter('bookmarks', opts)
1137 1138 hexfn = fm.hexfunc
1138 1139 marks = repo._bookmarks
1139 1140 if len(marks) == 0 and fm.isplain():
1140 1141 ui.status(_("no bookmarks set\n"))
1141 1142 for bmark, n in sorted(marks.iteritems()):
1142 1143 active = repo._activebookmark
1143 1144 if bmark == active:
1144 1145 prefix, label = '*', activebookmarklabel
1145 1146 else:
1146 1147 prefix, label = ' ', ''
1147 1148
1148 1149 fm.startitem()
1149 1150 if not ui.quiet:
1150 1151 fm.plain(' %s ' % prefix, label=label)
1151 1152 fm.write('bookmark', '%s', bmark, label=label)
1152 1153 pad = " " * (25 - encoding.colwidth(bmark))
1153 1154 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1154 1155 repo.changelog.rev(n), hexfn(n), label=label)
1155 1156 fm.data(active=(bmark == active))
1156 1157 fm.plain('\n')
1157 1158 fm.end()
1158 1159
1159 1160 @command('branch',
1160 1161 [('f', 'force', None,
1161 1162 _('set branch name even if it shadows an existing branch')),
1162 1163 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1163 1164 _('[-fC] [NAME]'))
1164 1165 def branch(ui, repo, label=None, **opts):
1165 1166 """set or show the current branch name
1166 1167
1167 1168 .. note::
1168 1169
1169 1170 Branch names are permanent and global. Use :hg:`bookmark` to create a
1170 1171 light-weight bookmark instead. See :hg:`help glossary` for more
1171 1172 information about named branches and bookmarks.
1172 1173
1173 1174 With no argument, show the current branch name. With one argument,
1174 1175 set the working directory branch name (the branch will not exist
1175 1176 in the repository until the next commit). Standard practice
1176 1177 recommends that primary development take place on the 'default'
1177 1178 branch.
1178 1179
1179 1180 Unless -f/--force is specified, branch will not let you set a
1180 1181 branch name that already exists.
1181 1182
1182 1183 Use -C/--clean to reset the working directory branch to that of
1183 1184 the parent of the working directory, negating a previous branch
1184 1185 change.
1185 1186
1186 1187 Use the command :hg:`update` to switch to an existing branch. Use
1187 1188 :hg:`commit --close-branch` to mark this branch head as closed.
1188 1189 When all heads of a branch are closed, the branch will be
1189 1190 considered closed.
1190 1191
1191 1192 Returns 0 on success.
1192 1193 """
1193 1194 if label:
1194 1195 label = label.strip()
1195 1196
1196 1197 if not opts.get('clean') and not label:
1197 1198 ui.write("%s\n" % repo.dirstate.branch())
1198 1199 return
1199 1200
1200 1201 with repo.wlock():
1201 1202 if opts.get('clean'):
1202 1203 label = repo[None].p1().branch()
1203 1204 repo.dirstate.setbranch(label)
1204 1205 ui.status(_('reset working directory to branch %s\n') % label)
1205 1206 elif label:
1206 1207 if not opts.get('force') and label in repo.branchmap():
1207 1208 if label not in [p.branch() for p in repo[None].parents()]:
1208 1209 raise error.Abort(_('a branch of the same name already'
1209 1210 ' exists'),
1210 1211 # i18n: "it" refers to an existing branch
1211 1212 hint=_("use 'hg update' to switch to it"))
1212 1213 scmutil.checknewlabel(repo, label, 'branch')
1213 1214 repo.dirstate.setbranch(label)
1214 1215 ui.status(_('marked working directory as branch %s\n') % label)
1215 1216
1216 1217 # find any open named branches aside from default
1217 1218 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1218 1219 if n != "default" and not c]
1219 1220 if not others:
1220 1221 ui.status(_('(branches are permanent and global, '
1221 1222 'did you want a bookmark?)\n'))
1222 1223
1223 1224 @command('branches',
1224 1225 [('a', 'active', False,
1225 1226 _('show only branches that have unmerged heads (DEPRECATED)')),
1226 1227 ('c', 'closed', False, _('show normal and closed branches')),
1227 1228 ] + formatteropts,
1228 1229 _('[-c]'))
1229 1230 def branches(ui, repo, active=False, closed=False, **opts):
1230 1231 """list repository named branches
1231 1232
1232 1233 List the repository's named branches, indicating which ones are
1233 1234 inactive. If -c/--closed is specified, also list branches which have
1234 1235 been marked closed (see :hg:`commit --close-branch`).
1235 1236
1236 1237 Use the command :hg:`update` to switch to an existing branch.
1237 1238
1238 1239 Returns 0.
1239 1240 """
1240 1241
1241 1242 fm = ui.formatter('branches', opts)
1242 1243 hexfunc = fm.hexfunc
1243 1244
1244 1245 allheads = set(repo.heads())
1245 1246 branches = []
1246 1247 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1247 1248 isactive = not isclosed and bool(set(heads) & allheads)
1248 1249 branches.append((tag, repo[tip], isactive, not isclosed))
1249 1250 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1250 1251 reverse=True)
1251 1252
1252 1253 for tag, ctx, isactive, isopen in branches:
1253 1254 if active and not isactive:
1254 1255 continue
1255 1256 if isactive:
1256 1257 label = 'branches.active'
1257 1258 notice = ''
1258 1259 elif not isopen:
1259 1260 if not closed:
1260 1261 continue
1261 1262 label = 'branches.closed'
1262 1263 notice = _(' (closed)')
1263 1264 else:
1264 1265 label = 'branches.inactive'
1265 1266 notice = _(' (inactive)')
1266 1267 current = (tag == repo.dirstate.branch())
1267 1268 if current:
1268 1269 label = 'branches.current'
1269 1270
1270 1271 fm.startitem()
1271 1272 fm.write('branch', '%s', tag, label=label)
1272 1273 rev = ctx.rev()
1273 1274 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1274 1275 fmt = ' ' * padsize + ' %d:%s'
1275 1276 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1276 1277 label='log.changeset changeset.%s' % ctx.phasestr())
1277 1278 fm.data(active=isactive, closed=not isopen, current=current)
1278 1279 if not ui.quiet:
1279 1280 fm.plain(notice)
1280 1281 fm.plain('\n')
1281 1282 fm.end()
1282 1283
1283 1284 @command('bundle',
1284 1285 [('f', 'force', None, _('run even when the destination is unrelated')),
1285 1286 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1286 1287 _('REV')),
1287 1288 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1288 1289 _('BRANCH')),
1289 1290 ('', 'base', [],
1290 1291 _('a base changeset assumed to be available at the destination'),
1291 1292 _('REV')),
1292 1293 ('a', 'all', None, _('bundle all changesets in the repository')),
1293 1294 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1294 1295 ] + remoteopts,
1295 1296 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1296 1297 def bundle(ui, repo, fname, dest=None, **opts):
1297 1298 """create a changegroup file
1298 1299
1299 1300 Generate a changegroup file collecting changesets to be added
1300 1301 to a repository.
1301 1302
1302 1303 To create a bundle containing all changesets, use -a/--all
1303 1304 (or --base null). Otherwise, hg assumes the destination will have
1304 1305 all the nodes you specify with --base parameters. Otherwise, hg
1305 1306 will assume the repository has all the nodes in destination, or
1306 1307 default-push/default if no destination is specified.
1307 1308
1308 1309 You can change bundle format with the -t/--type option. You can
1309 1310 specify a compression, a bundle version or both using a dash
1310 1311 (comp-version). The available compression methods are: none, bzip2,
1311 1312 and gzip (by default, bundles are compressed using bzip2). The
1312 1313 available formats are: v1, v2 (default to most suitable).
1313 1314
1314 1315 The bundle file can then be transferred using conventional means
1315 1316 and applied to another repository with the unbundle or pull
1316 1317 command. This is useful when direct push and pull are not
1317 1318 available or when exporting an entire repository is undesirable.
1318 1319
1319 1320 Applying bundles preserves all changeset contents including
1320 1321 permissions, copy/rename information, and revision history.
1321 1322
1322 1323 Returns 0 on success, 1 if no changes found.
1323 1324 """
1324 1325 revs = None
1325 1326 if 'rev' in opts:
1326 1327 revstrings = opts['rev']
1327 1328 revs = scmutil.revrange(repo, revstrings)
1328 1329 if revstrings and not revs:
1329 1330 raise error.Abort(_('no commits to bundle'))
1330 1331
1331 1332 bundletype = opts.get('type', 'bzip2').lower()
1332 1333 try:
1333 1334 bcompression, cgversion, params = exchange.parsebundlespec(
1334 1335 repo, bundletype, strict=False)
1335 1336 except error.UnsupportedBundleSpecification as e:
1336 1337 raise error.Abort(str(e),
1337 1338 hint=_("see 'hg help bundle' for supported "
1338 1339 "values for --type"))
1339 1340
1340 1341 # Packed bundles are a pseudo bundle format for now.
1341 1342 if cgversion == 's1':
1342 1343 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1343 1344 hint=_("use 'hg debugcreatestreamclonebundle'"))
1344 1345
1345 1346 if opts.get('all'):
1346 1347 if dest:
1347 1348 raise error.Abort(_("--all is incompatible with specifying "
1348 1349 "a destination"))
1349 1350 if opts.get('base'):
1350 1351 ui.warn(_("ignoring --base because --all was specified\n"))
1351 1352 base = ['null']
1352 1353 else:
1353 1354 base = scmutil.revrange(repo, opts.get('base'))
1354 1355 # TODO: get desired bundlecaps from command line.
1355 1356 bundlecaps = None
1356 1357 if cgversion not in changegroup.supportedoutgoingversions(repo):
1357 1358 raise error.Abort(_("repository does not support bundle version %s") %
1358 1359 cgversion)
1359 1360
1360 1361 if base:
1361 1362 if dest:
1362 1363 raise error.Abort(_("--base is incompatible with specifying "
1363 1364 "a destination"))
1364 1365 common = [repo.lookup(rev) for rev in base]
1365 1366 heads = revs and map(repo.lookup, revs) or None
1366 1367 outgoing = discovery.outgoing(repo, common, heads)
1367 1368 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1368 1369 bundlecaps=bundlecaps,
1369 1370 version=cgversion)
1370 1371 outgoing = None
1371 1372 else:
1372 1373 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1373 1374 dest, branches = hg.parseurl(dest, opts.get('branch'))
1374 1375 other = hg.peer(repo, opts, dest)
1375 1376 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1376 1377 heads = revs and map(repo.lookup, revs) or revs
1377 1378 outgoing = discovery.findcommonoutgoing(repo, other,
1378 1379 onlyheads=heads,
1379 1380 force=opts.get('force'),
1380 1381 portable=True)
1381 1382 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1382 1383 bundlecaps, version=cgversion)
1383 1384 if not cg:
1384 1385 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1385 1386 return 1
1386 1387
1387 1388 if cgversion == '01': #bundle1
1388 1389 if bcompression is None:
1389 1390 bcompression = 'UN'
1390 1391 bversion = 'HG10' + bcompression
1391 1392 bcompression = None
1392 1393 else:
1393 1394 assert cgversion == '02'
1394 1395 bversion = 'HG20'
1395 1396
1396 1397 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression)
1397 1398
1398 1399 @command('cat',
1399 1400 [('o', 'output', '',
1400 1401 _('print output to file with formatted name'), _('FORMAT')),
1401 1402 ('r', 'rev', '', _('print the given revision'), _('REV')),
1402 1403 ('', 'decode', None, _('apply any matching decode filter')),
1403 1404 ] + walkopts,
1404 1405 _('[OPTION]... FILE...'),
1405 1406 inferrepo=True)
1406 1407 def cat(ui, repo, file1, *pats, **opts):
1407 1408 """output the current or given revision of files
1408 1409
1409 1410 Print the specified files as they were at the given revision. If
1410 1411 no revision is given, the parent of the working directory is used.
1411 1412
1412 1413 Output may be to a file, in which case the name of the file is
1413 1414 given using a format string. The formatting rules as follows:
1414 1415
1415 1416 :``%%``: literal "%" character
1416 1417 :``%s``: basename of file being printed
1417 1418 :``%d``: dirname of file being printed, or '.' if in repository root
1418 1419 :``%p``: root-relative path name of file being printed
1419 1420 :``%H``: changeset hash (40 hexadecimal digits)
1420 1421 :``%R``: changeset revision number
1421 1422 :``%h``: short-form changeset hash (12 hexadecimal digits)
1422 1423 :``%r``: zero-padded changeset revision number
1423 1424 :``%b``: basename of the exporting repository
1424 1425
1425 1426 Returns 0 on success.
1426 1427 """
1427 1428 ctx = scmutil.revsingle(repo, opts.get('rev'))
1428 1429 m = scmutil.match(ctx, (file1,) + pats, opts)
1429 1430
1430 1431 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1431 1432
1432 1433 @command('^clone',
1433 1434 [('U', 'noupdate', None, _('the clone will include an empty working '
1434 1435 'directory (only a repository)')),
1435 1436 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1436 1437 _('REV')),
1437 1438 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1438 1439 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1439 1440 ('', 'pull', None, _('use pull protocol to copy metadata')),
1440 1441 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1441 1442 ] + remoteopts,
1442 1443 _('[OPTION]... SOURCE [DEST]'),
1443 1444 norepo=True)
1444 1445 def clone(ui, source, dest=None, **opts):
1445 1446 """make a copy of an existing repository
1446 1447
1447 1448 Create a copy of an existing repository in a new directory.
1448 1449
1449 1450 If no destination directory name is specified, it defaults to the
1450 1451 basename of the source.
1451 1452
1452 1453 The location of the source is added to the new repository's
1453 1454 ``.hg/hgrc`` file, as the default to be used for future pulls.
1454 1455
1455 1456 Only local paths and ``ssh://`` URLs are supported as
1456 1457 destinations. For ``ssh://`` destinations, no working directory or
1457 1458 ``.hg/hgrc`` will be created on the remote side.
1458 1459
1459 1460 If the source repository has a bookmark called '@' set, that
1460 1461 revision will be checked out in the new repository by default.
1461 1462
1462 1463 To check out a particular version, use -u/--update, or
1463 1464 -U/--noupdate to create a clone with no working directory.
1464 1465
1465 1466 To pull only a subset of changesets, specify one or more revisions
1466 1467 identifiers with -r/--rev or branches with -b/--branch. The
1467 1468 resulting clone will contain only the specified changesets and
1468 1469 their ancestors. These options (or 'clone src#rev dest') imply
1469 1470 --pull, even for local source repositories.
1470 1471
1471 1472 .. note::
1472 1473
1473 1474 Specifying a tag will include the tagged changeset but not the
1474 1475 changeset containing the tag.
1475 1476
1476 1477 .. container:: verbose
1477 1478
1478 1479 For efficiency, hardlinks are used for cloning whenever the
1479 1480 source and destination are on the same filesystem (note this
1480 1481 applies only to the repository data, not to the working
1481 1482 directory). Some filesystems, such as AFS, implement hardlinking
1482 1483 incorrectly, but do not report errors. In these cases, use the
1483 1484 --pull option to avoid hardlinking.
1484 1485
1485 1486 In some cases, you can clone repositories and the working
1486 1487 directory using full hardlinks with ::
1487 1488
1488 1489 $ cp -al REPO REPOCLONE
1489 1490
1490 1491 This is the fastest way to clone, but it is not always safe. The
1491 1492 operation is not atomic (making sure REPO is not modified during
1492 1493 the operation is up to you) and you have to make sure your
1493 1494 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1494 1495 so). Also, this is not compatible with certain extensions that
1495 1496 place their metadata under the .hg directory, such as mq.
1496 1497
1497 1498 Mercurial will update the working directory to the first applicable
1498 1499 revision from this list:
1499 1500
1500 1501 a) null if -U or the source repository has no changesets
1501 1502 b) if -u . and the source repository is local, the first parent of
1502 1503 the source repository's working directory
1503 1504 c) the changeset specified with -u (if a branch name, this means the
1504 1505 latest head of that branch)
1505 1506 d) the changeset specified with -r
1506 1507 e) the tipmost head specified with -b
1507 1508 f) the tipmost head specified with the url#branch source syntax
1508 1509 g) the revision marked with the '@' bookmark, if present
1509 1510 h) the tipmost head of the default branch
1510 1511 i) tip
1511 1512
1512 1513 When cloning from servers that support it, Mercurial may fetch
1513 1514 pre-generated data from a server-advertised URL. When this is done,
1514 1515 hooks operating on incoming changesets and changegroups may fire twice,
1515 1516 once for the bundle fetched from the URL and another for any additional
1516 1517 data not fetched from this URL. In addition, if an error occurs, the
1517 1518 repository may be rolled back to a partial clone. This behavior may
1518 1519 change in future releases. See :hg:`help -e clonebundles` for more.
1519 1520
1520 1521 Examples:
1521 1522
1522 1523 - clone a remote repository to a new directory named hg/::
1523 1524
1524 1525 hg clone https://www.mercurial-scm.org/repo/hg/
1525 1526
1526 1527 - create a lightweight local clone::
1527 1528
1528 1529 hg clone project/ project-feature/
1529 1530
1530 1531 - clone from an absolute path on an ssh server (note double-slash)::
1531 1532
1532 1533 hg clone ssh://user@server//home/projects/alpha/
1533 1534
1534 1535 - do a high-speed clone over a LAN while checking out a
1535 1536 specified version::
1536 1537
1537 1538 hg clone --uncompressed http://server/repo -u 1.5
1538 1539
1539 1540 - create a repository without changesets after a particular revision::
1540 1541
1541 1542 hg clone -r 04e544 experimental/ good/
1542 1543
1543 1544 - clone (and track) a particular named branch::
1544 1545
1545 1546 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1546 1547
1547 1548 See :hg:`help urls` for details on specifying URLs.
1548 1549
1549 1550 Returns 0 on success.
1550 1551 """
1551 1552 if opts.get('noupdate') and opts.get('updaterev'):
1552 1553 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1553 1554
1554 1555 r = hg.clone(ui, opts, source, dest,
1555 1556 pull=opts.get('pull'),
1556 1557 stream=opts.get('uncompressed'),
1557 1558 rev=opts.get('rev'),
1558 1559 update=opts.get('updaterev') or not opts.get('noupdate'),
1559 1560 branch=opts.get('branch'),
1560 1561 shareopts=opts.get('shareopts'))
1561 1562
1562 1563 return r is None
1563 1564
1564 1565 @command('^commit|ci',
1565 1566 [('A', 'addremove', None,
1566 1567 _('mark new/missing files as added/removed before committing')),
1567 1568 ('', 'close-branch', None,
1568 1569 _('mark a branch head as closed')),
1569 1570 ('', 'amend', None, _('amend the parent of the working directory')),
1570 1571 ('s', 'secret', None, _('use the secret phase for committing')),
1571 1572 ('e', 'edit', None, _('invoke editor on commit messages')),
1572 1573 ('i', 'interactive', None, _('use interactive mode')),
1573 1574 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1574 1575 _('[OPTION]... [FILE]...'),
1575 1576 inferrepo=True)
1576 1577 def commit(ui, repo, *pats, **opts):
1577 1578 """commit the specified files or all outstanding changes
1578 1579
1579 1580 Commit changes to the given files into the repository. Unlike a
1580 1581 centralized SCM, this operation is a local operation. See
1581 1582 :hg:`push` for a way to actively distribute your changes.
1582 1583
1583 1584 If a list of files is omitted, all changes reported by :hg:`status`
1584 1585 will be committed.
1585 1586
1586 1587 If you are committing the result of a merge, do not provide any
1587 1588 filenames or -I/-X filters.
1588 1589
1589 1590 If no commit message is specified, Mercurial starts your
1590 1591 configured editor where you can enter a message. In case your
1591 1592 commit fails, you will find a backup of your message in
1592 1593 ``.hg/last-message.txt``.
1593 1594
1594 1595 The --close-branch flag can be used to mark the current branch
1595 1596 head closed. When all heads of a branch are closed, the branch
1596 1597 will be considered closed and no longer listed.
1597 1598
1598 1599 The --amend flag can be used to amend the parent of the
1599 1600 working directory with a new commit that contains the changes
1600 1601 in the parent in addition to those currently reported by :hg:`status`,
1601 1602 if there are any. The old commit is stored in a backup bundle in
1602 1603 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1603 1604 on how to restore it).
1604 1605
1605 1606 Message, user and date are taken from the amended commit unless
1606 1607 specified. When a message isn't specified on the command line,
1607 1608 the editor will open with the message of the amended commit.
1608 1609
1609 1610 It is not possible to amend public changesets (see :hg:`help phases`)
1610 1611 or changesets that have children.
1611 1612
1612 1613 See :hg:`help dates` for a list of formats valid for -d/--date.
1613 1614
1614 1615 Returns 0 on success, 1 if nothing changed.
1615 1616
1616 1617 .. container:: verbose
1617 1618
1618 1619 Examples:
1619 1620
1620 1621 - commit all files ending in .py::
1621 1622
1622 1623 hg commit --include "set:**.py"
1623 1624
1624 1625 - commit all non-binary files::
1625 1626
1626 1627 hg commit --exclude "set:binary()"
1627 1628
1628 1629 - amend the current commit and set the date to now::
1629 1630
1630 1631 hg commit --amend --date now
1631 1632 """
1632 1633 wlock = lock = None
1633 1634 try:
1634 1635 wlock = repo.wlock()
1635 1636 lock = repo.lock()
1636 1637 return _docommit(ui, repo, *pats, **opts)
1637 1638 finally:
1638 1639 release(lock, wlock)
1639 1640
1640 1641 def _docommit(ui, repo, *pats, **opts):
1641 1642 if opts.get('interactive'):
1642 1643 opts.pop('interactive')
1643 1644 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1644 1645 cmdutil.recordfilter, *pats, **opts)
1645 1646 # ret can be 0 (no changes to record) or the value returned by
1646 1647 # commit(), 1 if nothing changed or None on success.
1647 1648 return 1 if ret == 0 else ret
1648 1649
1649 1650 if opts.get('subrepos'):
1650 1651 if opts.get('amend'):
1651 1652 raise error.Abort(_('cannot amend with --subrepos'))
1652 1653 # Let --subrepos on the command line override config setting.
1653 1654 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1654 1655
1655 1656 cmdutil.checkunfinished(repo, commit=True)
1656 1657
1657 1658 branch = repo[None].branch()
1658 1659 bheads = repo.branchheads(branch)
1659 1660
1660 1661 extra = {}
1661 1662 if opts.get('close_branch'):
1662 1663 extra['close'] = 1
1663 1664
1664 1665 if not bheads:
1665 1666 raise error.Abort(_('can only close branch heads'))
1666 1667 elif opts.get('amend'):
1667 1668 if repo[None].parents()[0].p1().branch() != branch and \
1668 1669 repo[None].parents()[0].p2().branch() != branch:
1669 1670 raise error.Abort(_('can only close branch heads'))
1670 1671
1671 1672 if opts.get('amend'):
1672 1673 if ui.configbool('ui', 'commitsubrepos'):
1673 1674 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1674 1675
1675 1676 old = repo['.']
1676 1677 if not old.mutable():
1677 1678 raise error.Abort(_('cannot amend public changesets'))
1678 1679 if len(repo[None].parents()) > 1:
1679 1680 raise error.Abort(_('cannot amend while merging'))
1680 1681 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1681 1682 if not allowunstable and old.children():
1682 1683 raise error.Abort(_('cannot amend changeset with children'))
1683 1684
1684 1685 # Currently histedit gets confused if an amend happens while histedit
1685 1686 # is in progress. Since we have a checkunfinished command, we are
1686 1687 # temporarily honoring it.
1687 1688 #
1688 1689 # Note: eventually this guard will be removed. Please do not expect
1689 1690 # this behavior to remain.
1690 1691 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1691 1692 cmdutil.checkunfinished(repo)
1692 1693
1693 1694 # commitfunc is used only for temporary amend commit by cmdutil.amend
1694 1695 def commitfunc(ui, repo, message, match, opts):
1695 1696 return repo.commit(message,
1696 1697 opts.get('user') or old.user(),
1697 1698 opts.get('date') or old.date(),
1698 1699 match,
1699 1700 extra=extra)
1700 1701
1701 1702 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1702 1703 if node == old.node():
1703 1704 ui.status(_("nothing changed\n"))
1704 1705 return 1
1705 1706 else:
1706 1707 def commitfunc(ui, repo, message, match, opts):
1707 1708 backup = ui.backupconfig('phases', 'new-commit')
1708 1709 baseui = repo.baseui
1709 1710 basebackup = baseui.backupconfig('phases', 'new-commit')
1710 1711 try:
1711 1712 if opts.get('secret'):
1712 1713 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1713 1714 # Propagate to subrepos
1714 1715 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1715 1716
1716 1717 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1717 1718 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1718 1719 return repo.commit(message, opts.get('user'), opts.get('date'),
1719 1720 match,
1720 1721 editor=editor,
1721 1722 extra=extra)
1722 1723 finally:
1723 1724 ui.restoreconfig(backup)
1724 1725 repo.baseui.restoreconfig(basebackup)
1725 1726
1726 1727
1727 1728 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1728 1729
1729 1730 if not node:
1730 1731 stat = cmdutil.postcommitstatus(repo, pats, opts)
1731 1732 if stat[3]:
1732 1733 ui.status(_("nothing changed (%d missing files, see "
1733 1734 "'hg status')\n") % len(stat[3]))
1734 1735 else:
1735 1736 ui.status(_("nothing changed\n"))
1736 1737 return 1
1737 1738
1738 1739 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1739 1740
1740 1741 @command('config|showconfig|debugconfig',
1741 1742 [('u', 'untrusted', None, _('show untrusted configuration options')),
1742 1743 ('e', 'edit', None, _('edit user config')),
1743 1744 ('l', 'local', None, _('edit repository config')),
1744 1745 ('g', 'global', None, _('edit global config'))] + formatteropts,
1745 1746 _('[-u] [NAME]...'),
1746 1747 optionalrepo=True)
1747 1748 def config(ui, repo, *values, **opts):
1748 1749 """show combined config settings from all hgrc files
1749 1750
1750 1751 With no arguments, print names and values of all config items.
1751 1752
1752 1753 With one argument of the form section.name, print just the value
1753 1754 of that config item.
1754 1755
1755 1756 With multiple arguments, print names and values of all config
1756 1757 items with matching section names.
1757 1758
1758 1759 With --edit, start an editor on the user-level config file. With
1759 1760 --global, edit the system-wide config file. With --local, edit the
1760 1761 repository-level config file.
1761 1762
1762 1763 With --debug, the source (filename and line number) is printed
1763 1764 for each config item.
1764 1765
1765 1766 See :hg:`help config` for more information about config files.
1766 1767
1767 1768 Returns 0 on success, 1 if NAME does not exist.
1768 1769
1769 1770 """
1770 1771
1771 1772 if opts.get('edit') or opts.get('local') or opts.get('global'):
1772 1773 if opts.get('local') and opts.get('global'):
1773 1774 raise error.Abort(_("can't use --local and --global together"))
1774 1775
1775 1776 if opts.get('local'):
1776 1777 if not repo:
1777 1778 raise error.Abort(_("can't use --local outside a repository"))
1778 1779 paths = [repo.join('hgrc')]
1779 1780 elif opts.get('global'):
1780 1781 paths = scmutil.systemrcpath()
1781 1782 else:
1782 1783 paths = scmutil.userrcpath()
1783 1784
1784 1785 for f in paths:
1785 1786 if os.path.exists(f):
1786 1787 break
1787 1788 else:
1788 1789 if opts.get('global'):
1789 1790 samplehgrc = uimod.samplehgrcs['global']
1790 1791 elif opts.get('local'):
1791 1792 samplehgrc = uimod.samplehgrcs['local']
1792 1793 else:
1793 1794 samplehgrc = uimod.samplehgrcs['user']
1794 1795
1795 1796 f = paths[0]
1796 1797 fp = open(f, "w")
1797 1798 fp.write(samplehgrc)
1798 1799 fp.close()
1799 1800
1800 1801 editor = ui.geteditor()
1801 1802 ui.system("%s \"%s\"" % (editor, f),
1802 1803 onerr=error.Abort, errprefix=_("edit failed"))
1803 1804 return
1804 1805
1805 1806 fm = ui.formatter('config', opts)
1806 1807 for f in scmutil.rcpath():
1807 1808 ui.debug('read config from: %s\n' % f)
1808 1809 untrusted = bool(opts.get('untrusted'))
1809 1810 if values:
1810 1811 sections = [v for v in values if '.' not in v]
1811 1812 items = [v for v in values if '.' in v]
1812 1813 if len(items) > 1 or items and sections:
1813 1814 raise error.Abort(_('only one config item permitted'))
1814 1815 matched = False
1815 1816 for section, name, value in ui.walkconfig(untrusted=untrusted):
1816 1817 value = str(value)
1817 1818 if fm.isplain():
1818 1819 value = value.replace('\n', '\\n')
1819 1820 entryname = section + '.' + name
1820 1821 if values:
1821 1822 for v in values:
1822 1823 if v == section:
1823 1824 fm.startitem()
1824 1825 fm.condwrite(ui.debugflag, 'source', '%s: ',
1825 1826 ui.configsource(section, name, untrusted))
1826 1827 fm.write('name value', '%s=%s\n', entryname, value)
1827 1828 matched = True
1828 1829 elif v == entryname:
1829 1830 fm.startitem()
1830 1831 fm.condwrite(ui.debugflag, 'source', '%s: ',
1831 1832 ui.configsource(section, name, untrusted))
1832 1833 fm.write('value', '%s\n', value)
1833 1834 fm.data(name=entryname)
1834 1835 matched = True
1835 1836 else:
1836 1837 fm.startitem()
1837 1838 fm.condwrite(ui.debugflag, 'source', '%s: ',
1838 1839 ui.configsource(section, name, untrusted))
1839 1840 fm.write('name value', '%s=%s\n', entryname, value)
1840 1841 matched = True
1841 1842 fm.end()
1842 1843 if matched:
1843 1844 return 0
1844 1845 return 1
1845 1846
1846 1847 @command('copy|cp',
1847 1848 [('A', 'after', None, _('record a copy that has already occurred')),
1848 1849 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1849 1850 ] + walkopts + dryrunopts,
1850 1851 _('[OPTION]... [SOURCE]... DEST'))
1851 1852 def copy(ui, repo, *pats, **opts):
1852 1853 """mark files as copied for the next commit
1853 1854
1854 1855 Mark dest as having copies of source files. If dest is a
1855 1856 directory, copies are put in that directory. If dest is a file,
1856 1857 the source must be a single file.
1857 1858
1858 1859 By default, this command copies the contents of files as they
1859 1860 exist in the working directory. If invoked with -A/--after, the
1860 1861 operation is recorded, but no copying is performed.
1861 1862
1862 1863 This command takes effect with the next commit. To undo a copy
1863 1864 before that, see :hg:`revert`.
1864 1865
1865 1866 Returns 0 on success, 1 if errors are encountered.
1866 1867 """
1867 1868 with repo.wlock(False):
1868 1869 return cmdutil.copy(ui, repo, pats, opts)
1869 1870
1870 1871 @command('debugdag',
1871 1872 [('t', 'tags', None, _('use tags as labels')),
1872 1873 ('b', 'branches', None, _('annotate with branch names')),
1873 1874 ('', 'dots', None, _('use dots for runs')),
1874 1875 ('s', 'spaces', None, _('separate elements by spaces'))],
1875 1876 _('[OPTION]... [FILE [REV]...]'),
1876 1877 optionalrepo=True)
1877 1878 def debugdag(ui, repo, file_=None, *revs, **opts):
1878 1879 """format the changelog or an index DAG as a concise textual description
1879 1880
1880 1881 If you pass a revlog index, the revlog's DAG is emitted. If you list
1881 1882 revision numbers, they get labeled in the output as rN.
1882 1883
1883 1884 Otherwise, the changelog DAG of the current repo is emitted.
1884 1885 """
1885 1886 spaces = opts.get('spaces')
1886 1887 dots = opts.get('dots')
1887 1888 if file_:
1888 1889 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1889 1890 revs = set((int(r) for r in revs))
1890 1891 def events():
1891 1892 for r in rlog:
1892 1893 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1893 1894 if p != -1))
1894 1895 if r in revs:
1895 1896 yield 'l', (r, "r%i" % r)
1896 1897 elif repo:
1897 1898 cl = repo.changelog
1898 1899 tags = opts.get('tags')
1899 1900 branches = opts.get('branches')
1900 1901 if tags:
1901 1902 labels = {}
1902 1903 for l, n in repo.tags().items():
1903 1904 labels.setdefault(cl.rev(n), []).append(l)
1904 1905 def events():
1905 1906 b = "default"
1906 1907 for r in cl:
1907 1908 if branches:
1908 1909 newb = cl.read(cl.node(r))[5]['branch']
1909 1910 if newb != b:
1910 1911 yield 'a', newb
1911 1912 b = newb
1912 1913 yield 'n', (r, list(p for p in cl.parentrevs(r)
1913 1914 if p != -1))
1914 1915 if tags:
1915 1916 ls = labels.get(r)
1916 1917 if ls:
1917 1918 for l in ls:
1918 1919 yield 'l', (r, l)
1919 1920 else:
1920 1921 raise error.Abort(_('need repo for changelog dag'))
1921 1922
1922 1923 for line in dagparser.dagtextlines(events(),
1923 1924 addspaces=spaces,
1924 1925 wraplabels=True,
1925 1926 wrapannotations=True,
1926 1927 wrapnonlinear=dots,
1927 1928 usedots=dots,
1928 1929 maxlinewidth=70):
1929 1930 ui.write(line)
1930 1931 ui.write("\n")
1931 1932
1932 1933 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
1933 1934 def debugdata(ui, repo, file_, rev=None, **opts):
1934 1935 """dump the contents of a data file revision"""
1935 1936 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
1936 1937 if rev is not None:
1937 1938 raise error.CommandError('debugdata', _('invalid arguments'))
1938 1939 file_, rev = None, file_
1939 1940 elif rev is None:
1940 1941 raise error.CommandError('debugdata', _('invalid arguments'))
1941 1942 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1942 1943 try:
1943 1944 ui.write(r.revision(r.lookup(rev)))
1944 1945 except KeyError:
1945 1946 raise error.Abort(_('invalid revision identifier %s') % rev)
1946 1947
1947 1948 @command('debugdate',
1948 1949 [('e', 'extended', None, _('try extended date formats'))],
1949 1950 _('[-e] DATE [RANGE]'),
1950 1951 norepo=True, optionalrepo=True)
1951 1952 def debugdate(ui, date, range=None, **opts):
1952 1953 """parse and display a date"""
1953 1954 if opts["extended"]:
1954 1955 d = util.parsedate(date, util.extendeddateformats)
1955 1956 else:
1956 1957 d = util.parsedate(date)
1957 1958 ui.write(("internal: %s %s\n") % d)
1958 1959 ui.write(("standard: %s\n") % util.datestr(d))
1959 1960 if range:
1960 1961 m = util.matchdate(range)
1961 1962 ui.write(("match: %s\n") % m(d[0]))
1962 1963
1963 1964 @command('debugdiscovery',
1964 1965 [('', 'old', None, _('use old-style discovery')),
1965 1966 ('', 'nonheads', None,
1966 1967 _('use old-style discovery with non-heads included')),
1967 1968 ] + remoteopts,
1968 1969 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1969 1970 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1970 1971 """runs the changeset discovery protocol in isolation"""
1971 1972 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1972 1973 opts.get('branch'))
1973 1974 remote = hg.peer(repo, opts, remoteurl)
1974 1975 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1975 1976
1976 1977 # make sure tests are repeatable
1977 1978 random.seed(12323)
1978 1979
1979 1980 def doit(localheads, remoteheads, remote=remote):
1980 1981 if opts.get('old'):
1981 1982 if localheads:
1982 1983 raise error.Abort('cannot use localheads with old style '
1983 1984 'discovery')
1984 1985 if not util.safehasattr(remote, 'branches'):
1985 1986 # enable in-client legacy support
1986 1987 remote = localrepo.locallegacypeer(remote.local())
1987 1988 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1988 1989 force=True)
1989 1990 common = set(common)
1990 1991 if not opts.get('nonheads'):
1991 1992 ui.write(("unpruned common: %s\n") %
1992 1993 " ".join(sorted(short(n) for n in common)))
1993 1994 dag = dagutil.revlogdag(repo.changelog)
1994 1995 all = dag.ancestorset(dag.internalizeall(common))
1995 1996 common = dag.externalizeall(dag.headsetofconnecteds(all))
1996 1997 else:
1997 1998 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1998 1999 common = set(common)
1999 2000 rheads = set(hds)
2000 2001 lheads = set(repo.heads())
2001 2002 ui.write(("common heads: %s\n") %
2002 2003 " ".join(sorted(short(n) for n in common)))
2003 2004 if lheads <= common:
2004 2005 ui.write(("local is subset\n"))
2005 2006 elif rheads <= common:
2006 2007 ui.write(("remote is subset\n"))
2007 2008
2008 2009 serverlogs = opts.get('serverlog')
2009 2010 if serverlogs:
2010 2011 for filename in serverlogs:
2011 2012 with open(filename, 'r') as logfile:
2012 2013 line = logfile.readline()
2013 2014 while line:
2014 2015 parts = line.strip().split(';')
2015 2016 op = parts[1]
2016 2017 if op == 'cg':
2017 2018 pass
2018 2019 elif op == 'cgss':
2019 2020 doit(parts[2].split(' '), parts[3].split(' '))
2020 2021 elif op == 'unb':
2021 2022 doit(parts[3].split(' '), parts[2].split(' '))
2022 2023 line = logfile.readline()
2023 2024 else:
2024 2025 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2025 2026 opts.get('remote_head'))
2026 2027 localrevs = opts.get('local_head')
2027 2028 doit(localrevs, remoterevs)
2028 2029
2029 2030 @command('debugextensions', formatteropts, [], norepo=True)
2030 2031 def debugextensions(ui, **opts):
2031 2032 '''show information about active extensions'''
2032 2033 exts = extensions.extensions(ui)
2033 2034 hgver = util.version()
2034 2035 fm = ui.formatter('debugextensions', opts)
2035 2036 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
2036 2037 isinternal = extensions.ismoduleinternal(extmod)
2037 2038 extsource = extmod.__file__
2038 2039 if isinternal:
2039 2040 exttestedwith = [] # never expose magic string to users
2040 2041 else:
2041 2042 exttestedwith = getattr(extmod, 'testedwith', '').split()
2042 2043 extbuglink = getattr(extmod, 'buglink', None)
2043 2044
2044 2045 fm.startitem()
2045 2046
2046 2047 if ui.quiet or ui.verbose:
2047 2048 fm.write('name', '%s\n', extname)
2048 2049 else:
2049 2050 fm.write('name', '%s', extname)
2050 2051 if isinternal or hgver in exttestedwith:
2051 2052 fm.plain('\n')
2052 2053 elif not exttestedwith:
2053 2054 fm.plain(_(' (untested!)\n'))
2054 2055 else:
2055 2056 lasttestedversion = exttestedwith[-1]
2056 2057 fm.plain(' (%s!)\n' % lasttestedversion)
2057 2058
2058 2059 fm.condwrite(ui.verbose and extsource, 'source',
2059 2060 _(' location: %s\n'), extsource or "")
2060 2061
2061 2062 if ui.verbose:
2062 2063 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
2063 2064 fm.data(bundled=isinternal)
2064 2065
2065 2066 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
2066 2067 _(' tested with: %s\n'),
2067 2068 fm.formatlist(exttestedwith, name='ver'))
2068 2069
2069 2070 fm.condwrite(ui.verbose and extbuglink, 'buglink',
2070 2071 _(' bug reporting: %s\n'), extbuglink or "")
2071 2072
2072 2073 fm.end()
2073 2074
2074 2075 @command('debugfileset',
2075 2076 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2076 2077 _('[-r REV] FILESPEC'))
2077 2078 def debugfileset(ui, repo, expr, **opts):
2078 2079 '''parse and apply a fileset specification'''
2079 2080 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2080 2081 if ui.verbose:
2081 2082 tree = fileset.parse(expr)
2082 2083 ui.note(fileset.prettyformat(tree), "\n")
2083 2084
2084 2085 for f in ctx.getfileset(expr):
2085 2086 ui.write("%s\n" % f)
2086 2087
2087 2088 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2088 2089 def debugfsinfo(ui, path="."):
2089 2090 """show information detected about current filesystem"""
2090 2091 util.writefile('.debugfsinfo', '')
2091 2092 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2092 2093 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2093 2094 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2094 2095 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
2095 2096 and 'yes' or 'no'))
2096 2097 os.unlink('.debugfsinfo')
2097 2098
2098 2099 @command('debuggetbundle',
2099 2100 [('H', 'head', [], _('id of head node'), _('ID')),
2100 2101 ('C', 'common', [], _('id of common node'), _('ID')),
2101 2102 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2102 2103 _('REPO FILE [-H|-C ID]...'),
2103 2104 norepo=True)
2104 2105 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2105 2106 """retrieves a bundle from a repo
2106 2107
2107 2108 Every ID must be a full-length hex node id string. Saves the bundle to the
2108 2109 given file.
2109 2110 """
2110 2111 repo = hg.peer(ui, opts, repopath)
2111 2112 if not repo.capable('getbundle'):
2112 2113 raise error.Abort("getbundle() not supported by target repository")
2113 2114 args = {}
2114 2115 if common:
2115 2116 args['common'] = [bin(s) for s in common]
2116 2117 if head:
2117 2118 args['heads'] = [bin(s) for s in head]
2118 2119 # TODO: get desired bundlecaps from command line.
2119 2120 args['bundlecaps'] = None
2120 2121 bundle = repo.getbundle('debug', **args)
2121 2122
2122 2123 bundletype = opts.get('type', 'bzip2').lower()
2123 2124 btypes = {'none': 'HG10UN',
2124 2125 'bzip2': 'HG10BZ',
2125 2126 'gzip': 'HG10GZ',
2126 2127 'bundle2': 'HG20'}
2127 2128 bundletype = btypes.get(bundletype)
2128 2129 if bundletype not in bundle2.bundletypes:
2129 2130 raise error.Abort(_('unknown bundle type specified with --type'))
2130 2131 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
2131 2132
2132 2133 @command('debugignore', [], '[FILE]')
2133 2134 def debugignore(ui, repo, *files, **opts):
2134 2135 """display the combined ignore pattern and information about ignored files
2135 2136
2136 2137 With no argument display the combined ignore pattern.
2137 2138
2138 2139 Given space separated file names, shows if the given file is ignored and
2139 2140 if so, show the ignore rule (file and line number) that matched it.
2140 2141 """
2141 2142 ignore = repo.dirstate._ignore
2142 2143 if not files:
2143 2144 # Show all the patterns
2144 2145 includepat = getattr(ignore, 'includepat', None)
2145 2146 if includepat is not None:
2146 2147 ui.write("%s\n" % includepat)
2147 2148 else:
2148 2149 raise error.Abort(_("no ignore patterns found"))
2149 2150 else:
2150 2151 for f in files:
2151 2152 nf = util.normpath(f)
2152 2153 ignored = None
2153 2154 ignoredata = None
2154 2155 if nf != '.':
2155 2156 if ignore(nf):
2156 2157 ignored = nf
2157 2158 ignoredata = repo.dirstate._ignorefileandline(nf)
2158 2159 else:
2159 2160 for p in util.finddirs(nf):
2160 2161 if ignore(p):
2161 2162 ignored = p
2162 2163 ignoredata = repo.dirstate._ignorefileandline(p)
2163 2164 break
2164 2165 if ignored:
2165 2166 if ignored == nf:
2166 2167 ui.write(_("%s is ignored\n") % f)
2167 2168 else:
2168 2169 ui.write(_("%s is ignored because of "
2169 2170 "containing folder %s\n")
2170 2171 % (f, ignored))
2171 2172 ignorefile, lineno, line = ignoredata
2172 2173 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2173 2174 % (ignorefile, lineno, line))
2174 2175 else:
2175 2176 ui.write(_("%s is not ignored\n") % f)
2176 2177
2177 2178 @command('debugindex', debugrevlogopts +
2178 2179 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2179 2180 _('[-f FORMAT] -c|-m|FILE'),
2180 2181 optionalrepo=True)
2181 2182 def debugindex(ui, repo, file_=None, **opts):
2182 2183 """dump the contents of an index file"""
2183 2184 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2184 2185 format = opts.get('format', 0)
2185 2186 if format not in (0, 1):
2186 2187 raise error.Abort(_("unknown format %d") % format)
2187 2188
2188 2189 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2189 2190 if generaldelta:
2190 2191 basehdr = ' delta'
2191 2192 else:
2192 2193 basehdr = ' base'
2193 2194
2194 2195 if ui.debugflag:
2195 2196 shortfn = hex
2196 2197 else:
2197 2198 shortfn = short
2198 2199
2199 2200 # There might not be anything in r, so have a sane default
2200 2201 idlen = 12
2201 2202 for i in r:
2202 2203 idlen = len(shortfn(r.node(i)))
2203 2204 break
2204 2205
2205 2206 if format == 0:
2206 2207 ui.write((" rev offset length " + basehdr + " linkrev"
2207 2208 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2208 2209 elif format == 1:
2209 2210 ui.write((" rev flag offset length"
2210 2211 " size " + basehdr + " link p1 p2"
2211 2212 " %s\n") % "nodeid".rjust(idlen))
2212 2213
2213 2214 for i in r:
2214 2215 node = r.node(i)
2215 2216 if generaldelta:
2216 2217 base = r.deltaparent(i)
2217 2218 else:
2218 2219 base = r.chainbase(i)
2219 2220 if format == 0:
2220 2221 try:
2221 2222 pp = r.parents(node)
2222 2223 except Exception:
2223 2224 pp = [nullid, nullid]
2224 2225 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2225 2226 i, r.start(i), r.length(i), base, r.linkrev(i),
2226 2227 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2227 2228 elif format == 1:
2228 2229 pr = r.parentrevs(i)
2229 2230 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2230 2231 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2231 2232 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2232 2233
2233 2234 @command('debugindexdot', debugrevlogopts,
2234 2235 _('-c|-m|FILE'), optionalrepo=True)
2235 2236 def debugindexdot(ui, repo, file_=None, **opts):
2236 2237 """dump an index DAG as a graphviz dot file"""
2237 2238 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
2238 2239 ui.write(("digraph G {\n"))
2239 2240 for i in r:
2240 2241 node = r.node(i)
2241 2242 pp = r.parents(node)
2242 2243 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2243 2244 if pp[1] != nullid:
2244 2245 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2245 2246 ui.write("}\n")
2246 2247
2247 2248 @command('debugdeltachain',
2248 2249 debugrevlogopts + formatteropts,
2249 2250 _('-c|-m|FILE'),
2250 2251 optionalrepo=True)
2251 2252 def debugdeltachain(ui, repo, file_=None, **opts):
2252 2253 """dump information about delta chains in a revlog
2253 2254
2254 2255 Output can be templatized. Available template keywords are:
2255 2256
2256 2257 :``rev``: revision number
2257 2258 :``chainid``: delta chain identifier (numbered by unique base)
2258 2259 :``chainlen``: delta chain length to this revision
2259 2260 :``prevrev``: previous revision in delta chain
2260 2261 :``deltatype``: role of delta / how it was computed
2261 2262 :``compsize``: compressed size of revision
2262 2263 :``uncompsize``: uncompressed size of revision
2263 2264 :``chainsize``: total size of compressed revisions in chain
2264 2265 :``chainratio``: total chain size divided by uncompressed revision size
2265 2266 (new delta chains typically start at ratio 2.00)
2266 2267 :``lindist``: linear distance from base revision in delta chain to end
2267 2268 of this revision
2268 2269 :``extradist``: total size of revisions not part of this delta chain from
2269 2270 base of delta chain to end of this revision; a measurement
2270 2271 of how much extra data we need to read/seek across to read
2271 2272 the delta chain for this revision
2272 2273 :``extraratio``: extradist divided by chainsize; another representation of
2273 2274 how much unrelated data is needed to load this delta chain
2274 2275 """
2275 2276 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
2276 2277 index = r.index
2277 2278 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2278 2279
2279 2280 def revinfo(rev):
2280 2281 e = index[rev]
2281 2282 compsize = e[1]
2282 2283 uncompsize = e[2]
2283 2284 chainsize = 0
2284 2285
2285 2286 if generaldelta:
2286 2287 if e[3] == e[5]:
2287 2288 deltatype = 'p1'
2288 2289 elif e[3] == e[6]:
2289 2290 deltatype = 'p2'
2290 2291 elif e[3] == rev - 1:
2291 2292 deltatype = 'prev'
2292 2293 elif e[3] == rev:
2293 2294 deltatype = 'base'
2294 2295 else:
2295 2296 deltatype = 'other'
2296 2297 else:
2297 2298 if e[3] == rev:
2298 2299 deltatype = 'base'
2299 2300 else:
2300 2301 deltatype = 'prev'
2301 2302
2302 2303 chain = r._deltachain(rev)[0]
2303 2304 for iterrev in chain:
2304 2305 e = index[iterrev]
2305 2306 chainsize += e[1]
2306 2307
2307 2308 return compsize, uncompsize, deltatype, chain, chainsize
2308 2309
2309 2310 fm = ui.formatter('debugdeltachain', opts)
2310 2311
2311 2312 fm.plain(' rev chain# chainlen prev delta '
2312 2313 'size rawsize chainsize ratio lindist extradist '
2313 2314 'extraratio\n')
2314 2315
2315 2316 chainbases = {}
2316 2317 for rev in r:
2317 2318 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
2318 2319 chainbase = chain[0]
2319 2320 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
2320 2321 basestart = r.start(chainbase)
2321 2322 revstart = r.start(rev)
2322 2323 lineardist = revstart + comp - basestart
2323 2324 extradist = lineardist - chainsize
2324 2325 try:
2325 2326 prevrev = chain[-2]
2326 2327 except IndexError:
2327 2328 prevrev = -1
2328 2329
2329 2330 chainratio = float(chainsize) / float(uncomp)
2330 2331 extraratio = float(extradist) / float(chainsize)
2331 2332
2332 2333 fm.startitem()
2333 2334 fm.write('rev chainid chainlen prevrev deltatype compsize '
2334 2335 'uncompsize chainsize chainratio lindist extradist '
2335 2336 'extraratio',
2336 2337 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
2337 2338 rev, chainid, len(chain), prevrev, deltatype, comp,
2338 2339 uncomp, chainsize, chainratio, lineardist, extradist,
2339 2340 extraratio,
2340 2341 rev=rev, chainid=chainid, chainlen=len(chain),
2341 2342 prevrev=prevrev, deltatype=deltatype, compsize=comp,
2342 2343 uncompsize=uncomp, chainsize=chainsize,
2343 2344 chainratio=chainratio, lindist=lineardist,
2344 2345 extradist=extradist, extraratio=extraratio)
2345 2346
2346 2347 fm.end()
2347 2348
2348 2349 @command('debuginstall', [] + formatteropts, '', norepo=True)
2349 2350 def debuginstall(ui, **opts):
2350 2351 '''test Mercurial installation
2351 2352
2352 2353 Returns 0 on success.
2353 2354 '''
2354 2355
2355 2356 def writetemp(contents):
2356 2357 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2357 2358 f = os.fdopen(fd, "wb")
2358 2359 f.write(contents)
2359 2360 f.close()
2360 2361 return name
2361 2362
2362 2363 problems = 0
2363 2364
2364 2365 fm = ui.formatter('debuginstall', opts)
2365 2366 fm.startitem()
2366 2367
2367 2368 # encoding
2368 2369 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
2369 2370 err = None
2370 2371 try:
2371 2372 encoding.fromlocal("test")
2372 2373 except error.Abort as inst:
2373 2374 err = inst
2374 2375 problems += 1
2375 2376 fm.condwrite(err, 'encodingerror', _(" %s\n"
2376 2377 " (check that your locale is properly set)\n"), err)
2377 2378
2378 2379 # Python
2379 2380 fm.write('pythonexe', _("checking Python executable (%s)\n"),
2380 2381 sys.executable)
2381 2382 fm.write('pythonver', _("checking Python version (%s)\n"),
2382 2383 ("%s.%s.%s" % sys.version_info[:3]))
2383 2384 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2384 2385 os.path.dirname(os.__file__))
2385 2386
2386 2387 security = set(sslutil.supportedprotocols)
2387 2388 if sslutil.hassni:
2388 2389 security.add('sni')
2389 2390
2390 2391 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
2391 2392 fm.formatlist(sorted(security), name='protocol',
2392 2393 fmt='%s', sep=','))
2393 2394
2394 2395 # These are warnings, not errors. So don't increment problem count. This
2395 2396 # may change in the future.
2396 2397 if 'tls1.2' not in security:
2397 2398 fm.plain(_(' TLS 1.2 not supported by Python install; '
2398 2399 'network connections lack modern security\n'))
2399 2400 if 'sni' not in security:
2400 2401 fm.plain(_(' SNI not supported by Python install; may have '
2401 2402 'connectivity issues with some servers\n'))
2402 2403
2403 2404 # TODO print CA cert info
2404 2405
2405 2406 # hg version
2406 2407 hgver = util.version()
2407 2408 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2408 2409 hgver.split('+')[0])
2409 2410 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2410 2411 '+'.join(hgver.split('+')[1:]))
2411 2412
2412 2413 # compiled modules
2413 2414 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2414 2415 policy.policy)
2415 2416 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2416 2417 os.path.dirname(__file__))
2417 2418
2418 2419 err = None
2419 2420 try:
2420 2421 from . import (
2421 2422 base85,
2422 2423 bdiff,
2423 2424 mpatch,
2424 2425 osutil,
2425 2426 )
2426 2427 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2427 2428 except Exception as inst:
2428 2429 err = inst
2429 2430 problems += 1
2430 2431 fm.condwrite(err, 'extensionserror', " %s\n", err)
2431 2432
2432 2433 compengines = util.compengines._engines.values()
2433 2434 fm.write('compengines', _('checking registered compression engines (%s)\n'),
2434 2435 fm.formatlist(sorted(e.name() for e in compengines),
2435 2436 name='compengine', fmt='%s', sep=', '))
2436 2437 fm.write('compenginesavail', _('checking available compression engines '
2437 2438 '(%s)\n'),
2438 2439 fm.formatlist(sorted(e.name() for e in compengines
2439 2440 if e.available()),
2440 2441 name='compengine', fmt='%s', sep=', '))
2441 2442
2442 2443 # templates
2443 2444 p = templater.templatepaths()
2444 2445 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
2445 2446 fm.condwrite(not p, '', _(" no template directories found\n"))
2446 2447 if p:
2447 2448 m = templater.templatepath("map-cmdline.default")
2448 2449 if m:
2449 2450 # template found, check if it is working
2450 2451 err = None
2451 2452 try:
2452 2453 templater.templater.frommapfile(m)
2453 2454 except Exception as inst:
2454 2455 err = inst
2455 2456 p = None
2456 2457 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
2457 2458 else:
2458 2459 p = None
2459 2460 fm.condwrite(p, 'defaulttemplate',
2460 2461 _("checking default template (%s)\n"), m)
2461 2462 fm.condwrite(not m, 'defaulttemplatenotfound',
2462 2463 _(" template '%s' not found\n"), "default")
2463 2464 if not p:
2464 2465 problems += 1
2465 2466 fm.condwrite(not p, '',
2466 2467 _(" (templates seem to have been installed incorrectly)\n"))
2467 2468
2468 2469 # editor
2469 2470 editor = ui.geteditor()
2470 2471 editor = util.expandpath(editor)
2471 2472 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
2472 2473 cmdpath = util.findexe(shlex.split(editor)[0])
2473 2474 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
2474 2475 _(" No commit editor set and can't find %s in PATH\n"
2475 2476 " (specify a commit editor in your configuration"
2476 2477 " file)\n"), not cmdpath and editor == 'vi' and editor)
2477 2478 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
2478 2479 _(" Can't find editor '%s' in PATH\n"
2479 2480 " (specify a commit editor in your configuration"
2480 2481 " file)\n"), not cmdpath and editor)
2481 2482 if not cmdpath and editor != 'vi':
2482 2483 problems += 1
2483 2484
2484 2485 # check username
2485 2486 username = None
2486 2487 err = None
2487 2488 try:
2488 2489 username = ui.username()
2489 2490 except error.Abort as e:
2490 2491 err = e
2491 2492 problems += 1
2492 2493
2493 2494 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
2494 2495 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
2495 2496 " (specify a username in your configuration file)\n"), err)
2496 2497
2497 2498 fm.condwrite(not problems, '',
2498 2499 _("no problems detected\n"))
2499 2500 if not problems:
2500 2501 fm.data(problems=problems)
2501 2502 fm.condwrite(problems, 'problems',
2502 2503 _("%d problems detected,"
2503 2504 " please check your install!\n"), problems)
2504 2505 fm.end()
2505 2506
2506 2507 return problems
2507 2508
2508 2509 @command('debugknown', [], _('REPO ID...'), norepo=True)
2509 2510 def debugknown(ui, repopath, *ids, **opts):
2510 2511 """test whether node ids are known to a repo
2511 2512
2512 2513 Every ID must be a full-length hex node id string. Returns a list of 0s
2513 2514 and 1s indicating unknown/known.
2514 2515 """
2515 2516 repo = hg.peer(ui, opts, repopath)
2516 2517 if not repo.capable('known'):
2517 2518 raise error.Abort("known() not supported by target repository")
2518 2519 flags = repo.known([bin(s) for s in ids])
2519 2520 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2520 2521
2521 2522 @command('debuglabelcomplete', [], _('LABEL...'))
2522 2523 def debuglabelcomplete(ui, repo, *args):
2523 2524 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2524 2525 debugnamecomplete(ui, repo, *args)
2525 2526
2526 2527 @command('debugmergestate', [], '')
2527 2528 def debugmergestate(ui, repo, *args):
2528 2529 """print merge state
2529 2530
2530 2531 Use --verbose to print out information about whether v1 or v2 merge state
2531 2532 was chosen."""
2532 2533 def _hashornull(h):
2533 2534 if h == nullhex:
2534 2535 return 'null'
2535 2536 else:
2536 2537 return h
2537 2538
2538 2539 def printrecords(version):
2539 2540 ui.write(('* version %s records\n') % version)
2540 2541 if version == 1:
2541 2542 records = v1records
2542 2543 else:
2543 2544 records = v2records
2544 2545
2545 2546 for rtype, record in records:
2546 2547 # pretty print some record types
2547 2548 if rtype == 'L':
2548 2549 ui.write(('local: %s\n') % record)
2549 2550 elif rtype == 'O':
2550 2551 ui.write(('other: %s\n') % record)
2551 2552 elif rtype == 'm':
2552 2553 driver, mdstate = record.split('\0', 1)
2553 2554 ui.write(('merge driver: %s (state "%s")\n')
2554 2555 % (driver, mdstate))
2555 2556 elif rtype in 'FDC':
2556 2557 r = record.split('\0')
2557 2558 f, state, hash, lfile, afile, anode, ofile = r[0:7]
2558 2559 if version == 1:
2559 2560 onode = 'not stored in v1 format'
2560 2561 flags = r[7]
2561 2562 else:
2562 2563 onode, flags = r[7:9]
2563 2564 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
2564 2565 % (f, rtype, state, _hashornull(hash)))
2565 2566 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
2566 2567 ui.write((' ancestor path: %s (node %s)\n')
2567 2568 % (afile, _hashornull(anode)))
2568 2569 ui.write((' other path: %s (node %s)\n')
2569 2570 % (ofile, _hashornull(onode)))
2570 2571 elif rtype == 'f':
2571 2572 filename, rawextras = record.split('\0', 1)
2572 2573 extras = rawextras.split('\0')
2573 2574 i = 0
2574 2575 extrastrings = []
2575 2576 while i < len(extras):
2576 2577 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
2577 2578 i += 2
2578 2579
2579 2580 ui.write(('file extras: %s (%s)\n')
2580 2581 % (filename, ', '.join(extrastrings)))
2581 2582 elif rtype == 'l':
2582 2583 labels = record.split('\0', 2)
2583 2584 labels = [l for l in labels if len(l) > 0]
2584 2585 ui.write(('labels:\n'))
2585 2586 ui.write((' local: %s\n' % labels[0]))
2586 2587 ui.write((' other: %s\n' % labels[1]))
2587 2588 if len(labels) > 2:
2588 2589 ui.write((' base: %s\n' % labels[2]))
2589 2590 else:
2590 2591 ui.write(('unrecognized entry: %s\t%s\n')
2591 2592 % (rtype, record.replace('\0', '\t')))
2592 2593
2593 2594 # Avoid mergestate.read() since it may raise an exception for unsupported
2594 2595 # merge state records. We shouldn't be doing this, but this is OK since this
2595 2596 # command is pretty low-level.
2596 2597 ms = mergemod.mergestate(repo)
2597 2598
2598 2599 # sort so that reasonable information is on top
2599 2600 v1records = ms._readrecordsv1()
2600 2601 v2records = ms._readrecordsv2()
2601 2602 order = 'LOml'
2602 2603 def key(r):
2603 2604 idx = order.find(r[0])
2604 2605 if idx == -1:
2605 2606 return (1, r[1])
2606 2607 else:
2607 2608 return (0, idx)
2608 2609 v1records.sort(key=key)
2609 2610 v2records.sort(key=key)
2610 2611
2611 2612 if not v1records and not v2records:
2612 2613 ui.write(('no merge state found\n'))
2613 2614 elif not v2records:
2614 2615 ui.note(('no version 2 merge state\n'))
2615 2616 printrecords(1)
2616 2617 elif ms._v1v2match(v1records, v2records):
2617 2618 ui.note(('v1 and v2 states match: using v2\n'))
2618 2619 printrecords(2)
2619 2620 else:
2620 2621 ui.note(('v1 and v2 states mismatch: using v1\n'))
2621 2622 printrecords(1)
2622 2623 if ui.verbose:
2623 2624 printrecords(2)
2624 2625
2625 2626 @command('debugnamecomplete', [], _('NAME...'))
2626 2627 def debugnamecomplete(ui, repo, *args):
2627 2628 '''complete "names" - tags, open branch names, bookmark names'''
2628 2629
2629 2630 names = set()
2630 2631 # since we previously only listed open branches, we will handle that
2631 2632 # specially (after this for loop)
2632 2633 for name, ns in repo.names.iteritems():
2633 2634 if name != 'branches':
2634 2635 names.update(ns.listnames(repo))
2635 2636 names.update(tag for (tag, heads, tip, closed)
2636 2637 in repo.branchmap().iterbranches() if not closed)
2637 2638 completions = set()
2638 2639 if not args:
2639 2640 args = ['']
2640 2641 for a in args:
2641 2642 completions.update(n for n in names if n.startswith(a))
2642 2643 ui.write('\n'.join(sorted(completions)))
2643 2644 ui.write('\n')
2644 2645
2645 2646 @command('debuglocks',
2646 2647 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2647 2648 ('W', 'force-wlock', None,
2648 2649 _('free the working state lock (DANGEROUS)'))],
2649 2650 _('[OPTION]...'))
2650 2651 def debuglocks(ui, repo, **opts):
2651 2652 """show or modify state of locks
2652 2653
2653 2654 By default, this command will show which locks are held. This
2654 2655 includes the user and process holding the lock, the amount of time
2655 2656 the lock has been held, and the machine name where the process is
2656 2657 running if it's not local.
2657 2658
2658 2659 Locks protect the integrity of Mercurial's data, so should be
2659 2660 treated with care. System crashes or other interruptions may cause
2660 2661 locks to not be properly released, though Mercurial will usually
2661 2662 detect and remove such stale locks automatically.
2662 2663
2663 2664 However, detecting stale locks may not always be possible (for
2664 2665 instance, on a shared filesystem). Removing locks may also be
2665 2666 blocked by filesystem permissions.
2666 2667
2667 2668 Returns 0 if no locks are held.
2668 2669
2669 2670 """
2670 2671
2671 2672 if opts.get('force_lock'):
2672 2673 repo.svfs.unlink('lock')
2673 2674 if opts.get('force_wlock'):
2674 2675 repo.vfs.unlink('wlock')
2675 2676 if opts.get('force_lock') or opts.get('force_lock'):
2676 2677 return 0
2677 2678
2678 2679 now = time.time()
2679 2680 held = 0
2680 2681
2681 2682 def report(vfs, name, method):
2682 2683 # this causes stale locks to get reaped for more accurate reporting
2683 2684 try:
2684 2685 l = method(False)
2685 2686 except error.LockHeld:
2686 2687 l = None
2687 2688
2688 2689 if l:
2689 2690 l.release()
2690 2691 else:
2691 2692 try:
2692 2693 stat = vfs.lstat(name)
2693 2694 age = now - stat.st_mtime
2694 2695 user = util.username(stat.st_uid)
2695 2696 locker = vfs.readlock(name)
2696 2697 if ":" in locker:
2697 2698 host, pid = locker.split(':')
2698 2699 if host == socket.gethostname():
2699 2700 locker = 'user %s, process %s' % (user, pid)
2700 2701 else:
2701 2702 locker = 'user %s, process %s, host %s' \
2702 2703 % (user, pid, host)
2703 2704 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
2704 2705 return 1
2705 2706 except OSError as e:
2706 2707 if e.errno != errno.ENOENT:
2707 2708 raise
2708 2709
2709 2710 ui.write(("%-6s free\n") % (name + ":"))
2710 2711 return 0
2711 2712
2712 2713 held += report(repo.svfs, "lock", repo.lock)
2713 2714 held += report(repo.vfs, "wlock", repo.wlock)
2714 2715
2715 2716 return held
2716 2717
2717 2718 @command('debugobsolete',
2718 2719 [('', 'flags', 0, _('markers flag')),
2719 2720 ('', 'record-parents', False,
2720 2721 _('record parent information for the precursor')),
2721 2722 ('r', 'rev', [], _('display markers relevant to REV')),
2722 2723 ('', 'index', False, _('display index of the marker')),
2723 2724 ('', 'delete', [], _('delete markers specified by indices')),
2724 2725 ] + commitopts2 + formatteropts,
2725 2726 _('[OBSOLETED [REPLACEMENT ...]]'))
2726 2727 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2727 2728 """create arbitrary obsolete marker
2728 2729
2729 2730 With no arguments, displays the list of obsolescence markers."""
2730 2731
2731 2732 def parsenodeid(s):
2732 2733 try:
2733 2734 # We do not use revsingle/revrange functions here to accept
2734 2735 # arbitrary node identifiers, possibly not present in the
2735 2736 # local repository.
2736 2737 n = bin(s)
2737 2738 if len(n) != len(nullid):
2738 2739 raise TypeError()
2739 2740 return n
2740 2741 except TypeError:
2741 2742 raise error.Abort('changeset references must be full hexadecimal '
2742 2743 'node identifiers')
2743 2744
2744 2745 if opts.get('delete'):
2745 2746 indices = []
2746 2747 for v in opts.get('delete'):
2747 2748 try:
2748 2749 indices.append(int(v))
2749 2750 except ValueError:
2750 2751 raise error.Abort(_('invalid index value: %r') % v,
2751 2752 hint=_('use integers for indices'))
2752 2753
2753 2754 if repo.currenttransaction():
2754 2755 raise error.Abort(_('cannot delete obsmarkers in the middle '
2755 2756 'of transaction.'))
2756 2757
2757 2758 with repo.lock():
2758 2759 n = repair.deleteobsmarkers(repo.obsstore, indices)
2759 2760 ui.write(_('deleted %i obsolescence markers\n') % n)
2760 2761
2761 2762 return
2762 2763
2763 2764 if precursor is not None:
2764 2765 if opts['rev']:
2765 2766 raise error.Abort('cannot select revision when creating marker')
2766 2767 metadata = {}
2767 2768 metadata['user'] = opts['user'] or ui.username()
2768 2769 succs = tuple(parsenodeid(succ) for succ in successors)
2769 2770 l = repo.lock()
2770 2771 try:
2771 2772 tr = repo.transaction('debugobsolete')
2772 2773 try:
2773 2774 date = opts.get('date')
2774 2775 if date:
2775 2776 date = util.parsedate(date)
2776 2777 else:
2777 2778 date = None
2778 2779 prec = parsenodeid(precursor)
2779 2780 parents = None
2780 2781 if opts['record_parents']:
2781 2782 if prec not in repo.unfiltered():
2782 2783 raise error.Abort('cannot used --record-parents on '
2783 2784 'unknown changesets')
2784 2785 parents = repo.unfiltered()[prec].parents()
2785 2786 parents = tuple(p.node() for p in parents)
2786 2787 repo.obsstore.create(tr, prec, succs, opts['flags'],
2787 2788 parents=parents, date=date,
2788 2789 metadata=metadata)
2789 2790 tr.close()
2790 2791 except ValueError as exc:
2791 2792 raise error.Abort(_('bad obsmarker input: %s') % exc)
2792 2793 finally:
2793 2794 tr.release()
2794 2795 finally:
2795 2796 l.release()
2796 2797 else:
2797 2798 if opts['rev']:
2798 2799 revs = scmutil.revrange(repo, opts['rev'])
2799 2800 nodes = [repo[r].node() for r in revs]
2800 2801 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2801 2802 markers.sort(key=lambda x: x._data)
2802 2803 else:
2803 2804 markers = obsolete.getmarkers(repo)
2804 2805
2805 2806 markerstoiter = markers
2806 2807 isrelevant = lambda m: True
2807 2808 if opts.get('rev') and opts.get('index'):
2808 2809 markerstoiter = obsolete.getmarkers(repo)
2809 2810 markerset = set(markers)
2810 2811 isrelevant = lambda m: m in markerset
2811 2812
2812 2813 fm = ui.formatter('debugobsolete', opts)
2813 2814 for i, m in enumerate(markerstoiter):
2814 2815 if not isrelevant(m):
2815 2816 # marker can be irrelevant when we're iterating over a set
2816 2817 # of markers (markerstoiter) which is bigger than the set
2817 2818 # of markers we want to display (markers)
2818 2819 # this can happen if both --index and --rev options are
2819 2820 # provided and thus we need to iterate over all of the markers
2820 2821 # to get the correct indices, but only display the ones that
2821 2822 # are relevant to --rev value
2822 2823 continue
2823 2824 fm.startitem()
2824 2825 ind = i if opts.get('index') else None
2825 2826 cmdutil.showmarker(fm, m, index=ind)
2826 2827 fm.end()
2827 2828
2828 2829 @command('debugpathcomplete',
2829 2830 [('f', 'full', None, _('complete an entire path')),
2830 2831 ('n', 'normal', None, _('show only normal files')),
2831 2832 ('a', 'added', None, _('show only added files')),
2832 2833 ('r', 'removed', None, _('show only removed files'))],
2833 2834 _('FILESPEC...'))
2834 2835 def debugpathcomplete(ui, repo, *specs, **opts):
2835 2836 '''complete part or all of a tracked path
2836 2837
2837 2838 This command supports shells that offer path name completion. It
2838 2839 currently completes only files already known to the dirstate.
2839 2840
2840 2841 Completion extends only to the next path segment unless
2841 2842 --full is specified, in which case entire paths are used.'''
2842 2843
2843 2844 def complete(path, acceptable):
2844 2845 dirstate = repo.dirstate
2845 2846 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2846 2847 rootdir = repo.root + os.sep
2847 2848 if spec != repo.root and not spec.startswith(rootdir):
2848 2849 return [], []
2849 2850 if os.path.isdir(spec):
2850 2851 spec += '/'
2851 2852 spec = spec[len(rootdir):]
2852 2853 fixpaths = pycompat.ossep != '/'
2853 2854 if fixpaths:
2854 2855 spec = spec.replace(os.sep, '/')
2855 2856 speclen = len(spec)
2856 2857 fullpaths = opts['full']
2857 2858 files, dirs = set(), set()
2858 2859 adddir, addfile = dirs.add, files.add
2859 2860 for f, st in dirstate.iteritems():
2860 2861 if f.startswith(spec) and st[0] in acceptable:
2861 2862 if fixpaths:
2862 2863 f = f.replace('/', os.sep)
2863 2864 if fullpaths:
2864 2865 addfile(f)
2865 2866 continue
2866 2867 s = f.find(os.sep, speclen)
2867 2868 if s >= 0:
2868 2869 adddir(f[:s])
2869 2870 else:
2870 2871 addfile(f)
2871 2872 return files, dirs
2872 2873
2873 2874 acceptable = ''
2874 2875 if opts['normal']:
2875 2876 acceptable += 'nm'
2876 2877 if opts['added']:
2877 2878 acceptable += 'a'
2878 2879 if opts['removed']:
2879 2880 acceptable += 'r'
2880 2881 cwd = repo.getcwd()
2881 2882 if not specs:
2882 2883 specs = ['.']
2883 2884
2884 2885 files, dirs = set(), set()
2885 2886 for spec in specs:
2886 2887 f, d = complete(spec, acceptable or 'nmar')
2887 2888 files.update(f)
2888 2889 dirs.update(d)
2889 2890 files.update(dirs)
2890 2891 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2891 2892 ui.write('\n')
2892 2893
2893 2894 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2894 2895 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2895 2896 '''access the pushkey key/value protocol
2896 2897
2897 2898 With two args, list the keys in the given namespace.
2898 2899
2899 2900 With five args, set a key to new if it currently is set to old.
2900 2901 Reports success or failure.
2901 2902 '''
2902 2903
2903 2904 target = hg.peer(ui, {}, repopath)
2904 2905 if keyinfo:
2905 2906 key, old, new = keyinfo
2906 2907 r = target.pushkey(namespace, key, old, new)
2907 2908 ui.status(str(r) + '\n')
2908 2909 return not r
2909 2910 else:
2910 2911 for k, v in sorted(target.listkeys(namespace).iteritems()):
2911 2912 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2912 2913 v.encode('string-escape')))
2913 2914
2914 2915 @command('debugpvec', [], _('A B'))
2915 2916 def debugpvec(ui, repo, a, b=None):
2916 2917 ca = scmutil.revsingle(repo, a)
2917 2918 cb = scmutil.revsingle(repo, b)
2918 2919 pa = pvec.ctxpvec(ca)
2919 2920 pb = pvec.ctxpvec(cb)
2920 2921 if pa == pb:
2921 2922 rel = "="
2922 2923 elif pa > pb:
2923 2924 rel = ">"
2924 2925 elif pa < pb:
2925 2926 rel = "<"
2926 2927 elif pa | pb:
2927 2928 rel = "|"
2928 2929 ui.write(_("a: %s\n") % pa)
2929 2930 ui.write(_("b: %s\n") % pb)
2930 2931 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2931 2932 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2932 2933 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2933 2934 pa.distance(pb), rel))
2934 2935
2935 2936 @command('debugrebuilddirstate|debugrebuildstate',
2936 2937 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
2937 2938 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
2938 2939 'the working copy parent')),
2939 2940 ],
2940 2941 _('[-r REV]'))
2941 2942 def debugrebuilddirstate(ui, repo, rev, **opts):
2942 2943 """rebuild the dirstate as it would look like for the given revision
2943 2944
2944 2945 If no revision is specified the first current parent will be used.
2945 2946
2946 2947 The dirstate will be set to the files of the given revision.
2947 2948 The actual working directory content or existing dirstate
2948 2949 information such as adds or removes is not considered.
2949 2950
2950 2951 ``minimal`` will only rebuild the dirstate status for files that claim to be
2951 2952 tracked but are not in the parent manifest, or that exist in the parent
2952 2953 manifest but are not in the dirstate. It will not change adds, removes, or
2953 2954 modified files that are in the working copy parent.
2954 2955
2955 2956 One use of this command is to make the next :hg:`status` invocation
2956 2957 check the actual file content.
2957 2958 """
2958 2959 ctx = scmutil.revsingle(repo, rev)
2959 2960 with repo.wlock():
2960 2961 dirstate = repo.dirstate
2961 2962 changedfiles = None
2962 2963 # See command doc for what minimal does.
2963 2964 if opts.get('minimal'):
2964 2965 manifestfiles = set(ctx.manifest().keys())
2965 2966 dirstatefiles = set(dirstate)
2966 2967 manifestonly = manifestfiles - dirstatefiles
2967 2968 dsonly = dirstatefiles - manifestfiles
2968 2969 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
2969 2970 changedfiles = manifestonly | dsnotadded
2970 2971
2971 2972 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2972 2973
2973 2974 @command('debugrebuildfncache', [], '')
2974 2975 def debugrebuildfncache(ui, repo):
2975 2976 """rebuild the fncache file"""
2976 2977 repair.rebuildfncache(ui, repo)
2977 2978
2978 2979 @command('debugrename',
2979 2980 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2980 2981 _('[-r REV] FILE'))
2981 2982 def debugrename(ui, repo, file1, *pats, **opts):
2982 2983 """dump rename information"""
2983 2984
2984 2985 ctx = scmutil.revsingle(repo, opts.get('rev'))
2985 2986 m = scmutil.match(ctx, (file1,) + pats, opts)
2986 2987 for abs in ctx.walk(m):
2987 2988 fctx = ctx[abs]
2988 2989 o = fctx.filelog().renamed(fctx.filenode())
2989 2990 rel = m.rel(abs)
2990 2991 if o:
2991 2992 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2992 2993 else:
2993 2994 ui.write(_("%s not renamed\n") % rel)
2994 2995
2995 2996 @command('debugrevlog', debugrevlogopts +
2996 2997 [('d', 'dump', False, _('dump index data'))],
2997 2998 _('-c|-m|FILE'),
2998 2999 optionalrepo=True)
2999 3000 def debugrevlog(ui, repo, file_=None, **opts):
3000 3001 """show data and statistics about a revlog"""
3001 3002 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
3002 3003
3003 3004 if opts.get("dump"):
3004 3005 numrevs = len(r)
3005 3006 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
3006 3007 " rawsize totalsize compression heads chainlen\n"))
3007 3008 ts = 0
3008 3009 heads = set()
3009 3010
3010 3011 for rev in xrange(numrevs):
3011 3012 dbase = r.deltaparent(rev)
3012 3013 if dbase == -1:
3013 3014 dbase = rev
3014 3015 cbase = r.chainbase(rev)
3015 3016 clen = r.chainlen(rev)
3016 3017 p1, p2 = r.parentrevs(rev)
3017 3018 rs = r.rawsize(rev)
3018 3019 ts = ts + rs
3019 3020 heads -= set(r.parentrevs(rev))
3020 3021 heads.add(rev)
3021 3022 try:
3022 3023 compression = ts / r.end(rev)
3023 3024 except ZeroDivisionError:
3024 3025 compression = 0
3025 3026 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
3026 3027 "%11d %5d %8d\n" %
3027 3028 (rev, p1, p2, r.start(rev), r.end(rev),
3028 3029 r.start(dbase), r.start(cbase),
3029 3030 r.start(p1), r.start(p2),
3030 3031 rs, ts, compression, len(heads), clen))
3031 3032 return 0
3032 3033
3033 3034 v = r.version
3034 3035 format = v & 0xFFFF
3035 3036 flags = []
3036 3037 gdelta = False
3037 3038 if v & revlog.REVLOGNGINLINEDATA:
3038 3039 flags.append('inline')
3039 3040 if v & revlog.REVLOGGENERALDELTA:
3040 3041 gdelta = True
3041 3042 flags.append('generaldelta')
3042 3043 if not flags:
3043 3044 flags = ['(none)']
3044 3045
3045 3046 nummerges = 0
3046 3047 numfull = 0
3047 3048 numprev = 0
3048 3049 nump1 = 0
3049 3050 nump2 = 0
3050 3051 numother = 0
3051 3052 nump1prev = 0
3052 3053 nump2prev = 0
3053 3054 chainlengths = []
3054 3055
3055 3056 datasize = [None, 0, 0]
3056 3057 fullsize = [None, 0, 0]
3057 3058 deltasize = [None, 0, 0]
3058 3059 chunktypecounts = {}
3059 3060 chunktypesizes = {}
3060 3061
3061 3062 def addsize(size, l):
3062 3063 if l[0] is None or size < l[0]:
3063 3064 l[0] = size
3064 3065 if size > l[1]:
3065 3066 l[1] = size
3066 3067 l[2] += size
3067 3068
3068 3069 numrevs = len(r)
3069 3070 for rev in xrange(numrevs):
3070 3071 p1, p2 = r.parentrevs(rev)
3071 3072 delta = r.deltaparent(rev)
3072 3073 if format > 0:
3073 3074 addsize(r.rawsize(rev), datasize)
3074 3075 if p2 != nullrev:
3075 3076 nummerges += 1
3076 3077 size = r.length(rev)
3077 3078 if delta == nullrev:
3078 3079 chainlengths.append(0)
3079 3080 numfull += 1
3080 3081 addsize(size, fullsize)
3081 3082 else:
3082 3083 chainlengths.append(chainlengths[delta] + 1)
3083 3084 addsize(size, deltasize)
3084 3085 if delta == rev - 1:
3085 3086 numprev += 1
3086 3087 if delta == p1:
3087 3088 nump1prev += 1
3088 3089 elif delta == p2:
3089 3090 nump2prev += 1
3090 3091 elif delta == p1:
3091 3092 nump1 += 1
3092 3093 elif delta == p2:
3093 3094 nump2 += 1
3094 3095 elif delta != nullrev:
3095 3096 numother += 1
3096 3097
3097 3098 # Obtain data on the raw chunks in the revlog.
3098 3099 chunk = r._chunkraw(rev, rev)[1]
3099 3100 if chunk:
3100 3101 chunktype = chunk[0]
3101 3102 else:
3102 3103 chunktype = 'empty'
3103 3104
3104 3105 if chunktype not in chunktypecounts:
3105 3106 chunktypecounts[chunktype] = 0
3106 3107 chunktypesizes[chunktype] = 0
3107 3108
3108 3109 chunktypecounts[chunktype] += 1
3109 3110 chunktypesizes[chunktype] += size
3110 3111
3111 3112 # Adjust size min value for empty cases
3112 3113 for size in (datasize, fullsize, deltasize):
3113 3114 if size[0] is None:
3114 3115 size[0] = 0
3115 3116
3116 3117 numdeltas = numrevs - numfull
3117 3118 numoprev = numprev - nump1prev - nump2prev
3118 3119 totalrawsize = datasize[2]
3119 3120 datasize[2] /= numrevs
3120 3121 fulltotal = fullsize[2]
3121 3122 fullsize[2] /= numfull
3122 3123 deltatotal = deltasize[2]
3123 3124 if numrevs - numfull > 0:
3124 3125 deltasize[2] /= numrevs - numfull
3125 3126 totalsize = fulltotal + deltatotal
3126 3127 avgchainlen = sum(chainlengths) / numrevs
3127 3128 maxchainlen = max(chainlengths)
3128 3129 compratio = 1
3129 3130 if totalsize:
3130 3131 compratio = totalrawsize / totalsize
3131 3132
3132 3133 basedfmtstr = '%%%dd\n'
3133 3134 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
3134 3135
3135 3136 def dfmtstr(max):
3136 3137 return basedfmtstr % len(str(max))
3137 3138 def pcfmtstr(max, padding=0):
3138 3139 return basepcfmtstr % (len(str(max)), ' ' * padding)
3139 3140
3140 3141 def pcfmt(value, total):
3141 3142 if total:
3142 3143 return (value, 100 * float(value) / total)
3143 3144 else:
3144 3145 return value, 100.0
3145 3146
3146 3147 ui.write(('format : %d\n') % format)
3147 3148 ui.write(('flags : %s\n') % ', '.join(flags))
3148 3149
3149 3150 ui.write('\n')
3150 3151 fmt = pcfmtstr(totalsize)
3151 3152 fmt2 = dfmtstr(totalsize)
3152 3153 ui.write(('revisions : ') + fmt2 % numrevs)
3153 3154 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
3154 3155 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
3155 3156 ui.write(('revisions : ') + fmt2 % numrevs)
3156 3157 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
3157 3158 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
3158 3159 ui.write(('revision size : ') + fmt2 % totalsize)
3159 3160 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
3160 3161 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
3161 3162
3162 3163 def fmtchunktype(chunktype):
3163 3164 if chunktype == 'empty':
3164 3165 return ' %s : ' % chunktype
3165 3166 elif chunktype in string.ascii_letters:
3166 3167 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3167 3168 else:
3168 3169 return ' 0x%s : ' % hex(chunktype)
3169 3170
3170 3171 ui.write('\n')
3171 3172 ui.write(('chunks : ') + fmt2 % numrevs)
3172 3173 for chunktype in sorted(chunktypecounts):
3173 3174 ui.write(fmtchunktype(chunktype))
3174 3175 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3175 3176 ui.write(('chunks size : ') + fmt2 % totalsize)
3176 3177 for chunktype in sorted(chunktypecounts):
3177 3178 ui.write(fmtchunktype(chunktype))
3178 3179 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3179 3180
3180 3181 ui.write('\n')
3181 3182 fmt = dfmtstr(max(avgchainlen, compratio))
3182 3183 ui.write(('avg chain length : ') + fmt % avgchainlen)
3183 3184 ui.write(('max chain length : ') + fmt % maxchainlen)
3184 3185 ui.write(('compression ratio : ') + fmt % compratio)
3185 3186
3186 3187 if format > 0:
3187 3188 ui.write('\n')
3188 3189 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
3189 3190 % tuple(datasize))
3190 3191 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
3191 3192 % tuple(fullsize))
3192 3193 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
3193 3194 % tuple(deltasize))
3194 3195
3195 3196 if numdeltas > 0:
3196 3197 ui.write('\n')
3197 3198 fmt = pcfmtstr(numdeltas)
3198 3199 fmt2 = pcfmtstr(numdeltas, 4)
3199 3200 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
3200 3201 if numprev > 0:
3201 3202 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
3202 3203 numprev))
3203 3204 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
3204 3205 numprev))
3205 3206 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
3206 3207 numprev))
3207 3208 if gdelta:
3208 3209 ui.write(('deltas against p1 : ')
3209 3210 + fmt % pcfmt(nump1, numdeltas))
3210 3211 ui.write(('deltas against p2 : ')
3211 3212 + fmt % pcfmt(nump2, numdeltas))
3212 3213 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
3213 3214 numdeltas))
3214 3215
3215 3216 @command('debugrevspec',
3216 3217 [('', 'optimize', None,
3217 3218 _('print parsed tree after optimizing (DEPRECATED)')),
3218 3219 ('p', 'show-stage', [],
3219 3220 _('print parsed tree at the given stage'), _('NAME')),
3220 3221 ('', 'no-optimized', False, _('evaluate tree without optimization')),
3221 3222 ('', 'verify-optimized', False, _('verify optimized result')),
3222 3223 ],
3223 3224 ('REVSPEC'))
3224 3225 def debugrevspec(ui, repo, expr, **opts):
3225 3226 """parse and apply a revision specification
3226 3227
3227 3228 Use -p/--show-stage option to print the parsed tree at the given stages.
3228 3229 Use -p all to print tree at every stage.
3229 3230
3230 3231 Use --verify-optimized to compare the optimized result with the unoptimized
3231 3232 one. Returns 1 if the optimized result differs.
3232 3233 """
3233 3234 stages = [
3234 3235 ('parsed', lambda tree: tree),
3235 3236 ('expanded', lambda tree: revset.expandaliases(ui, tree)),
3236 3237 ('concatenated', revset.foldconcat),
3237 3238 ('analyzed', revset.analyze),
3238 3239 ('optimized', revset.optimize),
3239 3240 ]
3240 3241 if opts['no_optimized']:
3241 3242 stages = stages[:-1]
3242 3243 if opts['verify_optimized'] and opts['no_optimized']:
3243 3244 raise error.Abort(_('cannot use --verify-optimized with '
3244 3245 '--no-optimized'))
3245 3246 stagenames = set(n for n, f in stages)
3246 3247
3247 3248 showalways = set()
3248 3249 showchanged = set()
3249 3250 if ui.verbose and not opts['show_stage']:
3250 3251 # show parsed tree by --verbose (deprecated)
3251 3252 showalways.add('parsed')
3252 3253 showchanged.update(['expanded', 'concatenated'])
3253 3254 if opts['optimize']:
3254 3255 showalways.add('optimized')
3255 3256 if opts['show_stage'] and opts['optimize']:
3256 3257 raise error.Abort(_('cannot use --optimize with --show-stage'))
3257 3258 if opts['show_stage'] == ['all']:
3258 3259 showalways.update(stagenames)
3259 3260 else:
3260 3261 for n in opts['show_stage']:
3261 3262 if n not in stagenames:
3262 3263 raise error.Abort(_('invalid stage name: %s') % n)
3263 3264 showalways.update(opts['show_stage'])
3264 3265
3265 3266 treebystage = {}
3266 3267 printedtree = None
3267 3268 tree = revset.parse(expr, lookup=repo.__contains__)
3268 3269 for n, f in stages:
3269 3270 treebystage[n] = tree = f(tree)
3270 3271 if n in showalways or (n in showchanged and tree != printedtree):
3271 3272 if opts['show_stage'] or n != 'parsed':
3272 3273 ui.write(("* %s:\n") % n)
3273 3274 ui.write(revset.prettyformat(tree), "\n")
3274 3275 printedtree = tree
3275 3276
3276 3277 if opts['verify_optimized']:
3277 3278 arevs = revset.makematcher(treebystage['analyzed'])(repo)
3278 3279 brevs = revset.makematcher(treebystage['optimized'])(repo)
3279 3280 if ui.verbose:
3280 3281 ui.note(("* analyzed set:\n"), revset.prettyformatset(arevs), "\n")
3281 3282 ui.note(("* optimized set:\n"), revset.prettyformatset(brevs), "\n")
3282 3283 arevs = list(arevs)
3283 3284 brevs = list(brevs)
3284 3285 if arevs == brevs:
3285 3286 return 0
3286 3287 ui.write(('--- analyzed\n'), label='diff.file_a')
3287 3288 ui.write(('+++ optimized\n'), label='diff.file_b')
3288 3289 sm = difflib.SequenceMatcher(None, arevs, brevs)
3289 3290 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3290 3291 if tag in ('delete', 'replace'):
3291 3292 for c in arevs[alo:ahi]:
3292 3293 ui.write('-%s\n' % c, label='diff.deleted')
3293 3294 if tag in ('insert', 'replace'):
3294 3295 for c in brevs[blo:bhi]:
3295 3296 ui.write('+%s\n' % c, label='diff.inserted')
3296 3297 if tag == 'equal':
3297 3298 for c in arevs[alo:ahi]:
3298 3299 ui.write(' %s\n' % c)
3299 3300 return 1
3300 3301
3301 3302 func = revset.makematcher(tree)
3302 3303 revs = func(repo)
3303 3304 if ui.verbose:
3304 3305 ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
3305 3306 for c in revs:
3306 3307 ui.write("%s\n" % c)
3307 3308
3308 3309 @command('debugsetparents', [], _('REV1 [REV2]'))
3309 3310 def debugsetparents(ui, repo, rev1, rev2=None):
3310 3311 """manually set the parents of the current working directory
3311 3312
3312 3313 This is useful for writing repository conversion tools, but should
3313 3314 be used with care. For example, neither the working directory nor the
3314 3315 dirstate is updated, so file status may be incorrect after running this
3315 3316 command.
3316 3317
3317 3318 Returns 0 on success.
3318 3319 """
3319 3320
3320 3321 r1 = scmutil.revsingle(repo, rev1).node()
3321 3322 r2 = scmutil.revsingle(repo, rev2, 'null').node()
3322 3323
3323 3324 with repo.wlock():
3324 3325 repo.setparents(r1, r2)
3325 3326
3326 3327 @command('debugdirstate|debugstate',
3327 3328 [('', 'nodates', None, _('do not display the saved mtime')),
3328 3329 ('', 'datesort', None, _('sort by saved mtime'))],
3329 3330 _('[OPTION]...'))
3330 3331 def debugstate(ui, repo, **opts):
3331 3332 """show the contents of the current dirstate"""
3332 3333
3333 3334 nodates = opts.get('nodates')
3334 3335 datesort = opts.get('datesort')
3335 3336
3336 3337 timestr = ""
3337 3338 if datesort:
3338 3339 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
3339 3340 else:
3340 3341 keyfunc = None # sort by filename
3341 3342 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
3342 3343 if ent[3] == -1:
3343 3344 timestr = 'unset '
3344 3345 elif nodates:
3345 3346 timestr = 'set '
3346 3347 else:
3347 3348 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
3348 3349 time.localtime(ent[3]))
3349 3350 if ent[1] & 0o20000:
3350 3351 mode = 'lnk'
3351 3352 else:
3352 3353 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
3353 3354 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
3354 3355 for f in repo.dirstate.copies():
3355 3356 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
3356 3357
3357 3358 @command('debugsub',
3358 3359 [('r', 'rev', '',
3359 3360 _('revision to check'), _('REV'))],
3360 3361 _('[-r REV] [REV]'))
3361 3362 def debugsub(ui, repo, rev=None):
3362 3363 ctx = scmutil.revsingle(repo, rev, None)
3363 3364 for k, v in sorted(ctx.substate.items()):
3364 3365 ui.write(('path %s\n') % k)
3365 3366 ui.write((' source %s\n') % v[0])
3366 3367 ui.write((' revision %s\n') % v[1])
3367 3368
3368 3369 @command('debugsuccessorssets',
3369 3370 [],
3370 3371 _('[REV]'))
3371 3372 def debugsuccessorssets(ui, repo, *revs):
3372 3373 """show set of successors for revision
3373 3374
3374 3375 A successors set of changeset A is a consistent group of revisions that
3375 3376 succeed A. It contains non-obsolete changesets only.
3376 3377
3377 3378 In most cases a changeset A has a single successors set containing a single
3378 3379 successor (changeset A replaced by A').
3379 3380
3380 3381 A changeset that is made obsolete with no successors are called "pruned".
3381 3382 Such changesets have no successors sets at all.
3382 3383
3383 3384 A changeset that has been "split" will have a successors set containing
3384 3385 more than one successor.
3385 3386
3386 3387 A changeset that has been rewritten in multiple different ways is called
3387 3388 "divergent". Such changesets have multiple successor sets (each of which
3388 3389 may also be split, i.e. have multiple successors).
3389 3390
3390 3391 Results are displayed as follows::
3391 3392
3392 3393 <rev1>
3393 3394 <successors-1A>
3394 3395 <rev2>
3395 3396 <successors-2A>
3396 3397 <successors-2B1> <successors-2B2> <successors-2B3>
3397 3398
3398 3399 Here rev2 has two possible (i.e. divergent) successors sets. The first
3399 3400 holds one element, whereas the second holds three (i.e. the changeset has
3400 3401 been split).
3401 3402 """
3402 3403 # passed to successorssets caching computation from one call to another
3403 3404 cache = {}
3404 3405 ctx2str = str
3405 3406 node2str = short
3406 3407 if ui.debug():
3407 3408 def ctx2str(ctx):
3408 3409 return ctx.hex()
3409 3410 node2str = hex
3410 3411 for rev in scmutil.revrange(repo, revs):
3411 3412 ctx = repo[rev]
3412 3413 ui.write('%s\n'% ctx2str(ctx))
3413 3414 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3414 3415 if succsset:
3415 3416 ui.write(' ')
3416 3417 ui.write(node2str(succsset[0]))
3417 3418 for node in succsset[1:]:
3418 3419 ui.write(' ')
3419 3420 ui.write(node2str(node))
3420 3421 ui.write('\n')
3421 3422
3422 3423 @command('debugtemplate',
3423 3424 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
3424 3425 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
3425 3426 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3426 3427 optionalrepo=True)
3427 3428 def debugtemplate(ui, repo, tmpl, **opts):
3428 3429 """parse and apply a template
3429 3430
3430 3431 If -r/--rev is given, the template is processed as a log template and
3431 3432 applied to the given changesets. Otherwise, it is processed as a generic
3432 3433 template.
3433 3434
3434 3435 Use --verbose to print the parsed tree.
3435 3436 """
3436 3437 revs = None
3437 3438 if opts['rev']:
3438 3439 if repo is None:
3439 3440 raise error.RepoError(_('there is no Mercurial repository here '
3440 3441 '(.hg not found)'))
3441 3442 revs = scmutil.revrange(repo, opts['rev'])
3442 3443
3443 3444 props = {}
3444 3445 for d in opts['define']:
3445 3446 try:
3446 3447 k, v = (e.strip() for e in d.split('=', 1))
3447 3448 if not k:
3448 3449 raise ValueError
3449 3450 props[k] = v
3450 3451 except ValueError:
3451 3452 raise error.Abort(_('malformed keyword definition: %s') % d)
3452 3453
3453 3454 if ui.verbose:
3454 3455 aliases = ui.configitems('templatealias')
3455 3456 tree = templater.parse(tmpl)
3456 3457 ui.note(templater.prettyformat(tree), '\n')
3457 3458 newtree = templater.expandaliases(tree, aliases)
3458 3459 if newtree != tree:
3459 3460 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
3460 3461
3461 3462 mapfile = None
3462 3463 if revs is None:
3463 3464 k = 'debugtemplate'
3464 3465 t = formatter.maketemplater(ui, k, tmpl)
3465 3466 ui.write(templater.stringify(t(k, **props)))
3466 3467 else:
3467 3468 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
3468 3469 mapfile, buffered=False)
3469 3470 for r in revs:
3470 3471 displayer.show(repo[r], **props)
3471 3472 displayer.close()
3472 3473
3473 3474 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3474 3475 def debugwalk(ui, repo, *pats, **opts):
3475 3476 """show how files match on given patterns"""
3476 3477 m = scmutil.match(repo[None], pats, opts)
3477 3478 items = list(repo.walk(m))
3478 3479 if not items:
3479 3480 return
3480 3481 f = lambda fn: fn
3481 3482 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
3482 3483 f = lambda fn: util.normpath(fn)
3483 3484 fmt = 'f %%-%ds %%-%ds %%s' % (
3484 3485 max([len(abs) for abs in items]),
3485 3486 max([len(m.rel(abs)) for abs in items]))
3486 3487 for abs in items:
3487 3488 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3488 3489 ui.write("%s\n" % line.rstrip())
3489 3490
3490 3491 @command('debugwireargs',
3491 3492 [('', 'three', '', 'three'),
3492 3493 ('', 'four', '', 'four'),
3493 3494 ('', 'five', '', 'five'),
3494 3495 ] + remoteopts,
3495 3496 _('REPO [OPTIONS]... [ONE [TWO]]'),
3496 3497 norepo=True)
3497 3498 def debugwireargs(ui, repopath, *vals, **opts):
3498 3499 repo = hg.peer(ui, opts, repopath)
3499 3500 for opt in remoteopts:
3500 3501 del opts[opt[1]]
3501 3502 args = {}
3502 3503 for k, v in opts.iteritems():
3503 3504 if v:
3504 3505 args[k] = v
3505 3506 # run twice to check that we don't mess up the stream for the next command
3506 3507 res1 = repo.debugwireargs(*vals, **args)
3507 3508 res2 = repo.debugwireargs(*vals, **args)
3508 3509 ui.write("%s\n" % res1)
3509 3510 if res1 != res2:
3510 3511 ui.warn("%s\n" % res2)
3511 3512
3512 3513 @command('^diff',
3513 3514 [('r', 'rev', [], _('revision'), _('REV')),
3514 3515 ('c', 'change', '', _('change made by revision'), _('REV'))
3515 3516 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3516 3517 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3517 3518 inferrepo=True)
3518 3519 def diff(ui, repo, *pats, **opts):
3519 3520 """diff repository (or selected files)
3520 3521
3521 3522 Show differences between revisions for the specified files.
3522 3523
3523 3524 Differences between files are shown using the unified diff format.
3524 3525
3525 3526 .. note::
3526 3527
3527 3528 :hg:`diff` may generate unexpected results for merges, as it will
3528 3529 default to comparing against the working directory's first
3529 3530 parent changeset if no revisions are specified.
3530 3531
3531 3532 When two revision arguments are given, then changes are shown
3532 3533 between those revisions. If only one revision is specified then
3533 3534 that revision is compared to the working directory, and, when no
3534 3535 revisions are specified, the working directory files are compared
3535 3536 to its first parent.
3536 3537
3537 3538 Alternatively you can specify -c/--change with a revision to see
3538 3539 the changes in that changeset relative to its first parent.
3539 3540
3540 3541 Without the -a/--text option, diff will avoid generating diffs of
3541 3542 files it detects as binary. With -a, diff will generate a diff
3542 3543 anyway, probably with undesirable results.
3543 3544
3544 3545 Use the -g/--git option to generate diffs in the git extended diff
3545 3546 format. For more information, read :hg:`help diffs`.
3546 3547
3547 3548 .. container:: verbose
3548 3549
3549 3550 Examples:
3550 3551
3551 3552 - compare a file in the current working directory to its parent::
3552 3553
3553 3554 hg diff foo.c
3554 3555
3555 3556 - compare two historical versions of a directory, with rename info::
3556 3557
3557 3558 hg diff --git -r 1.0:1.2 lib/
3558 3559
3559 3560 - get change stats relative to the last change on some date::
3560 3561
3561 3562 hg diff --stat -r "date('may 2')"
3562 3563
3563 3564 - diff all newly-added files that contain a keyword::
3564 3565
3565 3566 hg diff "set:added() and grep(GNU)"
3566 3567
3567 3568 - compare a revision and its parents::
3568 3569
3569 3570 hg diff -c 9353 # compare against first parent
3570 3571 hg diff -r 9353^:9353 # same using revset syntax
3571 3572 hg diff -r 9353^2:9353 # compare against the second parent
3572 3573
3573 3574 Returns 0 on success.
3574 3575 """
3575 3576
3576 3577 revs = opts.get('rev')
3577 3578 change = opts.get('change')
3578 3579 stat = opts.get('stat')
3579 3580 reverse = opts.get('reverse')
3580 3581
3581 3582 if revs and change:
3582 3583 msg = _('cannot specify --rev and --change at the same time')
3583 3584 raise error.Abort(msg)
3584 3585 elif change:
3585 3586 node2 = scmutil.revsingle(repo, change, None).node()
3586 3587 node1 = repo[node2].p1().node()
3587 3588 else:
3588 3589 node1, node2 = scmutil.revpair(repo, revs)
3589 3590
3590 3591 if reverse:
3591 3592 node1, node2 = node2, node1
3592 3593
3593 3594 diffopts = patch.diffallopts(ui, opts)
3594 3595 m = scmutil.match(repo[node2], pats, opts)
3595 3596 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3596 3597 listsubrepos=opts.get('subrepos'),
3597 3598 root=opts.get('root'))
3598 3599
3599 3600 @command('^export',
3600 3601 [('o', 'output', '',
3601 3602 _('print output to file with formatted name'), _('FORMAT')),
3602 3603 ('', 'switch-parent', None, _('diff against the second parent')),
3603 3604 ('r', 'rev', [], _('revisions to export'), _('REV')),
3604 3605 ] + diffopts,
3605 3606 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3606 3607 def export(ui, repo, *changesets, **opts):
3607 3608 """dump the header and diffs for one or more changesets
3608 3609
3609 3610 Print the changeset header and diffs for one or more revisions.
3610 3611 If no revision is given, the parent of the working directory is used.
3611 3612
3612 3613 The information shown in the changeset header is: author, date,
3613 3614 branch name (if non-default), changeset hash, parent(s) and commit
3614 3615 comment.
3615 3616
3616 3617 .. note::
3617 3618
3618 3619 :hg:`export` may generate unexpected diff output for merge
3619 3620 changesets, as it will compare the merge changeset against its
3620 3621 first parent only.
3621 3622
3622 3623 Output may be to a file, in which case the name of the file is
3623 3624 given using a format string. The formatting rules are as follows:
3624 3625
3625 3626 :``%%``: literal "%" character
3626 3627 :``%H``: changeset hash (40 hexadecimal digits)
3627 3628 :``%N``: number of patches being generated
3628 3629 :``%R``: changeset revision number
3629 3630 :``%b``: basename of the exporting repository
3630 3631 :``%h``: short-form changeset hash (12 hexadecimal digits)
3631 3632 :``%m``: first line of the commit message (only alphanumeric characters)
3632 3633 :``%n``: zero-padded sequence number, starting at 1
3633 3634 :``%r``: zero-padded changeset revision number
3634 3635
3635 3636 Without the -a/--text option, export will avoid generating diffs
3636 3637 of files it detects as binary. With -a, export will generate a
3637 3638 diff anyway, probably with undesirable results.
3638 3639
3639 3640 Use the -g/--git option to generate diffs in the git extended diff
3640 3641 format. See :hg:`help diffs` for more information.
3641 3642
3642 3643 With the --switch-parent option, the diff will be against the
3643 3644 second parent. It can be useful to review a merge.
3644 3645
3645 3646 .. container:: verbose
3646 3647
3647 3648 Examples:
3648 3649
3649 3650 - use export and import to transplant a bugfix to the current
3650 3651 branch::
3651 3652
3652 3653 hg export -r 9353 | hg import -
3653 3654
3654 3655 - export all the changesets between two revisions to a file with
3655 3656 rename information::
3656 3657
3657 3658 hg export --git -r 123:150 > changes.txt
3658 3659
3659 3660 - split outgoing changes into a series of patches with
3660 3661 descriptive names::
3661 3662
3662 3663 hg export -r "outgoing()" -o "%n-%m.patch"
3663 3664
3664 3665 Returns 0 on success.
3665 3666 """
3666 3667 changesets += tuple(opts.get('rev', []))
3667 3668 if not changesets:
3668 3669 changesets = ['.']
3669 3670 revs = scmutil.revrange(repo, changesets)
3670 3671 if not revs:
3671 3672 raise error.Abort(_("export requires at least one changeset"))
3672 3673 if len(revs) > 1:
3673 3674 ui.note(_('exporting patches:\n'))
3674 3675 else:
3675 3676 ui.note(_('exporting patch:\n'))
3676 3677 cmdutil.export(repo, revs, template=opts.get('output'),
3677 3678 switch_parent=opts.get('switch_parent'),
3678 3679 opts=patch.diffallopts(ui, opts))
3679 3680
3680 3681 @command('files',
3681 3682 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3682 3683 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3683 3684 ] + walkopts + formatteropts + subrepoopts,
3684 3685 _('[OPTION]... [FILE]...'))
3685 3686 def files(ui, repo, *pats, **opts):
3686 3687 """list tracked files
3687 3688
3688 3689 Print files under Mercurial control in the working directory or
3689 3690 specified revision for given files (excluding removed files).
3690 3691 Files can be specified as filenames or filesets.
3691 3692
3692 3693 If no files are given to match, this command prints the names
3693 3694 of all files under Mercurial control.
3694 3695
3695 3696 .. container:: verbose
3696 3697
3697 3698 Examples:
3698 3699
3699 3700 - list all files under the current directory::
3700 3701
3701 3702 hg files .
3702 3703
3703 3704 - shows sizes and flags for current revision::
3704 3705
3705 3706 hg files -vr .
3706 3707
3707 3708 - list all files named README::
3708 3709
3709 3710 hg files -I "**/README"
3710 3711
3711 3712 - list all binary files::
3712 3713
3713 3714 hg files "set:binary()"
3714 3715
3715 3716 - find files containing a regular expression::
3716 3717
3717 3718 hg files "set:grep('bob')"
3718 3719
3719 3720 - search tracked file contents with xargs and grep::
3720 3721
3721 3722 hg files -0 | xargs -0 grep foo
3722 3723
3723 3724 See :hg:`help patterns` and :hg:`help filesets` for more information
3724 3725 on specifying file patterns.
3725 3726
3726 3727 Returns 0 if a match is found, 1 otherwise.
3727 3728
3728 3729 """
3729 3730 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3730 3731
3731 3732 end = '\n'
3732 3733 if opts.get('print0'):
3733 3734 end = '\0'
3734 3735 fmt = '%s' + end
3735 3736
3736 3737 m = scmutil.match(ctx, pats, opts)
3737 3738 with ui.formatter('files', opts) as fm:
3738 3739 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
3739 3740
3740 3741 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3741 3742 def forget(ui, repo, *pats, **opts):
3742 3743 """forget the specified files on the next commit
3743 3744
3744 3745 Mark the specified files so they will no longer be tracked
3745 3746 after the next commit.
3746 3747
3747 3748 This only removes files from the current branch, not from the
3748 3749 entire project history, and it does not delete them from the
3749 3750 working directory.
3750 3751
3751 3752 To delete the file from the working directory, see :hg:`remove`.
3752 3753
3753 3754 To undo a forget before the next commit, see :hg:`add`.
3754 3755
3755 3756 .. container:: verbose
3756 3757
3757 3758 Examples:
3758 3759
3759 3760 - forget newly-added binary files::
3760 3761
3761 3762 hg forget "set:added() and binary()"
3762 3763
3763 3764 - forget files that would be excluded by .hgignore::
3764 3765
3765 3766 hg forget "set:hgignore()"
3766 3767
3767 3768 Returns 0 on success.
3768 3769 """
3769 3770
3770 3771 if not pats:
3771 3772 raise error.Abort(_('no files specified'))
3772 3773
3773 3774 m = scmutil.match(repo[None], pats, opts)
3774 3775 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3775 3776 return rejected and 1 or 0
3776 3777
3777 3778 @command(
3778 3779 'graft',
3779 3780 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3780 3781 ('c', 'continue', False, _('resume interrupted graft')),
3781 3782 ('e', 'edit', False, _('invoke editor on commit messages')),
3782 3783 ('', 'log', None, _('append graft info to log message')),
3783 3784 ('f', 'force', False, _('force graft')),
3784 3785 ('D', 'currentdate', False,
3785 3786 _('record the current date as commit date')),
3786 3787 ('U', 'currentuser', False,
3787 3788 _('record the current user as committer'), _('DATE'))]
3788 3789 + commitopts2 + mergetoolopts + dryrunopts,
3789 3790 _('[OPTION]... [-r REV]... REV...'))
3790 3791 def graft(ui, repo, *revs, **opts):
3791 3792 '''copy changes from other branches onto the current branch
3792 3793
3793 3794 This command uses Mercurial's merge logic to copy individual
3794 3795 changes from other branches without merging branches in the
3795 3796 history graph. This is sometimes known as 'backporting' or
3796 3797 'cherry-picking'. By default, graft will copy user, date, and
3797 3798 description from the source changesets.
3798 3799
3799 3800 Changesets that are ancestors of the current revision, that have
3800 3801 already been grafted, or that are merges will be skipped.
3801 3802
3802 3803 If --log is specified, log messages will have a comment appended
3803 3804 of the form::
3804 3805
3805 3806 (grafted from CHANGESETHASH)
3806 3807
3807 3808 If --force is specified, revisions will be grafted even if they
3808 3809 are already ancestors of or have been grafted to the destination.
3809 3810 This is useful when the revisions have since been backed out.
3810 3811
3811 3812 If a graft merge results in conflicts, the graft process is
3812 3813 interrupted so that the current merge can be manually resolved.
3813 3814 Once all conflicts are addressed, the graft process can be
3814 3815 continued with the -c/--continue option.
3815 3816
3816 3817 .. note::
3817 3818
3818 3819 The -c/--continue option does not reapply earlier options, except
3819 3820 for --force.
3820 3821
3821 3822 .. container:: verbose
3822 3823
3823 3824 Examples:
3824 3825
3825 3826 - copy a single change to the stable branch and edit its description::
3826 3827
3827 3828 hg update stable
3828 3829 hg graft --edit 9393
3829 3830
3830 3831 - graft a range of changesets with one exception, updating dates::
3831 3832
3832 3833 hg graft -D "2085::2093 and not 2091"
3833 3834
3834 3835 - continue a graft after resolving conflicts::
3835 3836
3836 3837 hg graft -c
3837 3838
3838 3839 - show the source of a grafted changeset::
3839 3840
3840 3841 hg log --debug -r .
3841 3842
3842 3843 - show revisions sorted by date::
3843 3844
3844 3845 hg log -r "sort(all(), date)"
3845 3846
3846 3847 See :hg:`help revisions` and :hg:`help revsets` for more about
3847 3848 specifying revisions.
3848 3849
3849 3850 Returns 0 on successful completion.
3850 3851 '''
3851 3852 with repo.wlock():
3852 3853 return _dograft(ui, repo, *revs, **opts)
3853 3854
3854 3855 def _dograft(ui, repo, *revs, **opts):
3855 3856 if revs and opts.get('rev'):
3856 3857 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
3857 3858 'revision ordering!\n'))
3858 3859
3859 3860 revs = list(revs)
3860 3861 revs.extend(opts.get('rev'))
3861 3862
3862 3863 if not opts.get('user') and opts.get('currentuser'):
3863 3864 opts['user'] = ui.username()
3864 3865 if not opts.get('date') and opts.get('currentdate'):
3865 3866 opts['date'] = "%d %d" % util.makedate()
3866 3867
3867 3868 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3868 3869
3869 3870 cont = False
3870 3871 if opts.get('continue'):
3871 3872 cont = True
3872 3873 if revs:
3873 3874 raise error.Abort(_("can't specify --continue and revisions"))
3874 3875 # read in unfinished revisions
3875 3876 try:
3876 3877 nodes = repo.vfs.read('graftstate').splitlines()
3877 3878 revs = [repo[node].rev() for node in nodes]
3878 3879 except IOError as inst:
3879 3880 if inst.errno != errno.ENOENT:
3880 3881 raise
3881 3882 cmdutil.wrongtooltocontinue(repo, _('graft'))
3882 3883 else:
3883 3884 cmdutil.checkunfinished(repo)
3884 3885 cmdutil.bailifchanged(repo)
3885 3886 if not revs:
3886 3887 raise error.Abort(_('no revisions specified'))
3887 3888 revs = scmutil.revrange(repo, revs)
3888 3889
3889 3890 skipped = set()
3890 3891 # check for merges
3891 3892 for rev in repo.revs('%ld and merge()', revs):
3892 3893 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3893 3894 skipped.add(rev)
3894 3895 revs = [r for r in revs if r not in skipped]
3895 3896 if not revs:
3896 3897 return -1
3897 3898
3898 3899 # Don't check in the --continue case, in effect retaining --force across
3899 3900 # --continues. That's because without --force, any revisions we decided to
3900 3901 # skip would have been filtered out here, so they wouldn't have made their
3901 3902 # way to the graftstate. With --force, any revisions we would have otherwise
3902 3903 # skipped would not have been filtered out, and if they hadn't been applied
3903 3904 # already, they'd have been in the graftstate.
3904 3905 if not (cont or opts.get('force')):
3905 3906 # check for ancestors of dest branch
3906 3907 crev = repo['.'].rev()
3907 3908 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3908 3909 # XXX make this lazy in the future
3909 3910 # don't mutate while iterating, create a copy
3910 3911 for rev in list(revs):
3911 3912 if rev in ancestors:
3912 3913 ui.warn(_('skipping ancestor revision %d:%s\n') %
3913 3914 (rev, repo[rev]))
3914 3915 # XXX remove on list is slow
3915 3916 revs.remove(rev)
3916 3917 if not revs:
3917 3918 return -1
3918 3919
3919 3920 # analyze revs for earlier grafts
3920 3921 ids = {}
3921 3922 for ctx in repo.set("%ld", revs):
3922 3923 ids[ctx.hex()] = ctx.rev()
3923 3924 n = ctx.extra().get('source')
3924 3925 if n:
3925 3926 ids[n] = ctx.rev()
3926 3927
3927 3928 # check ancestors for earlier grafts
3928 3929 ui.debug('scanning for duplicate grafts\n')
3929 3930
3930 3931 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3931 3932 ctx = repo[rev]
3932 3933 n = ctx.extra().get('source')
3933 3934 if n in ids:
3934 3935 try:
3935 3936 r = repo[n].rev()
3936 3937 except error.RepoLookupError:
3937 3938 r = None
3938 3939 if r in revs:
3939 3940 ui.warn(_('skipping revision %d:%s '
3940 3941 '(already grafted to %d:%s)\n')
3941 3942 % (r, repo[r], rev, ctx))
3942 3943 revs.remove(r)
3943 3944 elif ids[n] in revs:
3944 3945 if r is None:
3945 3946 ui.warn(_('skipping already grafted revision %d:%s '
3946 3947 '(%d:%s also has unknown origin %s)\n')
3947 3948 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3948 3949 else:
3949 3950 ui.warn(_('skipping already grafted revision %d:%s '
3950 3951 '(%d:%s also has origin %d:%s)\n')
3951 3952 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3952 3953 revs.remove(ids[n])
3953 3954 elif ctx.hex() in ids:
3954 3955 r = ids[ctx.hex()]
3955 3956 ui.warn(_('skipping already grafted revision %d:%s '
3956 3957 '(was grafted from %d:%s)\n') %
3957 3958 (r, repo[r], rev, ctx))
3958 3959 revs.remove(r)
3959 3960 if not revs:
3960 3961 return -1
3961 3962
3962 3963 for pos, ctx in enumerate(repo.set("%ld", revs)):
3963 3964 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3964 3965 ctx.description().split('\n', 1)[0])
3965 3966 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3966 3967 if names:
3967 3968 desc += ' (%s)' % ' '.join(names)
3968 3969 ui.status(_('grafting %s\n') % desc)
3969 3970 if opts.get('dry_run'):
3970 3971 continue
3971 3972
3972 3973 source = ctx.extra().get('source')
3973 3974 extra = {}
3974 3975 if source:
3975 3976 extra['source'] = source
3976 3977 extra['intermediate-source'] = ctx.hex()
3977 3978 else:
3978 3979 extra['source'] = ctx.hex()
3979 3980 user = ctx.user()
3980 3981 if opts.get('user'):
3981 3982 user = opts['user']
3982 3983 date = ctx.date()
3983 3984 if opts.get('date'):
3984 3985 date = opts['date']
3985 3986 message = ctx.description()
3986 3987 if opts.get('log'):
3987 3988 message += '\n(grafted from %s)' % ctx.hex()
3988 3989
3989 3990 # we don't merge the first commit when continuing
3990 3991 if not cont:
3991 3992 # perform the graft merge with p1(rev) as 'ancestor'
3992 3993 try:
3993 3994 # ui.forcemerge is an internal variable, do not document
3994 3995 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3995 3996 'graft')
3996 3997 stats = mergemod.graft(repo, ctx, ctx.p1(),
3997 3998 ['local', 'graft'])
3998 3999 finally:
3999 4000 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
4000 4001 # report any conflicts
4001 4002 if stats and stats[3] > 0:
4002 4003 # write out state for --continue
4003 4004 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
4004 4005 repo.vfs.write('graftstate', ''.join(nodelines))
4005 4006 extra = ''
4006 4007 if opts.get('user'):
4007 4008 extra += ' --user %s' % util.shellquote(opts['user'])
4008 4009 if opts.get('date'):
4009 4010 extra += ' --date %s' % util.shellquote(opts['date'])
4010 4011 if opts.get('log'):
4011 4012 extra += ' --log'
4012 4013 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
4013 4014 raise error.Abort(
4014 4015 _("unresolved conflicts, can't continue"),
4015 4016 hint=hint)
4016 4017 else:
4017 4018 cont = False
4018 4019
4019 4020 # commit
4020 4021 node = repo.commit(text=message, user=user,
4021 4022 date=date, extra=extra, editor=editor)
4022 4023 if node is None:
4023 4024 ui.warn(
4024 4025 _('note: graft of %d:%s created no changes to commit\n') %
4025 4026 (ctx.rev(), ctx))
4026 4027
4027 4028 # remove state when we complete successfully
4028 4029 if not opts.get('dry_run'):
4029 4030 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
4030 4031
4031 4032 return 0
4032 4033
4033 4034 @command('grep',
4034 4035 [('0', 'print0', None, _('end fields with NUL')),
4035 4036 ('', 'all', None, _('print all revisions that match')),
4036 4037 ('a', 'text', None, _('treat all files as text')),
4037 4038 ('f', 'follow', None,
4038 4039 _('follow changeset history,'
4039 4040 ' or file history across copies and renames')),
4040 4041 ('i', 'ignore-case', None, _('ignore case when matching')),
4041 4042 ('l', 'files-with-matches', None,
4042 4043 _('print only filenames and revisions that match')),
4043 4044 ('n', 'line-number', None, _('print matching line numbers')),
4044 4045 ('r', 'rev', [],
4045 4046 _('only search files changed within revision range'), _('REV')),
4046 4047 ('u', 'user', None, _('list the author (long with -v)')),
4047 4048 ('d', 'date', None, _('list the date (short with -q)')),
4048 4049 ] + formatteropts + walkopts,
4049 4050 _('[OPTION]... PATTERN [FILE]...'),
4050 4051 inferrepo=True)
4051 4052 def grep(ui, repo, pattern, *pats, **opts):
4052 4053 """search revision history for a pattern in specified files
4053 4054
4054 4055 Search revision history for a regular expression in the specified
4055 4056 files or the entire project.
4056 4057
4057 4058 By default, grep prints the most recent revision number for each
4058 4059 file in which it finds a match. To get it to print every revision
4059 4060 that contains a change in match status ("-" for a match that becomes
4060 4061 a non-match, or "+" for a non-match that becomes a match), use the
4061 4062 --all flag.
4062 4063
4063 4064 PATTERN can be any Python (roughly Perl-compatible) regular
4064 4065 expression.
4065 4066
4066 4067 If no FILEs are specified (and -f/--follow isn't set), all files in
4067 4068 the repository are searched, including those that don't exist in the
4068 4069 current branch or have been deleted in a prior changeset.
4069 4070
4070 4071 Returns 0 if a match is found, 1 otherwise.
4071 4072 """
4072 4073 reflags = re.M
4073 4074 if opts.get('ignore_case'):
4074 4075 reflags |= re.I
4075 4076 try:
4076 4077 regexp = util.re.compile(pattern, reflags)
4077 4078 except re.error as inst:
4078 4079 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
4079 4080 return 1
4080 4081 sep, eol = ':', '\n'
4081 4082 if opts.get('print0'):
4082 4083 sep = eol = '\0'
4083 4084
4084 4085 getfile = util.lrucachefunc(repo.file)
4085 4086
4086 4087 def matchlines(body):
4087 4088 begin = 0
4088 4089 linenum = 0
4089 4090 while begin < len(body):
4090 4091 match = regexp.search(body, begin)
4091 4092 if not match:
4092 4093 break
4093 4094 mstart, mend = match.span()
4094 4095 linenum += body.count('\n', begin, mstart) + 1
4095 4096 lstart = body.rfind('\n', begin, mstart) + 1 or begin
4096 4097 begin = body.find('\n', mend) + 1 or len(body) + 1
4097 4098 lend = begin - 1
4098 4099 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
4099 4100
4100 4101 class linestate(object):
4101 4102 def __init__(self, line, linenum, colstart, colend):
4102 4103 self.line = line
4103 4104 self.linenum = linenum
4104 4105 self.colstart = colstart
4105 4106 self.colend = colend
4106 4107
4107 4108 def __hash__(self):
4108 4109 return hash((self.linenum, self.line))
4109 4110
4110 4111 def __eq__(self, other):
4111 4112 return self.line == other.line
4112 4113
4113 4114 def findpos(self):
4114 4115 """Iterate all (start, end) indices of matches"""
4115 4116 yield self.colstart, self.colend
4116 4117 p = self.colend
4117 4118 while p < len(self.line):
4118 4119 m = regexp.search(self.line, p)
4119 4120 if not m:
4120 4121 break
4121 4122 yield m.span()
4122 4123 p = m.end()
4123 4124
4124 4125 matches = {}
4125 4126 copies = {}
4126 4127 def grepbody(fn, rev, body):
4127 4128 matches[rev].setdefault(fn, [])
4128 4129 m = matches[rev][fn]
4129 4130 for lnum, cstart, cend, line in matchlines(body):
4130 4131 s = linestate(line, lnum, cstart, cend)
4131 4132 m.append(s)
4132 4133
4133 4134 def difflinestates(a, b):
4134 4135 sm = difflib.SequenceMatcher(None, a, b)
4135 4136 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
4136 4137 if tag == 'insert':
4137 4138 for i in xrange(blo, bhi):
4138 4139 yield ('+', b[i])
4139 4140 elif tag == 'delete':
4140 4141 for i in xrange(alo, ahi):
4141 4142 yield ('-', a[i])
4142 4143 elif tag == 'replace':
4143 4144 for i in xrange(alo, ahi):
4144 4145 yield ('-', a[i])
4145 4146 for i in xrange(blo, bhi):
4146 4147 yield ('+', b[i])
4147 4148
4148 4149 def display(fm, fn, ctx, pstates, states):
4149 4150 rev = ctx.rev()
4150 4151 if fm.isplain():
4151 4152 formatuser = ui.shortuser
4152 4153 else:
4153 4154 formatuser = str
4154 4155 if ui.quiet:
4155 4156 datefmt = '%Y-%m-%d'
4156 4157 else:
4157 4158 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
4158 4159 found = False
4159 4160 @util.cachefunc
4160 4161 def binary():
4161 4162 flog = getfile(fn)
4162 4163 return util.binary(flog.read(ctx.filenode(fn)))
4163 4164
4164 4165 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
4165 4166 if opts.get('all'):
4166 4167 iter = difflinestates(pstates, states)
4167 4168 else:
4168 4169 iter = [('', l) for l in states]
4169 4170 for change, l in iter:
4170 4171 fm.startitem()
4171 4172 fm.data(node=fm.hexfunc(ctx.node()))
4172 4173 cols = [
4173 4174 ('filename', fn, True),
4174 4175 ('rev', rev, True),
4175 4176 ('linenumber', l.linenum, opts.get('line_number')),
4176 4177 ]
4177 4178 if opts.get('all'):
4178 4179 cols.append(('change', change, True))
4179 4180 cols.extend([
4180 4181 ('user', formatuser(ctx.user()), opts.get('user')),
4181 4182 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
4182 4183 ])
4183 4184 lastcol = next(name for name, data, cond in reversed(cols) if cond)
4184 4185 for name, data, cond in cols:
4185 4186 field = fieldnamemap.get(name, name)
4186 4187 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
4187 4188 if cond and name != lastcol:
4188 4189 fm.plain(sep, label='grep.sep')
4189 4190 if not opts.get('files_with_matches'):
4190 4191 fm.plain(sep, label='grep.sep')
4191 4192 if not opts.get('text') and binary():
4192 4193 fm.plain(_(" Binary file matches"))
4193 4194 else:
4194 4195 displaymatches(fm.nested('texts'), l)
4195 4196 fm.plain(eol)
4196 4197 found = True
4197 4198 if opts.get('files_with_matches'):
4198 4199 break
4199 4200 return found
4200 4201
4201 4202 def displaymatches(fm, l):
4202 4203 p = 0
4203 4204 for s, e in l.findpos():
4204 4205 if p < s:
4205 4206 fm.startitem()
4206 4207 fm.write('text', '%s', l.line[p:s])
4207 4208 fm.data(matched=False)
4208 4209 fm.startitem()
4209 4210 fm.write('text', '%s', l.line[s:e], label='grep.match')
4210 4211 fm.data(matched=True)
4211 4212 p = e
4212 4213 if p < len(l.line):
4213 4214 fm.startitem()
4214 4215 fm.write('text', '%s', l.line[p:])
4215 4216 fm.data(matched=False)
4216 4217 fm.end()
4217 4218
4218 4219 skip = {}
4219 4220 revfiles = {}
4220 4221 matchfn = scmutil.match(repo[None], pats, opts)
4221 4222 found = False
4222 4223 follow = opts.get('follow')
4223 4224
4224 4225 def prep(ctx, fns):
4225 4226 rev = ctx.rev()
4226 4227 pctx = ctx.p1()
4227 4228 parent = pctx.rev()
4228 4229 matches.setdefault(rev, {})
4229 4230 matches.setdefault(parent, {})
4230 4231 files = revfiles.setdefault(rev, [])
4231 4232 for fn in fns:
4232 4233 flog = getfile(fn)
4233 4234 try:
4234 4235 fnode = ctx.filenode(fn)
4235 4236 except error.LookupError:
4236 4237 continue
4237 4238
4238 4239 copied = flog.renamed(fnode)
4239 4240 copy = follow and copied and copied[0]
4240 4241 if copy:
4241 4242 copies.setdefault(rev, {})[fn] = copy
4242 4243 if fn in skip:
4243 4244 if copy:
4244 4245 skip[copy] = True
4245 4246 continue
4246 4247 files.append(fn)
4247 4248
4248 4249 if fn not in matches[rev]:
4249 4250 grepbody(fn, rev, flog.read(fnode))
4250 4251
4251 4252 pfn = copy or fn
4252 4253 if pfn not in matches[parent]:
4253 4254 try:
4254 4255 fnode = pctx.filenode(pfn)
4255 4256 grepbody(pfn, parent, flog.read(fnode))
4256 4257 except error.LookupError:
4257 4258 pass
4258 4259
4259 4260 fm = ui.formatter('grep', opts)
4260 4261 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4261 4262 rev = ctx.rev()
4262 4263 parent = ctx.p1().rev()
4263 4264 for fn in sorted(revfiles.get(rev, [])):
4264 4265 states = matches[rev][fn]
4265 4266 copy = copies.get(rev, {}).get(fn)
4266 4267 if fn in skip:
4267 4268 if copy:
4268 4269 skip[copy] = True
4269 4270 continue
4270 4271 pstates = matches.get(parent, {}).get(copy or fn, [])
4271 4272 if pstates or states:
4272 4273 r = display(fm, fn, ctx, pstates, states)
4273 4274 found = found or r
4274 4275 if r and not opts.get('all'):
4275 4276 skip[fn] = True
4276 4277 if copy:
4277 4278 skip[copy] = True
4278 4279 del matches[rev]
4279 4280 del revfiles[rev]
4280 4281 fm.end()
4281 4282
4282 4283 return not found
4283 4284
4284 4285 @command('heads',
4285 4286 [('r', 'rev', '',
4286 4287 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
4287 4288 ('t', 'topo', False, _('show topological heads only')),
4288 4289 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
4289 4290 ('c', 'closed', False, _('show normal and closed branch heads')),
4290 4291 ] + templateopts,
4291 4292 _('[-ct] [-r STARTREV] [REV]...'))
4292 4293 def heads(ui, repo, *branchrevs, **opts):
4293 4294 """show branch heads
4294 4295
4295 4296 With no arguments, show all open branch heads in the repository.
4296 4297 Branch heads are changesets that have no descendants on the
4297 4298 same branch. They are where development generally takes place and
4298 4299 are the usual targets for update and merge operations.
4299 4300
4300 4301 If one or more REVs are given, only open branch heads on the
4301 4302 branches associated with the specified changesets are shown. This
4302 4303 means that you can use :hg:`heads .` to see the heads on the
4303 4304 currently checked-out branch.
4304 4305
4305 4306 If -c/--closed is specified, also show branch heads marked closed
4306 4307 (see :hg:`commit --close-branch`).
4307 4308
4308 4309 If STARTREV is specified, only those heads that are descendants of
4309 4310 STARTREV will be displayed.
4310 4311
4311 4312 If -t/--topo is specified, named branch mechanics will be ignored and only
4312 4313 topological heads (changesets with no children) will be shown.
4313 4314
4314 4315 Returns 0 if matching heads are found, 1 if not.
4315 4316 """
4316 4317
4317 4318 start = None
4318 4319 if 'rev' in opts:
4319 4320 start = scmutil.revsingle(repo, opts['rev'], None).node()
4320 4321
4321 4322 if opts.get('topo'):
4322 4323 heads = [repo[h] for h in repo.heads(start)]
4323 4324 else:
4324 4325 heads = []
4325 4326 for branch in repo.branchmap():
4326 4327 heads += repo.branchheads(branch, start, opts.get('closed'))
4327 4328 heads = [repo[h] for h in heads]
4328 4329
4329 4330 if branchrevs:
4330 4331 branches = set(repo[br].branch() for br in branchrevs)
4331 4332 heads = [h for h in heads if h.branch() in branches]
4332 4333
4333 4334 if opts.get('active') and branchrevs:
4334 4335 dagheads = repo.heads(start)
4335 4336 heads = [h for h in heads if h.node() in dagheads]
4336 4337
4337 4338 if branchrevs:
4338 4339 haveheads = set(h.branch() for h in heads)
4339 4340 if branches - haveheads:
4340 4341 headless = ', '.join(b for b in branches - haveheads)
4341 4342 msg = _('no open branch heads found on branches %s')
4342 4343 if opts.get('rev'):
4343 4344 msg += _(' (started at %s)') % opts['rev']
4344 4345 ui.warn((msg + '\n') % headless)
4345 4346
4346 4347 if not heads:
4347 4348 return 1
4348 4349
4349 4350 heads = sorted(heads, key=lambda x: -x.rev())
4350 4351 displayer = cmdutil.show_changeset(ui, repo, opts)
4351 4352 for ctx in heads:
4352 4353 displayer.show(ctx)
4353 4354 displayer.close()
4354 4355
4355 4356 @command('help',
4356 4357 [('e', 'extension', None, _('show only help for extensions')),
4357 4358 ('c', 'command', None, _('show only help for commands')),
4358 4359 ('k', 'keyword', None, _('show topics matching keyword')),
4359 4360 ('s', 'system', [], _('show help for specific platform(s)')),
4360 4361 ],
4361 4362 _('[-ecks] [TOPIC]'),
4362 4363 norepo=True)
4363 4364 def help_(ui, name=None, **opts):
4364 4365 """show help for a given topic or a help overview
4365 4366
4366 4367 With no arguments, print a list of commands with short help messages.
4367 4368
4368 4369 Given a topic, extension, or command name, print help for that
4369 4370 topic.
4370 4371
4371 4372 Returns 0 if successful.
4372 4373 """
4373 4374
4374 4375 textwidth = ui.configint('ui', 'textwidth', 78)
4375 4376 termwidth = ui.termwidth() - 2
4376 4377 if textwidth <= 0 or termwidth < textwidth:
4377 4378 textwidth = termwidth
4378 4379
4379 4380 keep = opts.get('system') or []
4380 4381 if len(keep) == 0:
4381 4382 if sys.platform.startswith('win'):
4382 4383 keep.append('windows')
4383 4384 elif sys.platform == 'OpenVMS':
4384 4385 keep.append('vms')
4385 4386 elif sys.platform == 'plan9':
4386 4387 keep.append('plan9')
4387 4388 else:
4388 4389 keep.append('unix')
4389 4390 keep.append(sys.platform.lower())
4390 4391 if ui.verbose:
4391 4392 keep.append('verbose')
4392 4393
4393 4394 section = None
4394 4395 subtopic = None
4395 4396 if name and '.' in name:
4396 4397 name, remaining = name.split('.', 1)
4397 4398 remaining = encoding.lower(remaining)
4398 4399 if '.' in remaining:
4399 4400 subtopic, section = remaining.split('.', 1)
4400 4401 else:
4401 4402 if name in help.subtopics:
4402 4403 subtopic = remaining
4403 4404 else:
4404 4405 section = remaining
4405 4406
4406 4407 text = help.help_(ui, name, subtopic=subtopic, **opts)
4407 4408
4408 4409 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4409 4410 section=section)
4410 4411
4411 4412 # We could have been given a weird ".foo" section without a name
4412 4413 # to look for, or we could have simply failed to found "foo.bar"
4413 4414 # because bar isn't a section of foo
4414 4415 if section and not (formatted and name):
4415 4416 raise error.Abort(_("help section not found"))
4416 4417
4417 4418 if 'verbose' in pruned:
4418 4419 keep.append('omitted')
4419 4420 else:
4420 4421 keep.append('notomitted')
4421 4422 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4422 4423 section=section)
4423 4424 ui.write(formatted)
4424 4425
4425 4426
4426 4427 @command('identify|id',
4427 4428 [('r', 'rev', '',
4428 4429 _('identify the specified revision'), _('REV')),
4429 4430 ('n', 'num', None, _('show local revision number')),
4430 4431 ('i', 'id', None, _('show global revision id')),
4431 4432 ('b', 'branch', None, _('show branch')),
4432 4433 ('t', 'tags', None, _('show tags')),
4433 4434 ('B', 'bookmarks', None, _('show bookmarks')),
4434 4435 ] + remoteopts,
4435 4436 _('[-nibtB] [-r REV] [SOURCE]'),
4436 4437 optionalrepo=True)
4437 4438 def identify(ui, repo, source=None, rev=None,
4438 4439 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
4439 4440 """identify the working directory or specified revision
4440 4441
4441 4442 Print a summary identifying the repository state at REV using one or
4442 4443 two parent hash identifiers, followed by a "+" if the working
4443 4444 directory has uncommitted changes, the branch name (if not default),
4444 4445 a list of tags, and a list of bookmarks.
4445 4446
4446 4447 When REV is not given, print a summary of the current state of the
4447 4448 repository.
4448 4449
4449 4450 Specifying a path to a repository root or Mercurial bundle will
4450 4451 cause lookup to operate on that repository/bundle.
4451 4452
4452 4453 .. container:: verbose
4453 4454
4454 4455 Examples:
4455 4456
4456 4457 - generate a build identifier for the working directory::
4457 4458
4458 4459 hg id --id > build-id.dat
4459 4460
4460 4461 - find the revision corresponding to a tag::
4461 4462
4462 4463 hg id -n -r 1.3
4463 4464
4464 4465 - check the most recent revision of a remote repository::
4465 4466
4466 4467 hg id -r tip https://www.mercurial-scm.org/repo/hg/
4467 4468
4468 4469 See :hg:`log` for generating more information about specific revisions,
4469 4470 including full hash identifiers.
4470 4471
4471 4472 Returns 0 if successful.
4472 4473 """
4473 4474
4474 4475 if not repo and not source:
4475 4476 raise error.Abort(_("there is no Mercurial repository here "
4476 4477 "(.hg not found)"))
4477 4478
4478 4479 if ui.debugflag:
4479 4480 hexfunc = hex
4480 4481 else:
4481 4482 hexfunc = short
4482 4483 default = not (num or id or branch or tags or bookmarks)
4483 4484 output = []
4484 4485 revs = []
4485 4486
4486 4487 if source:
4487 4488 source, branches = hg.parseurl(ui.expandpath(source))
4488 4489 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
4489 4490 repo = peer.local()
4490 4491 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
4491 4492
4492 4493 if not repo:
4493 4494 if num or branch or tags:
4494 4495 raise error.Abort(
4495 4496 _("can't query remote revision number, branch, or tags"))
4496 4497 if not rev and revs:
4497 4498 rev = revs[0]
4498 4499 if not rev:
4499 4500 rev = "tip"
4500 4501
4501 4502 remoterev = peer.lookup(rev)
4502 4503 if default or id:
4503 4504 output = [hexfunc(remoterev)]
4504 4505
4505 4506 def getbms():
4506 4507 bms = []
4507 4508
4508 4509 if 'bookmarks' in peer.listkeys('namespaces'):
4509 4510 hexremoterev = hex(remoterev)
4510 4511 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
4511 4512 if bmr == hexremoterev]
4512 4513
4513 4514 return sorted(bms)
4514 4515
4515 4516 if bookmarks:
4516 4517 output.extend(getbms())
4517 4518 elif default and not ui.quiet:
4518 4519 # multiple bookmarks for a single parent separated by '/'
4519 4520 bm = '/'.join(getbms())
4520 4521 if bm:
4521 4522 output.append(bm)
4522 4523 else:
4523 4524 ctx = scmutil.revsingle(repo, rev, None)
4524 4525
4525 4526 if ctx.rev() is None:
4526 4527 ctx = repo[None]
4527 4528 parents = ctx.parents()
4528 4529 taglist = []
4529 4530 for p in parents:
4530 4531 taglist.extend(p.tags())
4531 4532
4532 4533 changed = ""
4533 4534 if default or id or num:
4534 4535 if (any(repo.status())
4535 4536 or any(ctx.sub(s).dirty() for s in ctx.substate)):
4536 4537 changed = '+'
4537 4538 if default or id:
4538 4539 output = ["%s%s" %
4539 4540 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4540 4541 if num:
4541 4542 output.append("%s%s" %
4542 4543 ('+'.join([str(p.rev()) for p in parents]), changed))
4543 4544 else:
4544 4545 if default or id:
4545 4546 output = [hexfunc(ctx.node())]
4546 4547 if num:
4547 4548 output.append(str(ctx.rev()))
4548 4549 taglist = ctx.tags()
4549 4550
4550 4551 if default and not ui.quiet:
4551 4552 b = ctx.branch()
4552 4553 if b != 'default':
4553 4554 output.append("(%s)" % b)
4554 4555
4555 4556 # multiple tags for a single parent separated by '/'
4556 4557 t = '/'.join(taglist)
4557 4558 if t:
4558 4559 output.append(t)
4559 4560
4560 4561 # multiple bookmarks for a single parent separated by '/'
4561 4562 bm = '/'.join(ctx.bookmarks())
4562 4563 if bm:
4563 4564 output.append(bm)
4564 4565 else:
4565 4566 if branch:
4566 4567 output.append(ctx.branch())
4567 4568
4568 4569 if tags:
4569 4570 output.extend(taglist)
4570 4571
4571 4572 if bookmarks:
4572 4573 output.extend(ctx.bookmarks())
4573 4574
4574 4575 ui.write("%s\n" % ' '.join(output))
4575 4576
4576 4577 @command('import|patch',
4577 4578 [('p', 'strip', 1,
4578 4579 _('directory strip option for patch. This has the same '
4579 4580 'meaning as the corresponding patch option'), _('NUM')),
4580 4581 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4581 4582 ('e', 'edit', False, _('invoke editor on commit messages')),
4582 4583 ('f', 'force', None,
4583 4584 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4584 4585 ('', 'no-commit', None,
4585 4586 _("don't commit, just update the working directory")),
4586 4587 ('', 'bypass', None,
4587 4588 _("apply patch without touching the working directory")),
4588 4589 ('', 'partial', None,
4589 4590 _('commit even if some hunks fail')),
4590 4591 ('', 'exact', None,
4591 4592 _('abort if patch would apply lossily')),
4592 4593 ('', 'prefix', '',
4593 4594 _('apply patch to subdirectory'), _('DIR')),
4594 4595 ('', 'import-branch', None,
4595 4596 _('use any branch information in patch (implied by --exact)'))] +
4596 4597 commitopts + commitopts2 + similarityopts,
4597 4598 _('[OPTION]... PATCH...'))
4598 4599 def import_(ui, repo, patch1=None, *patches, **opts):
4599 4600 """import an ordered set of patches
4600 4601
4601 4602 Import a list of patches and commit them individually (unless
4602 4603 --no-commit is specified).
4603 4604
4604 4605 To read a patch from standard input, use "-" as the patch name. If
4605 4606 a URL is specified, the patch will be downloaded from there.
4606 4607
4607 4608 Import first applies changes to the working directory (unless
4608 4609 --bypass is specified), import will abort if there are outstanding
4609 4610 changes.
4610 4611
4611 4612 Use --bypass to apply and commit patches directly to the
4612 4613 repository, without affecting the working directory. Without
4613 4614 --exact, patches will be applied on top of the working directory
4614 4615 parent revision.
4615 4616
4616 4617 You can import a patch straight from a mail message. Even patches
4617 4618 as attachments work (to use the body part, it must have type
4618 4619 text/plain or text/x-patch). From and Subject headers of email
4619 4620 message are used as default committer and commit message. All
4620 4621 text/plain body parts before first diff are added to the commit
4621 4622 message.
4622 4623
4623 4624 If the imported patch was generated by :hg:`export`, user and
4624 4625 description from patch override values from message headers and
4625 4626 body. Values given on command line with -m/--message and -u/--user
4626 4627 override these.
4627 4628
4628 4629 If --exact is specified, import will set the working directory to
4629 4630 the parent of each patch before applying it, and will abort if the
4630 4631 resulting changeset has a different ID than the one recorded in
4631 4632 the patch. This will guard against various ways that portable
4632 4633 patch formats and mail systems might fail to transfer Mercurial
4633 4634 data or metadata. See :hg:`bundle` for lossless transmission.
4634 4635
4635 4636 Use --partial to ensure a changeset will be created from the patch
4636 4637 even if some hunks fail to apply. Hunks that fail to apply will be
4637 4638 written to a <target-file>.rej file. Conflicts can then be resolved
4638 4639 by hand before :hg:`commit --amend` is run to update the created
4639 4640 changeset. This flag exists to let people import patches that
4640 4641 partially apply without losing the associated metadata (author,
4641 4642 date, description, ...).
4642 4643
4643 4644 .. note::
4644 4645
4645 4646 When no hunks apply cleanly, :hg:`import --partial` will create
4646 4647 an empty changeset, importing only the patch metadata.
4647 4648
4648 4649 With -s/--similarity, hg will attempt to discover renames and
4649 4650 copies in the patch in the same way as :hg:`addremove`.
4650 4651
4651 4652 It is possible to use external patch programs to perform the patch
4652 4653 by setting the ``ui.patch`` configuration option. For the default
4653 4654 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4654 4655 See :hg:`help config` for more information about configuration
4655 4656 files and how to use these options.
4656 4657
4657 4658 See :hg:`help dates` for a list of formats valid for -d/--date.
4658 4659
4659 4660 .. container:: verbose
4660 4661
4661 4662 Examples:
4662 4663
4663 4664 - import a traditional patch from a website and detect renames::
4664 4665
4665 4666 hg import -s 80 http://example.com/bugfix.patch
4666 4667
4667 4668 - import a changeset from an hgweb server::
4668 4669
4669 4670 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4670 4671
4671 4672 - import all the patches in an Unix-style mbox::
4672 4673
4673 4674 hg import incoming-patches.mbox
4674 4675
4675 4676 - attempt to exactly restore an exported changeset (not always
4676 4677 possible)::
4677 4678
4678 4679 hg import --exact proposed-fix.patch
4679 4680
4680 4681 - use an external tool to apply a patch which is too fuzzy for
4681 4682 the default internal tool.
4682 4683
4683 4684 hg import --config ui.patch="patch --merge" fuzzy.patch
4684 4685
4685 4686 - change the default fuzzing from 2 to a less strict 7
4686 4687
4687 4688 hg import --config ui.fuzz=7 fuzz.patch
4688 4689
4689 4690 Returns 0 on success, 1 on partial success (see --partial).
4690 4691 """
4691 4692
4692 4693 if not patch1:
4693 4694 raise error.Abort(_('need at least one patch to import'))
4694 4695
4695 4696 patches = (patch1,) + patches
4696 4697
4697 4698 date = opts.get('date')
4698 4699 if date:
4699 4700 opts['date'] = util.parsedate(date)
4700 4701
4701 4702 exact = opts.get('exact')
4702 4703 update = not opts.get('bypass')
4703 4704 if not update and opts.get('no_commit'):
4704 4705 raise error.Abort(_('cannot use --no-commit with --bypass'))
4705 4706 try:
4706 4707 sim = float(opts.get('similarity') or 0)
4707 4708 except ValueError:
4708 4709 raise error.Abort(_('similarity must be a number'))
4709 4710 if sim < 0 or sim > 100:
4710 4711 raise error.Abort(_('similarity must be between 0 and 100'))
4711 4712 if sim and not update:
4712 4713 raise error.Abort(_('cannot use --similarity with --bypass'))
4713 4714 if exact:
4714 4715 if opts.get('edit'):
4715 4716 raise error.Abort(_('cannot use --exact with --edit'))
4716 4717 if opts.get('prefix'):
4717 4718 raise error.Abort(_('cannot use --exact with --prefix'))
4718 4719
4719 4720 base = opts["base"]
4720 4721 wlock = dsguard = lock = tr = None
4721 4722 msgs = []
4722 4723 ret = 0
4723 4724
4724 4725
4725 4726 try:
4726 4727 wlock = repo.wlock()
4727 4728
4728 4729 if update:
4729 4730 cmdutil.checkunfinished(repo)
4730 4731 if (exact or not opts.get('force')):
4731 4732 cmdutil.bailifchanged(repo)
4732 4733
4733 4734 if not opts.get('no_commit'):
4734 4735 lock = repo.lock()
4735 4736 tr = repo.transaction('import')
4736 4737 else:
4737 4738 dsguard = dirstateguard.dirstateguard(repo, 'import')
4738 4739 parents = repo[None].parents()
4739 4740 for patchurl in patches:
4740 4741 if patchurl == '-':
4741 4742 ui.status(_('applying patch from stdin\n'))
4742 4743 patchfile = ui.fin
4743 4744 patchurl = 'stdin' # for error message
4744 4745 else:
4745 4746 patchurl = os.path.join(base, patchurl)
4746 4747 ui.status(_('applying %s\n') % patchurl)
4747 4748 patchfile = hg.openpath(ui, patchurl)
4748 4749
4749 4750 haspatch = False
4750 4751 for hunk in patch.split(patchfile):
4751 4752 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4752 4753 parents, opts,
4753 4754 msgs, hg.clean)
4754 4755 if msg:
4755 4756 haspatch = True
4756 4757 ui.note(msg + '\n')
4757 4758 if update or exact:
4758 4759 parents = repo[None].parents()
4759 4760 else:
4760 4761 parents = [repo[node]]
4761 4762 if rej:
4762 4763 ui.write_err(_("patch applied partially\n"))
4763 4764 ui.write_err(_("(fix the .rej files and run "
4764 4765 "`hg commit --amend`)\n"))
4765 4766 ret = 1
4766 4767 break
4767 4768
4768 4769 if not haspatch:
4769 4770 raise error.Abort(_('%s: no diffs found') % patchurl)
4770 4771
4771 4772 if tr:
4772 4773 tr.close()
4773 4774 if msgs:
4774 4775 repo.savecommitmessage('\n* * *\n'.join(msgs))
4775 4776 if dsguard:
4776 4777 dsguard.close()
4777 4778 return ret
4778 4779 finally:
4779 4780 if tr:
4780 4781 tr.release()
4781 4782 release(lock, dsguard, wlock)
4782 4783
4783 4784 @command('incoming|in',
4784 4785 [('f', 'force', None,
4785 4786 _('run even if remote repository is unrelated')),
4786 4787 ('n', 'newest-first', None, _('show newest record first')),
4787 4788 ('', 'bundle', '',
4788 4789 _('file to store the bundles into'), _('FILE')),
4789 4790 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4790 4791 ('B', 'bookmarks', False, _("compare bookmarks")),
4791 4792 ('b', 'branch', [],
4792 4793 _('a specific branch you would like to pull'), _('BRANCH')),
4793 4794 ] + logopts + remoteopts + subrepoopts,
4794 4795 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4795 4796 def incoming(ui, repo, source="default", **opts):
4796 4797 """show new changesets found in source
4797 4798
4798 4799 Show new changesets found in the specified path/URL or the default
4799 4800 pull location. These are the changesets that would have been pulled
4800 4801 if a pull at the time you issued this command.
4801 4802
4802 4803 See pull for valid source format details.
4803 4804
4804 4805 .. container:: verbose
4805 4806
4806 4807 With -B/--bookmarks, the result of bookmark comparison between
4807 4808 local and remote repositories is displayed. With -v/--verbose,
4808 4809 status is also displayed for each bookmark like below::
4809 4810
4810 4811 BM1 01234567890a added
4811 4812 BM2 1234567890ab advanced
4812 4813 BM3 234567890abc diverged
4813 4814 BM4 34567890abcd changed
4814 4815
4815 4816 The action taken locally when pulling depends on the
4816 4817 status of each bookmark:
4817 4818
4818 4819 :``added``: pull will create it
4819 4820 :``advanced``: pull will update it
4820 4821 :``diverged``: pull will create a divergent bookmark
4821 4822 :``changed``: result depends on remote changesets
4822 4823
4823 4824 From the point of view of pulling behavior, bookmark
4824 4825 existing only in the remote repository are treated as ``added``,
4825 4826 even if it is in fact locally deleted.
4826 4827
4827 4828 .. container:: verbose
4828 4829
4829 4830 For remote repository, using --bundle avoids downloading the
4830 4831 changesets twice if the incoming is followed by a pull.
4831 4832
4832 4833 Examples:
4833 4834
4834 4835 - show incoming changes with patches and full description::
4835 4836
4836 4837 hg incoming -vp
4837 4838
4838 4839 - show incoming changes excluding merges, store a bundle::
4839 4840
4840 4841 hg in -vpM --bundle incoming.hg
4841 4842 hg pull incoming.hg
4842 4843
4843 4844 - briefly list changes inside a bundle::
4844 4845
4845 4846 hg in changes.hg -T "{desc|firstline}\\n"
4846 4847
4847 4848 Returns 0 if there are incoming changes, 1 otherwise.
4848 4849 """
4849 4850 if opts.get('graph'):
4850 4851 cmdutil.checkunsupportedgraphflags([], opts)
4851 4852 def display(other, chlist, displayer):
4852 4853 revdag = cmdutil.graphrevs(other, chlist, opts)
4853 4854 cmdutil.displaygraph(ui, repo, revdag, displayer,
4854 4855 graphmod.asciiedges)
4855 4856
4856 4857 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4857 4858 return 0
4858 4859
4859 4860 if opts.get('bundle') and opts.get('subrepos'):
4860 4861 raise error.Abort(_('cannot combine --bundle and --subrepos'))
4861 4862
4862 4863 if opts.get('bookmarks'):
4863 4864 source, branches = hg.parseurl(ui.expandpath(source),
4864 4865 opts.get('branch'))
4865 4866 other = hg.peer(repo, opts, source)
4866 4867 if 'bookmarks' not in other.listkeys('namespaces'):
4867 4868 ui.warn(_("remote doesn't support bookmarks\n"))
4868 4869 return 0
4869 4870 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4870 4871 return bookmarks.incoming(ui, repo, other)
4871 4872
4872 4873 repo._subtoppath = ui.expandpath(source)
4873 4874 try:
4874 4875 return hg.incoming(ui, repo, source, opts)
4875 4876 finally:
4876 4877 del repo._subtoppath
4877 4878
4878 4879
4879 4880 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4880 4881 norepo=True)
4881 4882 def init(ui, dest=".", **opts):
4882 4883 """create a new repository in the given directory
4883 4884
4884 4885 Initialize a new repository in the given directory. If the given
4885 4886 directory does not exist, it will be created.
4886 4887
4887 4888 If no directory is given, the current directory is used.
4888 4889
4889 4890 It is possible to specify an ``ssh://`` URL as the destination.
4890 4891 See :hg:`help urls` for more information.
4891 4892
4892 4893 Returns 0 on success.
4893 4894 """
4894 4895 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4895 4896
4896 4897 @command('locate',
4897 4898 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4898 4899 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4899 4900 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4900 4901 ] + walkopts,
4901 4902 _('[OPTION]... [PATTERN]...'))
4902 4903 def locate(ui, repo, *pats, **opts):
4903 4904 """locate files matching specific patterns (DEPRECATED)
4904 4905
4905 4906 Print files under Mercurial control in the working directory whose
4906 4907 names match the given patterns.
4907 4908
4908 4909 By default, this command searches all directories in the working
4909 4910 directory. To search just the current directory and its
4910 4911 subdirectories, use "--include .".
4911 4912
4912 4913 If no patterns are given to match, this command prints the names
4913 4914 of all files under Mercurial control in the working directory.
4914 4915
4915 4916 If you want to feed the output of this command into the "xargs"
4916 4917 command, use the -0 option to both this command and "xargs". This
4917 4918 will avoid the problem of "xargs" treating single filenames that
4918 4919 contain whitespace as multiple filenames.
4919 4920
4920 4921 See :hg:`help files` for a more versatile command.
4921 4922
4922 4923 Returns 0 if a match is found, 1 otherwise.
4923 4924 """
4924 4925 if opts.get('print0'):
4925 4926 end = '\0'
4926 4927 else:
4927 4928 end = '\n'
4928 4929 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4929 4930
4930 4931 ret = 1
4931 4932 ctx = repo[rev]
4932 4933 m = scmutil.match(ctx, pats, opts, default='relglob',
4933 4934 badfn=lambda x, y: False)
4934 4935
4935 4936 for abs in ctx.matches(m):
4936 4937 if opts.get('fullpath'):
4937 4938 ui.write(repo.wjoin(abs), end)
4938 4939 else:
4939 4940 ui.write(((pats and m.rel(abs)) or abs), end)
4940 4941 ret = 0
4941 4942
4942 4943 return ret
4943 4944
4944 4945 @command('^log|history',
4945 4946 [('f', 'follow', None,
4946 4947 _('follow changeset history, or file history across copies and renames')),
4947 4948 ('', 'follow-first', None,
4948 4949 _('only follow the first parent of merge changesets (DEPRECATED)')),
4949 4950 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4950 4951 ('C', 'copies', None, _('show copied files')),
4951 4952 ('k', 'keyword', [],
4952 4953 _('do case-insensitive search for a given text'), _('TEXT')),
4953 4954 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4954 4955 ('', 'removed', None, _('include revisions where files were removed')),
4955 4956 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4956 4957 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4957 4958 ('', 'only-branch', [],
4958 4959 _('show only changesets within the given named branch (DEPRECATED)'),
4959 4960 _('BRANCH')),
4960 4961 ('b', 'branch', [],
4961 4962 _('show changesets within the given named branch'), _('BRANCH')),
4962 4963 ('P', 'prune', [],
4963 4964 _('do not display revision or any of its ancestors'), _('REV')),
4964 4965 ] + logopts + walkopts,
4965 4966 _('[OPTION]... [FILE]'),
4966 4967 inferrepo=True)
4967 4968 def log(ui, repo, *pats, **opts):
4968 4969 """show revision history of entire repository or files
4969 4970
4970 4971 Print the revision history of the specified files or the entire
4971 4972 project.
4972 4973
4973 4974 If no revision range is specified, the default is ``tip:0`` unless
4974 4975 --follow is set, in which case the working directory parent is
4975 4976 used as the starting revision.
4976 4977
4977 4978 File history is shown without following rename or copy history of
4978 4979 files. Use -f/--follow with a filename to follow history across
4979 4980 renames and copies. --follow without a filename will only show
4980 4981 ancestors or descendants of the starting revision.
4981 4982
4982 4983 By default this command prints revision number and changeset id,
4983 4984 tags, non-trivial parents, user, date and time, and a summary for
4984 4985 each commit. When the -v/--verbose switch is used, the list of
4985 4986 changed files and full commit message are shown.
4986 4987
4987 4988 With --graph the revisions are shown as an ASCII art DAG with the most
4988 4989 recent changeset at the top.
4989 4990 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4990 4991 and '+' represents a fork where the changeset from the lines below is a
4991 4992 parent of the 'o' merge on the same line.
4992 4993
4993 4994 .. note::
4994 4995
4995 4996 :hg:`log --patch` may generate unexpected diff output for merge
4996 4997 changesets, as it will only compare the merge changeset against
4997 4998 its first parent. Also, only files different from BOTH parents
4998 4999 will appear in files:.
4999 5000
5000 5001 .. note::
5001 5002
5002 5003 For performance reasons, :hg:`log FILE` may omit duplicate changes
5003 5004 made on branches and will not show removals or mode changes. To
5004 5005 see all such changes, use the --removed switch.
5005 5006
5006 5007 .. container:: verbose
5007 5008
5008 5009 Some examples:
5009 5010
5010 5011 - changesets with full descriptions and file lists::
5011 5012
5012 5013 hg log -v
5013 5014
5014 5015 - changesets ancestral to the working directory::
5015 5016
5016 5017 hg log -f
5017 5018
5018 5019 - last 10 commits on the current branch::
5019 5020
5020 5021 hg log -l 10 -b .
5021 5022
5022 5023 - changesets showing all modifications of a file, including removals::
5023 5024
5024 5025 hg log --removed file.c
5025 5026
5026 5027 - all changesets that touch a directory, with diffs, excluding merges::
5027 5028
5028 5029 hg log -Mp lib/
5029 5030
5030 5031 - all revision numbers that match a keyword::
5031 5032
5032 5033 hg log -k bug --template "{rev}\\n"
5033 5034
5034 5035 - the full hash identifier of the working directory parent::
5035 5036
5036 5037 hg log -r . --template "{node}\\n"
5037 5038
5038 5039 - list available log templates::
5039 5040
5040 5041 hg log -T list
5041 5042
5042 5043 - check if a given changeset is included in a tagged release::
5043 5044
5044 5045 hg log -r "a21ccf and ancestor(1.9)"
5045 5046
5046 5047 - find all changesets by some user in a date range::
5047 5048
5048 5049 hg log -k alice -d "may 2008 to jul 2008"
5049 5050
5050 5051 - summary of all changesets after the last tag::
5051 5052
5052 5053 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
5053 5054
5054 5055 See :hg:`help dates` for a list of formats valid for -d/--date.
5055 5056
5056 5057 See :hg:`help revisions` and :hg:`help revsets` for more about
5057 5058 specifying and ordering revisions.
5058 5059
5059 5060 See :hg:`help templates` for more about pre-packaged styles and
5060 5061 specifying custom templates.
5061 5062
5062 5063 Returns 0 on success.
5063 5064
5064 5065 """
5065 5066 if opts.get('follow') and opts.get('rev'):
5066 5067 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
5067 5068 del opts['follow']
5068 5069
5069 5070 if opts.get('graph'):
5070 5071 return cmdutil.graphlog(ui, repo, *pats, **opts)
5071 5072
5072 5073 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
5073 5074 limit = cmdutil.loglimit(opts)
5074 5075 count = 0
5075 5076
5076 5077 getrenamed = None
5077 5078 if opts.get('copies'):
5078 5079 endrev = None
5079 5080 if opts.get('rev'):
5080 5081 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
5081 5082 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
5082 5083
5083 5084 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5084 5085 for rev in revs:
5085 5086 if count == limit:
5086 5087 break
5087 5088 ctx = repo[rev]
5088 5089 copies = None
5089 5090 if getrenamed is not None and rev:
5090 5091 copies = []
5091 5092 for fn in ctx.files():
5092 5093 rename = getrenamed(fn, rev)
5093 5094 if rename:
5094 5095 copies.append((fn, rename[0]))
5095 5096 if filematcher:
5096 5097 revmatchfn = filematcher(ctx.rev())
5097 5098 else:
5098 5099 revmatchfn = None
5099 5100 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
5100 5101 if displayer.flush(ctx):
5101 5102 count += 1
5102 5103
5103 5104 displayer.close()
5104 5105
5105 5106 @command('manifest',
5106 5107 [('r', 'rev', '', _('revision to display'), _('REV')),
5107 5108 ('', 'all', False, _("list files from all revisions"))]
5108 5109 + formatteropts,
5109 5110 _('[-r REV]'))
5110 5111 def manifest(ui, repo, node=None, rev=None, **opts):
5111 5112 """output the current or given revision of the project manifest
5112 5113
5113 5114 Print a list of version controlled files for the given revision.
5114 5115 If no revision is given, the first parent of the working directory
5115 5116 is used, or the null revision if no revision is checked out.
5116 5117
5117 5118 With -v, print file permissions, symlink and executable bits.
5118 5119 With --debug, print file revision hashes.
5119 5120
5120 5121 If option --all is specified, the list of all files from all revisions
5121 5122 is printed. This includes deleted and renamed files.
5122 5123
5123 5124 Returns 0 on success.
5124 5125 """
5125 5126
5126 5127 fm = ui.formatter('manifest', opts)
5127 5128
5128 5129 if opts.get('all'):
5129 5130 if rev or node:
5130 5131 raise error.Abort(_("can't specify a revision with --all"))
5131 5132
5132 5133 res = []
5133 5134 prefix = "data/"
5134 5135 suffix = ".i"
5135 5136 plen = len(prefix)
5136 5137 slen = len(suffix)
5137 5138 with repo.lock():
5138 5139 for fn, b, size in repo.store.datafiles():
5139 5140 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
5140 5141 res.append(fn[plen:-slen])
5141 5142 for f in res:
5142 5143 fm.startitem()
5143 5144 fm.write("path", '%s\n', f)
5144 5145 fm.end()
5145 5146 return
5146 5147
5147 5148 if rev and node:
5148 5149 raise error.Abort(_("please specify just one revision"))
5149 5150
5150 5151 if not node:
5151 5152 node = rev
5152 5153
5153 5154 char = {'l': '@', 'x': '*', '': ''}
5154 5155 mode = {'l': '644', 'x': '755', '': '644'}
5155 5156 ctx = scmutil.revsingle(repo, node)
5156 5157 mf = ctx.manifest()
5157 5158 for f in ctx:
5158 5159 fm.startitem()
5159 5160 fl = ctx[f].flags()
5160 5161 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
5161 5162 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
5162 5163 fm.write('path', '%s\n', f)
5163 5164 fm.end()
5164 5165
5165 5166 @command('^merge',
5166 5167 [('f', 'force', None,
5167 5168 _('force a merge including outstanding changes (DEPRECATED)')),
5168 5169 ('r', 'rev', '', _('revision to merge'), _('REV')),
5169 5170 ('P', 'preview', None,
5170 5171 _('review revisions to merge (no merge is performed)'))
5171 5172 ] + mergetoolopts,
5172 5173 _('[-P] [[-r] REV]'))
5173 5174 def merge(ui, repo, node=None, **opts):
5174 5175 """merge another revision into working directory
5175 5176
5176 5177 The current working directory is updated with all changes made in
5177 5178 the requested revision since the last common predecessor revision.
5178 5179
5179 5180 Files that changed between either parent are marked as changed for
5180 5181 the next commit and a commit must be performed before any further
5181 5182 updates to the repository are allowed. The next commit will have
5182 5183 two parents.
5183 5184
5184 5185 ``--tool`` can be used to specify the merge tool used for file
5185 5186 merges. It overrides the HGMERGE environment variable and your
5186 5187 configuration files. See :hg:`help merge-tools` for options.
5187 5188
5188 5189 If no revision is specified, the working directory's parent is a
5189 5190 head revision, and the current branch contains exactly one other
5190 5191 head, the other head is merged with by default. Otherwise, an
5191 5192 explicit revision with which to merge with must be provided.
5192 5193
5193 5194 See :hg:`help resolve` for information on handling file conflicts.
5194 5195
5195 5196 To undo an uncommitted merge, use :hg:`update --clean .` which
5196 5197 will check out a clean copy of the original merge parent, losing
5197 5198 all changes.
5198 5199
5199 5200 Returns 0 on success, 1 if there are unresolved files.
5200 5201 """
5201 5202
5202 5203 if opts.get('rev') and node:
5203 5204 raise error.Abort(_("please specify just one revision"))
5204 5205 if not node:
5205 5206 node = opts.get('rev')
5206 5207
5207 5208 if node:
5208 5209 node = scmutil.revsingle(repo, node).node()
5209 5210
5210 5211 if not node:
5211 5212 node = repo[destutil.destmerge(repo)].node()
5212 5213
5213 5214 if opts.get('preview'):
5214 5215 # find nodes that are ancestors of p2 but not of p1
5215 5216 p1 = repo.lookup('.')
5216 5217 p2 = repo.lookup(node)
5217 5218 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
5218 5219
5219 5220 displayer = cmdutil.show_changeset(ui, repo, opts)
5220 5221 for node in nodes:
5221 5222 displayer.show(repo[node])
5222 5223 displayer.close()
5223 5224 return 0
5224 5225
5225 5226 try:
5226 5227 # ui.forcemerge is an internal variable, do not document
5227 5228 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
5228 5229 force = opts.get('force')
5229 5230 labels = ['working copy', 'merge rev']
5230 5231 return hg.merge(repo, node, force=force, mergeforce=force,
5231 5232 labels=labels)
5232 5233 finally:
5233 5234 ui.setconfig('ui', 'forcemerge', '', 'merge')
5234 5235
5235 5236 @command('outgoing|out',
5236 5237 [('f', 'force', None, _('run even when the destination is unrelated')),
5237 5238 ('r', 'rev', [],
5238 5239 _('a changeset intended to be included in the destination'), _('REV')),
5239 5240 ('n', 'newest-first', None, _('show newest record first')),
5240 5241 ('B', 'bookmarks', False, _('compare bookmarks')),
5241 5242 ('b', 'branch', [], _('a specific branch you would like to push'),
5242 5243 _('BRANCH')),
5243 5244 ] + logopts + remoteopts + subrepoopts,
5244 5245 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
5245 5246 def outgoing(ui, repo, dest=None, **opts):
5246 5247 """show changesets not found in the destination
5247 5248
5248 5249 Show changesets not found in the specified destination repository
5249 5250 or the default push location. These are the changesets that would
5250 5251 be pushed if a push was requested.
5251 5252
5252 5253 See pull for details of valid destination formats.
5253 5254
5254 5255 .. container:: verbose
5255 5256
5256 5257 With -B/--bookmarks, the result of bookmark comparison between
5257 5258 local and remote repositories is displayed. With -v/--verbose,
5258 5259 status is also displayed for each bookmark like below::
5259 5260
5260 5261 BM1 01234567890a added
5261 5262 BM2 deleted
5262 5263 BM3 234567890abc advanced
5263 5264 BM4 34567890abcd diverged
5264 5265 BM5 4567890abcde changed
5265 5266
5266 5267 The action taken when pushing depends on the
5267 5268 status of each bookmark:
5268 5269
5269 5270 :``added``: push with ``-B`` will create it
5270 5271 :``deleted``: push with ``-B`` will delete it
5271 5272 :``advanced``: push will update it
5272 5273 :``diverged``: push with ``-B`` will update it
5273 5274 :``changed``: push with ``-B`` will update it
5274 5275
5275 5276 From the point of view of pushing behavior, bookmarks
5276 5277 existing only in the remote repository are treated as
5277 5278 ``deleted``, even if it is in fact added remotely.
5278 5279
5279 5280 Returns 0 if there are outgoing changes, 1 otherwise.
5280 5281 """
5281 5282 if opts.get('graph'):
5282 5283 cmdutil.checkunsupportedgraphflags([], opts)
5283 5284 o, other = hg._outgoing(ui, repo, dest, opts)
5284 5285 if not o:
5285 5286 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5286 5287 return
5287 5288
5288 5289 revdag = cmdutil.graphrevs(repo, o, opts)
5289 5290 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5290 5291 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
5291 5292 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5292 5293 return 0
5293 5294
5294 5295 if opts.get('bookmarks'):
5295 5296 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5296 5297 dest, branches = hg.parseurl(dest, opts.get('branch'))
5297 5298 other = hg.peer(repo, opts, dest)
5298 5299 if 'bookmarks' not in other.listkeys('namespaces'):
5299 5300 ui.warn(_("remote doesn't support bookmarks\n"))
5300 5301 return 0
5301 5302 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
5302 5303 return bookmarks.outgoing(ui, repo, other)
5303 5304
5304 5305 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
5305 5306 try:
5306 5307 return hg.outgoing(ui, repo, dest, opts)
5307 5308 finally:
5308 5309 del repo._subtoppath
5309 5310
5310 5311 @command('parents',
5311 5312 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
5312 5313 ] + templateopts,
5313 5314 _('[-r REV] [FILE]'),
5314 5315 inferrepo=True)
5315 5316 def parents(ui, repo, file_=None, **opts):
5316 5317 """show the parents of the working directory or revision (DEPRECATED)
5317 5318
5318 5319 Print the working directory's parent revisions. If a revision is
5319 5320 given via -r/--rev, the parent of that revision will be printed.
5320 5321 If a file argument is given, the revision in which the file was
5321 5322 last changed (before the working directory revision or the
5322 5323 argument to --rev if given) is printed.
5323 5324
5324 5325 This command is equivalent to::
5325 5326
5326 5327 hg log -r "p1()+p2()" or
5327 5328 hg log -r "p1(REV)+p2(REV)" or
5328 5329 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5329 5330 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5330 5331
5331 5332 See :hg:`summary` and :hg:`help revsets` for related information.
5332 5333
5333 5334 Returns 0 on success.
5334 5335 """
5335 5336
5336 5337 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
5337 5338
5338 5339 if file_:
5339 5340 m = scmutil.match(ctx, (file_,), opts)
5340 5341 if m.anypats() or len(m.files()) != 1:
5341 5342 raise error.Abort(_('can only specify an explicit filename'))
5342 5343 file_ = m.files()[0]
5343 5344 filenodes = []
5344 5345 for cp in ctx.parents():
5345 5346 if not cp:
5346 5347 continue
5347 5348 try:
5348 5349 filenodes.append(cp.filenode(file_))
5349 5350 except error.LookupError:
5350 5351 pass
5351 5352 if not filenodes:
5352 5353 raise error.Abort(_("'%s' not found in manifest!") % file_)
5353 5354 p = []
5354 5355 for fn in filenodes:
5355 5356 fctx = repo.filectx(file_, fileid=fn)
5356 5357 p.append(fctx.node())
5357 5358 else:
5358 5359 p = [cp.node() for cp in ctx.parents()]
5359 5360
5360 5361 displayer = cmdutil.show_changeset(ui, repo, opts)
5361 5362 for n in p:
5362 5363 if n != nullid:
5363 5364 displayer.show(repo[n])
5364 5365 displayer.close()
5365 5366
5366 5367 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
5367 5368 def paths(ui, repo, search=None, **opts):
5368 5369 """show aliases for remote repositories
5369 5370
5370 5371 Show definition of symbolic path name NAME. If no name is given,
5371 5372 show definition of all available names.
5372 5373
5373 5374 Option -q/--quiet suppresses all output when searching for NAME
5374 5375 and shows only the path names when listing all definitions.
5375 5376
5376 5377 Path names are defined in the [paths] section of your
5377 5378 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5378 5379 repository, ``.hg/hgrc`` is used, too.
5379 5380
5380 5381 The path names ``default`` and ``default-push`` have a special
5381 5382 meaning. When performing a push or pull operation, they are used
5382 5383 as fallbacks if no location is specified on the command-line.
5383 5384 When ``default-push`` is set, it will be used for push and
5384 5385 ``default`` will be used for pull; otherwise ``default`` is used
5385 5386 as the fallback for both. When cloning a repository, the clone
5386 5387 source is written as ``default`` in ``.hg/hgrc``.
5387 5388
5388 5389 .. note::
5389 5390
5390 5391 ``default`` and ``default-push`` apply to all inbound (e.g.
5391 5392 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5392 5393 and :hg:`bundle`) operations.
5393 5394
5394 5395 See :hg:`help urls` for more information.
5395 5396
5396 5397 Returns 0 on success.
5397 5398 """
5398 5399 if search:
5399 5400 pathitems = [(name, path) for name, path in ui.paths.iteritems()
5400 5401 if name == search]
5401 5402 else:
5402 5403 pathitems = sorted(ui.paths.iteritems())
5403 5404
5404 5405 fm = ui.formatter('paths', opts)
5405 5406 if fm.isplain():
5406 5407 hidepassword = util.hidepassword
5407 5408 else:
5408 5409 hidepassword = str
5409 5410 if ui.quiet:
5410 5411 namefmt = '%s\n'
5411 5412 else:
5412 5413 namefmt = '%s = '
5413 5414 showsubopts = not search and not ui.quiet
5414 5415
5415 5416 for name, path in pathitems:
5416 5417 fm.startitem()
5417 5418 fm.condwrite(not search, 'name', namefmt, name)
5418 5419 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
5419 5420 for subopt, value in sorted(path.suboptions.items()):
5420 5421 assert subopt not in ('name', 'url')
5421 5422 if showsubopts:
5422 5423 fm.plain('%s:%s = ' % (name, subopt))
5423 5424 fm.condwrite(showsubopts, subopt, '%s\n', value)
5424 5425
5425 5426 fm.end()
5426 5427
5427 5428 if search and not pathitems:
5428 5429 if not ui.quiet:
5429 5430 ui.warn(_("not found!\n"))
5430 5431 return 1
5431 5432 else:
5432 5433 return 0
5433 5434
5434 5435 @command('phase',
5435 5436 [('p', 'public', False, _('set changeset phase to public')),
5436 5437 ('d', 'draft', False, _('set changeset phase to draft')),
5437 5438 ('s', 'secret', False, _('set changeset phase to secret')),
5438 5439 ('f', 'force', False, _('allow to move boundary backward')),
5439 5440 ('r', 'rev', [], _('target revision'), _('REV')),
5440 5441 ],
5441 5442 _('[-p|-d|-s] [-f] [-r] [REV...]'))
5442 5443 def phase(ui, repo, *revs, **opts):
5443 5444 """set or show the current phase name
5444 5445
5445 5446 With no argument, show the phase name of the current revision(s).
5446 5447
5447 5448 With one of -p/--public, -d/--draft or -s/--secret, change the
5448 5449 phase value of the specified revisions.
5449 5450
5450 5451 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
5451 5452 lower phase to an higher phase. Phases are ordered as follows::
5452 5453
5453 5454 public < draft < secret
5454 5455
5455 5456 Returns 0 on success, 1 if some phases could not be changed.
5456 5457
5457 5458 (For more information about the phases concept, see :hg:`help phases`.)
5458 5459 """
5459 5460 # search for a unique phase argument
5460 5461 targetphase = None
5461 5462 for idx, name in enumerate(phases.phasenames):
5462 5463 if opts[name]:
5463 5464 if targetphase is not None:
5464 5465 raise error.Abort(_('only one phase can be specified'))
5465 5466 targetphase = idx
5466 5467
5467 5468 # look for specified revision
5468 5469 revs = list(revs)
5469 5470 revs.extend(opts['rev'])
5470 5471 if not revs:
5471 5472 # display both parents as the second parent phase can influence
5472 5473 # the phase of a merge commit
5473 5474 revs = [c.rev() for c in repo[None].parents()]
5474 5475
5475 5476 revs = scmutil.revrange(repo, revs)
5476 5477
5477 5478 lock = None
5478 5479 ret = 0
5479 5480 if targetphase is None:
5480 5481 # display
5481 5482 for r in revs:
5482 5483 ctx = repo[r]
5483 5484 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5484 5485 else:
5485 5486 tr = None
5486 5487 lock = repo.lock()
5487 5488 try:
5488 5489 tr = repo.transaction("phase")
5489 5490 # set phase
5490 5491 if not revs:
5491 5492 raise error.Abort(_('empty revision set'))
5492 5493 nodes = [repo[r].node() for r in revs]
5493 5494 # moving revision from public to draft may hide them
5494 5495 # We have to check result on an unfiltered repository
5495 5496 unfi = repo.unfiltered()
5496 5497 getphase = unfi._phasecache.phase
5497 5498 olddata = [getphase(unfi, r) for r in unfi]
5498 5499 phases.advanceboundary(repo, tr, targetphase, nodes)
5499 5500 if opts['force']:
5500 5501 phases.retractboundary(repo, tr, targetphase, nodes)
5501 5502 tr.close()
5502 5503 finally:
5503 5504 if tr is not None:
5504 5505 tr.release()
5505 5506 lock.release()
5506 5507 getphase = unfi._phasecache.phase
5507 5508 newdata = [getphase(unfi, r) for r in unfi]
5508 5509 changes = sum(newdata[r] != olddata[r] for r in unfi)
5509 5510 cl = unfi.changelog
5510 5511 rejected = [n for n in nodes
5511 5512 if newdata[cl.rev(n)] < targetphase]
5512 5513 if rejected:
5513 5514 ui.warn(_('cannot move %i changesets to a higher '
5514 5515 'phase, use --force\n') % len(rejected))
5515 5516 ret = 1
5516 5517 if changes:
5517 5518 msg = _('phase changed for %i changesets\n') % changes
5518 5519 if ret:
5519 5520 ui.status(msg)
5520 5521 else:
5521 5522 ui.note(msg)
5522 5523 else:
5523 5524 ui.warn(_('no phases changed\n'))
5524 5525 return ret
5525 5526
5526 5527 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5527 5528 """Run after a changegroup has been added via pull/unbundle
5528 5529
5529 5530 This takes arguments below:
5530 5531
5531 5532 :modheads: change of heads by pull/unbundle
5532 5533 :optupdate: updating working directory is needed or not
5533 5534 :checkout: update destination revision (or None to default destination)
5534 5535 :brev: a name, which might be a bookmark to be activated after updating
5535 5536 """
5536 5537 if modheads == 0:
5537 5538 return
5538 5539 if optupdate:
5539 5540 try:
5540 5541 return hg.updatetotally(ui, repo, checkout, brev)
5541 5542 except error.UpdateAbort as inst:
5542 5543 msg = _("not updating: %s") % str(inst)
5543 5544 hint = inst.hint
5544 5545 raise error.UpdateAbort(msg, hint=hint)
5545 5546 if modheads > 1:
5546 5547 currentbranchheads = len(repo.branchheads())
5547 5548 if currentbranchheads == modheads:
5548 5549 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
5549 5550 elif currentbranchheads > 1:
5550 5551 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
5551 5552 "merge)\n"))
5552 5553 else:
5553 5554 ui.status(_("(run 'hg heads' to see heads)\n"))
5554 5555 else:
5555 5556 ui.status(_("(run 'hg update' to get a working copy)\n"))
5556 5557
5557 5558 @command('^pull',
5558 5559 [('u', 'update', None,
5559 5560 _('update to new branch head if changesets were pulled')),
5560 5561 ('f', 'force', None, _('run even when remote repository is unrelated')),
5561 5562 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
5562 5563 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
5563 5564 ('b', 'branch', [], _('a specific branch you would like to pull'),
5564 5565 _('BRANCH')),
5565 5566 ] + remoteopts,
5566 5567 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
5567 5568 def pull(ui, repo, source="default", **opts):
5568 5569 """pull changes from the specified source
5569 5570
5570 5571 Pull changes from a remote repository to a local one.
5571 5572
5572 5573 This finds all changes from the repository at the specified path
5573 5574 or URL and adds them to a local repository (the current one unless
5574 5575 -R is specified). By default, this does not update the copy of the
5575 5576 project in the working directory.
5576 5577
5577 5578 Use :hg:`incoming` if you want to see what would have been added
5578 5579 by a pull at the time you issued this command. If you then decide
5579 5580 to add those changes to the repository, you should use :hg:`pull
5580 5581 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5581 5582
5582 5583 If SOURCE is omitted, the 'default' path will be used.
5583 5584 See :hg:`help urls` for more information.
5584 5585
5585 5586 Specifying bookmark as ``.`` is equivalent to specifying the active
5586 5587 bookmark's name.
5587 5588
5588 5589 Returns 0 on success, 1 if an update had unresolved files.
5589 5590 """
5590 5591 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5591 5592 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5592 5593 other = hg.peer(repo, opts, source)
5593 5594 try:
5594 5595 revs, checkout = hg.addbranchrevs(repo, other, branches,
5595 5596 opts.get('rev'))
5596 5597
5597 5598
5598 5599 pullopargs = {}
5599 5600 if opts.get('bookmark'):
5600 5601 if not revs:
5601 5602 revs = []
5602 5603 # The list of bookmark used here is not the one used to actually
5603 5604 # update the bookmark name. This can result in the revision pulled
5604 5605 # not ending up with the name of the bookmark because of a race
5605 5606 # condition on the server. (See issue 4689 for details)
5606 5607 remotebookmarks = other.listkeys('bookmarks')
5607 5608 pullopargs['remotebookmarks'] = remotebookmarks
5608 5609 for b in opts['bookmark']:
5609 5610 b = repo._bookmarks.expandname(b)
5610 5611 if b not in remotebookmarks:
5611 5612 raise error.Abort(_('remote bookmark %s not found!') % b)
5612 5613 revs.append(remotebookmarks[b])
5613 5614
5614 5615 if revs:
5615 5616 try:
5616 5617 # When 'rev' is a bookmark name, we cannot guarantee that it
5617 5618 # will be updated with that name because of a race condition
5618 5619 # server side. (See issue 4689 for details)
5619 5620 oldrevs = revs
5620 5621 revs = [] # actually, nodes
5621 5622 for r in oldrevs:
5622 5623 node = other.lookup(r)
5623 5624 revs.append(node)
5624 5625 if r == checkout:
5625 5626 checkout = node
5626 5627 except error.CapabilityError:
5627 5628 err = _("other repository doesn't support revision lookup, "
5628 5629 "so a rev cannot be specified.")
5629 5630 raise error.Abort(err)
5630 5631
5631 5632 pullopargs.update(opts.get('opargs', {}))
5632 5633 modheads = exchange.pull(repo, other, heads=revs,
5633 5634 force=opts.get('force'),
5634 5635 bookmarks=opts.get('bookmark', ()),
5635 5636 opargs=pullopargs).cgresult
5636 5637
5637 5638 # brev is a name, which might be a bookmark to be activated at
5638 5639 # the end of the update. In other words, it is an explicit
5639 5640 # destination of the update
5640 5641 brev = None
5641 5642
5642 5643 if checkout:
5643 5644 checkout = str(repo.changelog.rev(checkout))
5644 5645
5645 5646 # order below depends on implementation of
5646 5647 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5647 5648 # because 'checkout' is determined without it.
5648 5649 if opts.get('rev'):
5649 5650 brev = opts['rev'][0]
5650 5651 elif opts.get('branch'):
5651 5652 brev = opts['branch'][0]
5652 5653 else:
5653 5654 brev = branches[0]
5654 5655 repo._subtoppath = source
5655 5656 try:
5656 5657 ret = postincoming(ui, repo, modheads, opts.get('update'),
5657 5658 checkout, brev)
5658 5659
5659 5660 finally:
5660 5661 del repo._subtoppath
5661 5662
5662 5663 finally:
5663 5664 other.close()
5664 5665 return ret
5665 5666
5666 5667 @command('^push',
5667 5668 [('f', 'force', None, _('force push')),
5668 5669 ('r', 'rev', [],
5669 5670 _('a changeset intended to be included in the destination'),
5670 5671 _('REV')),
5671 5672 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5672 5673 ('b', 'branch', [],
5673 5674 _('a specific branch you would like to push'), _('BRANCH')),
5674 5675 ('', 'new-branch', False, _('allow pushing a new branch')),
5675 5676 ] + remoteopts,
5676 5677 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5677 5678 def push(ui, repo, dest=None, **opts):
5678 5679 """push changes to the specified destination
5679 5680
5680 5681 Push changesets from the local repository to the specified
5681 5682 destination.
5682 5683
5683 5684 This operation is symmetrical to pull: it is identical to a pull
5684 5685 in the destination repository from the current one.
5685 5686
5686 5687 By default, push will not allow creation of new heads at the
5687 5688 destination, since multiple heads would make it unclear which head
5688 5689 to use. In this situation, it is recommended to pull and merge
5689 5690 before pushing.
5690 5691
5691 5692 Use --new-branch if you want to allow push to create a new named
5692 5693 branch that is not present at the destination. This allows you to
5693 5694 only create a new branch without forcing other changes.
5694 5695
5695 5696 .. note::
5696 5697
5697 5698 Extra care should be taken with the -f/--force option,
5698 5699 which will push all new heads on all branches, an action which will
5699 5700 almost always cause confusion for collaborators.
5700 5701
5701 5702 If -r/--rev is used, the specified revision and all its ancestors
5702 5703 will be pushed to the remote repository.
5703 5704
5704 5705 If -B/--bookmark is used, the specified bookmarked revision, its
5705 5706 ancestors, and the bookmark will be pushed to the remote
5706 5707 repository. Specifying ``.`` is equivalent to specifying the active
5707 5708 bookmark's name.
5708 5709
5709 5710 Please see :hg:`help urls` for important details about ``ssh://``
5710 5711 URLs. If DESTINATION is omitted, a default path will be used.
5711 5712
5712 5713 Returns 0 if push was successful, 1 if nothing to push.
5713 5714 """
5714 5715
5715 5716 if opts.get('bookmark'):
5716 5717 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5717 5718 for b in opts['bookmark']:
5718 5719 # translate -B options to -r so changesets get pushed
5719 5720 b = repo._bookmarks.expandname(b)
5720 5721 if b in repo._bookmarks:
5721 5722 opts.setdefault('rev', []).append(b)
5722 5723 else:
5723 5724 # if we try to push a deleted bookmark, translate it to null
5724 5725 # this lets simultaneous -r, -b options continue working
5725 5726 opts.setdefault('rev', []).append("null")
5726 5727
5727 5728 path = ui.paths.getpath(dest, default=('default-push', 'default'))
5728 5729 if not path:
5729 5730 raise error.Abort(_('default repository not configured!'),
5730 5731 hint=_("see 'hg help config.paths'"))
5731 5732 dest = path.pushloc or path.loc
5732 5733 branches = (path.branch, opts.get('branch') or [])
5733 5734 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5734 5735 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5735 5736 other = hg.peer(repo, opts, dest)
5736 5737
5737 5738 if revs:
5738 5739 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5739 5740 if not revs:
5740 5741 raise error.Abort(_("specified revisions evaluate to an empty set"),
5741 5742 hint=_("use different revision arguments"))
5742 5743 elif path.pushrev:
5743 5744 # It doesn't make any sense to specify ancestor revisions. So limit
5744 5745 # to DAG heads to make discovery simpler.
5745 5746 expr = revset.formatspec('heads(%r)', path.pushrev)
5746 5747 revs = scmutil.revrange(repo, [expr])
5747 5748 revs = [repo[rev].node() for rev in revs]
5748 5749 if not revs:
5749 5750 raise error.Abort(_('default push revset for path evaluates to an '
5750 5751 'empty set'))
5751 5752
5752 5753 repo._subtoppath = dest
5753 5754 try:
5754 5755 # push subrepos depth-first for coherent ordering
5755 5756 c = repo['']
5756 5757 subs = c.substate # only repos that are committed
5757 5758 for s in sorted(subs):
5758 5759 result = c.sub(s).push(opts)
5759 5760 if result == 0:
5760 5761 return not result
5761 5762 finally:
5762 5763 del repo._subtoppath
5763 5764 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5764 5765 newbranch=opts.get('new_branch'),
5765 5766 bookmarks=opts.get('bookmark', ()),
5766 5767 opargs=opts.get('opargs'))
5767 5768
5768 5769 result = not pushop.cgresult
5769 5770
5770 5771 if pushop.bkresult is not None:
5771 5772 if pushop.bkresult == 2:
5772 5773 result = 2
5773 5774 elif not result and pushop.bkresult:
5774 5775 result = 2
5775 5776
5776 5777 return result
5777 5778
5778 5779 @command('recover', [])
5779 5780 def recover(ui, repo):
5780 5781 """roll back an interrupted transaction
5781 5782
5782 5783 Recover from an interrupted commit or pull.
5783 5784
5784 5785 This command tries to fix the repository status after an
5785 5786 interrupted operation. It should only be necessary when Mercurial
5786 5787 suggests it.
5787 5788
5788 5789 Returns 0 if successful, 1 if nothing to recover or verify fails.
5789 5790 """
5790 5791 if repo.recover():
5791 5792 return hg.verify(repo)
5792 5793 return 1
5793 5794
5794 5795 @command('^remove|rm',
5795 5796 [('A', 'after', None, _('record delete for missing files')),
5796 5797 ('f', 'force', None,
5797 5798 _('forget added files, delete modified files')),
5798 5799 ] + subrepoopts + walkopts,
5799 5800 _('[OPTION]... FILE...'),
5800 5801 inferrepo=True)
5801 5802 def remove(ui, repo, *pats, **opts):
5802 5803 """remove the specified files on the next commit
5803 5804
5804 5805 Schedule the indicated files for removal from the current branch.
5805 5806
5806 5807 This command schedules the files to be removed at the next commit.
5807 5808 To undo a remove before that, see :hg:`revert`. To undo added
5808 5809 files, see :hg:`forget`.
5809 5810
5810 5811 .. container:: verbose
5811 5812
5812 5813 -A/--after can be used to remove only files that have already
5813 5814 been deleted, -f/--force can be used to force deletion, and -Af
5814 5815 can be used to remove files from the next revision without
5815 5816 deleting them from the working directory.
5816 5817
5817 5818 The following table details the behavior of remove for different
5818 5819 file states (columns) and option combinations (rows). The file
5819 5820 states are Added [A], Clean [C], Modified [M] and Missing [!]
5820 5821 (as reported by :hg:`status`). The actions are Warn, Remove
5821 5822 (from branch) and Delete (from disk):
5822 5823
5823 5824 ========= == == == ==
5824 5825 opt/state A C M !
5825 5826 ========= == == == ==
5826 5827 none W RD W R
5827 5828 -f R RD RD R
5828 5829 -A W W W R
5829 5830 -Af R R R R
5830 5831 ========= == == == ==
5831 5832
5832 5833 .. note::
5833 5834
5834 5835 :hg:`remove` never deletes files in Added [A] state from the
5835 5836 working directory, not even if ``--force`` is specified.
5836 5837
5837 5838 Returns 0 on success, 1 if any warnings encountered.
5838 5839 """
5839 5840
5840 5841 after, force = opts.get('after'), opts.get('force')
5841 5842 if not pats and not after:
5842 5843 raise error.Abort(_('no files specified'))
5843 5844
5844 5845 m = scmutil.match(repo[None], pats, opts)
5845 5846 subrepos = opts.get('subrepos')
5846 5847 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5847 5848
5848 5849 @command('rename|move|mv',
5849 5850 [('A', 'after', None, _('record a rename that has already occurred')),
5850 5851 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5851 5852 ] + walkopts + dryrunopts,
5852 5853 _('[OPTION]... SOURCE... DEST'))
5853 5854 def rename(ui, repo, *pats, **opts):
5854 5855 """rename files; equivalent of copy + remove
5855 5856
5856 5857 Mark dest as copies of sources; mark sources for deletion. If dest
5857 5858 is a directory, copies are put in that directory. If dest is a
5858 5859 file, there can only be one source.
5859 5860
5860 5861 By default, this command copies the contents of files as they
5861 5862 exist in the working directory. If invoked with -A/--after, the
5862 5863 operation is recorded, but no copying is performed.
5863 5864
5864 5865 This command takes effect at the next commit. To undo a rename
5865 5866 before that, see :hg:`revert`.
5866 5867
5867 5868 Returns 0 on success, 1 if errors are encountered.
5868 5869 """
5869 5870 with repo.wlock(False):
5870 5871 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5871 5872
5872 5873 @command('resolve',
5873 5874 [('a', 'all', None, _('select all unresolved files')),
5874 5875 ('l', 'list', None, _('list state of files needing merge')),
5875 5876 ('m', 'mark', None, _('mark files as resolved')),
5876 5877 ('u', 'unmark', None, _('mark files as unresolved')),
5877 5878 ('n', 'no-status', None, _('hide status prefix'))]
5878 5879 + mergetoolopts + walkopts + formatteropts,
5879 5880 _('[OPTION]... [FILE]...'),
5880 5881 inferrepo=True)
5881 5882 def resolve(ui, repo, *pats, **opts):
5882 5883 """redo merges or set/view the merge status of files
5883 5884
5884 5885 Merges with unresolved conflicts are often the result of
5885 5886 non-interactive merging using the ``internal:merge`` configuration
5886 5887 setting, or a command-line merge tool like ``diff3``. The resolve
5887 5888 command is used to manage the files involved in a merge, after
5888 5889 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5889 5890 working directory must have two parents). See :hg:`help
5890 5891 merge-tools` for information on configuring merge tools.
5891 5892
5892 5893 The resolve command can be used in the following ways:
5893 5894
5894 5895 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5895 5896 files, discarding any previous merge attempts. Re-merging is not
5896 5897 performed for files already marked as resolved. Use ``--all/-a``
5897 5898 to select all unresolved files. ``--tool`` can be used to specify
5898 5899 the merge tool used for the given files. It overrides the HGMERGE
5899 5900 environment variable and your configuration files. Previous file
5900 5901 contents are saved with a ``.orig`` suffix.
5901 5902
5902 5903 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5903 5904 (e.g. after having manually fixed-up the files). The default is
5904 5905 to mark all unresolved files.
5905 5906
5906 5907 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5907 5908 default is to mark all resolved files.
5908 5909
5909 5910 - :hg:`resolve -l`: list files which had or still have conflicts.
5910 5911 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5911 5912
5912 5913 .. note::
5913 5914
5914 5915 Mercurial will not let you commit files with unresolved merge
5915 5916 conflicts. You must use :hg:`resolve -m ...` before you can
5916 5917 commit after a conflicting merge.
5917 5918
5918 5919 Returns 0 on success, 1 if any files fail a resolve attempt.
5919 5920 """
5920 5921
5921 5922 flaglist = 'all mark unmark list no_status'.split()
5922 5923 all, mark, unmark, show, nostatus = \
5923 5924 [opts.get(o) for o in flaglist]
5924 5925
5925 5926 if (show and (mark or unmark)) or (mark and unmark):
5926 5927 raise error.Abort(_("too many options specified"))
5927 5928 if pats and all:
5928 5929 raise error.Abort(_("can't specify --all and patterns"))
5929 5930 if not (all or pats or show or mark or unmark):
5930 5931 raise error.Abort(_('no files or directories specified'),
5931 5932 hint=('use --all to re-merge all unresolved files'))
5932 5933
5933 5934 if show:
5934 5935 fm = ui.formatter('resolve', opts)
5935 5936 ms = mergemod.mergestate.read(repo)
5936 5937 m = scmutil.match(repo[None], pats, opts)
5937 5938 for f in ms:
5938 5939 if not m(f):
5939 5940 continue
5940 5941 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
5941 5942 'd': 'driverresolved'}[ms[f]]
5942 5943 fm.startitem()
5943 5944 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
5944 5945 fm.write('path', '%s\n', f, label=l)
5945 5946 fm.end()
5946 5947 return 0
5947 5948
5948 5949 with repo.wlock():
5949 5950 ms = mergemod.mergestate.read(repo)
5950 5951
5951 5952 if not (ms.active() or repo.dirstate.p2() != nullid):
5952 5953 raise error.Abort(
5953 5954 _('resolve command not applicable when not merging'))
5954 5955
5955 5956 wctx = repo[None]
5956 5957
5957 5958 if ms.mergedriver and ms.mdstate() == 'u':
5958 5959 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5959 5960 ms.commit()
5960 5961 # allow mark and unmark to go through
5961 5962 if not mark and not unmark and not proceed:
5962 5963 return 1
5963 5964
5964 5965 m = scmutil.match(wctx, pats, opts)
5965 5966 ret = 0
5966 5967 didwork = False
5967 5968 runconclude = False
5968 5969
5969 5970 tocomplete = []
5970 5971 for f in ms:
5971 5972 if not m(f):
5972 5973 continue
5973 5974
5974 5975 didwork = True
5975 5976
5976 5977 # don't let driver-resolved files be marked, and run the conclude
5977 5978 # step if asked to resolve
5978 5979 if ms[f] == "d":
5979 5980 exact = m.exact(f)
5980 5981 if mark:
5981 5982 if exact:
5982 5983 ui.warn(_('not marking %s as it is driver-resolved\n')
5983 5984 % f)
5984 5985 elif unmark:
5985 5986 if exact:
5986 5987 ui.warn(_('not unmarking %s as it is driver-resolved\n')
5987 5988 % f)
5988 5989 else:
5989 5990 runconclude = True
5990 5991 continue
5991 5992
5992 5993 if mark:
5993 5994 ms.mark(f, "r")
5994 5995 elif unmark:
5995 5996 ms.mark(f, "u")
5996 5997 else:
5997 5998 # backup pre-resolve (merge uses .orig for its own purposes)
5998 5999 a = repo.wjoin(f)
5999 6000 try:
6000 6001 util.copyfile(a, a + ".resolve")
6001 6002 except (IOError, OSError) as inst:
6002 6003 if inst.errno != errno.ENOENT:
6003 6004 raise
6004 6005
6005 6006 try:
6006 6007 # preresolve file
6007 6008 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6008 6009 'resolve')
6009 6010 complete, r = ms.preresolve(f, wctx)
6010 6011 if not complete:
6011 6012 tocomplete.append(f)
6012 6013 elif r:
6013 6014 ret = 1
6014 6015 finally:
6015 6016 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6016 6017 ms.commit()
6017 6018
6018 6019 # replace filemerge's .orig file with our resolve file, but only
6019 6020 # for merges that are complete
6020 6021 if complete:
6021 6022 try:
6022 6023 util.rename(a + ".resolve",
6023 6024 scmutil.origpath(ui, repo, a))
6024 6025 except OSError as inst:
6025 6026 if inst.errno != errno.ENOENT:
6026 6027 raise
6027 6028
6028 6029 for f in tocomplete:
6029 6030 try:
6030 6031 # resolve file
6031 6032 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6032 6033 'resolve')
6033 6034 r = ms.resolve(f, wctx)
6034 6035 if r:
6035 6036 ret = 1
6036 6037 finally:
6037 6038 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6038 6039 ms.commit()
6039 6040
6040 6041 # replace filemerge's .orig file with our resolve file
6041 6042 a = repo.wjoin(f)
6042 6043 try:
6043 6044 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
6044 6045 except OSError as inst:
6045 6046 if inst.errno != errno.ENOENT:
6046 6047 raise
6047 6048
6048 6049 ms.commit()
6049 6050 ms.recordactions()
6050 6051
6051 6052 if not didwork and pats:
6052 6053 hint = None
6053 6054 if not any([p for p in pats if p.find(':') >= 0]):
6054 6055 pats = ['path:%s' % p for p in pats]
6055 6056 m = scmutil.match(wctx, pats, opts)
6056 6057 for f in ms:
6057 6058 if not m(f):
6058 6059 continue
6059 6060 flags = ''.join(['-%s ' % o[0] for o in flaglist
6060 6061 if opts.get(o)])
6061 6062 hint = _("(try: hg resolve %s%s)\n") % (
6062 6063 flags,
6063 6064 ' '.join(pats))
6064 6065 break
6065 6066 ui.warn(_("arguments do not match paths that need resolving\n"))
6066 6067 if hint:
6067 6068 ui.warn(hint)
6068 6069 elif ms.mergedriver and ms.mdstate() != 's':
6069 6070 # run conclude step when either a driver-resolved file is requested
6070 6071 # or there are no driver-resolved files
6071 6072 # we can't use 'ret' to determine whether any files are unresolved
6072 6073 # because we might not have tried to resolve some
6073 6074 if ((runconclude or not list(ms.driverresolved()))
6074 6075 and not list(ms.unresolved())):
6075 6076 proceed = mergemod.driverconclude(repo, ms, wctx)
6076 6077 ms.commit()
6077 6078 if not proceed:
6078 6079 return 1
6079 6080
6080 6081 # Nudge users into finishing an unfinished operation
6081 6082 unresolvedf = list(ms.unresolved())
6082 6083 driverresolvedf = list(ms.driverresolved())
6083 6084 if not unresolvedf and not driverresolvedf:
6084 6085 ui.status(_('(no more unresolved files)\n'))
6085 6086 cmdutil.checkafterresolved(repo)
6086 6087 elif not unresolvedf:
6087 6088 ui.status(_('(no more unresolved files -- '
6088 6089 'run "hg resolve --all" to conclude)\n'))
6089 6090
6090 6091 return ret
6091 6092
6092 6093 @command('revert',
6093 6094 [('a', 'all', None, _('revert all changes when no arguments given')),
6094 6095 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6095 6096 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
6096 6097 ('C', 'no-backup', None, _('do not save backup copies of files')),
6097 6098 ('i', 'interactive', None,
6098 6099 _('interactively select the changes (EXPERIMENTAL)')),
6099 6100 ] + walkopts + dryrunopts,
6100 6101 _('[OPTION]... [-r REV] [NAME]...'))
6101 6102 def revert(ui, repo, *pats, **opts):
6102 6103 """restore files to their checkout state
6103 6104
6104 6105 .. note::
6105 6106
6106 6107 To check out earlier revisions, you should use :hg:`update REV`.
6107 6108 To cancel an uncommitted merge (and lose your changes),
6108 6109 use :hg:`update --clean .`.
6109 6110
6110 6111 With no revision specified, revert the specified files or directories
6111 6112 to the contents they had in the parent of the working directory.
6112 6113 This restores the contents of files to an unmodified
6113 6114 state and unschedules adds, removes, copies, and renames. If the
6114 6115 working directory has two parents, you must explicitly specify a
6115 6116 revision.
6116 6117
6117 6118 Using the -r/--rev or -d/--date options, revert the given files or
6118 6119 directories to their states as of a specific revision. Because
6119 6120 revert does not change the working directory parents, this will
6120 6121 cause these files to appear modified. This can be helpful to "back
6121 6122 out" some or all of an earlier change. See :hg:`backout` for a
6122 6123 related method.
6123 6124
6124 6125 Modified files are saved with a .orig suffix before reverting.
6125 6126 To disable these backups, use --no-backup. It is possible to store
6126 6127 the backup files in a custom directory relative to the root of the
6127 6128 repository by setting the ``ui.origbackuppath`` configuration
6128 6129 option.
6129 6130
6130 6131 See :hg:`help dates` for a list of formats valid for -d/--date.
6131 6132
6132 6133 See :hg:`help backout` for a way to reverse the effect of an
6133 6134 earlier changeset.
6134 6135
6135 6136 Returns 0 on success.
6136 6137 """
6137 6138
6138 6139 if opts.get("date"):
6139 6140 if opts.get("rev"):
6140 6141 raise error.Abort(_("you can't specify a revision and a date"))
6141 6142 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
6142 6143
6143 6144 parent, p2 = repo.dirstate.parents()
6144 6145 if not opts.get('rev') and p2 != nullid:
6145 6146 # revert after merge is a trap for new users (issue2915)
6146 6147 raise error.Abort(_('uncommitted merge with no revision specified'),
6147 6148 hint=_("use 'hg update' or see 'hg help revert'"))
6148 6149
6149 6150 ctx = scmutil.revsingle(repo, opts.get('rev'))
6150 6151
6151 6152 if (not (pats or opts.get('include') or opts.get('exclude') or
6152 6153 opts.get('all') or opts.get('interactive'))):
6153 6154 msg = _("no files or directories specified")
6154 6155 if p2 != nullid:
6155 6156 hint = _("uncommitted merge, use --all to discard all changes,"
6156 6157 " or 'hg update -C .' to abort the merge")
6157 6158 raise error.Abort(msg, hint=hint)
6158 6159 dirty = any(repo.status())
6159 6160 node = ctx.node()
6160 6161 if node != parent:
6161 6162 if dirty:
6162 6163 hint = _("uncommitted changes, use --all to discard all"
6163 6164 " changes, or 'hg update %s' to update") % ctx.rev()
6164 6165 else:
6165 6166 hint = _("use --all to revert all files,"
6166 6167 " or 'hg update %s' to update") % ctx.rev()
6167 6168 elif dirty:
6168 6169 hint = _("uncommitted changes, use --all to discard all changes")
6169 6170 else:
6170 6171 hint = _("use --all to revert all files")
6171 6172 raise error.Abort(msg, hint=hint)
6172 6173
6173 6174 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
6174 6175
6175 6176 @command('rollback', dryrunopts +
6176 6177 [('f', 'force', False, _('ignore safety measures'))])
6177 6178 def rollback(ui, repo, **opts):
6178 6179 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6179 6180
6180 6181 Please use :hg:`commit --amend` instead of rollback to correct
6181 6182 mistakes in the last commit.
6182 6183
6183 6184 This command should be used with care. There is only one level of
6184 6185 rollback, and there is no way to undo a rollback. It will also
6185 6186 restore the dirstate at the time of the last transaction, losing
6186 6187 any dirstate changes since that time. This command does not alter
6187 6188 the working directory.
6188 6189
6189 6190 Transactions are used to encapsulate the effects of all commands
6190 6191 that create new changesets or propagate existing changesets into a
6191 6192 repository.
6192 6193
6193 6194 .. container:: verbose
6194 6195
6195 6196 For example, the following commands are transactional, and their
6196 6197 effects can be rolled back:
6197 6198
6198 6199 - commit
6199 6200 - import
6200 6201 - pull
6201 6202 - push (with this repository as the destination)
6202 6203 - unbundle
6203 6204
6204 6205 To avoid permanent data loss, rollback will refuse to rollback a
6205 6206 commit transaction if it isn't checked out. Use --force to
6206 6207 override this protection.
6207 6208
6208 6209 The rollback command can be entirely disabled by setting the
6209 6210 ``ui.rollback`` configuration setting to false. If you're here
6210 6211 because you want to use rollback and it's disabled, you can
6211 6212 re-enable the command by setting ``ui.rollback`` to true.
6212 6213
6213 6214 This command is not intended for use on public repositories. Once
6214 6215 changes are visible for pull by other users, rolling a transaction
6215 6216 back locally is ineffective (someone else may already have pulled
6216 6217 the changes). Furthermore, a race is possible with readers of the
6217 6218 repository; for example an in-progress pull from the repository
6218 6219 may fail if a rollback is performed.
6219 6220
6220 6221 Returns 0 on success, 1 if no rollback data is available.
6221 6222 """
6222 6223 if not ui.configbool('ui', 'rollback', True):
6223 6224 raise error.Abort(_('rollback is disabled because it is unsafe'),
6224 6225 hint=('see `hg help -v rollback` for information'))
6225 6226 return repo.rollback(dryrun=opts.get('dry_run'),
6226 6227 force=opts.get('force'))
6227 6228
6228 6229 @command('root', [])
6229 6230 def root(ui, repo):
6230 6231 """print the root (top) of the current working directory
6231 6232
6232 6233 Print the root directory of the current repository.
6233 6234
6234 6235 Returns 0 on success.
6235 6236 """
6236 6237 ui.write(repo.root + "\n")
6237 6238
6238 6239 @command('^serve',
6239 6240 [('A', 'accesslog', '', _('name of access log file to write to'),
6240 6241 _('FILE')),
6241 6242 ('d', 'daemon', None, _('run server in background')),
6242 6243 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
6243 6244 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
6244 6245 # use string type, then we can check if something was passed
6245 6246 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
6246 6247 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
6247 6248 _('ADDR')),
6248 6249 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
6249 6250 _('PREFIX')),
6250 6251 ('n', 'name', '',
6251 6252 _('name to show in web pages (default: working directory)'), _('NAME')),
6252 6253 ('', 'web-conf', '',
6253 6254 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
6254 6255 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
6255 6256 _('FILE')),
6256 6257 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
6257 6258 ('', 'stdio', None, _('for remote clients')),
6258 6259 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
6259 6260 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
6260 6261 ('', 'style', '', _('template style to use'), _('STYLE')),
6261 6262 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
6262 6263 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
6263 6264 _('[OPTION]...'),
6264 6265 optionalrepo=True)
6265 6266 def serve(ui, repo, **opts):
6266 6267 """start stand-alone webserver
6267 6268
6268 6269 Start a local HTTP repository browser and pull server. You can use
6269 6270 this for ad-hoc sharing and browsing of repositories. It is
6270 6271 recommended to use a real web server to serve a repository for
6271 6272 longer periods of time.
6272 6273
6273 6274 Please note that the server does not implement access control.
6274 6275 This means that, by default, anybody can read from the server and
6275 6276 nobody can write to it by default. Set the ``web.allow_push``
6276 6277 option to ``*`` to allow everybody to push to the server. You
6277 6278 should use a real web server if you need to authenticate users.
6278 6279
6279 6280 By default, the server logs accesses to stdout and errors to
6280 6281 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6281 6282 files.
6282 6283
6283 6284 To have the server choose a free port number to listen on, specify
6284 6285 a port number of 0; in this case, the server will print the port
6285 6286 number it uses.
6286 6287
6287 6288 Returns 0 on success.
6288 6289 """
6289 6290
6290 6291 if opts["stdio"] and opts["cmdserver"]:
6291 6292 raise error.Abort(_("cannot use --stdio with --cmdserver"))
6292 6293
6293 6294 if opts["stdio"]:
6294 6295 if repo is None:
6295 6296 raise error.RepoError(_("there is no Mercurial repository here"
6296 6297 " (.hg not found)"))
6297 6298 s = sshserver.sshserver(ui, repo)
6298 6299 s.serve_forever()
6299 6300
6300 6301 if opts["cmdserver"]:
6301 6302 service = commandserver.createservice(ui, repo, opts)
6302 6303 else:
6303 6304 service = hgweb.createservice(ui, repo, opts)
6304 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
6305 return server.runservice(opts, initfn=service.init, runfn=service.run)
6305 6306
6306 6307 @command('^status|st',
6307 6308 [('A', 'all', None, _('show status of all files')),
6308 6309 ('m', 'modified', None, _('show only modified files')),
6309 6310 ('a', 'added', None, _('show only added files')),
6310 6311 ('r', 'removed', None, _('show only removed files')),
6311 6312 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
6312 6313 ('c', 'clean', None, _('show only files without changes')),
6313 6314 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
6314 6315 ('i', 'ignored', None, _('show only ignored files')),
6315 6316 ('n', 'no-status', None, _('hide status prefix')),
6316 6317 ('C', 'copies', None, _('show source of copied files')),
6317 6318 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
6318 6319 ('', 'rev', [], _('show difference from revision'), _('REV')),
6319 6320 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
6320 6321 ] + walkopts + subrepoopts + formatteropts,
6321 6322 _('[OPTION]... [FILE]...'),
6322 6323 inferrepo=True)
6323 6324 def status(ui, repo, *pats, **opts):
6324 6325 """show changed files in the working directory
6325 6326
6326 6327 Show status of files in the repository. If names are given, only
6327 6328 files that match are shown. Files that are clean or ignored or
6328 6329 the source of a copy/move operation, are not listed unless
6329 6330 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6330 6331 Unless options described with "show only ..." are given, the
6331 6332 options -mardu are used.
6332 6333
6333 6334 Option -q/--quiet hides untracked (unknown and ignored) files
6334 6335 unless explicitly requested with -u/--unknown or -i/--ignored.
6335 6336
6336 6337 .. note::
6337 6338
6338 6339 :hg:`status` may appear to disagree with diff if permissions have
6339 6340 changed or a merge has occurred. The standard diff format does
6340 6341 not report permission changes and diff only reports changes
6341 6342 relative to one merge parent.
6342 6343
6343 6344 If one revision is given, it is used as the base revision.
6344 6345 If two revisions are given, the differences between them are
6345 6346 shown. The --change option can also be used as a shortcut to list
6346 6347 the changed files of a revision from its first parent.
6347 6348
6348 6349 The codes used to show the status of files are::
6349 6350
6350 6351 M = modified
6351 6352 A = added
6352 6353 R = removed
6353 6354 C = clean
6354 6355 ! = missing (deleted by non-hg command, but still tracked)
6355 6356 ? = not tracked
6356 6357 I = ignored
6357 6358 = origin of the previous file (with --copies)
6358 6359
6359 6360 .. container:: verbose
6360 6361
6361 6362 Examples:
6362 6363
6363 6364 - show changes in the working directory relative to a
6364 6365 changeset::
6365 6366
6366 6367 hg status --rev 9353
6367 6368
6368 6369 - show changes in the working directory relative to the
6369 6370 current directory (see :hg:`help patterns` for more information)::
6370 6371
6371 6372 hg status re:
6372 6373
6373 6374 - show all changes including copies in an existing changeset::
6374 6375
6375 6376 hg status --copies --change 9353
6376 6377
6377 6378 - get a NUL separated list of added files, suitable for xargs::
6378 6379
6379 6380 hg status -an0
6380 6381
6381 6382 Returns 0 on success.
6382 6383 """
6383 6384
6384 6385 revs = opts.get('rev')
6385 6386 change = opts.get('change')
6386 6387
6387 6388 if revs and change:
6388 6389 msg = _('cannot specify --rev and --change at the same time')
6389 6390 raise error.Abort(msg)
6390 6391 elif change:
6391 6392 node2 = scmutil.revsingle(repo, change, None).node()
6392 6393 node1 = repo[node2].p1().node()
6393 6394 else:
6394 6395 node1, node2 = scmutil.revpair(repo, revs)
6395 6396
6396 6397 if pats:
6397 6398 cwd = repo.getcwd()
6398 6399 else:
6399 6400 cwd = ''
6400 6401
6401 6402 if opts.get('print0'):
6402 6403 end = '\0'
6403 6404 else:
6404 6405 end = '\n'
6405 6406 copy = {}
6406 6407 states = 'modified added removed deleted unknown ignored clean'.split()
6407 6408 show = [k for k in states if opts.get(k)]
6408 6409 if opts.get('all'):
6409 6410 show += ui.quiet and (states[:4] + ['clean']) or states
6410 6411 if not show:
6411 6412 if ui.quiet:
6412 6413 show = states[:4]
6413 6414 else:
6414 6415 show = states[:5]
6415 6416
6416 6417 m = scmutil.match(repo[node2], pats, opts)
6417 6418 stat = repo.status(node1, node2, m,
6418 6419 'ignored' in show, 'clean' in show, 'unknown' in show,
6419 6420 opts.get('subrepos'))
6420 6421 changestates = zip(states, 'MAR!?IC', stat)
6421 6422
6422 6423 if (opts.get('all') or opts.get('copies')
6423 6424 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
6424 6425 copy = copies.pathcopies(repo[node1], repo[node2], m)
6425 6426
6426 6427 fm = ui.formatter('status', opts)
6427 6428 fmt = '%s' + end
6428 6429 showchar = not opts.get('no_status')
6429 6430
6430 6431 for state, char, files in changestates:
6431 6432 if state in show:
6432 6433 label = 'status.' + state
6433 6434 for f in files:
6434 6435 fm.startitem()
6435 6436 fm.condwrite(showchar, 'status', '%s ', char, label=label)
6436 6437 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
6437 6438 if f in copy:
6438 6439 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
6439 6440 label='status.copied')
6440 6441 fm.end()
6441 6442
6442 6443 @command('^summary|sum',
6443 6444 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
6444 6445 def summary(ui, repo, **opts):
6445 6446 """summarize working directory state
6446 6447
6447 6448 This generates a brief summary of the working directory state,
6448 6449 including parents, branch, commit status, phase and available updates.
6449 6450
6450 6451 With the --remote option, this will check the default paths for
6451 6452 incoming and outgoing changes. This can be time-consuming.
6452 6453
6453 6454 Returns 0 on success.
6454 6455 """
6455 6456
6456 6457 ctx = repo[None]
6457 6458 parents = ctx.parents()
6458 6459 pnode = parents[0].node()
6459 6460 marks = []
6460 6461
6461 6462 ms = None
6462 6463 try:
6463 6464 ms = mergemod.mergestate.read(repo)
6464 6465 except error.UnsupportedMergeRecords as e:
6465 6466 s = ' '.join(e.recordtypes)
6466 6467 ui.warn(
6467 6468 _('warning: merge state has unsupported record types: %s\n') % s)
6468 6469 unresolved = 0
6469 6470 else:
6470 6471 unresolved = [f for f in ms if ms[f] == 'u']
6471 6472
6472 6473 for p in parents:
6473 6474 # label with log.changeset (instead of log.parent) since this
6474 6475 # shows a working directory parent *changeset*:
6475 6476 # i18n: column positioning for "hg summary"
6476 6477 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
6477 6478 label='log.changeset changeset.%s' % p.phasestr())
6478 6479 ui.write(' '.join(p.tags()), label='log.tag')
6479 6480 if p.bookmarks():
6480 6481 marks.extend(p.bookmarks())
6481 6482 if p.rev() == -1:
6482 6483 if not len(repo):
6483 6484 ui.write(_(' (empty repository)'))
6484 6485 else:
6485 6486 ui.write(_(' (no revision checked out)'))
6486 6487 ui.write('\n')
6487 6488 if p.description():
6488 6489 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
6489 6490 label='log.summary')
6490 6491
6491 6492 branch = ctx.branch()
6492 6493 bheads = repo.branchheads(branch)
6493 6494 # i18n: column positioning for "hg summary"
6494 6495 m = _('branch: %s\n') % branch
6495 6496 if branch != 'default':
6496 6497 ui.write(m, label='log.branch')
6497 6498 else:
6498 6499 ui.status(m, label='log.branch')
6499 6500
6500 6501 if marks:
6501 6502 active = repo._activebookmark
6502 6503 # i18n: column positioning for "hg summary"
6503 6504 ui.write(_('bookmarks:'), label='log.bookmark')
6504 6505 if active is not None:
6505 6506 if active in marks:
6506 6507 ui.write(' *' + active, label=activebookmarklabel)
6507 6508 marks.remove(active)
6508 6509 else:
6509 6510 ui.write(' [%s]' % active, label=activebookmarklabel)
6510 6511 for m in marks:
6511 6512 ui.write(' ' + m, label='log.bookmark')
6512 6513 ui.write('\n', label='log.bookmark')
6513 6514
6514 6515 status = repo.status(unknown=True)
6515 6516
6516 6517 c = repo.dirstate.copies()
6517 6518 copied, renamed = [], []
6518 6519 for d, s in c.iteritems():
6519 6520 if s in status.removed:
6520 6521 status.removed.remove(s)
6521 6522 renamed.append(d)
6522 6523 else:
6523 6524 copied.append(d)
6524 6525 if d in status.added:
6525 6526 status.added.remove(d)
6526 6527
6527 6528 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6528 6529
6529 6530 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
6530 6531 (ui.label(_('%d added'), 'status.added'), status.added),
6531 6532 (ui.label(_('%d removed'), 'status.removed'), status.removed),
6532 6533 (ui.label(_('%d renamed'), 'status.copied'), renamed),
6533 6534 (ui.label(_('%d copied'), 'status.copied'), copied),
6534 6535 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
6535 6536 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
6536 6537 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
6537 6538 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
6538 6539 t = []
6539 6540 for l, s in labels:
6540 6541 if s:
6541 6542 t.append(l % len(s))
6542 6543
6543 6544 t = ', '.join(t)
6544 6545 cleanworkdir = False
6545 6546
6546 6547 if repo.vfs.exists('graftstate'):
6547 6548 t += _(' (graft in progress)')
6548 6549 if repo.vfs.exists('updatestate'):
6549 6550 t += _(' (interrupted update)')
6550 6551 elif len(parents) > 1:
6551 6552 t += _(' (merge)')
6552 6553 elif branch != parents[0].branch():
6553 6554 t += _(' (new branch)')
6554 6555 elif (parents[0].closesbranch() and
6555 6556 pnode in repo.branchheads(branch, closed=True)):
6556 6557 t += _(' (head closed)')
6557 6558 elif not (status.modified or status.added or status.removed or renamed or
6558 6559 copied or subs):
6559 6560 t += _(' (clean)')
6560 6561 cleanworkdir = True
6561 6562 elif pnode not in bheads:
6562 6563 t += _(' (new branch head)')
6563 6564
6564 6565 if parents:
6565 6566 pendingphase = max(p.phase() for p in parents)
6566 6567 else:
6567 6568 pendingphase = phases.public
6568 6569
6569 6570 if pendingphase > phases.newcommitphase(ui):
6570 6571 t += ' (%s)' % phases.phasenames[pendingphase]
6571 6572
6572 6573 if cleanworkdir:
6573 6574 # i18n: column positioning for "hg summary"
6574 6575 ui.status(_('commit: %s\n') % t.strip())
6575 6576 else:
6576 6577 # i18n: column positioning for "hg summary"
6577 6578 ui.write(_('commit: %s\n') % t.strip())
6578 6579
6579 6580 # all ancestors of branch heads - all ancestors of parent = new csets
6580 6581 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
6581 6582 bheads))
6582 6583
6583 6584 if new == 0:
6584 6585 # i18n: column positioning for "hg summary"
6585 6586 ui.status(_('update: (current)\n'))
6586 6587 elif pnode not in bheads:
6587 6588 # i18n: column positioning for "hg summary"
6588 6589 ui.write(_('update: %d new changesets (update)\n') % new)
6589 6590 else:
6590 6591 # i18n: column positioning for "hg summary"
6591 6592 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
6592 6593 (new, len(bheads)))
6593 6594
6594 6595 t = []
6595 6596 draft = len(repo.revs('draft()'))
6596 6597 if draft:
6597 6598 t.append(_('%d draft') % draft)
6598 6599 secret = len(repo.revs('secret()'))
6599 6600 if secret:
6600 6601 t.append(_('%d secret') % secret)
6601 6602
6602 6603 if draft or secret:
6603 6604 ui.status(_('phases: %s\n') % ', '.join(t))
6604 6605
6605 6606 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6606 6607 for trouble in ("unstable", "divergent", "bumped"):
6607 6608 numtrouble = len(repo.revs(trouble + "()"))
6608 6609 # We write all the possibilities to ease translation
6609 6610 troublemsg = {
6610 6611 "unstable": _("unstable: %d changesets"),
6611 6612 "divergent": _("divergent: %d changesets"),
6612 6613 "bumped": _("bumped: %d changesets"),
6613 6614 }
6614 6615 if numtrouble > 0:
6615 6616 ui.status(troublemsg[trouble] % numtrouble + "\n")
6616 6617
6617 6618 cmdutil.summaryhooks(ui, repo)
6618 6619
6619 6620 if opts.get('remote'):
6620 6621 needsincoming, needsoutgoing = True, True
6621 6622 else:
6622 6623 needsincoming, needsoutgoing = False, False
6623 6624 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6624 6625 if i:
6625 6626 needsincoming = True
6626 6627 if o:
6627 6628 needsoutgoing = True
6628 6629 if not needsincoming and not needsoutgoing:
6629 6630 return
6630 6631
6631 6632 def getincoming():
6632 6633 source, branches = hg.parseurl(ui.expandpath('default'))
6633 6634 sbranch = branches[0]
6634 6635 try:
6635 6636 other = hg.peer(repo, {}, source)
6636 6637 except error.RepoError:
6637 6638 if opts.get('remote'):
6638 6639 raise
6639 6640 return source, sbranch, None, None, None
6640 6641 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6641 6642 if revs:
6642 6643 revs = [other.lookup(rev) for rev in revs]
6643 6644 ui.debug('comparing with %s\n' % util.hidepassword(source))
6644 6645 repo.ui.pushbuffer()
6645 6646 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6646 6647 repo.ui.popbuffer()
6647 6648 return source, sbranch, other, commoninc, commoninc[1]
6648 6649
6649 6650 if needsincoming:
6650 6651 source, sbranch, sother, commoninc, incoming = getincoming()
6651 6652 else:
6652 6653 source = sbranch = sother = commoninc = incoming = None
6653 6654
6654 6655 def getoutgoing():
6655 6656 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
6656 6657 dbranch = branches[0]
6657 6658 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6658 6659 if source != dest:
6659 6660 try:
6660 6661 dother = hg.peer(repo, {}, dest)
6661 6662 except error.RepoError:
6662 6663 if opts.get('remote'):
6663 6664 raise
6664 6665 return dest, dbranch, None, None
6665 6666 ui.debug('comparing with %s\n' % util.hidepassword(dest))
6666 6667 elif sother is None:
6667 6668 # there is no explicit destination peer, but source one is invalid
6668 6669 return dest, dbranch, None, None
6669 6670 else:
6670 6671 dother = sother
6671 6672 if (source != dest or (sbranch is not None and sbranch != dbranch)):
6672 6673 common = None
6673 6674 else:
6674 6675 common = commoninc
6675 6676 if revs:
6676 6677 revs = [repo.lookup(rev) for rev in revs]
6677 6678 repo.ui.pushbuffer()
6678 6679 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
6679 6680 commoninc=common)
6680 6681 repo.ui.popbuffer()
6681 6682 return dest, dbranch, dother, outgoing
6682 6683
6683 6684 if needsoutgoing:
6684 6685 dest, dbranch, dother, outgoing = getoutgoing()
6685 6686 else:
6686 6687 dest = dbranch = dother = outgoing = None
6687 6688
6688 6689 if opts.get('remote'):
6689 6690 t = []
6690 6691 if incoming:
6691 6692 t.append(_('1 or more incoming'))
6692 6693 o = outgoing.missing
6693 6694 if o:
6694 6695 t.append(_('%d outgoing') % len(o))
6695 6696 other = dother or sother
6696 6697 if 'bookmarks' in other.listkeys('namespaces'):
6697 6698 counts = bookmarks.summary(repo, other)
6698 6699 if counts[0] > 0:
6699 6700 t.append(_('%d incoming bookmarks') % counts[0])
6700 6701 if counts[1] > 0:
6701 6702 t.append(_('%d outgoing bookmarks') % counts[1])
6702 6703
6703 6704 if t:
6704 6705 # i18n: column positioning for "hg summary"
6705 6706 ui.write(_('remote: %s\n') % (', '.join(t)))
6706 6707 else:
6707 6708 # i18n: column positioning for "hg summary"
6708 6709 ui.status(_('remote: (synced)\n'))
6709 6710
6710 6711 cmdutil.summaryremotehooks(ui, repo, opts,
6711 6712 ((source, sbranch, sother, commoninc),
6712 6713 (dest, dbranch, dother, outgoing)))
6713 6714
6714 6715 @command('tag',
6715 6716 [('f', 'force', None, _('force tag')),
6716 6717 ('l', 'local', None, _('make the tag local')),
6717 6718 ('r', 'rev', '', _('revision to tag'), _('REV')),
6718 6719 ('', 'remove', None, _('remove a tag')),
6719 6720 # -l/--local is already there, commitopts cannot be used
6720 6721 ('e', 'edit', None, _('invoke editor on commit messages')),
6721 6722 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6722 6723 ] + commitopts2,
6723 6724 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6724 6725 def tag(ui, repo, name1, *names, **opts):
6725 6726 """add one or more tags for the current or given revision
6726 6727
6727 6728 Name a particular revision using <name>.
6728 6729
6729 6730 Tags are used to name particular revisions of the repository and are
6730 6731 very useful to compare different revisions, to go back to significant
6731 6732 earlier versions or to mark branch points as releases, etc. Changing
6732 6733 an existing tag is normally disallowed; use -f/--force to override.
6733 6734
6734 6735 If no revision is given, the parent of the working directory is
6735 6736 used.
6736 6737
6737 6738 To facilitate version control, distribution, and merging of tags,
6738 6739 they are stored as a file named ".hgtags" which is managed similarly
6739 6740 to other project files and can be hand-edited if necessary. This
6740 6741 also means that tagging creates a new commit. The file
6741 6742 ".hg/localtags" is used for local tags (not shared among
6742 6743 repositories).
6743 6744
6744 6745 Tag commits are usually made at the head of a branch. If the parent
6745 6746 of the working directory is not a branch head, :hg:`tag` aborts; use
6746 6747 -f/--force to force the tag commit to be based on a non-head
6747 6748 changeset.
6748 6749
6749 6750 See :hg:`help dates` for a list of formats valid for -d/--date.
6750 6751
6751 6752 Since tag names have priority over branch names during revision
6752 6753 lookup, using an existing branch name as a tag name is discouraged.
6753 6754
6754 6755 Returns 0 on success.
6755 6756 """
6756 6757 wlock = lock = None
6757 6758 try:
6758 6759 wlock = repo.wlock()
6759 6760 lock = repo.lock()
6760 6761 rev_ = "."
6761 6762 names = [t.strip() for t in (name1,) + names]
6762 6763 if len(names) != len(set(names)):
6763 6764 raise error.Abort(_('tag names must be unique'))
6764 6765 for n in names:
6765 6766 scmutil.checknewlabel(repo, n, 'tag')
6766 6767 if not n:
6767 6768 raise error.Abort(_('tag names cannot consist entirely of '
6768 6769 'whitespace'))
6769 6770 if opts.get('rev') and opts.get('remove'):
6770 6771 raise error.Abort(_("--rev and --remove are incompatible"))
6771 6772 if opts.get('rev'):
6772 6773 rev_ = opts['rev']
6773 6774 message = opts.get('message')
6774 6775 if opts.get('remove'):
6775 6776 if opts.get('local'):
6776 6777 expectedtype = 'local'
6777 6778 else:
6778 6779 expectedtype = 'global'
6779 6780
6780 6781 for n in names:
6781 6782 if not repo.tagtype(n):
6782 6783 raise error.Abort(_("tag '%s' does not exist") % n)
6783 6784 if repo.tagtype(n) != expectedtype:
6784 6785 if expectedtype == 'global':
6785 6786 raise error.Abort(_("tag '%s' is not a global tag") % n)
6786 6787 else:
6787 6788 raise error.Abort(_("tag '%s' is not a local tag") % n)
6788 6789 rev_ = 'null'
6789 6790 if not message:
6790 6791 # we don't translate commit messages
6791 6792 message = 'Removed tag %s' % ', '.join(names)
6792 6793 elif not opts.get('force'):
6793 6794 for n in names:
6794 6795 if n in repo.tags():
6795 6796 raise error.Abort(_("tag '%s' already exists "
6796 6797 "(use -f to force)") % n)
6797 6798 if not opts.get('local'):
6798 6799 p1, p2 = repo.dirstate.parents()
6799 6800 if p2 != nullid:
6800 6801 raise error.Abort(_('uncommitted merge'))
6801 6802 bheads = repo.branchheads()
6802 6803 if not opts.get('force') and bheads and p1 not in bheads:
6803 6804 raise error.Abort(_('working directory is not at a branch head '
6804 6805 '(use -f to force)'))
6805 6806 r = scmutil.revsingle(repo, rev_).node()
6806 6807
6807 6808 if not message:
6808 6809 # we don't translate commit messages
6809 6810 message = ('Added tag %s for changeset %s' %
6810 6811 (', '.join(names), short(r)))
6811 6812
6812 6813 date = opts.get('date')
6813 6814 if date:
6814 6815 date = util.parsedate(date)
6815 6816
6816 6817 if opts.get('remove'):
6817 6818 editform = 'tag.remove'
6818 6819 else:
6819 6820 editform = 'tag.add'
6820 6821 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6821 6822
6822 6823 # don't allow tagging the null rev
6823 6824 if (not opts.get('remove') and
6824 6825 scmutil.revsingle(repo, rev_).rev() == nullrev):
6825 6826 raise error.Abort(_("cannot tag null revision"))
6826 6827
6827 6828 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6828 6829 editor=editor)
6829 6830 finally:
6830 6831 release(lock, wlock)
6831 6832
6832 6833 @command('tags', formatteropts, '')
6833 6834 def tags(ui, repo, **opts):
6834 6835 """list repository tags
6835 6836
6836 6837 This lists both regular and local tags. When the -v/--verbose
6837 6838 switch is used, a third column "local" is printed for local tags.
6838 6839 When the -q/--quiet switch is used, only the tag name is printed.
6839 6840
6840 6841 Returns 0 on success.
6841 6842 """
6842 6843
6843 6844 fm = ui.formatter('tags', opts)
6844 6845 hexfunc = fm.hexfunc
6845 6846 tagtype = ""
6846 6847
6847 6848 for t, n in reversed(repo.tagslist()):
6848 6849 hn = hexfunc(n)
6849 6850 label = 'tags.normal'
6850 6851 tagtype = ''
6851 6852 if repo.tagtype(t) == 'local':
6852 6853 label = 'tags.local'
6853 6854 tagtype = 'local'
6854 6855
6855 6856 fm.startitem()
6856 6857 fm.write('tag', '%s', t, label=label)
6857 6858 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6858 6859 fm.condwrite(not ui.quiet, 'rev node', fmt,
6859 6860 repo.changelog.rev(n), hn, label=label)
6860 6861 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6861 6862 tagtype, label=label)
6862 6863 fm.plain('\n')
6863 6864 fm.end()
6864 6865
6865 6866 @command('tip',
6866 6867 [('p', 'patch', None, _('show patch')),
6867 6868 ('g', 'git', None, _('use git extended diff format')),
6868 6869 ] + templateopts,
6869 6870 _('[-p] [-g]'))
6870 6871 def tip(ui, repo, **opts):
6871 6872 """show the tip revision (DEPRECATED)
6872 6873
6873 6874 The tip revision (usually just called the tip) is the changeset
6874 6875 most recently added to the repository (and therefore the most
6875 6876 recently changed head).
6876 6877
6877 6878 If you have just made a commit, that commit will be the tip. If
6878 6879 you have just pulled changes from another repository, the tip of
6879 6880 that repository becomes the current tip. The "tip" tag is special
6880 6881 and cannot be renamed or assigned to a different changeset.
6881 6882
6882 6883 This command is deprecated, please use :hg:`heads` instead.
6883 6884
6884 6885 Returns 0 on success.
6885 6886 """
6886 6887 displayer = cmdutil.show_changeset(ui, repo, opts)
6887 6888 displayer.show(repo['tip'])
6888 6889 displayer.close()
6889 6890
6890 6891 @command('unbundle',
6891 6892 [('u', 'update', None,
6892 6893 _('update to new branch head if changesets were unbundled'))],
6893 6894 _('[-u] FILE...'))
6894 6895 def unbundle(ui, repo, fname1, *fnames, **opts):
6895 6896 """apply one or more changegroup files
6896 6897
6897 6898 Apply one or more compressed changegroup files generated by the
6898 6899 bundle command.
6899 6900
6900 6901 Returns 0 on success, 1 if an update has unresolved files.
6901 6902 """
6902 6903 fnames = (fname1,) + fnames
6903 6904
6904 6905 with repo.lock():
6905 6906 for fname in fnames:
6906 6907 f = hg.openpath(ui, fname)
6907 6908 gen = exchange.readbundle(ui, f, fname)
6908 6909 if isinstance(gen, bundle2.unbundle20):
6909 6910 tr = repo.transaction('unbundle')
6910 6911 try:
6911 6912 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6912 6913 url='bundle:' + fname)
6913 6914 tr.close()
6914 6915 except error.BundleUnknownFeatureError as exc:
6915 6916 raise error.Abort(_('%s: unknown bundle feature, %s')
6916 6917 % (fname, exc),
6917 6918 hint=_("see https://mercurial-scm.org/"
6918 6919 "wiki/BundleFeature for more "
6919 6920 "information"))
6920 6921 finally:
6921 6922 if tr:
6922 6923 tr.release()
6923 6924 changes = [r.get('return', 0)
6924 6925 for r in op.records['changegroup']]
6925 6926 modheads = changegroup.combineresults(changes)
6926 6927 elif isinstance(gen, streamclone.streamcloneapplier):
6927 6928 raise error.Abort(
6928 6929 _('packed bundles cannot be applied with '
6929 6930 '"hg unbundle"'),
6930 6931 hint=_('use "hg debugapplystreamclonebundle"'))
6931 6932 else:
6932 6933 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
6933 6934
6934 6935 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
6935 6936
6936 6937 @command('^update|up|checkout|co',
6937 6938 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6938 6939 ('c', 'check', None, _('require clean working directory')),
6939 6940 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6940 6941 ('r', 'rev', '', _('revision'), _('REV'))
6941 6942 ] + mergetoolopts,
6942 6943 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6943 6944 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6944 6945 tool=None):
6945 6946 """update working directory (or switch revisions)
6946 6947
6947 6948 Update the repository's working directory to the specified
6948 6949 changeset. If no changeset is specified, update to the tip of the
6949 6950 current named branch and move the active bookmark (see :hg:`help
6950 6951 bookmarks`).
6951 6952
6952 6953 Update sets the working directory's parent revision to the specified
6953 6954 changeset (see :hg:`help parents`).
6954 6955
6955 6956 If the changeset is not a descendant or ancestor of the working
6956 6957 directory's parent, the update is aborted. With the -c/--check
6957 6958 option, the working directory is checked for uncommitted changes; if
6958 6959 none are found, the working directory is updated to the specified
6959 6960 changeset.
6960 6961
6961 6962 .. container:: verbose
6962 6963
6963 6964 The following rules apply when the working directory contains
6964 6965 uncommitted changes:
6965 6966
6966 6967 1. If neither -c/--check nor -C/--clean is specified, and if
6967 6968 the requested changeset is an ancestor or descendant of
6968 6969 the working directory's parent, the uncommitted changes
6969 6970 are merged into the requested changeset and the merged
6970 6971 result is left uncommitted. If the requested changeset is
6971 6972 not an ancestor or descendant (that is, it is on another
6972 6973 branch), the update is aborted and the uncommitted changes
6973 6974 are preserved.
6974 6975
6975 6976 2. With the -c/--check option, the update is aborted and the
6976 6977 uncommitted changes are preserved.
6977 6978
6978 6979 3. With the -C/--clean option, uncommitted changes are discarded and
6979 6980 the working directory is updated to the requested changeset.
6980 6981
6981 6982 To cancel an uncommitted merge (and lose your changes), use
6982 6983 :hg:`update --clean .`.
6983 6984
6984 6985 Use null as the changeset to remove the working directory (like
6985 6986 :hg:`clone -U`).
6986 6987
6987 6988 If you want to revert just one file to an older revision, use
6988 6989 :hg:`revert [-r REV] NAME`.
6989 6990
6990 6991 See :hg:`help dates` for a list of formats valid for -d/--date.
6991 6992
6992 6993 Returns 0 on success, 1 if there are unresolved files.
6993 6994 """
6994 6995 if rev and node:
6995 6996 raise error.Abort(_("please specify just one revision"))
6996 6997
6997 6998 if rev is None or rev == '':
6998 6999 rev = node
6999 7000
7000 7001 if date and rev is not None:
7001 7002 raise error.Abort(_("you can't specify a revision and a date"))
7002 7003
7003 7004 if check and clean:
7004 7005 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
7005 7006
7006 7007 with repo.wlock():
7007 7008 cmdutil.clearunfinished(repo)
7008 7009
7009 7010 if date:
7010 7011 rev = cmdutil.finddate(ui, repo, date)
7011 7012
7012 7013 # if we defined a bookmark, we have to remember the original name
7013 7014 brev = rev
7014 7015 rev = scmutil.revsingle(repo, rev, rev).rev()
7015 7016
7016 7017 if check:
7017 7018 cmdutil.bailifchanged(repo, merge=False)
7018 7019
7019 7020 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
7020 7021
7021 7022 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
7022 7023
7023 7024 @command('verify', [])
7024 7025 def verify(ui, repo):
7025 7026 """verify the integrity of the repository
7026 7027
7027 7028 Verify the integrity of the current repository.
7028 7029
7029 7030 This will perform an extensive check of the repository's
7030 7031 integrity, validating the hashes and checksums of each entry in
7031 7032 the changelog, manifest, and tracked files, as well as the
7032 7033 integrity of their crosslinks and indices.
7033 7034
7034 7035 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7035 7036 for more information about recovery from corruption of the
7036 7037 repository.
7037 7038
7038 7039 Returns 0 on success, 1 if errors are encountered.
7039 7040 """
7040 7041 return hg.verify(repo)
7041 7042
7042 7043 @command('version', [] + formatteropts, norepo=True)
7043 7044 def version_(ui, **opts):
7044 7045 """output version and copyright information"""
7045 7046 fm = ui.formatter("version", opts)
7046 7047 fm.startitem()
7047 7048 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
7048 7049 util.version())
7049 7050 license = _(
7050 7051 "(see https://mercurial-scm.org for more information)\n"
7051 7052 "\nCopyright (C) 2005-2016 Matt Mackall and others\n"
7052 7053 "This is free software; see the source for copying conditions. "
7053 7054 "There is NO\nwarranty; "
7054 7055 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7055 7056 )
7056 7057 if not ui.quiet:
7057 7058 fm.plain(license)
7058 7059
7059 7060 if ui.verbose:
7060 7061 fm.plain(_("\nEnabled extensions:\n\n"))
7061 7062 # format names and versions into columns
7062 7063 names = []
7063 7064 vers = []
7064 7065 isinternals = []
7065 7066 for name, module in extensions.extensions():
7066 7067 names.append(name)
7067 7068 vers.append(extensions.moduleversion(module) or None)
7068 7069 isinternals.append(extensions.ismoduleinternal(module))
7069 7070 fn = fm.nested("extensions")
7070 7071 if names:
7071 7072 namefmt = " %%-%ds " % max(len(n) for n in names)
7072 7073 places = [_("external"), _("internal")]
7073 7074 for n, v, p in zip(names, vers, isinternals):
7074 7075 fn.startitem()
7075 7076 fn.condwrite(ui.verbose, "name", namefmt, n)
7076 7077 if ui.verbose:
7077 7078 fn.plain("%s " % places[p])
7078 7079 fn.data(bundled=p)
7079 7080 fn.condwrite(ui.verbose and v, "ver", "%s", v)
7080 7081 if ui.verbose:
7081 7082 fn.plain("\n")
7082 7083 fn.end()
7083 7084 fm.end()
7084 7085
7085 7086 def loadcmdtable(ui, name, cmdtable):
7086 7087 """Load command functions from specified cmdtable
7087 7088 """
7088 7089 overrides = [cmd for cmd in cmdtable if cmd in table]
7089 7090 if overrides:
7090 7091 ui.warn(_("extension '%s' overrides commands: %s\n")
7091 7092 % (name, " ".join(overrides)))
7092 7093 table.update(cmdtable)
@@ -1,55 +1,55 b''
1 1 #!/usr/bin/env python
2 2
3 3 from __future__ import absolute_import
4 4
5 5 """
6 6 Small and dumb HTTP server for use in tests.
7 7 """
8 8
9 9 import optparse
10 10 import signal
11 11 import sys
12 12
13 13 from mercurial import (
14 cmdutil,
14 server,
15 15 util,
16 16 )
17 17
18 18 httpserver = util.httpserver
19 19 OptionParser = optparse.OptionParser
20 20
21 21 class simplehttpservice(object):
22 22 def __init__(self, host, port):
23 23 self.address = (host, port)
24 24 def init(self):
25 25 self.httpd = httpserver.httpserver(
26 26 self.address, httpserver.simplehttprequesthandler)
27 27 def run(self):
28 28 self.httpd.serve_forever()
29 29
30 30 if __name__ == '__main__':
31 31 parser = OptionParser()
32 32 parser.add_option('-p', '--port', dest='port', type='int', default=8000,
33 33 help='TCP port to listen on', metavar='PORT')
34 34 parser.add_option('-H', '--host', dest='host', default='localhost',
35 35 help='hostname or IP to listen on', metavar='HOST')
36 36 parser.add_option('--pid', dest='pid',
37 37 help='file name where the PID of the server is stored')
38 38 parser.add_option('-f', '--foreground', dest='foreground',
39 39 action='store_true',
40 40 help='do not start the HTTP server in the background')
41 41 parser.add_option('--daemon-postexec', action='append')
42 42
43 43 (options, args) = parser.parse_args()
44 44
45 45 signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
46 46
47 47 if options.foreground and options.pid:
48 48 parser.error("options --pid and --foreground are mutually exclusive")
49 49
50 50 opts = {'pid_file': options.pid,
51 51 'daemon': not options.foreground,
52 52 'daemon_postexec': options.daemon_postexec}
53 53 service = simplehttpservice(options.host, options.port)
54 cmdutil.service(opts, initfn=service.init, runfn=service.run,
54 server.runservice(opts, initfn=service.init, runfn=service.run,
55 55 runargs=[sys.executable, __file__] + sys.argv[1:])
@@ -1,82 +1,82 b''
1 1 #!/usr/bin/env python
2 2
3 3 """dummy SMTP server for use in tests"""
4 4
5 5 from __future__ import absolute_import
6 6
7 7 import asyncore
8 8 import optparse
9 9 import smtpd
10 10 import ssl
11 11 import sys
12 12
13 13 from mercurial import (
14 cmdutil,
14 server,
15 15 sslutil,
16 16 ui as uimod,
17 17 )
18 18
19 19 def log(msg):
20 20 sys.stdout.write(msg)
21 21 sys.stdout.flush()
22 22
23 23 class dummysmtpserver(smtpd.SMTPServer):
24 24 def __init__(self, localaddr):
25 25 smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
26 26
27 27 def process_message(self, peer, mailfrom, rcpttos, data):
28 28 log('%s from=%s to=%s\n' % (peer[0], mailfrom, ', '.join(rcpttos)))
29 29
30 30 class dummysmtpsecureserver(dummysmtpserver):
31 31 def __init__(self, localaddr, certfile):
32 32 dummysmtpserver.__init__(self, localaddr)
33 33 self._certfile = certfile
34 34
35 35 def handle_accept(self):
36 36 pair = self.accept()
37 37 if not pair:
38 38 return
39 39 conn, addr = pair
40 40 ui = uimod.ui()
41 41 try:
42 42 # wrap_socket() would block, but we don't care
43 43 conn = sslutil.wrapserversocket(conn, ui, certfile=self._certfile)
44 44 except ssl.SSLError:
45 45 log('%s ssl error\n' % addr[0])
46 46 conn.close()
47 47 return
48 48 smtpd.SMTPChannel(self, conn, addr)
49 49
50 50 def run():
51 51 try:
52 52 asyncore.loop()
53 53 except KeyboardInterrupt:
54 54 pass
55 55
56 56 def main():
57 57 op = optparse.OptionParser()
58 58 op.add_option('-d', '--daemon', action='store_true')
59 59 op.add_option('--daemon-postexec', action='append')
60 60 op.add_option('-p', '--port', type=int, default=8025)
61 61 op.add_option('-a', '--address', default='localhost')
62 62 op.add_option('--pid-file', metavar='FILE')
63 63 op.add_option('--tls', choices=['none', 'smtps'], default='none')
64 64 op.add_option('--certificate', metavar='FILE')
65 65
66 66 opts, args = op.parse_args()
67 67 if opts.tls == 'smtps' and not opts.certificate:
68 68 op.error('--certificate must be specified')
69 69
70 70 addr = (opts.address, opts.port)
71 71 def init():
72 72 if opts.tls == 'none':
73 73 dummysmtpserver(addr)
74 74 else:
75 75 dummysmtpsecureserver(addr, opts.certificate)
76 76 log('listening at %s:%d\n' % addr)
77 77
78 cmdutil.service(vars(opts), initfn=init, runfn=run,
78 server.runservice(vars(opts), initfn=init, runfn=run,
79 79 runargs=[sys.executable, __file__] + sys.argv[1:])
80 80
81 81 if __name__ == '__main__':
82 82 main()
General Comments 0
You need to be logged in to leave comments. Login now