##// END OF EJS Templates
server: move service table and factory from commandserver...
Yuya Nishihara -
r30507:dd539e2d default
parent child Browse files
Show More
@@ -1,643 +1,644 b''
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
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 """command server extension for cHg (EXPERIMENTAL)
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'getpager' command
20 20 checks if pager is enabled and which pager should be executed
21 21
22 22 'setenv' command
23 23 replace os.environ completely
24 24
25 25 'setumask' command
26 26 set umask
27 27
28 28 'validate' command
29 29 reload the config and check if the server is up to date
30 30
31 31 Config
32 32 ------
33 33
34 34 ::
35 35
36 36 [chgserver]
37 37 idletimeout = 3600 # seconds, after which an idle server will exit
38 38 skiphash = False # whether to skip config or env change checks
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import errno
44 44 import hashlib
45 45 import inspect
46 46 import os
47 47 import re
48 48 import signal
49 49 import struct
50 50 import sys
51 51 import time
52 52
53 53 from mercurial.i18n import _
54 54
55 55 from mercurial import (
56 56 cmdutil,
57 57 commands,
58 58 commandserver,
59 59 dispatch,
60 60 error,
61 61 extensions,
62 62 osutil,
63 server,
63 64 util,
64 65 )
65 66
66 67 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
67 68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
68 69 # be specifying the version(s) of Mercurial they are tested with, or
69 70 # leave the attribute unspecified.
70 71 testedwith = 'ships-with-hg-core'
71 72
72 73 _log = commandserver.log
73 74
74 75 def _hashlist(items):
75 76 """return sha1 hexdigest for a list"""
76 77 return hashlib.sha1(str(items)).hexdigest()
77 78
78 79 # sensitive config sections affecting confighash
79 80 _configsections = [
80 81 'alias', # affects global state commands.table
81 82 'extdiff', # uisetup will register new commands
82 83 'extensions',
83 84 ]
84 85
85 86 # sensitive environment variables affecting confighash
86 87 _envre = re.compile(r'''\A(?:
87 88 CHGHG
88 89 |HG.*
89 90 |LANG(?:UAGE)?
90 91 |LC_.*
91 92 |LD_.*
92 93 |PATH
93 94 |PYTHON.*
94 95 |TERM(?:INFO)?
95 96 |TZ
96 97 )\Z''', re.X)
97 98
98 99 def _confighash(ui):
99 100 """return a quick hash for detecting config/env changes
100 101
101 102 confighash is the hash of sensitive config items and environment variables.
102 103
103 104 for chgserver, it is designed that once confighash changes, the server is
104 105 not qualified to serve its client and should redirect the client to a new
105 106 server. different from mtimehash, confighash change will not mark the
106 107 server outdated and exit since the user can have different configs at the
107 108 same time.
108 109 """
109 110 sectionitems = []
110 111 for section in _configsections:
111 112 sectionitems.append(ui.configitems(section))
112 113 sectionhash = _hashlist(sectionitems)
113 114 envitems = [(k, v) for k, v in os.environ.iteritems() if _envre.match(k)]
114 115 envhash = _hashlist(sorted(envitems))
115 116 return sectionhash[:6] + envhash[:6]
116 117
117 118 def _getmtimepaths(ui):
118 119 """get a list of paths that should be checked to detect change
119 120
120 121 The list will include:
121 122 - extensions (will not cover all files for complex extensions)
122 123 - mercurial/__version__.py
123 124 - python binary
124 125 """
125 126 modules = [m for n, m in extensions.extensions(ui)]
126 127 try:
127 128 from mercurial import __version__
128 129 modules.append(__version__)
129 130 except ImportError:
130 131 pass
131 132 files = [sys.executable]
132 133 for m in modules:
133 134 try:
134 135 files.append(inspect.getabsfile(m))
135 136 except TypeError:
136 137 pass
137 138 return sorted(set(files))
138 139
139 140 def _mtimehash(paths):
140 141 """return a quick hash for detecting file changes
141 142
142 143 mtimehash calls stat on given paths and calculate a hash based on size and
143 144 mtime of each file. mtimehash does not read file content because reading is
144 145 expensive. therefore it's not 100% reliable for detecting content changes.
145 146 it's possible to return different hashes for same file contents.
146 147 it's also possible to return a same hash for different file contents for
147 148 some carefully crafted situation.
148 149
149 150 for chgserver, it is designed that once mtimehash changes, the server is
150 151 considered outdated immediately and should no longer provide service.
151 152
152 153 mtimehash is not included in confighash because we only know the paths of
153 154 extensions after importing them (there is imp.find_module but that faces
154 155 race conditions). We need to calculate confighash without importing.
155 156 """
156 157 def trystat(path):
157 158 try:
158 159 st = os.stat(path)
159 160 return (st.st_mtime, st.st_size)
160 161 except OSError:
161 162 # could be ENOENT, EPERM etc. not fatal in any case
162 163 pass
163 164 return _hashlist(map(trystat, paths))[:12]
164 165
165 166 class hashstate(object):
166 167 """a structure storing confighash, mtimehash, paths used for mtimehash"""
167 168 def __init__(self, confighash, mtimehash, mtimepaths):
168 169 self.confighash = confighash
169 170 self.mtimehash = mtimehash
170 171 self.mtimepaths = mtimepaths
171 172
172 173 @staticmethod
173 174 def fromui(ui, mtimepaths=None):
174 175 if mtimepaths is None:
175 176 mtimepaths = _getmtimepaths(ui)
176 177 confighash = _confighash(ui)
177 178 mtimehash = _mtimehash(mtimepaths)
178 179 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
179 180 return hashstate(confighash, mtimehash, mtimepaths)
180 181
181 182 # copied from hgext/pager.py:uisetup()
182 183 def _setuppagercmd(ui, options, cmd):
183 184 if not ui.formatted():
184 185 return
185 186
186 187 p = ui.config("pager", "pager", os.environ.get("PAGER"))
187 188 usepager = False
188 189 always = util.parsebool(options['pager'])
189 190 auto = options['pager'] == 'auto'
190 191
191 192 if not p:
192 193 pass
193 194 elif always:
194 195 usepager = True
195 196 elif not auto:
196 197 usepager = False
197 198 else:
198 199 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
199 200 attend = ui.configlist('pager', 'attend', attended)
200 201 ignore = ui.configlist('pager', 'ignore')
201 202 cmds, _ = cmdutil.findcmd(cmd, commands.table)
202 203
203 204 for cmd in cmds:
204 205 var = 'attend-%s' % cmd
205 206 if ui.config('pager', var):
206 207 usepager = ui.configbool('pager', var)
207 208 break
208 209 if (cmd in attend or
209 210 (cmd not in ignore and not attend)):
210 211 usepager = True
211 212 break
212 213
213 214 if usepager:
214 215 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
215 216 ui.setconfig('ui', 'interactive', False, 'pager')
216 217 return p
217 218
218 219 def _newchgui(srcui, csystem):
219 220 class chgui(srcui.__class__):
220 221 def __init__(self, src=None):
221 222 super(chgui, self).__init__(src)
222 223 if src:
223 224 self._csystem = getattr(src, '_csystem', csystem)
224 225 else:
225 226 self._csystem = csystem
226 227
227 228 def system(self, cmd, environ=None, cwd=None, onerr=None,
228 229 errprefix=None):
229 230 # fallback to the original system method if the output needs to be
230 231 # captured (to self._buffers), or the output stream is not stdout
231 232 # (e.g. stderr, cStringIO), because the chg client is not aware of
232 233 # these situations and will behave differently (write to stdout).
233 234 if (any(s[1] for s in self._bufferstates)
234 235 or not util.safehasattr(self.fout, 'fileno')
235 236 or self.fout.fileno() != util.stdout.fileno()):
236 237 return super(chgui, self).system(cmd, environ, cwd, onerr,
237 238 errprefix)
238 239 # copied from mercurial/util.py:system()
239 240 self.flush()
240 241 def py2shell(val):
241 242 if val is None or val is False:
242 243 return '0'
243 244 if val is True:
244 245 return '1'
245 246 return str(val)
246 247 env = os.environ.copy()
247 248 if environ:
248 249 env.update((k, py2shell(v)) for k, v in environ.iteritems())
249 250 env['HG'] = util.hgexecutable()
250 251 rc = self._csystem(cmd, env, cwd)
251 252 if rc and onerr:
252 253 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
253 254 util.explainexit(rc)[0])
254 255 if errprefix:
255 256 errmsg = '%s: %s' % (errprefix, errmsg)
256 257 raise onerr(errmsg)
257 258 return rc
258 259
259 260 return chgui(srcui)
260 261
261 262 def _loadnewui(srcui, args):
262 263 newui = srcui.__class__()
263 264 for a in ['fin', 'fout', 'ferr', 'environ']:
264 265 setattr(newui, a, getattr(srcui, a))
265 266 if util.safehasattr(srcui, '_csystem'):
266 267 newui._csystem = srcui._csystem
267 268
268 269 # internal config: extensions.chgserver
269 270 newui.setconfig('extensions', 'chgserver',
270 271 srcui.config('extensions', 'chgserver'), '--config')
271 272
272 273 # command line args
273 274 args = args[:]
274 275 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
275 276
276 277 # stolen from tortoisehg.util.copydynamicconfig()
277 278 for section, name, value in srcui.walkconfig():
278 279 source = srcui.configsource(section, name)
279 280 if ':' in source or source == '--config':
280 281 # path:line or command line
281 282 continue
282 283 if source == 'none':
283 284 # ui.configsource returns 'none' by default
284 285 source = ''
285 286 newui.setconfig(section, name, value, source)
286 287
287 288 # load wd and repo config, copied from dispatch.py
288 289 cwds = dispatch._earlygetopt(['--cwd'], args)
289 290 cwd = cwds and os.path.realpath(cwds[-1]) or None
290 291 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
291 292 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
292 293
293 294 return (newui, newlui)
294 295
295 296 class channeledsystem(object):
296 297 """Propagate ui.system() request in the following format:
297 298
298 299 payload length (unsigned int),
299 300 cmd, '\0',
300 301 cwd, '\0',
301 302 envkey, '=', val, '\0',
302 303 ...
303 304 envkey, '=', val
304 305
305 306 and waits:
306 307
307 308 exitcode length (unsigned int),
308 309 exitcode (int)
309 310 """
310 311 def __init__(self, in_, out, channel):
311 312 self.in_ = in_
312 313 self.out = out
313 314 self.channel = channel
314 315
315 316 def __call__(self, cmd, environ, cwd):
316 317 args = [util.quotecommand(cmd), os.path.abspath(cwd or '.')]
317 318 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
318 319 data = '\0'.join(args)
319 320 self.out.write(struct.pack('>cI', self.channel, len(data)))
320 321 self.out.write(data)
321 322 self.out.flush()
322 323
323 324 length = self.in_.read(4)
324 325 length, = struct.unpack('>I', length)
325 326 if length != 4:
326 327 raise error.Abort(_('invalid response'))
327 328 rc, = struct.unpack('>i', self.in_.read(4))
328 329 return rc
329 330
330 331 _iochannels = [
331 332 # server.ch, ui.fp, mode
332 333 ('cin', 'fin', 'rb'),
333 334 ('cout', 'fout', 'wb'),
334 335 ('cerr', 'ferr', 'wb'),
335 336 ]
336 337
337 338 class chgcmdserver(commandserver.server):
338 339 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
339 340 super(chgcmdserver, self).__init__(
340 341 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
341 342 self.clientsock = sock
342 343 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
343 344 self.hashstate = hashstate
344 345 self.baseaddress = baseaddress
345 346 if hashstate is not None:
346 347 self.capabilities = self.capabilities.copy()
347 348 self.capabilities['validate'] = chgcmdserver.validate
348 349
349 350 def cleanup(self):
350 351 super(chgcmdserver, self).cleanup()
351 352 # dispatch._runcatch() does not flush outputs if exception is not
352 353 # handled by dispatch._dispatch()
353 354 self.ui.flush()
354 355 self._restoreio()
355 356
356 357 def attachio(self):
357 358 """Attach to client's stdio passed via unix domain socket; all
358 359 channels except cresult will no longer be used
359 360 """
360 361 # tell client to sendmsg() with 1-byte payload, which makes it
361 362 # distinctive from "attachio\n" command consumed by client.read()
362 363 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
363 364 clientfds = osutil.recvfds(self.clientsock.fileno())
364 365 _log('received fds: %r\n' % clientfds)
365 366
366 367 ui = self.ui
367 368 ui.flush()
368 369 first = self._saveio()
369 370 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
370 371 assert fd > 0
371 372 fp = getattr(ui, fn)
372 373 os.dup2(fd, fp.fileno())
373 374 os.close(fd)
374 375 if not first:
375 376 continue
376 377 # reset buffering mode when client is first attached. as we want
377 378 # to see output immediately on pager, the mode stays unchanged
378 379 # when client re-attached. ferr is unchanged because it should
379 380 # be unbuffered no matter if it is a tty or not.
380 381 if fn == 'ferr':
381 382 newfp = fp
382 383 else:
383 384 # make it line buffered explicitly because the default is
384 385 # decided on first write(), where fout could be a pager.
385 386 if fp.isatty():
386 387 bufsize = 1 # line buffered
387 388 else:
388 389 bufsize = -1 # system default
389 390 newfp = os.fdopen(fp.fileno(), mode, bufsize)
390 391 setattr(ui, fn, newfp)
391 392 setattr(self, cn, newfp)
392 393
393 394 self.cresult.write(struct.pack('>i', len(clientfds)))
394 395
395 396 def _saveio(self):
396 397 if self._oldios:
397 398 return False
398 399 ui = self.ui
399 400 for cn, fn, _mode in _iochannels:
400 401 ch = getattr(self, cn)
401 402 fp = getattr(ui, fn)
402 403 fd = os.dup(fp.fileno())
403 404 self._oldios.append((ch, fp, fd))
404 405 return True
405 406
406 407 def _restoreio(self):
407 408 ui = self.ui
408 409 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
409 410 newfp = getattr(ui, fn)
410 411 # close newfp while it's associated with client; otherwise it
411 412 # would be closed when newfp is deleted
412 413 if newfp is not fp:
413 414 newfp.close()
414 415 # restore original fd: fp is open again
415 416 os.dup2(fd, fp.fileno())
416 417 os.close(fd)
417 418 setattr(self, cn, ch)
418 419 setattr(ui, fn, fp)
419 420 del self._oldios[:]
420 421
421 422 def validate(self):
422 423 """Reload the config and check if the server is up to date
423 424
424 425 Read a list of '\0' separated arguments.
425 426 Write a non-empty list of '\0' separated instruction strings or '\0'
426 427 if the list is empty.
427 428 An instruction string could be either:
428 429 - "unlink $path", the client should unlink the path to stop the
429 430 outdated server.
430 431 - "redirect $path", the client should attempt to connect to $path
431 432 first. If it does not work, start a new server. It implies
432 433 "reconnect".
433 434 - "exit $n", the client should exit directly with code n.
434 435 This may happen if we cannot parse the config.
435 436 - "reconnect", the client should close the connection and
436 437 reconnect.
437 438 If neither "reconnect" nor "redirect" is included in the instruction
438 439 list, the client can continue with this server after completing all
439 440 the instructions.
440 441 """
441 442 args = self._readlist()
442 443 try:
443 444 self.ui, lui = _loadnewui(self.ui, args)
444 445 except error.ParseError as inst:
445 446 dispatch._formatparse(self.ui.warn, inst)
446 447 self.ui.flush()
447 448 self.cresult.write('exit 255')
448 449 return
449 450 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
450 451 insts = []
451 452 if newhash.mtimehash != self.hashstate.mtimehash:
452 453 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
453 454 insts.append('unlink %s' % addr)
454 455 # mtimehash is empty if one or more extensions fail to load.
455 456 # to be compatible with hg, still serve the client this time.
456 457 if self.hashstate.mtimehash:
457 458 insts.append('reconnect')
458 459 if newhash.confighash != self.hashstate.confighash:
459 460 addr = _hashaddress(self.baseaddress, newhash.confighash)
460 461 insts.append('redirect %s' % addr)
461 462 _log('validate: %s\n' % insts)
462 463 self.cresult.write('\0'.join(insts) or '\0')
463 464
464 465 def chdir(self):
465 466 """Change current directory
466 467
467 468 Note that the behavior of --cwd option is bit different from this.
468 469 It does not affect --config parameter.
469 470 """
470 471 path = self._readstr()
471 472 if not path:
472 473 return
473 474 _log('chdir to %r\n' % path)
474 475 os.chdir(path)
475 476
476 477 def setumask(self):
477 478 """Change umask"""
478 479 mask = struct.unpack('>I', self._read(4))[0]
479 480 _log('setumask %r\n' % mask)
480 481 os.umask(mask)
481 482
482 483 def getpager(self):
483 484 """Read cmdargs and write pager command to r-channel if enabled
484 485
485 486 If pager isn't enabled, this writes '\0' because channeledoutput
486 487 does not allow to write empty data.
487 488 """
488 489 args = self._readlist()
489 490 try:
490 491 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
491 492 args)
492 493 except (error.Abort, error.AmbiguousCommand, error.CommandError,
493 494 error.UnknownCommand):
494 495 cmd = None
495 496 options = {}
496 497 if not cmd or 'pager' not in options:
497 498 self.cresult.write('\0')
498 499 return
499 500
500 501 pagercmd = _setuppagercmd(self.ui, options, cmd)
501 502 if pagercmd:
502 503 # Python's SIGPIPE is SIG_IGN by default. change to SIG_DFL so
503 504 # we can exit if the pipe to the pager is closed
504 505 if util.safehasattr(signal, 'SIGPIPE') and \
505 506 signal.getsignal(signal.SIGPIPE) == signal.SIG_IGN:
506 507 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
507 508 self.cresult.write(pagercmd)
508 509 else:
509 510 self.cresult.write('\0')
510 511
511 512 def setenv(self):
512 513 """Clear and update os.environ
513 514
514 515 Note that not all variables can make an effect on the running process.
515 516 """
516 517 l = self._readlist()
517 518 try:
518 519 newenv = dict(s.split('=', 1) for s in l)
519 520 except ValueError:
520 521 raise ValueError('unexpected value in setenv request')
521 522 _log('setenv: %r\n' % sorted(newenv.keys()))
522 523 os.environ.clear()
523 524 os.environ.update(newenv)
524 525
525 526 capabilities = commandserver.server.capabilities.copy()
526 527 capabilities.update({'attachio': attachio,
527 528 'chdir': chdir,
528 529 'getpager': getpager,
529 530 'setenv': setenv,
530 531 'setumask': setumask})
531 532
532 533 def _tempaddress(address):
533 534 return '%s.%d.tmp' % (address, os.getpid())
534 535
535 536 def _hashaddress(address, hashstr):
536 537 return '%s-%s' % (address, hashstr)
537 538
538 539 class chgunixservicehandler(object):
539 540 """Set of operations for chg services"""
540 541
541 542 pollinterval = 1 # [sec]
542 543
543 544 def __init__(self, ui):
544 545 self.ui = ui
545 546 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
546 547 self._lastactive = time.time()
547 548
548 549 def bindsocket(self, sock, address):
549 550 self._inithashstate(address)
550 551 self._checkextensions()
551 552 self._bind(sock)
552 553 self._createsymlink()
553 554
554 555 def _inithashstate(self, address):
555 556 self._baseaddress = address
556 557 if self.ui.configbool('chgserver', 'skiphash', False):
557 558 self._hashstate = None
558 559 self._realaddress = address
559 560 return
560 561 self._hashstate = hashstate.fromui(self.ui)
561 562 self._realaddress = _hashaddress(address, self._hashstate.confighash)
562 563
563 564 def _checkextensions(self):
564 565 if not self._hashstate:
565 566 return
566 567 if extensions.notloaded():
567 568 # one or more extensions failed to load. mtimehash becomes
568 569 # meaningless because we do not know the paths of those extensions.
569 570 # set mtimehash to an illegal hash value to invalidate the server.
570 571 self._hashstate.mtimehash = ''
571 572
572 573 def _bind(self, sock):
573 574 # use a unique temp address so we can stat the file and do ownership
574 575 # check later
575 576 tempaddress = _tempaddress(self._realaddress)
576 577 util.bindunixsocket(sock, tempaddress)
577 578 self._socketstat = os.stat(tempaddress)
578 579 # rename will replace the old socket file if exists atomically. the
579 580 # old server will detect ownership change and exit.
580 581 util.rename(tempaddress, self._realaddress)
581 582
582 583 def _createsymlink(self):
583 584 if self._baseaddress == self._realaddress:
584 585 return
585 586 tempaddress = _tempaddress(self._baseaddress)
586 587 os.symlink(os.path.basename(self._realaddress), tempaddress)
587 588 util.rename(tempaddress, self._baseaddress)
588 589
589 590 def _issocketowner(self):
590 591 try:
591 592 stat = os.stat(self._realaddress)
592 593 return (stat.st_ino == self._socketstat.st_ino and
593 594 stat.st_mtime == self._socketstat.st_mtime)
594 595 except OSError:
595 596 return False
596 597
597 598 def unlinksocket(self, address):
598 599 if not self._issocketowner():
599 600 return
600 601 # it is possible to have a race condition here that we may
601 602 # remove another server's socket file. but that's okay
602 603 # since that server will detect and exit automatically and
603 604 # the client will start a new server on demand.
604 605 try:
605 606 os.unlink(self._realaddress)
606 607 except OSError as exc:
607 608 if exc.errno != errno.ENOENT:
608 609 raise
609 610
610 611 def printbanner(self, address):
611 612 # no "listening at" message should be printed to simulate hg behavior
612 613 pass
613 614
614 615 def shouldexit(self):
615 616 if not self._issocketowner():
616 617 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
617 618 return True
618 619 if time.time() - self._lastactive > self._idletimeout:
619 620 self.ui.debug('being idle too long. exiting.\n')
620 621 return True
621 622 return False
622 623
623 624 def newconnection(self):
624 625 self._lastactive = time.time()
625 626
626 627 def createcmdserver(self, repo, conn, fin, fout):
627 628 return chgcmdserver(self.ui, repo, fin, fout, conn,
628 629 self._hashstate, self._baseaddress)
629 630
630 631 def chgunixservice(ui, repo, opts):
631 632 if repo:
632 633 # one chgserver can serve multiple repos. drop repo information
633 634 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
634 635 h = chgunixservicehandler(ui)
635 636 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
636 637
637 638 def uisetup(ui):
638 commandserver._servicemap['chgunix'] = chgunixservice
639 server._cmdservicemap['chgunix'] = chgunixservice
639 640
640 641 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
641 642 # start another chg. drop it to avoid possible side effects.
642 643 if 'CHGINTERNALMARK' in os.environ:
643 644 del os.environ['CHGINTERNALMARK']
@@ -1,7093 +1,7092 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 commandserver,
39 38 copies,
40 39 dagparser,
41 40 dagutil,
42 41 destutil,
43 42 dirstateguard,
44 43 discovery,
45 44 encoding,
46 45 error,
47 46 exchange,
48 47 extensions,
49 48 fileset,
50 49 formatter,
51 50 graphmod,
52 51 hbisect,
53 52 help,
54 53 hg,
55 54 hgweb,
56 55 localrepo,
57 56 lock as lockmod,
58 57 merge as mergemod,
59 58 minirst,
60 59 obsolete,
61 60 patch,
62 61 phases,
63 62 policy,
64 63 pvec,
65 64 pycompat,
66 65 repair,
67 66 revlog,
68 67 revset,
69 68 scmutil,
70 69 server,
71 70 setdiscovery,
72 71 sshserver,
73 72 sslutil,
74 73 streamclone,
75 74 templatekw,
76 75 templater,
77 76 treediscovery,
78 77 ui as uimod,
79 78 util,
80 79 )
81 80
82 81 release = lockmod.release
83 82
84 83 table = {}
85 84
86 85 command = cmdutil.command(table)
87 86
88 87 # label constants
89 88 # until 3.5, bookmarks.current was the advertised name, not
90 89 # bookmarks.active, so we must use both to avoid breaking old
91 90 # custom styles
92 91 activebookmarklabel = 'bookmarks.active bookmarks.current'
93 92
94 93 # common command options
95 94
96 95 globalopts = [
97 96 ('R', 'repository', '',
98 97 _('repository root directory or name of overlay bundle file'),
99 98 _('REPO')),
100 99 ('', 'cwd', '',
101 100 _('change working directory'), _('DIR')),
102 101 ('y', 'noninteractive', None,
103 102 _('do not prompt, automatically pick the first choice for all prompts')),
104 103 ('q', 'quiet', None, _('suppress output')),
105 104 ('v', 'verbose', None, _('enable additional output')),
106 105 ('', 'config', [],
107 106 _('set/override config option (use \'section.name=value\')'),
108 107 _('CONFIG')),
109 108 ('', 'debug', None, _('enable debugging output')),
110 109 ('', 'debugger', None, _('start debugger')),
111 110 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
112 111 _('ENCODE')),
113 112 ('', 'encodingmode', encoding.encodingmode,
114 113 _('set the charset encoding mode'), _('MODE')),
115 114 ('', 'traceback', None, _('always print a traceback on exception')),
116 115 ('', 'time', None, _('time how long the command takes')),
117 116 ('', 'profile', None, _('print command execution profile')),
118 117 ('', 'version', None, _('output version information and exit')),
119 118 ('h', 'help', None, _('display help and exit')),
120 119 ('', 'hidden', False, _('consider hidden changesets')),
121 120 ]
122 121
123 122 dryrunopts = [('n', 'dry-run', None,
124 123 _('do not perform actions, just print output'))]
125 124
126 125 remoteopts = [
127 126 ('e', 'ssh', '',
128 127 _('specify ssh command to use'), _('CMD')),
129 128 ('', 'remotecmd', '',
130 129 _('specify hg command to run on the remote side'), _('CMD')),
131 130 ('', 'insecure', None,
132 131 _('do not verify server certificate (ignoring web.cacerts config)')),
133 132 ]
134 133
135 134 walkopts = [
136 135 ('I', 'include', [],
137 136 _('include names matching the given patterns'), _('PATTERN')),
138 137 ('X', 'exclude', [],
139 138 _('exclude names matching the given patterns'), _('PATTERN')),
140 139 ]
141 140
142 141 commitopts = [
143 142 ('m', 'message', '',
144 143 _('use text as commit message'), _('TEXT')),
145 144 ('l', 'logfile', '',
146 145 _('read commit message from file'), _('FILE')),
147 146 ]
148 147
149 148 commitopts2 = [
150 149 ('d', 'date', '',
151 150 _('record the specified date as commit date'), _('DATE')),
152 151 ('u', 'user', '',
153 152 _('record the specified user as committer'), _('USER')),
154 153 ]
155 154
156 155 # hidden for now
157 156 formatteropts = [
158 157 ('T', 'template', '',
159 158 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
160 159 ]
161 160
162 161 templateopts = [
163 162 ('', 'style', '',
164 163 _('display using template map file (DEPRECATED)'), _('STYLE')),
165 164 ('T', 'template', '',
166 165 _('display with template'), _('TEMPLATE')),
167 166 ]
168 167
169 168 logopts = [
170 169 ('p', 'patch', None, _('show patch')),
171 170 ('g', 'git', None, _('use git extended diff format')),
172 171 ('l', 'limit', '',
173 172 _('limit number of changes displayed'), _('NUM')),
174 173 ('M', 'no-merges', None, _('do not show merges')),
175 174 ('', 'stat', None, _('output diffstat-style summary of changes')),
176 175 ('G', 'graph', None, _("show the revision DAG")),
177 176 ] + templateopts
178 177
179 178 diffopts = [
180 179 ('a', 'text', None, _('treat all files as text')),
181 180 ('g', 'git', None, _('use git extended diff format')),
182 181 ('', 'nodates', None, _('omit dates from diff headers'))
183 182 ]
184 183
185 184 diffwsopts = [
186 185 ('w', 'ignore-all-space', None,
187 186 _('ignore white space when comparing lines')),
188 187 ('b', 'ignore-space-change', None,
189 188 _('ignore changes in the amount of white space')),
190 189 ('B', 'ignore-blank-lines', None,
191 190 _('ignore changes whose lines are all blank')),
192 191 ]
193 192
194 193 diffopts2 = [
195 194 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
196 195 ('p', 'show-function', None, _('show which function each change is in')),
197 196 ('', 'reverse', None, _('produce a diff that undoes the changes')),
198 197 ] + diffwsopts + [
199 198 ('U', 'unified', '',
200 199 _('number of lines of context to show'), _('NUM')),
201 200 ('', 'stat', None, _('output diffstat-style summary of changes')),
202 201 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
203 202 ]
204 203
205 204 mergetoolopts = [
206 205 ('t', 'tool', '', _('specify merge tool')),
207 206 ]
208 207
209 208 similarityopts = [
210 209 ('s', 'similarity', '',
211 210 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
212 211 ]
213 212
214 213 subrepoopts = [
215 214 ('S', 'subrepos', None,
216 215 _('recurse into subrepositories'))
217 216 ]
218 217
219 218 debugrevlogopts = [
220 219 ('c', 'changelog', False, _('open changelog')),
221 220 ('m', 'manifest', False, _('open manifest')),
222 221 ('', 'dir', '', _('open directory manifest')),
223 222 ]
224 223
225 224 # Commands start here, listed alphabetically
226 225
227 226 @command('^add',
228 227 walkopts + subrepoopts + dryrunopts,
229 228 _('[OPTION]... [FILE]...'),
230 229 inferrepo=True)
231 230 def add(ui, repo, *pats, **opts):
232 231 """add the specified files on the next commit
233 232
234 233 Schedule files to be version controlled and added to the
235 234 repository.
236 235
237 236 The files will be added to the repository at the next commit. To
238 237 undo an add before that, see :hg:`forget`.
239 238
240 239 If no names are given, add all files to the repository (except
241 240 files matching ``.hgignore``).
242 241
243 242 .. container:: verbose
244 243
245 244 Examples:
246 245
247 246 - New (unknown) files are added
248 247 automatically by :hg:`add`::
249 248
250 249 $ ls
251 250 foo.c
252 251 $ hg status
253 252 ? foo.c
254 253 $ hg add
255 254 adding foo.c
256 255 $ hg status
257 256 A foo.c
258 257
259 258 - Specific files to be added can be specified::
260 259
261 260 $ ls
262 261 bar.c foo.c
263 262 $ hg status
264 263 ? bar.c
265 264 ? foo.c
266 265 $ hg add bar.c
267 266 $ hg status
268 267 A bar.c
269 268 ? foo.c
270 269
271 270 Returns 0 if all files are successfully added.
272 271 """
273 272
274 273 m = scmutil.match(repo[None], pats, opts)
275 274 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
276 275 return rejected and 1 or 0
277 276
278 277 @command('addremove',
279 278 similarityopts + subrepoopts + walkopts + dryrunopts,
280 279 _('[OPTION]... [FILE]...'),
281 280 inferrepo=True)
282 281 def addremove(ui, repo, *pats, **opts):
283 282 """add all new files, delete all missing files
284 283
285 284 Add all new files and remove all missing files from the
286 285 repository.
287 286
288 287 Unless names are given, new files are ignored if they match any of
289 288 the patterns in ``.hgignore``. As with add, these changes take
290 289 effect at the next commit.
291 290
292 291 Use the -s/--similarity option to detect renamed files. This
293 292 option takes a percentage between 0 (disabled) and 100 (files must
294 293 be identical) as its parameter. With a parameter greater than 0,
295 294 this compares every removed file with every added file and records
296 295 those similar enough as renames. Detecting renamed files this way
297 296 can be expensive. After using this option, :hg:`status -C` can be
298 297 used to check which files were identified as moved or renamed. If
299 298 not specified, -s/--similarity defaults to 100 and only renames of
300 299 identical files are detected.
301 300
302 301 .. container:: verbose
303 302
304 303 Examples:
305 304
306 305 - A number of files (bar.c and foo.c) are new,
307 306 while foobar.c has been removed (without using :hg:`remove`)
308 307 from the repository::
309 308
310 309 $ ls
311 310 bar.c foo.c
312 311 $ hg status
313 312 ! foobar.c
314 313 ? bar.c
315 314 ? foo.c
316 315 $ hg addremove
317 316 adding bar.c
318 317 adding foo.c
319 318 removing foobar.c
320 319 $ hg status
321 320 A bar.c
322 321 A foo.c
323 322 R foobar.c
324 323
325 324 - A file foobar.c was moved to foo.c without using :hg:`rename`.
326 325 Afterwards, it was edited slightly::
327 326
328 327 $ ls
329 328 foo.c
330 329 $ hg status
331 330 ! foobar.c
332 331 ? foo.c
333 332 $ hg addremove --similarity 90
334 333 removing foobar.c
335 334 adding foo.c
336 335 recording removal of foobar.c as rename to foo.c (94% similar)
337 336 $ hg status -C
338 337 A foo.c
339 338 foobar.c
340 339 R foobar.c
341 340
342 341 Returns 0 if all files are successfully added.
343 342 """
344 343 try:
345 344 sim = float(opts.get('similarity') or 100)
346 345 except ValueError:
347 346 raise error.Abort(_('similarity must be a number'))
348 347 if sim < 0 or sim > 100:
349 348 raise error.Abort(_('similarity must be between 0 and 100'))
350 349 matcher = scmutil.match(repo[None], pats, opts)
351 350 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
352 351
353 352 @command('^annotate|blame',
354 353 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
355 354 ('', 'follow', None,
356 355 _('follow copies/renames and list the filename (DEPRECATED)')),
357 356 ('', 'no-follow', None, _("don't follow copies and renames")),
358 357 ('a', 'text', None, _('treat all files as text')),
359 358 ('u', 'user', None, _('list the author (long with -v)')),
360 359 ('f', 'file', None, _('list the filename')),
361 360 ('d', 'date', None, _('list the date (short with -q)')),
362 361 ('n', 'number', None, _('list the revision number (default)')),
363 362 ('c', 'changeset', None, _('list the changeset')),
364 363 ('l', 'line-number', None, _('show line number at the first appearance'))
365 364 ] + diffwsopts + walkopts + formatteropts,
366 365 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
367 366 inferrepo=True)
368 367 def annotate(ui, repo, *pats, **opts):
369 368 """show changeset information by line for each file
370 369
371 370 List changes in files, showing the revision id responsible for
372 371 each line.
373 372
374 373 This command is useful for discovering when a change was made and
375 374 by whom.
376 375
377 376 If you include --file, --user, or --date, the revision number is
378 377 suppressed unless you also include --number.
379 378
380 379 Without the -a/--text option, annotate will avoid processing files
381 380 it detects as binary. With -a, annotate will annotate the file
382 381 anyway, although the results will probably be neither useful
383 382 nor desirable.
384 383
385 384 Returns 0 on success.
386 385 """
387 386 if not pats:
388 387 raise error.Abort(_('at least one filename or pattern is required'))
389 388
390 389 if opts.get('follow'):
391 390 # --follow is deprecated and now just an alias for -f/--file
392 391 # to mimic the behavior of Mercurial before version 1.5
393 392 opts['file'] = True
394 393
395 394 ctx = scmutil.revsingle(repo, opts.get('rev'))
396 395
397 396 fm = ui.formatter('annotate', opts)
398 397 if ui.quiet:
399 398 datefunc = util.shortdate
400 399 else:
401 400 datefunc = util.datestr
402 401 if ctx.rev() is None:
403 402 def hexfn(node):
404 403 if node is None:
405 404 return None
406 405 else:
407 406 return fm.hexfunc(node)
408 407 if opts.get('changeset'):
409 408 # omit "+" suffix which is appended to node hex
410 409 def formatrev(rev):
411 410 if rev is None:
412 411 return '%d' % ctx.p1().rev()
413 412 else:
414 413 return '%d' % rev
415 414 else:
416 415 def formatrev(rev):
417 416 if rev is None:
418 417 return '%d+' % ctx.p1().rev()
419 418 else:
420 419 return '%d ' % rev
421 420 def formathex(hex):
422 421 if hex is None:
423 422 return '%s+' % fm.hexfunc(ctx.p1().node())
424 423 else:
425 424 return '%s ' % hex
426 425 else:
427 426 hexfn = fm.hexfunc
428 427 formatrev = formathex = str
429 428
430 429 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
431 430 ('number', ' ', lambda x: x[0].rev(), formatrev),
432 431 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
433 432 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
434 433 ('file', ' ', lambda x: x[0].path(), str),
435 434 ('line_number', ':', lambda x: x[1], str),
436 435 ]
437 436 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
438 437
439 438 if (not opts.get('user') and not opts.get('changeset')
440 439 and not opts.get('date') and not opts.get('file')):
441 440 opts['number'] = True
442 441
443 442 linenumber = opts.get('line_number') is not None
444 443 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
445 444 raise error.Abort(_('at least one of -n/-c is required for -l'))
446 445
447 446 if fm.isplain():
448 447 def makefunc(get, fmt):
449 448 return lambda x: fmt(get(x))
450 449 else:
451 450 def makefunc(get, fmt):
452 451 return get
453 452 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
454 453 if opts.get(op)]
455 454 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
456 455 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
457 456 if opts.get(op))
458 457
459 458 def bad(x, y):
460 459 raise error.Abort("%s: %s" % (x, y))
461 460
462 461 m = scmutil.match(ctx, pats, opts, badfn=bad)
463 462
464 463 follow = not opts.get('no_follow')
465 464 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
466 465 whitespace=True)
467 466 for abs in ctx.walk(m):
468 467 fctx = ctx[abs]
469 468 if not opts.get('text') and util.binary(fctx.data()):
470 469 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
471 470 continue
472 471
473 472 lines = fctx.annotate(follow=follow, linenumber=linenumber,
474 473 diffopts=diffopts)
475 474 if not lines:
476 475 continue
477 476 formats = []
478 477 pieces = []
479 478
480 479 for f, sep in funcmap:
481 480 l = [f(n) for n, dummy in lines]
482 481 if fm.isplain():
483 482 sizes = [encoding.colwidth(x) for x in l]
484 483 ml = max(sizes)
485 484 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
486 485 else:
487 486 formats.append(['%s' for x in l])
488 487 pieces.append(l)
489 488
490 489 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
491 490 fm.startitem()
492 491 fm.write(fields, "".join(f), *p)
493 492 fm.write('line', ": %s", l[1])
494 493
495 494 if not lines[-1][1].endswith('\n'):
496 495 fm.plain('\n')
497 496
498 497 fm.end()
499 498
500 499 @command('archive',
501 500 [('', 'no-decode', None, _('do not pass files through decoders')),
502 501 ('p', 'prefix', '', _('directory prefix for files in archive'),
503 502 _('PREFIX')),
504 503 ('r', 'rev', '', _('revision to distribute'), _('REV')),
505 504 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
506 505 ] + subrepoopts + walkopts,
507 506 _('[OPTION]... DEST'))
508 507 def archive(ui, repo, dest, **opts):
509 508 '''create an unversioned archive of a repository revision
510 509
511 510 By default, the revision used is the parent of the working
512 511 directory; use -r/--rev to specify a different revision.
513 512
514 513 The archive type is automatically detected based on file
515 514 extension (to override, use -t/--type).
516 515
517 516 .. container:: verbose
518 517
519 518 Examples:
520 519
521 520 - create a zip file containing the 1.0 release::
522 521
523 522 hg archive -r 1.0 project-1.0.zip
524 523
525 524 - create a tarball excluding .hg files::
526 525
527 526 hg archive project.tar.gz -X ".hg*"
528 527
529 528 Valid types are:
530 529
531 530 :``files``: a directory full of files (default)
532 531 :``tar``: tar archive, uncompressed
533 532 :``tbz2``: tar archive, compressed using bzip2
534 533 :``tgz``: tar archive, compressed using gzip
535 534 :``uzip``: zip archive, uncompressed
536 535 :``zip``: zip archive, compressed using deflate
537 536
538 537 The exact name of the destination archive or directory is given
539 538 using a format string; see :hg:`help export` for details.
540 539
541 540 Each member added to an archive file has a directory prefix
542 541 prepended. Use -p/--prefix to specify a format string for the
543 542 prefix. The default is the basename of the archive, with suffixes
544 543 removed.
545 544
546 545 Returns 0 on success.
547 546 '''
548 547
549 548 ctx = scmutil.revsingle(repo, opts.get('rev'))
550 549 if not ctx:
551 550 raise error.Abort(_('no working directory: please specify a revision'))
552 551 node = ctx.node()
553 552 dest = cmdutil.makefilename(repo, dest, node)
554 553 if os.path.realpath(dest) == repo.root:
555 554 raise error.Abort(_('repository root cannot be destination'))
556 555
557 556 kind = opts.get('type') or archival.guesskind(dest) or 'files'
558 557 prefix = opts.get('prefix')
559 558
560 559 if dest == '-':
561 560 if kind == 'files':
562 561 raise error.Abort(_('cannot archive plain files to stdout'))
563 562 dest = cmdutil.makefileobj(repo, dest)
564 563 if not prefix:
565 564 prefix = os.path.basename(repo.root) + '-%h'
566 565
567 566 prefix = cmdutil.makefilename(repo, prefix, node)
568 567 matchfn = scmutil.match(ctx, [], opts)
569 568 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
570 569 matchfn, prefix, subrepos=opts.get('subrepos'))
571 570
572 571 @command('backout',
573 572 [('', 'merge', None, _('merge with old dirstate parent after backout')),
574 573 ('', 'commit', None,
575 574 _('commit if no conflicts were encountered (DEPRECATED)')),
576 575 ('', 'no-commit', None, _('do not commit')),
577 576 ('', 'parent', '',
578 577 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
579 578 ('r', 'rev', '', _('revision to backout'), _('REV')),
580 579 ('e', 'edit', False, _('invoke editor on commit messages')),
581 580 ] + mergetoolopts + walkopts + commitopts + commitopts2,
582 581 _('[OPTION]... [-r] REV'))
583 582 def backout(ui, repo, node=None, rev=None, **opts):
584 583 '''reverse effect of earlier changeset
585 584
586 585 Prepare a new changeset with the effect of REV undone in the
587 586 current working directory. If no conflicts were encountered,
588 587 it will be committed immediately.
589 588
590 589 If REV is the parent of the working directory, then this new changeset
591 590 is committed automatically (unless --no-commit is specified).
592 591
593 592 .. note::
594 593
595 594 :hg:`backout` cannot be used to fix either an unwanted or
596 595 incorrect merge.
597 596
598 597 .. container:: verbose
599 598
600 599 Examples:
601 600
602 601 - Reverse the effect of the parent of the working directory.
603 602 This backout will be committed immediately::
604 603
605 604 hg backout -r .
606 605
607 606 - Reverse the effect of previous bad revision 23::
608 607
609 608 hg backout -r 23
610 609
611 610 - Reverse the effect of previous bad revision 23 and
612 611 leave changes uncommitted::
613 612
614 613 hg backout -r 23 --no-commit
615 614 hg commit -m "Backout revision 23"
616 615
617 616 By default, the pending changeset will have one parent,
618 617 maintaining a linear history. With --merge, the pending
619 618 changeset will instead have two parents: the old parent of the
620 619 working directory and a new child of REV that simply undoes REV.
621 620
622 621 Before version 1.7, the behavior without --merge was equivalent
623 622 to specifying --merge followed by :hg:`update --clean .` to
624 623 cancel the merge and leave the child of REV as a head to be
625 624 merged separately.
626 625
627 626 See :hg:`help dates` for a list of formats valid for -d/--date.
628 627
629 628 See :hg:`help revert` for a way to restore files to the state
630 629 of another revision.
631 630
632 631 Returns 0 on success, 1 if nothing to backout or there are unresolved
633 632 files.
634 633 '''
635 634 wlock = lock = None
636 635 try:
637 636 wlock = repo.wlock()
638 637 lock = repo.lock()
639 638 return _dobackout(ui, repo, node, rev, **opts)
640 639 finally:
641 640 release(lock, wlock)
642 641
643 642 def _dobackout(ui, repo, node=None, rev=None, **opts):
644 643 if opts.get('commit') and opts.get('no_commit'):
645 644 raise error.Abort(_("cannot use --commit with --no-commit"))
646 645 if opts.get('merge') and opts.get('no_commit'):
647 646 raise error.Abort(_("cannot use --merge with --no-commit"))
648 647
649 648 if rev and node:
650 649 raise error.Abort(_("please specify just one revision"))
651 650
652 651 if not rev:
653 652 rev = node
654 653
655 654 if not rev:
656 655 raise error.Abort(_("please specify a revision to backout"))
657 656
658 657 date = opts.get('date')
659 658 if date:
660 659 opts['date'] = util.parsedate(date)
661 660
662 661 cmdutil.checkunfinished(repo)
663 662 cmdutil.bailifchanged(repo)
664 663 node = scmutil.revsingle(repo, rev).node()
665 664
666 665 op1, op2 = repo.dirstate.parents()
667 666 if not repo.changelog.isancestor(node, op1):
668 667 raise error.Abort(_('cannot backout change that is not an ancestor'))
669 668
670 669 p1, p2 = repo.changelog.parents(node)
671 670 if p1 == nullid:
672 671 raise error.Abort(_('cannot backout a change with no parents'))
673 672 if p2 != nullid:
674 673 if not opts.get('parent'):
675 674 raise error.Abort(_('cannot backout a merge changeset'))
676 675 p = repo.lookup(opts['parent'])
677 676 if p not in (p1, p2):
678 677 raise error.Abort(_('%s is not a parent of %s') %
679 678 (short(p), short(node)))
680 679 parent = p
681 680 else:
682 681 if opts.get('parent'):
683 682 raise error.Abort(_('cannot use --parent on non-merge changeset'))
684 683 parent = p1
685 684
686 685 # the backout should appear on the same branch
687 686 branch = repo.dirstate.branch()
688 687 bheads = repo.branchheads(branch)
689 688 rctx = scmutil.revsingle(repo, hex(parent))
690 689 if not opts.get('merge') and op1 != node:
691 690 dsguard = dirstateguard.dirstateguard(repo, 'backout')
692 691 try:
693 692 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
694 693 'backout')
695 694 stats = mergemod.update(repo, parent, True, True, node, False)
696 695 repo.setparents(op1, op2)
697 696 dsguard.close()
698 697 hg._showstats(repo, stats)
699 698 if stats[3]:
700 699 repo.ui.status(_("use 'hg resolve' to retry unresolved "
701 700 "file merges\n"))
702 701 return 1
703 702 finally:
704 703 ui.setconfig('ui', 'forcemerge', '', '')
705 704 lockmod.release(dsguard)
706 705 else:
707 706 hg.clean(repo, node, show_stats=False)
708 707 repo.dirstate.setbranch(branch)
709 708 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
710 709
711 710 if opts.get('no_commit'):
712 711 msg = _("changeset %s backed out, "
713 712 "don't forget to commit.\n")
714 713 ui.status(msg % short(node))
715 714 return 0
716 715
717 716 def commitfunc(ui, repo, message, match, opts):
718 717 editform = 'backout'
719 718 e = cmdutil.getcommiteditor(editform=editform, **opts)
720 719 if not message:
721 720 # we don't translate commit messages
722 721 message = "Backed out changeset %s" % short(node)
723 722 e = cmdutil.getcommiteditor(edit=True, editform=editform)
724 723 return repo.commit(message, opts.get('user'), opts.get('date'),
725 724 match, editor=e)
726 725 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
727 726 if not newnode:
728 727 ui.status(_("nothing changed\n"))
729 728 return 1
730 729 cmdutil.commitstatus(repo, newnode, branch, bheads)
731 730
732 731 def nice(node):
733 732 return '%d:%s' % (repo.changelog.rev(node), short(node))
734 733 ui.status(_('changeset %s backs out changeset %s\n') %
735 734 (nice(repo.changelog.tip()), nice(node)))
736 735 if opts.get('merge') and op1 != node:
737 736 hg.clean(repo, op1, show_stats=False)
738 737 ui.status(_('merging with changeset %s\n')
739 738 % nice(repo.changelog.tip()))
740 739 try:
741 740 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
742 741 'backout')
743 742 return hg.merge(repo, hex(repo.changelog.tip()))
744 743 finally:
745 744 ui.setconfig('ui', 'forcemerge', '', '')
746 745 return 0
747 746
748 747 @command('bisect',
749 748 [('r', 'reset', False, _('reset bisect state')),
750 749 ('g', 'good', False, _('mark changeset good')),
751 750 ('b', 'bad', False, _('mark changeset bad')),
752 751 ('s', 'skip', False, _('skip testing changeset')),
753 752 ('e', 'extend', False, _('extend the bisect range')),
754 753 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
755 754 ('U', 'noupdate', False, _('do not update to target'))],
756 755 _("[-gbsr] [-U] [-c CMD] [REV]"))
757 756 def bisect(ui, repo, rev=None, extra=None, command=None,
758 757 reset=None, good=None, bad=None, skip=None, extend=None,
759 758 noupdate=None):
760 759 """subdivision search of changesets
761 760
762 761 This command helps to find changesets which introduce problems. To
763 762 use, mark the earliest changeset you know exhibits the problem as
764 763 bad, then mark the latest changeset which is free from the problem
765 764 as good. Bisect will update your working directory to a revision
766 765 for testing (unless the -U/--noupdate option is specified). Once
767 766 you have performed tests, mark the working directory as good or
768 767 bad, and bisect will either update to another candidate changeset
769 768 or announce that it has found the bad revision.
770 769
771 770 As a shortcut, you can also use the revision argument to mark a
772 771 revision as good or bad without checking it out first.
773 772
774 773 If you supply a command, it will be used for automatic bisection.
775 774 The environment variable HG_NODE will contain the ID of the
776 775 changeset being tested. The exit status of the command will be
777 776 used to mark revisions as good or bad: status 0 means good, 125
778 777 means to skip the revision, 127 (command not found) will abort the
779 778 bisection, and any other non-zero exit status means the revision
780 779 is bad.
781 780
782 781 .. container:: verbose
783 782
784 783 Some examples:
785 784
786 785 - start a bisection with known bad revision 34, and good revision 12::
787 786
788 787 hg bisect --bad 34
789 788 hg bisect --good 12
790 789
791 790 - advance the current bisection by marking current revision as good or
792 791 bad::
793 792
794 793 hg bisect --good
795 794 hg bisect --bad
796 795
797 796 - mark the current revision, or a known revision, to be skipped (e.g. if
798 797 that revision is not usable because of another issue)::
799 798
800 799 hg bisect --skip
801 800 hg bisect --skip 23
802 801
803 802 - skip all revisions that do not touch directories ``foo`` or ``bar``::
804 803
805 804 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
806 805
807 806 - forget the current bisection::
808 807
809 808 hg bisect --reset
810 809
811 810 - use 'make && make tests' to automatically find the first broken
812 811 revision::
813 812
814 813 hg bisect --reset
815 814 hg bisect --bad 34
816 815 hg bisect --good 12
817 816 hg bisect --command "make && make tests"
818 817
819 818 - see all changesets whose states are already known in the current
820 819 bisection::
821 820
822 821 hg log -r "bisect(pruned)"
823 822
824 823 - see the changeset currently being bisected (especially useful
825 824 if running with -U/--noupdate)::
826 825
827 826 hg log -r "bisect(current)"
828 827
829 828 - see all changesets that took part in the current bisection::
830 829
831 830 hg log -r "bisect(range)"
832 831
833 832 - you can even get a nice graph::
834 833
835 834 hg log --graph -r "bisect(range)"
836 835
837 836 See :hg:`help revsets` for more about the `bisect()` keyword.
838 837
839 838 Returns 0 on success.
840 839 """
841 840 # backward compatibility
842 841 if rev in "good bad reset init".split():
843 842 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
844 843 cmd, rev, extra = rev, extra, None
845 844 if cmd == "good":
846 845 good = True
847 846 elif cmd == "bad":
848 847 bad = True
849 848 else:
850 849 reset = True
851 850 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
852 851 raise error.Abort(_('incompatible arguments'))
853 852
854 853 cmdutil.checkunfinished(repo)
855 854
856 855 if reset:
857 856 hbisect.resetstate(repo)
858 857 return
859 858
860 859 state = hbisect.load_state(repo)
861 860
862 861 # update state
863 862 if good or bad or skip:
864 863 if rev:
865 864 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
866 865 else:
867 866 nodes = [repo.lookup('.')]
868 867 if good:
869 868 state['good'] += nodes
870 869 elif bad:
871 870 state['bad'] += nodes
872 871 elif skip:
873 872 state['skip'] += nodes
874 873 hbisect.save_state(repo, state)
875 874 if not (state['good'] and state['bad']):
876 875 return
877 876
878 877 def mayupdate(repo, node, show_stats=True):
879 878 """common used update sequence"""
880 879 if noupdate:
881 880 return
882 881 cmdutil.bailifchanged(repo)
883 882 return hg.clean(repo, node, show_stats=show_stats)
884 883
885 884 displayer = cmdutil.show_changeset(ui, repo, {})
886 885
887 886 if command:
888 887 changesets = 1
889 888 if noupdate:
890 889 try:
891 890 node = state['current'][0]
892 891 except LookupError:
893 892 raise error.Abort(_('current bisect revision is unknown - '
894 893 'start a new bisect to fix'))
895 894 else:
896 895 node, p2 = repo.dirstate.parents()
897 896 if p2 != nullid:
898 897 raise error.Abort(_('current bisect revision is a merge'))
899 898 if rev:
900 899 node = repo[scmutil.revsingle(repo, rev, node)].node()
901 900 try:
902 901 while changesets:
903 902 # update state
904 903 state['current'] = [node]
905 904 hbisect.save_state(repo, state)
906 905 status = ui.system(command, environ={'HG_NODE': hex(node)})
907 906 if status == 125:
908 907 transition = "skip"
909 908 elif status == 0:
910 909 transition = "good"
911 910 # status < 0 means process was killed
912 911 elif status == 127:
913 912 raise error.Abort(_("failed to execute %s") % command)
914 913 elif status < 0:
915 914 raise error.Abort(_("%s killed") % command)
916 915 else:
917 916 transition = "bad"
918 917 state[transition].append(node)
919 918 ctx = repo[node]
920 919 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
921 920 hbisect.checkstate(state)
922 921 # bisect
923 922 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
924 923 # update to next check
925 924 node = nodes[0]
926 925 mayupdate(repo, node, show_stats=False)
927 926 finally:
928 927 state['current'] = [node]
929 928 hbisect.save_state(repo, state)
930 929 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
931 930 return
932 931
933 932 hbisect.checkstate(state)
934 933
935 934 # actually bisect
936 935 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
937 936 if extend:
938 937 if not changesets:
939 938 extendnode = hbisect.extendrange(repo, state, nodes, good)
940 939 if extendnode is not None:
941 940 ui.write(_("Extending search to changeset %d:%s\n")
942 941 % (extendnode.rev(), extendnode))
943 942 state['current'] = [extendnode.node()]
944 943 hbisect.save_state(repo, state)
945 944 return mayupdate(repo, extendnode.node())
946 945 raise error.Abort(_("nothing to extend"))
947 946
948 947 if changesets == 0:
949 948 hbisect.printresult(ui, repo, state, displayer, nodes, good)
950 949 else:
951 950 assert len(nodes) == 1 # only a single node can be tested next
952 951 node = nodes[0]
953 952 # compute the approximate number of remaining tests
954 953 tests, size = 0, 2
955 954 while size <= changesets:
956 955 tests, size = tests + 1, size * 2
957 956 rev = repo.changelog.rev(node)
958 957 ui.write(_("Testing changeset %d:%s "
959 958 "(%d changesets remaining, ~%d tests)\n")
960 959 % (rev, short(node), changesets, tests))
961 960 state['current'] = [node]
962 961 hbisect.save_state(repo, state)
963 962 return mayupdate(repo, node)
964 963
965 964 @command('bookmarks|bookmark',
966 965 [('f', 'force', False, _('force')),
967 966 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
968 967 ('d', 'delete', False, _('delete a given bookmark')),
969 968 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
970 969 ('i', 'inactive', False, _('mark a bookmark inactive')),
971 970 ] + formatteropts,
972 971 _('hg bookmarks [OPTIONS]... [NAME]...'))
973 972 def bookmark(ui, repo, *names, **opts):
974 973 '''create a new bookmark or list existing bookmarks
975 974
976 975 Bookmarks are labels on changesets to help track lines of development.
977 976 Bookmarks are unversioned and can be moved, renamed and deleted.
978 977 Deleting or moving a bookmark has no effect on the associated changesets.
979 978
980 979 Creating or updating to a bookmark causes it to be marked as 'active'.
981 980 The active bookmark is indicated with a '*'.
982 981 When a commit is made, the active bookmark will advance to the new commit.
983 982 A plain :hg:`update` will also advance an active bookmark, if possible.
984 983 Updating away from a bookmark will cause it to be deactivated.
985 984
986 985 Bookmarks can be pushed and pulled between repositories (see
987 986 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
988 987 diverged, a new 'divergent bookmark' of the form 'name@path' will
989 988 be created. Using :hg:`merge` will resolve the divergence.
990 989
991 990 A bookmark named '@' has the special property that :hg:`clone` will
992 991 check it out by default if it exists.
993 992
994 993 .. container:: verbose
995 994
996 995 Examples:
997 996
998 997 - create an active bookmark for a new line of development::
999 998
1000 999 hg book new-feature
1001 1000
1002 1001 - create an inactive bookmark as a place marker::
1003 1002
1004 1003 hg book -i reviewed
1005 1004
1006 1005 - create an inactive bookmark on another changeset::
1007 1006
1008 1007 hg book -r .^ tested
1009 1008
1010 1009 - rename bookmark turkey to dinner::
1011 1010
1012 1011 hg book -m turkey dinner
1013 1012
1014 1013 - move the '@' bookmark from another branch::
1015 1014
1016 1015 hg book -f @
1017 1016 '''
1018 1017 force = opts.get('force')
1019 1018 rev = opts.get('rev')
1020 1019 delete = opts.get('delete')
1021 1020 rename = opts.get('rename')
1022 1021 inactive = opts.get('inactive')
1023 1022
1024 1023 def checkformat(mark):
1025 1024 mark = mark.strip()
1026 1025 if not mark:
1027 1026 raise error.Abort(_("bookmark names cannot consist entirely of "
1028 1027 "whitespace"))
1029 1028 scmutil.checknewlabel(repo, mark, 'bookmark')
1030 1029 return mark
1031 1030
1032 1031 def checkconflict(repo, mark, cur, force=False, target=None):
1033 1032 if mark in marks and not force:
1034 1033 if target:
1035 1034 if marks[mark] == target and target == cur:
1036 1035 # re-activating a bookmark
1037 1036 return
1038 1037 anc = repo.changelog.ancestors([repo[target].rev()])
1039 1038 bmctx = repo[marks[mark]]
1040 1039 divs = [repo[b].node() for b in marks
1041 1040 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1042 1041
1043 1042 # allow resolving a single divergent bookmark even if moving
1044 1043 # the bookmark across branches when a revision is specified
1045 1044 # that contains a divergent bookmark
1046 1045 if bmctx.rev() not in anc and target in divs:
1047 1046 bookmarks.deletedivergent(repo, [target], mark)
1048 1047 return
1049 1048
1050 1049 deletefrom = [b for b in divs
1051 1050 if repo[b].rev() in anc or b == target]
1052 1051 bookmarks.deletedivergent(repo, deletefrom, mark)
1053 1052 if bookmarks.validdest(repo, bmctx, repo[target]):
1054 1053 ui.status(_("moving bookmark '%s' forward from %s\n") %
1055 1054 (mark, short(bmctx.node())))
1056 1055 return
1057 1056 raise error.Abort(_("bookmark '%s' already exists "
1058 1057 "(use -f to force)") % mark)
1059 1058 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1060 1059 and not force):
1061 1060 raise error.Abort(
1062 1061 _("a bookmark cannot have the name of an existing branch"))
1063 1062
1064 1063 if delete and rename:
1065 1064 raise error.Abort(_("--delete and --rename are incompatible"))
1066 1065 if delete and rev:
1067 1066 raise error.Abort(_("--rev is incompatible with --delete"))
1068 1067 if rename and rev:
1069 1068 raise error.Abort(_("--rev is incompatible with --rename"))
1070 1069 if not names and (delete or rev):
1071 1070 raise error.Abort(_("bookmark name required"))
1072 1071
1073 1072 if delete or rename or names or inactive:
1074 1073 wlock = lock = tr = None
1075 1074 try:
1076 1075 wlock = repo.wlock()
1077 1076 lock = repo.lock()
1078 1077 cur = repo.changectx('.').node()
1079 1078 marks = repo._bookmarks
1080 1079 if delete:
1081 1080 tr = repo.transaction('bookmark')
1082 1081 for mark in names:
1083 1082 if mark not in marks:
1084 1083 raise error.Abort(_("bookmark '%s' does not exist") %
1085 1084 mark)
1086 1085 if mark == repo._activebookmark:
1087 1086 bookmarks.deactivate(repo)
1088 1087 del marks[mark]
1089 1088
1090 1089 elif rename:
1091 1090 tr = repo.transaction('bookmark')
1092 1091 if not names:
1093 1092 raise error.Abort(_("new bookmark name required"))
1094 1093 elif len(names) > 1:
1095 1094 raise error.Abort(_("only one new bookmark name allowed"))
1096 1095 mark = checkformat(names[0])
1097 1096 if rename not in marks:
1098 1097 raise error.Abort(_("bookmark '%s' does not exist")
1099 1098 % rename)
1100 1099 checkconflict(repo, mark, cur, force)
1101 1100 marks[mark] = marks[rename]
1102 1101 if repo._activebookmark == rename and not inactive:
1103 1102 bookmarks.activate(repo, mark)
1104 1103 del marks[rename]
1105 1104 elif names:
1106 1105 tr = repo.transaction('bookmark')
1107 1106 newact = None
1108 1107 for mark in names:
1109 1108 mark = checkformat(mark)
1110 1109 if newact is None:
1111 1110 newact = mark
1112 1111 if inactive and mark == repo._activebookmark:
1113 1112 bookmarks.deactivate(repo)
1114 1113 return
1115 1114 tgt = cur
1116 1115 if rev:
1117 1116 tgt = scmutil.revsingle(repo, rev).node()
1118 1117 checkconflict(repo, mark, cur, force, tgt)
1119 1118 marks[mark] = tgt
1120 1119 if not inactive and cur == marks[newact] and not rev:
1121 1120 bookmarks.activate(repo, newact)
1122 1121 elif cur != tgt and newact == repo._activebookmark:
1123 1122 bookmarks.deactivate(repo)
1124 1123 elif inactive:
1125 1124 if len(marks) == 0:
1126 1125 ui.status(_("no bookmarks set\n"))
1127 1126 elif not repo._activebookmark:
1128 1127 ui.status(_("no active bookmark\n"))
1129 1128 else:
1130 1129 bookmarks.deactivate(repo)
1131 1130 if tr is not None:
1132 1131 marks.recordchange(tr)
1133 1132 tr.close()
1134 1133 finally:
1135 1134 lockmod.release(tr, lock, wlock)
1136 1135 else: # show bookmarks
1137 1136 fm = ui.formatter('bookmarks', opts)
1138 1137 hexfn = fm.hexfunc
1139 1138 marks = repo._bookmarks
1140 1139 if len(marks) == 0 and fm.isplain():
1141 1140 ui.status(_("no bookmarks set\n"))
1142 1141 for bmark, n in sorted(marks.iteritems()):
1143 1142 active = repo._activebookmark
1144 1143 if bmark == active:
1145 1144 prefix, label = '*', activebookmarklabel
1146 1145 else:
1147 1146 prefix, label = ' ', ''
1148 1147
1149 1148 fm.startitem()
1150 1149 if not ui.quiet:
1151 1150 fm.plain(' %s ' % prefix, label=label)
1152 1151 fm.write('bookmark', '%s', bmark, label=label)
1153 1152 pad = " " * (25 - encoding.colwidth(bmark))
1154 1153 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1155 1154 repo.changelog.rev(n), hexfn(n), label=label)
1156 1155 fm.data(active=(bmark == active))
1157 1156 fm.plain('\n')
1158 1157 fm.end()
1159 1158
1160 1159 @command('branch',
1161 1160 [('f', 'force', None,
1162 1161 _('set branch name even if it shadows an existing branch')),
1163 1162 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1164 1163 _('[-fC] [NAME]'))
1165 1164 def branch(ui, repo, label=None, **opts):
1166 1165 """set or show the current branch name
1167 1166
1168 1167 .. note::
1169 1168
1170 1169 Branch names are permanent and global. Use :hg:`bookmark` to create a
1171 1170 light-weight bookmark instead. See :hg:`help glossary` for more
1172 1171 information about named branches and bookmarks.
1173 1172
1174 1173 With no argument, show the current branch name. With one argument,
1175 1174 set the working directory branch name (the branch will not exist
1176 1175 in the repository until the next commit). Standard practice
1177 1176 recommends that primary development take place on the 'default'
1178 1177 branch.
1179 1178
1180 1179 Unless -f/--force is specified, branch will not let you set a
1181 1180 branch name that already exists.
1182 1181
1183 1182 Use -C/--clean to reset the working directory branch to that of
1184 1183 the parent of the working directory, negating a previous branch
1185 1184 change.
1186 1185
1187 1186 Use the command :hg:`update` to switch to an existing branch. Use
1188 1187 :hg:`commit --close-branch` to mark this branch head as closed.
1189 1188 When all heads of a branch are closed, the branch will be
1190 1189 considered closed.
1191 1190
1192 1191 Returns 0 on success.
1193 1192 """
1194 1193 if label:
1195 1194 label = label.strip()
1196 1195
1197 1196 if not opts.get('clean') and not label:
1198 1197 ui.write("%s\n" % repo.dirstate.branch())
1199 1198 return
1200 1199
1201 1200 with repo.wlock():
1202 1201 if opts.get('clean'):
1203 1202 label = repo[None].p1().branch()
1204 1203 repo.dirstate.setbranch(label)
1205 1204 ui.status(_('reset working directory to branch %s\n') % label)
1206 1205 elif label:
1207 1206 if not opts.get('force') and label in repo.branchmap():
1208 1207 if label not in [p.branch() for p in repo[None].parents()]:
1209 1208 raise error.Abort(_('a branch of the same name already'
1210 1209 ' exists'),
1211 1210 # i18n: "it" refers to an existing branch
1212 1211 hint=_("use 'hg update' to switch to it"))
1213 1212 scmutil.checknewlabel(repo, label, 'branch')
1214 1213 repo.dirstate.setbranch(label)
1215 1214 ui.status(_('marked working directory as branch %s\n') % label)
1216 1215
1217 1216 # find any open named branches aside from default
1218 1217 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1219 1218 if n != "default" and not c]
1220 1219 if not others:
1221 1220 ui.status(_('(branches are permanent and global, '
1222 1221 'did you want a bookmark?)\n'))
1223 1222
1224 1223 @command('branches',
1225 1224 [('a', 'active', False,
1226 1225 _('show only branches that have unmerged heads (DEPRECATED)')),
1227 1226 ('c', 'closed', False, _('show normal and closed branches')),
1228 1227 ] + formatteropts,
1229 1228 _('[-c]'))
1230 1229 def branches(ui, repo, active=False, closed=False, **opts):
1231 1230 """list repository named branches
1232 1231
1233 1232 List the repository's named branches, indicating which ones are
1234 1233 inactive. If -c/--closed is specified, also list branches which have
1235 1234 been marked closed (see :hg:`commit --close-branch`).
1236 1235
1237 1236 Use the command :hg:`update` to switch to an existing branch.
1238 1237
1239 1238 Returns 0.
1240 1239 """
1241 1240
1242 1241 fm = ui.formatter('branches', opts)
1243 1242 hexfunc = fm.hexfunc
1244 1243
1245 1244 allheads = set(repo.heads())
1246 1245 branches = []
1247 1246 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1248 1247 isactive = not isclosed and bool(set(heads) & allheads)
1249 1248 branches.append((tag, repo[tip], isactive, not isclosed))
1250 1249 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1251 1250 reverse=True)
1252 1251
1253 1252 for tag, ctx, isactive, isopen in branches:
1254 1253 if active and not isactive:
1255 1254 continue
1256 1255 if isactive:
1257 1256 label = 'branches.active'
1258 1257 notice = ''
1259 1258 elif not isopen:
1260 1259 if not closed:
1261 1260 continue
1262 1261 label = 'branches.closed'
1263 1262 notice = _(' (closed)')
1264 1263 else:
1265 1264 label = 'branches.inactive'
1266 1265 notice = _(' (inactive)')
1267 1266 current = (tag == repo.dirstate.branch())
1268 1267 if current:
1269 1268 label = 'branches.current'
1270 1269
1271 1270 fm.startitem()
1272 1271 fm.write('branch', '%s', tag, label=label)
1273 1272 rev = ctx.rev()
1274 1273 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1275 1274 fmt = ' ' * padsize + ' %d:%s'
1276 1275 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1277 1276 label='log.changeset changeset.%s' % ctx.phasestr())
1278 1277 fm.data(active=isactive, closed=not isopen, current=current)
1279 1278 if not ui.quiet:
1280 1279 fm.plain(notice)
1281 1280 fm.plain('\n')
1282 1281 fm.end()
1283 1282
1284 1283 @command('bundle',
1285 1284 [('f', 'force', None, _('run even when the destination is unrelated')),
1286 1285 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1287 1286 _('REV')),
1288 1287 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1289 1288 _('BRANCH')),
1290 1289 ('', 'base', [],
1291 1290 _('a base changeset assumed to be available at the destination'),
1292 1291 _('REV')),
1293 1292 ('a', 'all', None, _('bundle all changesets in the repository')),
1294 1293 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1295 1294 ] + remoteopts,
1296 1295 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1297 1296 def bundle(ui, repo, fname, dest=None, **opts):
1298 1297 """create a changegroup file
1299 1298
1300 1299 Generate a changegroup file collecting changesets to be added
1301 1300 to a repository.
1302 1301
1303 1302 To create a bundle containing all changesets, use -a/--all
1304 1303 (or --base null). Otherwise, hg assumes the destination will have
1305 1304 all the nodes you specify with --base parameters. Otherwise, hg
1306 1305 will assume the repository has all the nodes in destination, or
1307 1306 default-push/default if no destination is specified.
1308 1307
1309 1308 You can change bundle format with the -t/--type option. You can
1310 1309 specify a compression, a bundle version or both using a dash
1311 1310 (comp-version). The available compression methods are: none, bzip2,
1312 1311 and gzip (by default, bundles are compressed using bzip2). The
1313 1312 available formats are: v1, v2 (default to most suitable).
1314 1313
1315 1314 The bundle file can then be transferred using conventional means
1316 1315 and applied to another repository with the unbundle or pull
1317 1316 command. This is useful when direct push and pull are not
1318 1317 available or when exporting an entire repository is undesirable.
1319 1318
1320 1319 Applying bundles preserves all changeset contents including
1321 1320 permissions, copy/rename information, and revision history.
1322 1321
1323 1322 Returns 0 on success, 1 if no changes found.
1324 1323 """
1325 1324 revs = None
1326 1325 if 'rev' in opts:
1327 1326 revstrings = opts['rev']
1328 1327 revs = scmutil.revrange(repo, revstrings)
1329 1328 if revstrings and not revs:
1330 1329 raise error.Abort(_('no commits to bundle'))
1331 1330
1332 1331 bundletype = opts.get('type', 'bzip2').lower()
1333 1332 try:
1334 1333 bcompression, cgversion, params = exchange.parsebundlespec(
1335 1334 repo, bundletype, strict=False)
1336 1335 except error.UnsupportedBundleSpecification as e:
1337 1336 raise error.Abort(str(e),
1338 1337 hint=_("see 'hg help bundle' for supported "
1339 1338 "values for --type"))
1340 1339
1341 1340 # Packed bundles are a pseudo bundle format for now.
1342 1341 if cgversion == 's1':
1343 1342 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1344 1343 hint=_("use 'hg debugcreatestreamclonebundle'"))
1345 1344
1346 1345 if opts.get('all'):
1347 1346 if dest:
1348 1347 raise error.Abort(_("--all is incompatible with specifying "
1349 1348 "a destination"))
1350 1349 if opts.get('base'):
1351 1350 ui.warn(_("ignoring --base because --all was specified\n"))
1352 1351 base = ['null']
1353 1352 else:
1354 1353 base = scmutil.revrange(repo, opts.get('base'))
1355 1354 # TODO: get desired bundlecaps from command line.
1356 1355 bundlecaps = None
1357 1356 if cgversion not in changegroup.supportedoutgoingversions(repo):
1358 1357 raise error.Abort(_("repository does not support bundle version %s") %
1359 1358 cgversion)
1360 1359
1361 1360 if base:
1362 1361 if dest:
1363 1362 raise error.Abort(_("--base is incompatible with specifying "
1364 1363 "a destination"))
1365 1364 common = [repo.lookup(rev) for rev in base]
1366 1365 heads = revs and map(repo.lookup, revs) or None
1367 1366 outgoing = discovery.outgoing(repo, common, heads)
1368 1367 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1369 1368 bundlecaps=bundlecaps,
1370 1369 version=cgversion)
1371 1370 outgoing = None
1372 1371 else:
1373 1372 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1374 1373 dest, branches = hg.parseurl(dest, opts.get('branch'))
1375 1374 other = hg.peer(repo, opts, dest)
1376 1375 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1377 1376 heads = revs and map(repo.lookup, revs) or revs
1378 1377 outgoing = discovery.findcommonoutgoing(repo, other,
1379 1378 onlyheads=heads,
1380 1379 force=opts.get('force'),
1381 1380 portable=True)
1382 1381 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1383 1382 bundlecaps, version=cgversion)
1384 1383 if not cg:
1385 1384 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1386 1385 return 1
1387 1386
1388 1387 if cgversion == '01': #bundle1
1389 1388 if bcompression is None:
1390 1389 bcompression = 'UN'
1391 1390 bversion = 'HG10' + bcompression
1392 1391 bcompression = None
1393 1392 else:
1394 1393 assert cgversion == '02'
1395 1394 bversion = 'HG20'
1396 1395
1397 1396 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression)
1398 1397
1399 1398 @command('cat',
1400 1399 [('o', 'output', '',
1401 1400 _('print output to file with formatted name'), _('FORMAT')),
1402 1401 ('r', 'rev', '', _('print the given revision'), _('REV')),
1403 1402 ('', 'decode', None, _('apply any matching decode filter')),
1404 1403 ] + walkopts,
1405 1404 _('[OPTION]... FILE...'),
1406 1405 inferrepo=True)
1407 1406 def cat(ui, repo, file1, *pats, **opts):
1408 1407 """output the current or given revision of files
1409 1408
1410 1409 Print the specified files as they were at the given revision. If
1411 1410 no revision is given, the parent of the working directory is used.
1412 1411
1413 1412 Output may be to a file, in which case the name of the file is
1414 1413 given using a format string. The formatting rules as follows:
1415 1414
1416 1415 :``%%``: literal "%" character
1417 1416 :``%s``: basename of file being printed
1418 1417 :``%d``: dirname of file being printed, or '.' if in repository root
1419 1418 :``%p``: root-relative path name of file being printed
1420 1419 :``%H``: changeset hash (40 hexadecimal digits)
1421 1420 :``%R``: changeset revision number
1422 1421 :``%h``: short-form changeset hash (12 hexadecimal digits)
1423 1422 :``%r``: zero-padded changeset revision number
1424 1423 :``%b``: basename of the exporting repository
1425 1424
1426 1425 Returns 0 on success.
1427 1426 """
1428 1427 ctx = scmutil.revsingle(repo, opts.get('rev'))
1429 1428 m = scmutil.match(ctx, (file1,) + pats, opts)
1430 1429
1431 1430 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1432 1431
1433 1432 @command('^clone',
1434 1433 [('U', 'noupdate', None, _('the clone will include an empty working '
1435 1434 'directory (only a repository)')),
1436 1435 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1437 1436 _('REV')),
1438 1437 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1439 1438 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1440 1439 ('', 'pull', None, _('use pull protocol to copy metadata')),
1441 1440 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1442 1441 ] + remoteopts,
1443 1442 _('[OPTION]... SOURCE [DEST]'),
1444 1443 norepo=True)
1445 1444 def clone(ui, source, dest=None, **opts):
1446 1445 """make a copy of an existing repository
1447 1446
1448 1447 Create a copy of an existing repository in a new directory.
1449 1448
1450 1449 If no destination directory name is specified, it defaults to the
1451 1450 basename of the source.
1452 1451
1453 1452 The location of the source is added to the new repository's
1454 1453 ``.hg/hgrc`` file, as the default to be used for future pulls.
1455 1454
1456 1455 Only local paths and ``ssh://`` URLs are supported as
1457 1456 destinations. For ``ssh://`` destinations, no working directory or
1458 1457 ``.hg/hgrc`` will be created on the remote side.
1459 1458
1460 1459 If the source repository has a bookmark called '@' set, that
1461 1460 revision will be checked out in the new repository by default.
1462 1461
1463 1462 To check out a particular version, use -u/--update, or
1464 1463 -U/--noupdate to create a clone with no working directory.
1465 1464
1466 1465 To pull only a subset of changesets, specify one or more revisions
1467 1466 identifiers with -r/--rev or branches with -b/--branch. The
1468 1467 resulting clone will contain only the specified changesets and
1469 1468 their ancestors. These options (or 'clone src#rev dest') imply
1470 1469 --pull, even for local source repositories.
1471 1470
1472 1471 .. note::
1473 1472
1474 1473 Specifying a tag will include the tagged changeset but not the
1475 1474 changeset containing the tag.
1476 1475
1477 1476 .. container:: verbose
1478 1477
1479 1478 For efficiency, hardlinks are used for cloning whenever the
1480 1479 source and destination are on the same filesystem (note this
1481 1480 applies only to the repository data, not to the working
1482 1481 directory). Some filesystems, such as AFS, implement hardlinking
1483 1482 incorrectly, but do not report errors. In these cases, use the
1484 1483 --pull option to avoid hardlinking.
1485 1484
1486 1485 In some cases, you can clone repositories and the working
1487 1486 directory using full hardlinks with ::
1488 1487
1489 1488 $ cp -al REPO REPOCLONE
1490 1489
1491 1490 This is the fastest way to clone, but it is not always safe. The
1492 1491 operation is not atomic (making sure REPO is not modified during
1493 1492 the operation is up to you) and you have to make sure your
1494 1493 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1495 1494 so). Also, this is not compatible with certain extensions that
1496 1495 place their metadata under the .hg directory, such as mq.
1497 1496
1498 1497 Mercurial will update the working directory to the first applicable
1499 1498 revision from this list:
1500 1499
1501 1500 a) null if -U or the source repository has no changesets
1502 1501 b) if -u . and the source repository is local, the first parent of
1503 1502 the source repository's working directory
1504 1503 c) the changeset specified with -u (if a branch name, this means the
1505 1504 latest head of that branch)
1506 1505 d) the changeset specified with -r
1507 1506 e) the tipmost head specified with -b
1508 1507 f) the tipmost head specified with the url#branch source syntax
1509 1508 g) the revision marked with the '@' bookmark, if present
1510 1509 h) the tipmost head of the default branch
1511 1510 i) tip
1512 1511
1513 1512 When cloning from servers that support it, Mercurial may fetch
1514 1513 pre-generated data from a server-advertised URL. When this is done,
1515 1514 hooks operating on incoming changesets and changegroups may fire twice,
1516 1515 once for the bundle fetched from the URL and another for any additional
1517 1516 data not fetched from this URL. In addition, if an error occurs, the
1518 1517 repository may be rolled back to a partial clone. This behavior may
1519 1518 change in future releases. See :hg:`help -e clonebundles` for more.
1520 1519
1521 1520 Examples:
1522 1521
1523 1522 - clone a remote repository to a new directory named hg/::
1524 1523
1525 1524 hg clone https://www.mercurial-scm.org/repo/hg/
1526 1525
1527 1526 - create a lightweight local clone::
1528 1527
1529 1528 hg clone project/ project-feature/
1530 1529
1531 1530 - clone from an absolute path on an ssh server (note double-slash)::
1532 1531
1533 1532 hg clone ssh://user@server//home/projects/alpha/
1534 1533
1535 1534 - do a high-speed clone over a LAN while checking out a
1536 1535 specified version::
1537 1536
1538 1537 hg clone --uncompressed http://server/repo -u 1.5
1539 1538
1540 1539 - create a repository without changesets after a particular revision::
1541 1540
1542 1541 hg clone -r 04e544 experimental/ good/
1543 1542
1544 1543 - clone (and track) a particular named branch::
1545 1544
1546 1545 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1547 1546
1548 1547 See :hg:`help urls` for details on specifying URLs.
1549 1548
1550 1549 Returns 0 on success.
1551 1550 """
1552 1551 if opts.get('noupdate') and opts.get('updaterev'):
1553 1552 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1554 1553
1555 1554 r = hg.clone(ui, opts, source, dest,
1556 1555 pull=opts.get('pull'),
1557 1556 stream=opts.get('uncompressed'),
1558 1557 rev=opts.get('rev'),
1559 1558 update=opts.get('updaterev') or not opts.get('noupdate'),
1560 1559 branch=opts.get('branch'),
1561 1560 shareopts=opts.get('shareopts'))
1562 1561
1563 1562 return r is None
1564 1563
1565 1564 @command('^commit|ci',
1566 1565 [('A', 'addremove', None,
1567 1566 _('mark new/missing files as added/removed before committing')),
1568 1567 ('', 'close-branch', None,
1569 1568 _('mark a branch head as closed')),
1570 1569 ('', 'amend', None, _('amend the parent of the working directory')),
1571 1570 ('s', 'secret', None, _('use the secret phase for committing')),
1572 1571 ('e', 'edit', None, _('invoke editor on commit messages')),
1573 1572 ('i', 'interactive', None, _('use interactive mode')),
1574 1573 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1575 1574 _('[OPTION]... [FILE]...'),
1576 1575 inferrepo=True)
1577 1576 def commit(ui, repo, *pats, **opts):
1578 1577 """commit the specified files or all outstanding changes
1579 1578
1580 1579 Commit changes to the given files into the repository. Unlike a
1581 1580 centralized SCM, this operation is a local operation. See
1582 1581 :hg:`push` for a way to actively distribute your changes.
1583 1582
1584 1583 If a list of files is omitted, all changes reported by :hg:`status`
1585 1584 will be committed.
1586 1585
1587 1586 If you are committing the result of a merge, do not provide any
1588 1587 filenames or -I/-X filters.
1589 1588
1590 1589 If no commit message is specified, Mercurial starts your
1591 1590 configured editor where you can enter a message. In case your
1592 1591 commit fails, you will find a backup of your message in
1593 1592 ``.hg/last-message.txt``.
1594 1593
1595 1594 The --close-branch flag can be used to mark the current branch
1596 1595 head closed. When all heads of a branch are closed, the branch
1597 1596 will be considered closed and no longer listed.
1598 1597
1599 1598 The --amend flag can be used to amend the parent of the
1600 1599 working directory with a new commit that contains the changes
1601 1600 in the parent in addition to those currently reported by :hg:`status`,
1602 1601 if there are any. The old commit is stored in a backup bundle in
1603 1602 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1604 1603 on how to restore it).
1605 1604
1606 1605 Message, user and date are taken from the amended commit unless
1607 1606 specified. When a message isn't specified on the command line,
1608 1607 the editor will open with the message of the amended commit.
1609 1608
1610 1609 It is not possible to amend public changesets (see :hg:`help phases`)
1611 1610 or changesets that have children.
1612 1611
1613 1612 See :hg:`help dates` for a list of formats valid for -d/--date.
1614 1613
1615 1614 Returns 0 on success, 1 if nothing changed.
1616 1615
1617 1616 .. container:: verbose
1618 1617
1619 1618 Examples:
1620 1619
1621 1620 - commit all files ending in .py::
1622 1621
1623 1622 hg commit --include "set:**.py"
1624 1623
1625 1624 - commit all non-binary files::
1626 1625
1627 1626 hg commit --exclude "set:binary()"
1628 1627
1629 1628 - amend the current commit and set the date to now::
1630 1629
1631 1630 hg commit --amend --date now
1632 1631 """
1633 1632 wlock = lock = None
1634 1633 try:
1635 1634 wlock = repo.wlock()
1636 1635 lock = repo.lock()
1637 1636 return _docommit(ui, repo, *pats, **opts)
1638 1637 finally:
1639 1638 release(lock, wlock)
1640 1639
1641 1640 def _docommit(ui, repo, *pats, **opts):
1642 1641 if opts.get('interactive'):
1643 1642 opts.pop('interactive')
1644 1643 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1645 1644 cmdutil.recordfilter, *pats, **opts)
1646 1645 # ret can be 0 (no changes to record) or the value returned by
1647 1646 # commit(), 1 if nothing changed or None on success.
1648 1647 return 1 if ret == 0 else ret
1649 1648
1650 1649 if opts.get('subrepos'):
1651 1650 if opts.get('amend'):
1652 1651 raise error.Abort(_('cannot amend with --subrepos'))
1653 1652 # Let --subrepos on the command line override config setting.
1654 1653 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1655 1654
1656 1655 cmdutil.checkunfinished(repo, commit=True)
1657 1656
1658 1657 branch = repo[None].branch()
1659 1658 bheads = repo.branchheads(branch)
1660 1659
1661 1660 extra = {}
1662 1661 if opts.get('close_branch'):
1663 1662 extra['close'] = 1
1664 1663
1665 1664 if not bheads:
1666 1665 raise error.Abort(_('can only close branch heads'))
1667 1666 elif opts.get('amend'):
1668 1667 if repo[None].parents()[0].p1().branch() != branch and \
1669 1668 repo[None].parents()[0].p2().branch() != branch:
1670 1669 raise error.Abort(_('can only close branch heads'))
1671 1670
1672 1671 if opts.get('amend'):
1673 1672 if ui.configbool('ui', 'commitsubrepos'):
1674 1673 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1675 1674
1676 1675 old = repo['.']
1677 1676 if not old.mutable():
1678 1677 raise error.Abort(_('cannot amend public changesets'))
1679 1678 if len(repo[None].parents()) > 1:
1680 1679 raise error.Abort(_('cannot amend while merging'))
1681 1680 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1682 1681 if not allowunstable and old.children():
1683 1682 raise error.Abort(_('cannot amend changeset with children'))
1684 1683
1685 1684 # Currently histedit gets confused if an amend happens while histedit
1686 1685 # is in progress. Since we have a checkunfinished command, we are
1687 1686 # temporarily honoring it.
1688 1687 #
1689 1688 # Note: eventually this guard will be removed. Please do not expect
1690 1689 # this behavior to remain.
1691 1690 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1692 1691 cmdutil.checkunfinished(repo)
1693 1692
1694 1693 # commitfunc is used only for temporary amend commit by cmdutil.amend
1695 1694 def commitfunc(ui, repo, message, match, opts):
1696 1695 return repo.commit(message,
1697 1696 opts.get('user') or old.user(),
1698 1697 opts.get('date') or old.date(),
1699 1698 match,
1700 1699 extra=extra)
1701 1700
1702 1701 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1703 1702 if node == old.node():
1704 1703 ui.status(_("nothing changed\n"))
1705 1704 return 1
1706 1705 else:
1707 1706 def commitfunc(ui, repo, message, match, opts):
1708 1707 backup = ui.backupconfig('phases', 'new-commit')
1709 1708 baseui = repo.baseui
1710 1709 basebackup = baseui.backupconfig('phases', 'new-commit')
1711 1710 try:
1712 1711 if opts.get('secret'):
1713 1712 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1714 1713 # Propagate to subrepos
1715 1714 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1716 1715
1717 1716 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1718 1717 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1719 1718 return repo.commit(message, opts.get('user'), opts.get('date'),
1720 1719 match,
1721 1720 editor=editor,
1722 1721 extra=extra)
1723 1722 finally:
1724 1723 ui.restoreconfig(backup)
1725 1724 repo.baseui.restoreconfig(basebackup)
1726 1725
1727 1726
1728 1727 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1729 1728
1730 1729 if not node:
1731 1730 stat = cmdutil.postcommitstatus(repo, pats, opts)
1732 1731 if stat[3]:
1733 1732 ui.status(_("nothing changed (%d missing files, see "
1734 1733 "'hg status')\n") % len(stat[3]))
1735 1734 else:
1736 1735 ui.status(_("nothing changed\n"))
1737 1736 return 1
1738 1737
1739 1738 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1740 1739
1741 1740 @command('config|showconfig|debugconfig',
1742 1741 [('u', 'untrusted', None, _('show untrusted configuration options')),
1743 1742 ('e', 'edit', None, _('edit user config')),
1744 1743 ('l', 'local', None, _('edit repository config')),
1745 1744 ('g', 'global', None, _('edit global config'))] + formatteropts,
1746 1745 _('[-u] [NAME]...'),
1747 1746 optionalrepo=True)
1748 1747 def config(ui, repo, *values, **opts):
1749 1748 """show combined config settings from all hgrc files
1750 1749
1751 1750 With no arguments, print names and values of all config items.
1752 1751
1753 1752 With one argument of the form section.name, print just the value
1754 1753 of that config item.
1755 1754
1756 1755 With multiple arguments, print names and values of all config
1757 1756 items with matching section names.
1758 1757
1759 1758 With --edit, start an editor on the user-level config file. With
1760 1759 --global, edit the system-wide config file. With --local, edit the
1761 1760 repository-level config file.
1762 1761
1763 1762 With --debug, the source (filename and line number) is printed
1764 1763 for each config item.
1765 1764
1766 1765 See :hg:`help config` for more information about config files.
1767 1766
1768 1767 Returns 0 on success, 1 if NAME does not exist.
1769 1768
1770 1769 """
1771 1770
1772 1771 if opts.get('edit') or opts.get('local') or opts.get('global'):
1773 1772 if opts.get('local') and opts.get('global'):
1774 1773 raise error.Abort(_("can't use --local and --global together"))
1775 1774
1776 1775 if opts.get('local'):
1777 1776 if not repo:
1778 1777 raise error.Abort(_("can't use --local outside a repository"))
1779 1778 paths = [repo.join('hgrc')]
1780 1779 elif opts.get('global'):
1781 1780 paths = scmutil.systemrcpath()
1782 1781 else:
1783 1782 paths = scmutil.userrcpath()
1784 1783
1785 1784 for f in paths:
1786 1785 if os.path.exists(f):
1787 1786 break
1788 1787 else:
1789 1788 if opts.get('global'):
1790 1789 samplehgrc = uimod.samplehgrcs['global']
1791 1790 elif opts.get('local'):
1792 1791 samplehgrc = uimod.samplehgrcs['local']
1793 1792 else:
1794 1793 samplehgrc = uimod.samplehgrcs['user']
1795 1794
1796 1795 f = paths[0]
1797 1796 fp = open(f, "w")
1798 1797 fp.write(samplehgrc)
1799 1798 fp.close()
1800 1799
1801 1800 editor = ui.geteditor()
1802 1801 ui.system("%s \"%s\"" % (editor, f),
1803 1802 onerr=error.Abort, errprefix=_("edit failed"))
1804 1803 return
1805 1804
1806 1805 fm = ui.formatter('config', opts)
1807 1806 for f in scmutil.rcpath():
1808 1807 ui.debug('read config from: %s\n' % f)
1809 1808 untrusted = bool(opts.get('untrusted'))
1810 1809 if values:
1811 1810 sections = [v for v in values if '.' not in v]
1812 1811 items = [v for v in values if '.' in v]
1813 1812 if len(items) > 1 or items and sections:
1814 1813 raise error.Abort(_('only one config item permitted'))
1815 1814 matched = False
1816 1815 for section, name, value in ui.walkconfig(untrusted=untrusted):
1817 1816 value = str(value)
1818 1817 if fm.isplain():
1819 1818 value = value.replace('\n', '\\n')
1820 1819 entryname = section + '.' + name
1821 1820 if values:
1822 1821 for v in values:
1823 1822 if v == section:
1824 1823 fm.startitem()
1825 1824 fm.condwrite(ui.debugflag, 'source', '%s: ',
1826 1825 ui.configsource(section, name, untrusted))
1827 1826 fm.write('name value', '%s=%s\n', entryname, value)
1828 1827 matched = True
1829 1828 elif v == entryname:
1830 1829 fm.startitem()
1831 1830 fm.condwrite(ui.debugflag, 'source', '%s: ',
1832 1831 ui.configsource(section, name, untrusted))
1833 1832 fm.write('value', '%s\n', value)
1834 1833 fm.data(name=entryname)
1835 1834 matched = True
1836 1835 else:
1837 1836 fm.startitem()
1838 1837 fm.condwrite(ui.debugflag, 'source', '%s: ',
1839 1838 ui.configsource(section, name, untrusted))
1840 1839 fm.write('name value', '%s=%s\n', entryname, value)
1841 1840 matched = True
1842 1841 fm.end()
1843 1842 if matched:
1844 1843 return 0
1845 1844 return 1
1846 1845
1847 1846 @command('copy|cp',
1848 1847 [('A', 'after', None, _('record a copy that has already occurred')),
1849 1848 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1850 1849 ] + walkopts + dryrunopts,
1851 1850 _('[OPTION]... [SOURCE]... DEST'))
1852 1851 def copy(ui, repo, *pats, **opts):
1853 1852 """mark files as copied for the next commit
1854 1853
1855 1854 Mark dest as having copies of source files. If dest is a
1856 1855 directory, copies are put in that directory. If dest is a file,
1857 1856 the source must be a single file.
1858 1857
1859 1858 By default, this command copies the contents of files as they
1860 1859 exist in the working directory. If invoked with -A/--after, the
1861 1860 operation is recorded, but no copying is performed.
1862 1861
1863 1862 This command takes effect with the next commit. To undo a copy
1864 1863 before that, see :hg:`revert`.
1865 1864
1866 1865 Returns 0 on success, 1 if errors are encountered.
1867 1866 """
1868 1867 with repo.wlock(False):
1869 1868 return cmdutil.copy(ui, repo, pats, opts)
1870 1869
1871 1870 @command('debugdag',
1872 1871 [('t', 'tags', None, _('use tags as labels')),
1873 1872 ('b', 'branches', None, _('annotate with branch names')),
1874 1873 ('', 'dots', None, _('use dots for runs')),
1875 1874 ('s', 'spaces', None, _('separate elements by spaces'))],
1876 1875 _('[OPTION]... [FILE [REV]...]'),
1877 1876 optionalrepo=True)
1878 1877 def debugdag(ui, repo, file_=None, *revs, **opts):
1879 1878 """format the changelog or an index DAG as a concise textual description
1880 1879
1881 1880 If you pass a revlog index, the revlog's DAG is emitted. If you list
1882 1881 revision numbers, they get labeled in the output as rN.
1883 1882
1884 1883 Otherwise, the changelog DAG of the current repo is emitted.
1885 1884 """
1886 1885 spaces = opts.get('spaces')
1887 1886 dots = opts.get('dots')
1888 1887 if file_:
1889 1888 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1890 1889 revs = set((int(r) for r in revs))
1891 1890 def events():
1892 1891 for r in rlog:
1893 1892 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1894 1893 if p != -1))
1895 1894 if r in revs:
1896 1895 yield 'l', (r, "r%i" % r)
1897 1896 elif repo:
1898 1897 cl = repo.changelog
1899 1898 tags = opts.get('tags')
1900 1899 branches = opts.get('branches')
1901 1900 if tags:
1902 1901 labels = {}
1903 1902 for l, n in repo.tags().items():
1904 1903 labels.setdefault(cl.rev(n), []).append(l)
1905 1904 def events():
1906 1905 b = "default"
1907 1906 for r in cl:
1908 1907 if branches:
1909 1908 newb = cl.read(cl.node(r))[5]['branch']
1910 1909 if newb != b:
1911 1910 yield 'a', newb
1912 1911 b = newb
1913 1912 yield 'n', (r, list(p for p in cl.parentrevs(r)
1914 1913 if p != -1))
1915 1914 if tags:
1916 1915 ls = labels.get(r)
1917 1916 if ls:
1918 1917 for l in ls:
1919 1918 yield 'l', (r, l)
1920 1919 else:
1921 1920 raise error.Abort(_('need repo for changelog dag'))
1922 1921
1923 1922 for line in dagparser.dagtextlines(events(),
1924 1923 addspaces=spaces,
1925 1924 wraplabels=True,
1926 1925 wrapannotations=True,
1927 1926 wrapnonlinear=dots,
1928 1927 usedots=dots,
1929 1928 maxlinewidth=70):
1930 1929 ui.write(line)
1931 1930 ui.write("\n")
1932 1931
1933 1932 @command('debugdata', debugrevlogopts, _('-c|-m|FILE REV'))
1934 1933 def debugdata(ui, repo, file_, rev=None, **opts):
1935 1934 """dump the contents of a data file revision"""
1936 1935 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
1937 1936 if rev is not None:
1938 1937 raise error.CommandError('debugdata', _('invalid arguments'))
1939 1938 file_, rev = None, file_
1940 1939 elif rev is None:
1941 1940 raise error.CommandError('debugdata', _('invalid arguments'))
1942 1941 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1943 1942 try:
1944 1943 ui.write(r.revision(r.lookup(rev)))
1945 1944 except KeyError:
1946 1945 raise error.Abort(_('invalid revision identifier %s') % rev)
1947 1946
1948 1947 @command('debugdate',
1949 1948 [('e', 'extended', None, _('try extended date formats'))],
1950 1949 _('[-e] DATE [RANGE]'),
1951 1950 norepo=True, optionalrepo=True)
1952 1951 def debugdate(ui, date, range=None, **opts):
1953 1952 """parse and display a date"""
1954 1953 if opts["extended"]:
1955 1954 d = util.parsedate(date, util.extendeddateformats)
1956 1955 else:
1957 1956 d = util.parsedate(date)
1958 1957 ui.write(("internal: %s %s\n") % d)
1959 1958 ui.write(("standard: %s\n") % util.datestr(d))
1960 1959 if range:
1961 1960 m = util.matchdate(range)
1962 1961 ui.write(("match: %s\n") % m(d[0]))
1963 1962
1964 1963 @command('debugdiscovery',
1965 1964 [('', 'old', None, _('use old-style discovery')),
1966 1965 ('', 'nonheads', None,
1967 1966 _('use old-style discovery with non-heads included')),
1968 1967 ] + remoteopts,
1969 1968 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1970 1969 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1971 1970 """runs the changeset discovery protocol in isolation"""
1972 1971 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1973 1972 opts.get('branch'))
1974 1973 remote = hg.peer(repo, opts, remoteurl)
1975 1974 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1976 1975
1977 1976 # make sure tests are repeatable
1978 1977 random.seed(12323)
1979 1978
1980 1979 def doit(localheads, remoteheads, remote=remote):
1981 1980 if opts.get('old'):
1982 1981 if localheads:
1983 1982 raise error.Abort('cannot use localheads with old style '
1984 1983 'discovery')
1985 1984 if not util.safehasattr(remote, 'branches'):
1986 1985 # enable in-client legacy support
1987 1986 remote = localrepo.locallegacypeer(remote.local())
1988 1987 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1989 1988 force=True)
1990 1989 common = set(common)
1991 1990 if not opts.get('nonheads'):
1992 1991 ui.write(("unpruned common: %s\n") %
1993 1992 " ".join(sorted(short(n) for n in common)))
1994 1993 dag = dagutil.revlogdag(repo.changelog)
1995 1994 all = dag.ancestorset(dag.internalizeall(common))
1996 1995 common = dag.externalizeall(dag.headsetofconnecteds(all))
1997 1996 else:
1998 1997 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1999 1998 common = set(common)
2000 1999 rheads = set(hds)
2001 2000 lheads = set(repo.heads())
2002 2001 ui.write(("common heads: %s\n") %
2003 2002 " ".join(sorted(short(n) for n in common)))
2004 2003 if lheads <= common:
2005 2004 ui.write(("local is subset\n"))
2006 2005 elif rheads <= common:
2007 2006 ui.write(("remote is subset\n"))
2008 2007
2009 2008 serverlogs = opts.get('serverlog')
2010 2009 if serverlogs:
2011 2010 for filename in serverlogs:
2012 2011 with open(filename, 'r') as logfile:
2013 2012 line = logfile.readline()
2014 2013 while line:
2015 2014 parts = line.strip().split(';')
2016 2015 op = parts[1]
2017 2016 if op == 'cg':
2018 2017 pass
2019 2018 elif op == 'cgss':
2020 2019 doit(parts[2].split(' '), parts[3].split(' '))
2021 2020 elif op == 'unb':
2022 2021 doit(parts[3].split(' '), parts[2].split(' '))
2023 2022 line = logfile.readline()
2024 2023 else:
2025 2024 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2026 2025 opts.get('remote_head'))
2027 2026 localrevs = opts.get('local_head')
2028 2027 doit(localrevs, remoterevs)
2029 2028
2030 2029 @command('debugextensions', formatteropts, [], norepo=True)
2031 2030 def debugextensions(ui, **opts):
2032 2031 '''show information about active extensions'''
2033 2032 exts = extensions.extensions(ui)
2034 2033 hgver = util.version()
2035 2034 fm = ui.formatter('debugextensions', opts)
2036 2035 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
2037 2036 isinternal = extensions.ismoduleinternal(extmod)
2038 2037 extsource = extmod.__file__
2039 2038 if isinternal:
2040 2039 exttestedwith = [] # never expose magic string to users
2041 2040 else:
2042 2041 exttestedwith = getattr(extmod, 'testedwith', '').split()
2043 2042 extbuglink = getattr(extmod, 'buglink', None)
2044 2043
2045 2044 fm.startitem()
2046 2045
2047 2046 if ui.quiet or ui.verbose:
2048 2047 fm.write('name', '%s\n', extname)
2049 2048 else:
2050 2049 fm.write('name', '%s', extname)
2051 2050 if isinternal or hgver in exttestedwith:
2052 2051 fm.plain('\n')
2053 2052 elif not exttestedwith:
2054 2053 fm.plain(_(' (untested!)\n'))
2055 2054 else:
2056 2055 lasttestedversion = exttestedwith[-1]
2057 2056 fm.plain(' (%s!)\n' % lasttestedversion)
2058 2057
2059 2058 fm.condwrite(ui.verbose and extsource, 'source',
2060 2059 _(' location: %s\n'), extsource or "")
2061 2060
2062 2061 if ui.verbose:
2063 2062 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
2064 2063 fm.data(bundled=isinternal)
2065 2064
2066 2065 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
2067 2066 _(' tested with: %s\n'),
2068 2067 fm.formatlist(exttestedwith, name='ver'))
2069 2068
2070 2069 fm.condwrite(ui.verbose and extbuglink, 'buglink',
2071 2070 _(' bug reporting: %s\n'), extbuglink or "")
2072 2071
2073 2072 fm.end()
2074 2073
2075 2074 @command('debugfileset',
2076 2075 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2077 2076 _('[-r REV] FILESPEC'))
2078 2077 def debugfileset(ui, repo, expr, **opts):
2079 2078 '''parse and apply a fileset specification'''
2080 2079 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2081 2080 if ui.verbose:
2082 2081 tree = fileset.parse(expr)
2083 2082 ui.note(fileset.prettyformat(tree), "\n")
2084 2083
2085 2084 for f in ctx.getfileset(expr):
2086 2085 ui.write("%s\n" % f)
2087 2086
2088 2087 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2089 2088 def debugfsinfo(ui, path="."):
2090 2089 """show information detected about current filesystem"""
2091 2090 util.writefile('.debugfsinfo', '')
2092 2091 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2093 2092 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2094 2093 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2095 2094 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
2096 2095 and 'yes' or 'no'))
2097 2096 os.unlink('.debugfsinfo')
2098 2097
2099 2098 @command('debuggetbundle',
2100 2099 [('H', 'head', [], _('id of head node'), _('ID')),
2101 2100 ('C', 'common', [], _('id of common node'), _('ID')),
2102 2101 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2103 2102 _('REPO FILE [-H|-C ID]...'),
2104 2103 norepo=True)
2105 2104 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2106 2105 """retrieves a bundle from a repo
2107 2106
2108 2107 Every ID must be a full-length hex node id string. Saves the bundle to the
2109 2108 given file.
2110 2109 """
2111 2110 repo = hg.peer(ui, opts, repopath)
2112 2111 if not repo.capable('getbundle'):
2113 2112 raise error.Abort("getbundle() not supported by target repository")
2114 2113 args = {}
2115 2114 if common:
2116 2115 args['common'] = [bin(s) for s in common]
2117 2116 if head:
2118 2117 args['heads'] = [bin(s) for s in head]
2119 2118 # TODO: get desired bundlecaps from command line.
2120 2119 args['bundlecaps'] = None
2121 2120 bundle = repo.getbundle('debug', **args)
2122 2121
2123 2122 bundletype = opts.get('type', 'bzip2').lower()
2124 2123 btypes = {'none': 'HG10UN',
2125 2124 'bzip2': 'HG10BZ',
2126 2125 'gzip': 'HG10GZ',
2127 2126 'bundle2': 'HG20'}
2128 2127 bundletype = btypes.get(bundletype)
2129 2128 if bundletype not in bundle2.bundletypes:
2130 2129 raise error.Abort(_('unknown bundle type specified with --type'))
2131 2130 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
2132 2131
2133 2132 @command('debugignore', [], '[FILE]')
2134 2133 def debugignore(ui, repo, *files, **opts):
2135 2134 """display the combined ignore pattern and information about ignored files
2136 2135
2137 2136 With no argument display the combined ignore pattern.
2138 2137
2139 2138 Given space separated file names, shows if the given file is ignored and
2140 2139 if so, show the ignore rule (file and line number) that matched it.
2141 2140 """
2142 2141 ignore = repo.dirstate._ignore
2143 2142 if not files:
2144 2143 # Show all the patterns
2145 2144 includepat = getattr(ignore, 'includepat', None)
2146 2145 if includepat is not None:
2147 2146 ui.write("%s\n" % includepat)
2148 2147 else:
2149 2148 raise error.Abort(_("no ignore patterns found"))
2150 2149 else:
2151 2150 for f in files:
2152 2151 nf = util.normpath(f)
2153 2152 ignored = None
2154 2153 ignoredata = None
2155 2154 if nf != '.':
2156 2155 if ignore(nf):
2157 2156 ignored = nf
2158 2157 ignoredata = repo.dirstate._ignorefileandline(nf)
2159 2158 else:
2160 2159 for p in util.finddirs(nf):
2161 2160 if ignore(p):
2162 2161 ignored = p
2163 2162 ignoredata = repo.dirstate._ignorefileandline(p)
2164 2163 break
2165 2164 if ignored:
2166 2165 if ignored == nf:
2167 2166 ui.write(_("%s is ignored\n") % f)
2168 2167 else:
2169 2168 ui.write(_("%s is ignored because of "
2170 2169 "containing folder %s\n")
2171 2170 % (f, ignored))
2172 2171 ignorefile, lineno, line = ignoredata
2173 2172 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
2174 2173 % (ignorefile, lineno, line))
2175 2174 else:
2176 2175 ui.write(_("%s is not ignored\n") % f)
2177 2176
2178 2177 @command('debugindex', debugrevlogopts +
2179 2178 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2180 2179 _('[-f FORMAT] -c|-m|FILE'),
2181 2180 optionalrepo=True)
2182 2181 def debugindex(ui, repo, file_=None, **opts):
2183 2182 """dump the contents of an index file"""
2184 2183 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2185 2184 format = opts.get('format', 0)
2186 2185 if format not in (0, 1):
2187 2186 raise error.Abort(_("unknown format %d") % format)
2188 2187
2189 2188 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2190 2189 if generaldelta:
2191 2190 basehdr = ' delta'
2192 2191 else:
2193 2192 basehdr = ' base'
2194 2193
2195 2194 if ui.debugflag:
2196 2195 shortfn = hex
2197 2196 else:
2198 2197 shortfn = short
2199 2198
2200 2199 # There might not be anything in r, so have a sane default
2201 2200 idlen = 12
2202 2201 for i in r:
2203 2202 idlen = len(shortfn(r.node(i)))
2204 2203 break
2205 2204
2206 2205 if format == 0:
2207 2206 ui.write((" rev offset length " + basehdr + " linkrev"
2208 2207 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2209 2208 elif format == 1:
2210 2209 ui.write((" rev flag offset length"
2211 2210 " size " + basehdr + " link p1 p2"
2212 2211 " %s\n") % "nodeid".rjust(idlen))
2213 2212
2214 2213 for i in r:
2215 2214 node = r.node(i)
2216 2215 if generaldelta:
2217 2216 base = r.deltaparent(i)
2218 2217 else:
2219 2218 base = r.chainbase(i)
2220 2219 if format == 0:
2221 2220 try:
2222 2221 pp = r.parents(node)
2223 2222 except Exception:
2224 2223 pp = [nullid, nullid]
2225 2224 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2226 2225 i, r.start(i), r.length(i), base, r.linkrev(i),
2227 2226 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2228 2227 elif format == 1:
2229 2228 pr = r.parentrevs(i)
2230 2229 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2231 2230 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2232 2231 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2233 2232
2234 2233 @command('debugindexdot', debugrevlogopts,
2235 2234 _('-c|-m|FILE'), optionalrepo=True)
2236 2235 def debugindexdot(ui, repo, file_=None, **opts):
2237 2236 """dump an index DAG as a graphviz dot file"""
2238 2237 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
2239 2238 ui.write(("digraph G {\n"))
2240 2239 for i in r:
2241 2240 node = r.node(i)
2242 2241 pp = r.parents(node)
2243 2242 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2244 2243 if pp[1] != nullid:
2245 2244 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2246 2245 ui.write("}\n")
2247 2246
2248 2247 @command('debugdeltachain',
2249 2248 debugrevlogopts + formatteropts,
2250 2249 _('-c|-m|FILE'),
2251 2250 optionalrepo=True)
2252 2251 def debugdeltachain(ui, repo, file_=None, **opts):
2253 2252 """dump information about delta chains in a revlog
2254 2253
2255 2254 Output can be templatized. Available template keywords are:
2256 2255
2257 2256 :``rev``: revision number
2258 2257 :``chainid``: delta chain identifier (numbered by unique base)
2259 2258 :``chainlen``: delta chain length to this revision
2260 2259 :``prevrev``: previous revision in delta chain
2261 2260 :``deltatype``: role of delta / how it was computed
2262 2261 :``compsize``: compressed size of revision
2263 2262 :``uncompsize``: uncompressed size of revision
2264 2263 :``chainsize``: total size of compressed revisions in chain
2265 2264 :``chainratio``: total chain size divided by uncompressed revision size
2266 2265 (new delta chains typically start at ratio 2.00)
2267 2266 :``lindist``: linear distance from base revision in delta chain to end
2268 2267 of this revision
2269 2268 :``extradist``: total size of revisions not part of this delta chain from
2270 2269 base of delta chain to end of this revision; a measurement
2271 2270 of how much extra data we need to read/seek across to read
2272 2271 the delta chain for this revision
2273 2272 :``extraratio``: extradist divided by chainsize; another representation of
2274 2273 how much unrelated data is needed to load this delta chain
2275 2274 """
2276 2275 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
2277 2276 index = r.index
2278 2277 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2279 2278
2280 2279 def revinfo(rev):
2281 2280 e = index[rev]
2282 2281 compsize = e[1]
2283 2282 uncompsize = e[2]
2284 2283 chainsize = 0
2285 2284
2286 2285 if generaldelta:
2287 2286 if e[3] == e[5]:
2288 2287 deltatype = 'p1'
2289 2288 elif e[3] == e[6]:
2290 2289 deltatype = 'p2'
2291 2290 elif e[3] == rev - 1:
2292 2291 deltatype = 'prev'
2293 2292 elif e[3] == rev:
2294 2293 deltatype = 'base'
2295 2294 else:
2296 2295 deltatype = 'other'
2297 2296 else:
2298 2297 if e[3] == rev:
2299 2298 deltatype = 'base'
2300 2299 else:
2301 2300 deltatype = 'prev'
2302 2301
2303 2302 chain = r._deltachain(rev)[0]
2304 2303 for iterrev in chain:
2305 2304 e = index[iterrev]
2306 2305 chainsize += e[1]
2307 2306
2308 2307 return compsize, uncompsize, deltatype, chain, chainsize
2309 2308
2310 2309 fm = ui.formatter('debugdeltachain', opts)
2311 2310
2312 2311 fm.plain(' rev chain# chainlen prev delta '
2313 2312 'size rawsize chainsize ratio lindist extradist '
2314 2313 'extraratio\n')
2315 2314
2316 2315 chainbases = {}
2317 2316 for rev in r:
2318 2317 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
2319 2318 chainbase = chain[0]
2320 2319 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
2321 2320 basestart = r.start(chainbase)
2322 2321 revstart = r.start(rev)
2323 2322 lineardist = revstart + comp - basestart
2324 2323 extradist = lineardist - chainsize
2325 2324 try:
2326 2325 prevrev = chain[-2]
2327 2326 except IndexError:
2328 2327 prevrev = -1
2329 2328
2330 2329 chainratio = float(chainsize) / float(uncomp)
2331 2330 extraratio = float(extradist) / float(chainsize)
2332 2331
2333 2332 fm.startitem()
2334 2333 fm.write('rev chainid chainlen prevrev deltatype compsize '
2335 2334 'uncompsize chainsize chainratio lindist extradist '
2336 2335 'extraratio',
2337 2336 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
2338 2337 rev, chainid, len(chain), prevrev, deltatype, comp,
2339 2338 uncomp, chainsize, chainratio, lineardist, extradist,
2340 2339 extraratio,
2341 2340 rev=rev, chainid=chainid, chainlen=len(chain),
2342 2341 prevrev=prevrev, deltatype=deltatype, compsize=comp,
2343 2342 uncompsize=uncomp, chainsize=chainsize,
2344 2343 chainratio=chainratio, lindist=lineardist,
2345 2344 extradist=extradist, extraratio=extraratio)
2346 2345
2347 2346 fm.end()
2348 2347
2349 2348 @command('debuginstall', [] + formatteropts, '', norepo=True)
2350 2349 def debuginstall(ui, **opts):
2351 2350 '''test Mercurial installation
2352 2351
2353 2352 Returns 0 on success.
2354 2353 '''
2355 2354
2356 2355 def writetemp(contents):
2357 2356 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2358 2357 f = os.fdopen(fd, "wb")
2359 2358 f.write(contents)
2360 2359 f.close()
2361 2360 return name
2362 2361
2363 2362 problems = 0
2364 2363
2365 2364 fm = ui.formatter('debuginstall', opts)
2366 2365 fm.startitem()
2367 2366
2368 2367 # encoding
2369 2368 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
2370 2369 err = None
2371 2370 try:
2372 2371 encoding.fromlocal("test")
2373 2372 except error.Abort as inst:
2374 2373 err = inst
2375 2374 problems += 1
2376 2375 fm.condwrite(err, 'encodingerror', _(" %s\n"
2377 2376 " (check that your locale is properly set)\n"), err)
2378 2377
2379 2378 # Python
2380 2379 fm.write('pythonexe', _("checking Python executable (%s)\n"),
2381 2380 sys.executable)
2382 2381 fm.write('pythonver', _("checking Python version (%s)\n"),
2383 2382 ("%s.%s.%s" % sys.version_info[:3]))
2384 2383 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
2385 2384 os.path.dirname(os.__file__))
2386 2385
2387 2386 security = set(sslutil.supportedprotocols)
2388 2387 if sslutil.hassni:
2389 2388 security.add('sni')
2390 2389
2391 2390 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
2392 2391 fm.formatlist(sorted(security), name='protocol',
2393 2392 fmt='%s', sep=','))
2394 2393
2395 2394 # These are warnings, not errors. So don't increment problem count. This
2396 2395 # may change in the future.
2397 2396 if 'tls1.2' not in security:
2398 2397 fm.plain(_(' TLS 1.2 not supported by Python install; '
2399 2398 'network connections lack modern security\n'))
2400 2399 if 'sni' not in security:
2401 2400 fm.plain(_(' SNI not supported by Python install; may have '
2402 2401 'connectivity issues with some servers\n'))
2403 2402
2404 2403 # TODO print CA cert info
2405 2404
2406 2405 # hg version
2407 2406 hgver = util.version()
2408 2407 fm.write('hgver', _("checking Mercurial version (%s)\n"),
2409 2408 hgver.split('+')[0])
2410 2409 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
2411 2410 '+'.join(hgver.split('+')[1:]))
2412 2411
2413 2412 # compiled modules
2414 2413 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
2415 2414 policy.policy)
2416 2415 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
2417 2416 os.path.dirname(__file__))
2418 2417
2419 2418 err = None
2420 2419 try:
2421 2420 from . import (
2422 2421 base85,
2423 2422 bdiff,
2424 2423 mpatch,
2425 2424 osutil,
2426 2425 )
2427 2426 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2428 2427 except Exception as inst:
2429 2428 err = inst
2430 2429 problems += 1
2431 2430 fm.condwrite(err, 'extensionserror', " %s\n", err)
2432 2431
2433 2432 compengines = util.compengines._engines.values()
2434 2433 fm.write('compengines', _('checking registered compression engines (%s)\n'),
2435 2434 fm.formatlist(sorted(e.name() for e in compengines),
2436 2435 name='compengine', fmt='%s', sep=', '))
2437 2436 fm.write('compenginesavail', _('checking available compression engines '
2438 2437 '(%s)\n'),
2439 2438 fm.formatlist(sorted(e.name() for e in compengines
2440 2439 if e.available()),
2441 2440 name='compengine', fmt='%s', sep=', '))
2442 2441
2443 2442 # templates
2444 2443 p = templater.templatepaths()
2445 2444 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
2446 2445 fm.condwrite(not p, '', _(" no template directories found\n"))
2447 2446 if p:
2448 2447 m = templater.templatepath("map-cmdline.default")
2449 2448 if m:
2450 2449 # template found, check if it is working
2451 2450 err = None
2452 2451 try:
2453 2452 templater.templater.frommapfile(m)
2454 2453 except Exception as inst:
2455 2454 err = inst
2456 2455 p = None
2457 2456 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
2458 2457 else:
2459 2458 p = None
2460 2459 fm.condwrite(p, 'defaulttemplate',
2461 2460 _("checking default template (%s)\n"), m)
2462 2461 fm.condwrite(not m, 'defaulttemplatenotfound',
2463 2462 _(" template '%s' not found\n"), "default")
2464 2463 if not p:
2465 2464 problems += 1
2466 2465 fm.condwrite(not p, '',
2467 2466 _(" (templates seem to have been installed incorrectly)\n"))
2468 2467
2469 2468 # editor
2470 2469 editor = ui.geteditor()
2471 2470 editor = util.expandpath(editor)
2472 2471 fm.write('editor', _("checking commit editor... (%s)\n"), editor)
2473 2472 cmdpath = util.findexe(shlex.split(editor)[0])
2474 2473 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
2475 2474 _(" No commit editor set and can't find %s in PATH\n"
2476 2475 " (specify a commit editor in your configuration"
2477 2476 " file)\n"), not cmdpath and editor == 'vi' and editor)
2478 2477 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
2479 2478 _(" Can't find editor '%s' in PATH\n"
2480 2479 " (specify a commit editor in your configuration"
2481 2480 " file)\n"), not cmdpath and editor)
2482 2481 if not cmdpath and editor != 'vi':
2483 2482 problems += 1
2484 2483
2485 2484 # check username
2486 2485 username = None
2487 2486 err = None
2488 2487 try:
2489 2488 username = ui.username()
2490 2489 except error.Abort as e:
2491 2490 err = e
2492 2491 problems += 1
2493 2492
2494 2493 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
2495 2494 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
2496 2495 " (specify a username in your configuration file)\n"), err)
2497 2496
2498 2497 fm.condwrite(not problems, '',
2499 2498 _("no problems detected\n"))
2500 2499 if not problems:
2501 2500 fm.data(problems=problems)
2502 2501 fm.condwrite(problems, 'problems',
2503 2502 _("%d problems detected,"
2504 2503 " please check your install!\n"), problems)
2505 2504 fm.end()
2506 2505
2507 2506 return problems
2508 2507
2509 2508 @command('debugknown', [], _('REPO ID...'), norepo=True)
2510 2509 def debugknown(ui, repopath, *ids, **opts):
2511 2510 """test whether node ids are known to a repo
2512 2511
2513 2512 Every ID must be a full-length hex node id string. Returns a list of 0s
2514 2513 and 1s indicating unknown/known.
2515 2514 """
2516 2515 repo = hg.peer(ui, opts, repopath)
2517 2516 if not repo.capable('known'):
2518 2517 raise error.Abort("known() not supported by target repository")
2519 2518 flags = repo.known([bin(s) for s in ids])
2520 2519 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2521 2520
2522 2521 @command('debuglabelcomplete', [], _('LABEL...'))
2523 2522 def debuglabelcomplete(ui, repo, *args):
2524 2523 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2525 2524 debugnamecomplete(ui, repo, *args)
2526 2525
2527 2526 @command('debugmergestate', [], '')
2528 2527 def debugmergestate(ui, repo, *args):
2529 2528 """print merge state
2530 2529
2531 2530 Use --verbose to print out information about whether v1 or v2 merge state
2532 2531 was chosen."""
2533 2532 def _hashornull(h):
2534 2533 if h == nullhex:
2535 2534 return 'null'
2536 2535 else:
2537 2536 return h
2538 2537
2539 2538 def printrecords(version):
2540 2539 ui.write(('* version %s records\n') % version)
2541 2540 if version == 1:
2542 2541 records = v1records
2543 2542 else:
2544 2543 records = v2records
2545 2544
2546 2545 for rtype, record in records:
2547 2546 # pretty print some record types
2548 2547 if rtype == 'L':
2549 2548 ui.write(('local: %s\n') % record)
2550 2549 elif rtype == 'O':
2551 2550 ui.write(('other: %s\n') % record)
2552 2551 elif rtype == 'm':
2553 2552 driver, mdstate = record.split('\0', 1)
2554 2553 ui.write(('merge driver: %s (state "%s")\n')
2555 2554 % (driver, mdstate))
2556 2555 elif rtype in 'FDC':
2557 2556 r = record.split('\0')
2558 2557 f, state, hash, lfile, afile, anode, ofile = r[0:7]
2559 2558 if version == 1:
2560 2559 onode = 'not stored in v1 format'
2561 2560 flags = r[7]
2562 2561 else:
2563 2562 onode, flags = r[7:9]
2564 2563 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
2565 2564 % (f, rtype, state, _hashornull(hash)))
2566 2565 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
2567 2566 ui.write((' ancestor path: %s (node %s)\n')
2568 2567 % (afile, _hashornull(anode)))
2569 2568 ui.write((' other path: %s (node %s)\n')
2570 2569 % (ofile, _hashornull(onode)))
2571 2570 elif rtype == 'f':
2572 2571 filename, rawextras = record.split('\0', 1)
2573 2572 extras = rawextras.split('\0')
2574 2573 i = 0
2575 2574 extrastrings = []
2576 2575 while i < len(extras):
2577 2576 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
2578 2577 i += 2
2579 2578
2580 2579 ui.write(('file extras: %s (%s)\n')
2581 2580 % (filename, ', '.join(extrastrings)))
2582 2581 elif rtype == 'l':
2583 2582 labels = record.split('\0', 2)
2584 2583 labels = [l for l in labels if len(l) > 0]
2585 2584 ui.write(('labels:\n'))
2586 2585 ui.write((' local: %s\n' % labels[0]))
2587 2586 ui.write((' other: %s\n' % labels[1]))
2588 2587 if len(labels) > 2:
2589 2588 ui.write((' base: %s\n' % labels[2]))
2590 2589 else:
2591 2590 ui.write(('unrecognized entry: %s\t%s\n')
2592 2591 % (rtype, record.replace('\0', '\t')))
2593 2592
2594 2593 # Avoid mergestate.read() since it may raise an exception for unsupported
2595 2594 # merge state records. We shouldn't be doing this, but this is OK since this
2596 2595 # command is pretty low-level.
2597 2596 ms = mergemod.mergestate(repo)
2598 2597
2599 2598 # sort so that reasonable information is on top
2600 2599 v1records = ms._readrecordsv1()
2601 2600 v2records = ms._readrecordsv2()
2602 2601 order = 'LOml'
2603 2602 def key(r):
2604 2603 idx = order.find(r[0])
2605 2604 if idx == -1:
2606 2605 return (1, r[1])
2607 2606 else:
2608 2607 return (0, idx)
2609 2608 v1records.sort(key=key)
2610 2609 v2records.sort(key=key)
2611 2610
2612 2611 if not v1records and not v2records:
2613 2612 ui.write(('no merge state found\n'))
2614 2613 elif not v2records:
2615 2614 ui.note(('no version 2 merge state\n'))
2616 2615 printrecords(1)
2617 2616 elif ms._v1v2match(v1records, v2records):
2618 2617 ui.note(('v1 and v2 states match: using v2\n'))
2619 2618 printrecords(2)
2620 2619 else:
2621 2620 ui.note(('v1 and v2 states mismatch: using v1\n'))
2622 2621 printrecords(1)
2623 2622 if ui.verbose:
2624 2623 printrecords(2)
2625 2624
2626 2625 @command('debugnamecomplete', [], _('NAME...'))
2627 2626 def debugnamecomplete(ui, repo, *args):
2628 2627 '''complete "names" - tags, open branch names, bookmark names'''
2629 2628
2630 2629 names = set()
2631 2630 # since we previously only listed open branches, we will handle that
2632 2631 # specially (after this for loop)
2633 2632 for name, ns in repo.names.iteritems():
2634 2633 if name != 'branches':
2635 2634 names.update(ns.listnames(repo))
2636 2635 names.update(tag for (tag, heads, tip, closed)
2637 2636 in repo.branchmap().iterbranches() if not closed)
2638 2637 completions = set()
2639 2638 if not args:
2640 2639 args = ['']
2641 2640 for a in args:
2642 2641 completions.update(n for n in names if n.startswith(a))
2643 2642 ui.write('\n'.join(sorted(completions)))
2644 2643 ui.write('\n')
2645 2644
2646 2645 @command('debuglocks',
2647 2646 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2648 2647 ('W', 'force-wlock', None,
2649 2648 _('free the working state lock (DANGEROUS)'))],
2650 2649 _('[OPTION]...'))
2651 2650 def debuglocks(ui, repo, **opts):
2652 2651 """show or modify state of locks
2653 2652
2654 2653 By default, this command will show which locks are held. This
2655 2654 includes the user and process holding the lock, the amount of time
2656 2655 the lock has been held, and the machine name where the process is
2657 2656 running if it's not local.
2658 2657
2659 2658 Locks protect the integrity of Mercurial's data, so should be
2660 2659 treated with care. System crashes or other interruptions may cause
2661 2660 locks to not be properly released, though Mercurial will usually
2662 2661 detect and remove such stale locks automatically.
2663 2662
2664 2663 However, detecting stale locks may not always be possible (for
2665 2664 instance, on a shared filesystem). Removing locks may also be
2666 2665 blocked by filesystem permissions.
2667 2666
2668 2667 Returns 0 if no locks are held.
2669 2668
2670 2669 """
2671 2670
2672 2671 if opts.get('force_lock'):
2673 2672 repo.svfs.unlink('lock')
2674 2673 if opts.get('force_wlock'):
2675 2674 repo.vfs.unlink('wlock')
2676 2675 if opts.get('force_lock') or opts.get('force_lock'):
2677 2676 return 0
2678 2677
2679 2678 now = time.time()
2680 2679 held = 0
2681 2680
2682 2681 def report(vfs, name, method):
2683 2682 # this causes stale locks to get reaped for more accurate reporting
2684 2683 try:
2685 2684 l = method(False)
2686 2685 except error.LockHeld:
2687 2686 l = None
2688 2687
2689 2688 if l:
2690 2689 l.release()
2691 2690 else:
2692 2691 try:
2693 2692 stat = vfs.lstat(name)
2694 2693 age = now - stat.st_mtime
2695 2694 user = util.username(stat.st_uid)
2696 2695 locker = vfs.readlock(name)
2697 2696 if ":" in locker:
2698 2697 host, pid = locker.split(':')
2699 2698 if host == socket.gethostname():
2700 2699 locker = 'user %s, process %s' % (user, pid)
2701 2700 else:
2702 2701 locker = 'user %s, process %s, host %s' \
2703 2702 % (user, pid, host)
2704 2703 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
2705 2704 return 1
2706 2705 except OSError as e:
2707 2706 if e.errno != errno.ENOENT:
2708 2707 raise
2709 2708
2710 2709 ui.write(("%-6s free\n") % (name + ":"))
2711 2710 return 0
2712 2711
2713 2712 held += report(repo.svfs, "lock", repo.lock)
2714 2713 held += report(repo.vfs, "wlock", repo.wlock)
2715 2714
2716 2715 return held
2717 2716
2718 2717 @command('debugobsolete',
2719 2718 [('', 'flags', 0, _('markers flag')),
2720 2719 ('', 'record-parents', False,
2721 2720 _('record parent information for the precursor')),
2722 2721 ('r', 'rev', [], _('display markers relevant to REV')),
2723 2722 ('', 'index', False, _('display index of the marker')),
2724 2723 ('', 'delete', [], _('delete markers specified by indices')),
2725 2724 ] + commitopts2 + formatteropts,
2726 2725 _('[OBSOLETED [REPLACEMENT ...]]'))
2727 2726 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2728 2727 """create arbitrary obsolete marker
2729 2728
2730 2729 With no arguments, displays the list of obsolescence markers."""
2731 2730
2732 2731 def parsenodeid(s):
2733 2732 try:
2734 2733 # We do not use revsingle/revrange functions here to accept
2735 2734 # arbitrary node identifiers, possibly not present in the
2736 2735 # local repository.
2737 2736 n = bin(s)
2738 2737 if len(n) != len(nullid):
2739 2738 raise TypeError()
2740 2739 return n
2741 2740 except TypeError:
2742 2741 raise error.Abort('changeset references must be full hexadecimal '
2743 2742 'node identifiers')
2744 2743
2745 2744 if opts.get('delete'):
2746 2745 indices = []
2747 2746 for v in opts.get('delete'):
2748 2747 try:
2749 2748 indices.append(int(v))
2750 2749 except ValueError:
2751 2750 raise error.Abort(_('invalid index value: %r') % v,
2752 2751 hint=_('use integers for indices'))
2753 2752
2754 2753 if repo.currenttransaction():
2755 2754 raise error.Abort(_('cannot delete obsmarkers in the middle '
2756 2755 'of transaction.'))
2757 2756
2758 2757 with repo.lock():
2759 2758 n = repair.deleteobsmarkers(repo.obsstore, indices)
2760 2759 ui.write(_('deleted %i obsolescence markers\n') % n)
2761 2760
2762 2761 return
2763 2762
2764 2763 if precursor is not None:
2765 2764 if opts['rev']:
2766 2765 raise error.Abort('cannot select revision when creating marker')
2767 2766 metadata = {}
2768 2767 metadata['user'] = opts['user'] or ui.username()
2769 2768 succs = tuple(parsenodeid(succ) for succ in successors)
2770 2769 l = repo.lock()
2771 2770 try:
2772 2771 tr = repo.transaction('debugobsolete')
2773 2772 try:
2774 2773 date = opts.get('date')
2775 2774 if date:
2776 2775 date = util.parsedate(date)
2777 2776 else:
2778 2777 date = None
2779 2778 prec = parsenodeid(precursor)
2780 2779 parents = None
2781 2780 if opts['record_parents']:
2782 2781 if prec not in repo.unfiltered():
2783 2782 raise error.Abort('cannot used --record-parents on '
2784 2783 'unknown changesets')
2785 2784 parents = repo.unfiltered()[prec].parents()
2786 2785 parents = tuple(p.node() for p in parents)
2787 2786 repo.obsstore.create(tr, prec, succs, opts['flags'],
2788 2787 parents=parents, date=date,
2789 2788 metadata=metadata)
2790 2789 tr.close()
2791 2790 except ValueError as exc:
2792 2791 raise error.Abort(_('bad obsmarker input: %s') % exc)
2793 2792 finally:
2794 2793 tr.release()
2795 2794 finally:
2796 2795 l.release()
2797 2796 else:
2798 2797 if opts['rev']:
2799 2798 revs = scmutil.revrange(repo, opts['rev'])
2800 2799 nodes = [repo[r].node() for r in revs]
2801 2800 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2802 2801 markers.sort(key=lambda x: x._data)
2803 2802 else:
2804 2803 markers = obsolete.getmarkers(repo)
2805 2804
2806 2805 markerstoiter = markers
2807 2806 isrelevant = lambda m: True
2808 2807 if opts.get('rev') and opts.get('index'):
2809 2808 markerstoiter = obsolete.getmarkers(repo)
2810 2809 markerset = set(markers)
2811 2810 isrelevant = lambda m: m in markerset
2812 2811
2813 2812 fm = ui.formatter('debugobsolete', opts)
2814 2813 for i, m in enumerate(markerstoiter):
2815 2814 if not isrelevant(m):
2816 2815 # marker can be irrelevant when we're iterating over a set
2817 2816 # of markers (markerstoiter) which is bigger than the set
2818 2817 # of markers we want to display (markers)
2819 2818 # this can happen if both --index and --rev options are
2820 2819 # provided and thus we need to iterate over all of the markers
2821 2820 # to get the correct indices, but only display the ones that
2822 2821 # are relevant to --rev value
2823 2822 continue
2824 2823 fm.startitem()
2825 2824 ind = i if opts.get('index') else None
2826 2825 cmdutil.showmarker(fm, m, index=ind)
2827 2826 fm.end()
2828 2827
2829 2828 @command('debugpathcomplete',
2830 2829 [('f', 'full', None, _('complete an entire path')),
2831 2830 ('n', 'normal', None, _('show only normal files')),
2832 2831 ('a', 'added', None, _('show only added files')),
2833 2832 ('r', 'removed', None, _('show only removed files'))],
2834 2833 _('FILESPEC...'))
2835 2834 def debugpathcomplete(ui, repo, *specs, **opts):
2836 2835 '''complete part or all of a tracked path
2837 2836
2838 2837 This command supports shells that offer path name completion. It
2839 2838 currently completes only files already known to the dirstate.
2840 2839
2841 2840 Completion extends only to the next path segment unless
2842 2841 --full is specified, in which case entire paths are used.'''
2843 2842
2844 2843 def complete(path, acceptable):
2845 2844 dirstate = repo.dirstate
2846 2845 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2847 2846 rootdir = repo.root + os.sep
2848 2847 if spec != repo.root and not spec.startswith(rootdir):
2849 2848 return [], []
2850 2849 if os.path.isdir(spec):
2851 2850 spec += '/'
2852 2851 spec = spec[len(rootdir):]
2853 2852 fixpaths = pycompat.ossep != '/'
2854 2853 if fixpaths:
2855 2854 spec = spec.replace(os.sep, '/')
2856 2855 speclen = len(spec)
2857 2856 fullpaths = opts['full']
2858 2857 files, dirs = set(), set()
2859 2858 adddir, addfile = dirs.add, files.add
2860 2859 for f, st in dirstate.iteritems():
2861 2860 if f.startswith(spec) and st[0] in acceptable:
2862 2861 if fixpaths:
2863 2862 f = f.replace('/', os.sep)
2864 2863 if fullpaths:
2865 2864 addfile(f)
2866 2865 continue
2867 2866 s = f.find(os.sep, speclen)
2868 2867 if s >= 0:
2869 2868 adddir(f[:s])
2870 2869 else:
2871 2870 addfile(f)
2872 2871 return files, dirs
2873 2872
2874 2873 acceptable = ''
2875 2874 if opts['normal']:
2876 2875 acceptable += 'nm'
2877 2876 if opts['added']:
2878 2877 acceptable += 'a'
2879 2878 if opts['removed']:
2880 2879 acceptable += 'r'
2881 2880 cwd = repo.getcwd()
2882 2881 if not specs:
2883 2882 specs = ['.']
2884 2883
2885 2884 files, dirs = set(), set()
2886 2885 for spec in specs:
2887 2886 f, d = complete(spec, acceptable or 'nmar')
2888 2887 files.update(f)
2889 2888 dirs.update(d)
2890 2889 files.update(dirs)
2891 2890 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2892 2891 ui.write('\n')
2893 2892
2894 2893 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2895 2894 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2896 2895 '''access the pushkey key/value protocol
2897 2896
2898 2897 With two args, list the keys in the given namespace.
2899 2898
2900 2899 With five args, set a key to new if it currently is set to old.
2901 2900 Reports success or failure.
2902 2901 '''
2903 2902
2904 2903 target = hg.peer(ui, {}, repopath)
2905 2904 if keyinfo:
2906 2905 key, old, new = keyinfo
2907 2906 r = target.pushkey(namespace, key, old, new)
2908 2907 ui.status(str(r) + '\n')
2909 2908 return not r
2910 2909 else:
2911 2910 for k, v in sorted(target.listkeys(namespace).iteritems()):
2912 2911 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2913 2912 v.encode('string-escape')))
2914 2913
2915 2914 @command('debugpvec', [], _('A B'))
2916 2915 def debugpvec(ui, repo, a, b=None):
2917 2916 ca = scmutil.revsingle(repo, a)
2918 2917 cb = scmutil.revsingle(repo, b)
2919 2918 pa = pvec.ctxpvec(ca)
2920 2919 pb = pvec.ctxpvec(cb)
2921 2920 if pa == pb:
2922 2921 rel = "="
2923 2922 elif pa > pb:
2924 2923 rel = ">"
2925 2924 elif pa < pb:
2926 2925 rel = "<"
2927 2926 elif pa | pb:
2928 2927 rel = "|"
2929 2928 ui.write(_("a: %s\n") % pa)
2930 2929 ui.write(_("b: %s\n") % pb)
2931 2930 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2932 2931 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2933 2932 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2934 2933 pa.distance(pb), rel))
2935 2934
2936 2935 @command('debugrebuilddirstate|debugrebuildstate',
2937 2936 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
2938 2937 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
2939 2938 'the working copy parent')),
2940 2939 ],
2941 2940 _('[-r REV]'))
2942 2941 def debugrebuilddirstate(ui, repo, rev, **opts):
2943 2942 """rebuild the dirstate as it would look like for the given revision
2944 2943
2945 2944 If no revision is specified the first current parent will be used.
2946 2945
2947 2946 The dirstate will be set to the files of the given revision.
2948 2947 The actual working directory content or existing dirstate
2949 2948 information such as adds or removes is not considered.
2950 2949
2951 2950 ``minimal`` will only rebuild the dirstate status for files that claim to be
2952 2951 tracked but are not in the parent manifest, or that exist in the parent
2953 2952 manifest but are not in the dirstate. It will not change adds, removes, or
2954 2953 modified files that are in the working copy parent.
2955 2954
2956 2955 One use of this command is to make the next :hg:`status` invocation
2957 2956 check the actual file content.
2958 2957 """
2959 2958 ctx = scmutil.revsingle(repo, rev)
2960 2959 with repo.wlock():
2961 2960 dirstate = repo.dirstate
2962 2961 changedfiles = None
2963 2962 # See command doc for what minimal does.
2964 2963 if opts.get('minimal'):
2965 2964 manifestfiles = set(ctx.manifest().keys())
2966 2965 dirstatefiles = set(dirstate)
2967 2966 manifestonly = manifestfiles - dirstatefiles
2968 2967 dsonly = dirstatefiles - manifestfiles
2969 2968 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
2970 2969 changedfiles = manifestonly | dsnotadded
2971 2970
2972 2971 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2973 2972
2974 2973 @command('debugrebuildfncache', [], '')
2975 2974 def debugrebuildfncache(ui, repo):
2976 2975 """rebuild the fncache file"""
2977 2976 repair.rebuildfncache(ui, repo)
2978 2977
2979 2978 @command('debugrename',
2980 2979 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2981 2980 _('[-r REV] FILE'))
2982 2981 def debugrename(ui, repo, file1, *pats, **opts):
2983 2982 """dump rename information"""
2984 2983
2985 2984 ctx = scmutil.revsingle(repo, opts.get('rev'))
2986 2985 m = scmutil.match(ctx, (file1,) + pats, opts)
2987 2986 for abs in ctx.walk(m):
2988 2987 fctx = ctx[abs]
2989 2988 o = fctx.filelog().renamed(fctx.filenode())
2990 2989 rel = m.rel(abs)
2991 2990 if o:
2992 2991 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2993 2992 else:
2994 2993 ui.write(_("%s not renamed\n") % rel)
2995 2994
2996 2995 @command('debugrevlog', debugrevlogopts +
2997 2996 [('d', 'dump', False, _('dump index data'))],
2998 2997 _('-c|-m|FILE'),
2999 2998 optionalrepo=True)
3000 2999 def debugrevlog(ui, repo, file_=None, **opts):
3001 3000 """show data and statistics about a revlog"""
3002 3001 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
3003 3002
3004 3003 if opts.get("dump"):
3005 3004 numrevs = len(r)
3006 3005 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
3007 3006 " rawsize totalsize compression heads chainlen\n"))
3008 3007 ts = 0
3009 3008 heads = set()
3010 3009
3011 3010 for rev in xrange(numrevs):
3012 3011 dbase = r.deltaparent(rev)
3013 3012 if dbase == -1:
3014 3013 dbase = rev
3015 3014 cbase = r.chainbase(rev)
3016 3015 clen = r.chainlen(rev)
3017 3016 p1, p2 = r.parentrevs(rev)
3018 3017 rs = r.rawsize(rev)
3019 3018 ts = ts + rs
3020 3019 heads -= set(r.parentrevs(rev))
3021 3020 heads.add(rev)
3022 3021 try:
3023 3022 compression = ts / r.end(rev)
3024 3023 except ZeroDivisionError:
3025 3024 compression = 0
3026 3025 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
3027 3026 "%11d %5d %8d\n" %
3028 3027 (rev, p1, p2, r.start(rev), r.end(rev),
3029 3028 r.start(dbase), r.start(cbase),
3030 3029 r.start(p1), r.start(p2),
3031 3030 rs, ts, compression, len(heads), clen))
3032 3031 return 0
3033 3032
3034 3033 v = r.version
3035 3034 format = v & 0xFFFF
3036 3035 flags = []
3037 3036 gdelta = False
3038 3037 if v & revlog.REVLOGNGINLINEDATA:
3039 3038 flags.append('inline')
3040 3039 if v & revlog.REVLOGGENERALDELTA:
3041 3040 gdelta = True
3042 3041 flags.append('generaldelta')
3043 3042 if not flags:
3044 3043 flags = ['(none)']
3045 3044
3046 3045 nummerges = 0
3047 3046 numfull = 0
3048 3047 numprev = 0
3049 3048 nump1 = 0
3050 3049 nump2 = 0
3051 3050 numother = 0
3052 3051 nump1prev = 0
3053 3052 nump2prev = 0
3054 3053 chainlengths = []
3055 3054
3056 3055 datasize = [None, 0, 0]
3057 3056 fullsize = [None, 0, 0]
3058 3057 deltasize = [None, 0, 0]
3059 3058 chunktypecounts = {}
3060 3059 chunktypesizes = {}
3061 3060
3062 3061 def addsize(size, l):
3063 3062 if l[0] is None or size < l[0]:
3064 3063 l[0] = size
3065 3064 if size > l[1]:
3066 3065 l[1] = size
3067 3066 l[2] += size
3068 3067
3069 3068 numrevs = len(r)
3070 3069 for rev in xrange(numrevs):
3071 3070 p1, p2 = r.parentrevs(rev)
3072 3071 delta = r.deltaparent(rev)
3073 3072 if format > 0:
3074 3073 addsize(r.rawsize(rev), datasize)
3075 3074 if p2 != nullrev:
3076 3075 nummerges += 1
3077 3076 size = r.length(rev)
3078 3077 if delta == nullrev:
3079 3078 chainlengths.append(0)
3080 3079 numfull += 1
3081 3080 addsize(size, fullsize)
3082 3081 else:
3083 3082 chainlengths.append(chainlengths[delta] + 1)
3084 3083 addsize(size, deltasize)
3085 3084 if delta == rev - 1:
3086 3085 numprev += 1
3087 3086 if delta == p1:
3088 3087 nump1prev += 1
3089 3088 elif delta == p2:
3090 3089 nump2prev += 1
3091 3090 elif delta == p1:
3092 3091 nump1 += 1
3093 3092 elif delta == p2:
3094 3093 nump2 += 1
3095 3094 elif delta != nullrev:
3096 3095 numother += 1
3097 3096
3098 3097 # Obtain data on the raw chunks in the revlog.
3099 3098 chunk = r._chunkraw(rev, rev)[1]
3100 3099 if chunk:
3101 3100 chunktype = chunk[0]
3102 3101 else:
3103 3102 chunktype = 'empty'
3104 3103
3105 3104 if chunktype not in chunktypecounts:
3106 3105 chunktypecounts[chunktype] = 0
3107 3106 chunktypesizes[chunktype] = 0
3108 3107
3109 3108 chunktypecounts[chunktype] += 1
3110 3109 chunktypesizes[chunktype] += size
3111 3110
3112 3111 # Adjust size min value for empty cases
3113 3112 for size in (datasize, fullsize, deltasize):
3114 3113 if size[0] is None:
3115 3114 size[0] = 0
3116 3115
3117 3116 numdeltas = numrevs - numfull
3118 3117 numoprev = numprev - nump1prev - nump2prev
3119 3118 totalrawsize = datasize[2]
3120 3119 datasize[2] /= numrevs
3121 3120 fulltotal = fullsize[2]
3122 3121 fullsize[2] /= numfull
3123 3122 deltatotal = deltasize[2]
3124 3123 if numrevs - numfull > 0:
3125 3124 deltasize[2] /= numrevs - numfull
3126 3125 totalsize = fulltotal + deltatotal
3127 3126 avgchainlen = sum(chainlengths) / numrevs
3128 3127 maxchainlen = max(chainlengths)
3129 3128 compratio = 1
3130 3129 if totalsize:
3131 3130 compratio = totalrawsize / totalsize
3132 3131
3133 3132 basedfmtstr = '%%%dd\n'
3134 3133 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
3135 3134
3136 3135 def dfmtstr(max):
3137 3136 return basedfmtstr % len(str(max))
3138 3137 def pcfmtstr(max, padding=0):
3139 3138 return basepcfmtstr % (len(str(max)), ' ' * padding)
3140 3139
3141 3140 def pcfmt(value, total):
3142 3141 if total:
3143 3142 return (value, 100 * float(value) / total)
3144 3143 else:
3145 3144 return value, 100.0
3146 3145
3147 3146 ui.write(('format : %d\n') % format)
3148 3147 ui.write(('flags : %s\n') % ', '.join(flags))
3149 3148
3150 3149 ui.write('\n')
3151 3150 fmt = pcfmtstr(totalsize)
3152 3151 fmt2 = dfmtstr(totalsize)
3153 3152 ui.write(('revisions : ') + fmt2 % numrevs)
3154 3153 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
3155 3154 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
3156 3155 ui.write(('revisions : ') + fmt2 % numrevs)
3157 3156 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
3158 3157 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
3159 3158 ui.write(('revision size : ') + fmt2 % totalsize)
3160 3159 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
3161 3160 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
3162 3161
3163 3162 def fmtchunktype(chunktype):
3164 3163 if chunktype == 'empty':
3165 3164 return ' %s : ' % chunktype
3166 3165 elif chunktype in string.ascii_letters:
3167 3166 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3168 3167 else:
3169 3168 return ' 0x%s : ' % hex(chunktype)
3170 3169
3171 3170 ui.write('\n')
3172 3171 ui.write(('chunks : ') + fmt2 % numrevs)
3173 3172 for chunktype in sorted(chunktypecounts):
3174 3173 ui.write(fmtchunktype(chunktype))
3175 3174 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3176 3175 ui.write(('chunks size : ') + fmt2 % totalsize)
3177 3176 for chunktype in sorted(chunktypecounts):
3178 3177 ui.write(fmtchunktype(chunktype))
3179 3178 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3180 3179
3181 3180 ui.write('\n')
3182 3181 fmt = dfmtstr(max(avgchainlen, compratio))
3183 3182 ui.write(('avg chain length : ') + fmt % avgchainlen)
3184 3183 ui.write(('max chain length : ') + fmt % maxchainlen)
3185 3184 ui.write(('compression ratio : ') + fmt % compratio)
3186 3185
3187 3186 if format > 0:
3188 3187 ui.write('\n')
3189 3188 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
3190 3189 % tuple(datasize))
3191 3190 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
3192 3191 % tuple(fullsize))
3193 3192 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
3194 3193 % tuple(deltasize))
3195 3194
3196 3195 if numdeltas > 0:
3197 3196 ui.write('\n')
3198 3197 fmt = pcfmtstr(numdeltas)
3199 3198 fmt2 = pcfmtstr(numdeltas, 4)
3200 3199 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
3201 3200 if numprev > 0:
3202 3201 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
3203 3202 numprev))
3204 3203 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
3205 3204 numprev))
3206 3205 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
3207 3206 numprev))
3208 3207 if gdelta:
3209 3208 ui.write(('deltas against p1 : ')
3210 3209 + fmt % pcfmt(nump1, numdeltas))
3211 3210 ui.write(('deltas against p2 : ')
3212 3211 + fmt % pcfmt(nump2, numdeltas))
3213 3212 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
3214 3213 numdeltas))
3215 3214
3216 3215 @command('debugrevspec',
3217 3216 [('', 'optimize', None,
3218 3217 _('print parsed tree after optimizing (DEPRECATED)')),
3219 3218 ('p', 'show-stage', [],
3220 3219 _('print parsed tree at the given stage'), _('NAME')),
3221 3220 ('', 'no-optimized', False, _('evaluate tree without optimization')),
3222 3221 ('', 'verify-optimized', False, _('verify optimized result')),
3223 3222 ],
3224 3223 ('REVSPEC'))
3225 3224 def debugrevspec(ui, repo, expr, **opts):
3226 3225 """parse and apply a revision specification
3227 3226
3228 3227 Use -p/--show-stage option to print the parsed tree at the given stages.
3229 3228 Use -p all to print tree at every stage.
3230 3229
3231 3230 Use --verify-optimized to compare the optimized result with the unoptimized
3232 3231 one. Returns 1 if the optimized result differs.
3233 3232 """
3234 3233 stages = [
3235 3234 ('parsed', lambda tree: tree),
3236 3235 ('expanded', lambda tree: revset.expandaliases(ui, tree)),
3237 3236 ('concatenated', revset.foldconcat),
3238 3237 ('analyzed', revset.analyze),
3239 3238 ('optimized', revset.optimize),
3240 3239 ]
3241 3240 if opts['no_optimized']:
3242 3241 stages = stages[:-1]
3243 3242 if opts['verify_optimized'] and opts['no_optimized']:
3244 3243 raise error.Abort(_('cannot use --verify-optimized with '
3245 3244 '--no-optimized'))
3246 3245 stagenames = set(n for n, f in stages)
3247 3246
3248 3247 showalways = set()
3249 3248 showchanged = set()
3250 3249 if ui.verbose and not opts['show_stage']:
3251 3250 # show parsed tree by --verbose (deprecated)
3252 3251 showalways.add('parsed')
3253 3252 showchanged.update(['expanded', 'concatenated'])
3254 3253 if opts['optimize']:
3255 3254 showalways.add('optimized')
3256 3255 if opts['show_stage'] and opts['optimize']:
3257 3256 raise error.Abort(_('cannot use --optimize with --show-stage'))
3258 3257 if opts['show_stage'] == ['all']:
3259 3258 showalways.update(stagenames)
3260 3259 else:
3261 3260 for n in opts['show_stage']:
3262 3261 if n not in stagenames:
3263 3262 raise error.Abort(_('invalid stage name: %s') % n)
3264 3263 showalways.update(opts['show_stage'])
3265 3264
3266 3265 treebystage = {}
3267 3266 printedtree = None
3268 3267 tree = revset.parse(expr, lookup=repo.__contains__)
3269 3268 for n, f in stages:
3270 3269 treebystage[n] = tree = f(tree)
3271 3270 if n in showalways or (n in showchanged and tree != printedtree):
3272 3271 if opts['show_stage'] or n != 'parsed':
3273 3272 ui.write(("* %s:\n") % n)
3274 3273 ui.write(revset.prettyformat(tree), "\n")
3275 3274 printedtree = tree
3276 3275
3277 3276 if opts['verify_optimized']:
3278 3277 arevs = revset.makematcher(treebystage['analyzed'])(repo)
3279 3278 brevs = revset.makematcher(treebystage['optimized'])(repo)
3280 3279 if ui.verbose:
3281 3280 ui.note(("* analyzed set:\n"), revset.prettyformatset(arevs), "\n")
3282 3281 ui.note(("* optimized set:\n"), revset.prettyformatset(brevs), "\n")
3283 3282 arevs = list(arevs)
3284 3283 brevs = list(brevs)
3285 3284 if arevs == brevs:
3286 3285 return 0
3287 3286 ui.write(('--- analyzed\n'), label='diff.file_a')
3288 3287 ui.write(('+++ optimized\n'), label='diff.file_b')
3289 3288 sm = difflib.SequenceMatcher(None, arevs, brevs)
3290 3289 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3291 3290 if tag in ('delete', 'replace'):
3292 3291 for c in arevs[alo:ahi]:
3293 3292 ui.write('-%s\n' % c, label='diff.deleted')
3294 3293 if tag in ('insert', 'replace'):
3295 3294 for c in brevs[blo:bhi]:
3296 3295 ui.write('+%s\n' % c, label='diff.inserted')
3297 3296 if tag == 'equal':
3298 3297 for c in arevs[alo:ahi]:
3299 3298 ui.write(' %s\n' % c)
3300 3299 return 1
3301 3300
3302 3301 func = revset.makematcher(tree)
3303 3302 revs = func(repo)
3304 3303 if ui.verbose:
3305 3304 ui.note(("* set:\n"), revset.prettyformatset(revs), "\n")
3306 3305 for c in revs:
3307 3306 ui.write("%s\n" % c)
3308 3307
3309 3308 @command('debugsetparents', [], _('REV1 [REV2]'))
3310 3309 def debugsetparents(ui, repo, rev1, rev2=None):
3311 3310 """manually set the parents of the current working directory
3312 3311
3313 3312 This is useful for writing repository conversion tools, but should
3314 3313 be used with care. For example, neither the working directory nor the
3315 3314 dirstate is updated, so file status may be incorrect after running this
3316 3315 command.
3317 3316
3318 3317 Returns 0 on success.
3319 3318 """
3320 3319
3321 3320 r1 = scmutil.revsingle(repo, rev1).node()
3322 3321 r2 = scmutil.revsingle(repo, rev2, 'null').node()
3323 3322
3324 3323 with repo.wlock():
3325 3324 repo.setparents(r1, r2)
3326 3325
3327 3326 @command('debugdirstate|debugstate',
3328 3327 [('', 'nodates', None, _('do not display the saved mtime')),
3329 3328 ('', 'datesort', None, _('sort by saved mtime'))],
3330 3329 _('[OPTION]...'))
3331 3330 def debugstate(ui, repo, **opts):
3332 3331 """show the contents of the current dirstate"""
3333 3332
3334 3333 nodates = opts.get('nodates')
3335 3334 datesort = opts.get('datesort')
3336 3335
3337 3336 timestr = ""
3338 3337 if datesort:
3339 3338 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
3340 3339 else:
3341 3340 keyfunc = None # sort by filename
3342 3341 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
3343 3342 if ent[3] == -1:
3344 3343 timestr = 'unset '
3345 3344 elif nodates:
3346 3345 timestr = 'set '
3347 3346 else:
3348 3347 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
3349 3348 time.localtime(ent[3]))
3350 3349 if ent[1] & 0o20000:
3351 3350 mode = 'lnk'
3352 3351 else:
3353 3352 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
3354 3353 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
3355 3354 for f in repo.dirstate.copies():
3356 3355 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
3357 3356
3358 3357 @command('debugsub',
3359 3358 [('r', 'rev', '',
3360 3359 _('revision to check'), _('REV'))],
3361 3360 _('[-r REV] [REV]'))
3362 3361 def debugsub(ui, repo, rev=None):
3363 3362 ctx = scmutil.revsingle(repo, rev, None)
3364 3363 for k, v in sorted(ctx.substate.items()):
3365 3364 ui.write(('path %s\n') % k)
3366 3365 ui.write((' source %s\n') % v[0])
3367 3366 ui.write((' revision %s\n') % v[1])
3368 3367
3369 3368 @command('debugsuccessorssets',
3370 3369 [],
3371 3370 _('[REV]'))
3372 3371 def debugsuccessorssets(ui, repo, *revs):
3373 3372 """show set of successors for revision
3374 3373
3375 3374 A successors set of changeset A is a consistent group of revisions that
3376 3375 succeed A. It contains non-obsolete changesets only.
3377 3376
3378 3377 In most cases a changeset A has a single successors set containing a single
3379 3378 successor (changeset A replaced by A').
3380 3379
3381 3380 A changeset that is made obsolete with no successors are called "pruned".
3382 3381 Such changesets have no successors sets at all.
3383 3382
3384 3383 A changeset that has been "split" will have a successors set containing
3385 3384 more than one successor.
3386 3385
3387 3386 A changeset that has been rewritten in multiple different ways is called
3388 3387 "divergent". Such changesets have multiple successor sets (each of which
3389 3388 may also be split, i.e. have multiple successors).
3390 3389
3391 3390 Results are displayed as follows::
3392 3391
3393 3392 <rev1>
3394 3393 <successors-1A>
3395 3394 <rev2>
3396 3395 <successors-2A>
3397 3396 <successors-2B1> <successors-2B2> <successors-2B3>
3398 3397
3399 3398 Here rev2 has two possible (i.e. divergent) successors sets. The first
3400 3399 holds one element, whereas the second holds three (i.e. the changeset has
3401 3400 been split).
3402 3401 """
3403 3402 # passed to successorssets caching computation from one call to another
3404 3403 cache = {}
3405 3404 ctx2str = str
3406 3405 node2str = short
3407 3406 if ui.debug():
3408 3407 def ctx2str(ctx):
3409 3408 return ctx.hex()
3410 3409 node2str = hex
3411 3410 for rev in scmutil.revrange(repo, revs):
3412 3411 ctx = repo[rev]
3413 3412 ui.write('%s\n'% ctx2str(ctx))
3414 3413 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
3415 3414 if succsset:
3416 3415 ui.write(' ')
3417 3416 ui.write(node2str(succsset[0]))
3418 3417 for node in succsset[1:]:
3419 3418 ui.write(' ')
3420 3419 ui.write(node2str(node))
3421 3420 ui.write('\n')
3422 3421
3423 3422 @command('debugtemplate',
3424 3423 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
3425 3424 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
3426 3425 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3427 3426 optionalrepo=True)
3428 3427 def debugtemplate(ui, repo, tmpl, **opts):
3429 3428 """parse and apply a template
3430 3429
3431 3430 If -r/--rev is given, the template is processed as a log template and
3432 3431 applied to the given changesets. Otherwise, it is processed as a generic
3433 3432 template.
3434 3433
3435 3434 Use --verbose to print the parsed tree.
3436 3435 """
3437 3436 revs = None
3438 3437 if opts['rev']:
3439 3438 if repo is None:
3440 3439 raise error.RepoError(_('there is no Mercurial repository here '
3441 3440 '(.hg not found)'))
3442 3441 revs = scmutil.revrange(repo, opts['rev'])
3443 3442
3444 3443 props = {}
3445 3444 for d in opts['define']:
3446 3445 try:
3447 3446 k, v = (e.strip() for e in d.split('=', 1))
3448 3447 if not k:
3449 3448 raise ValueError
3450 3449 props[k] = v
3451 3450 except ValueError:
3452 3451 raise error.Abort(_('malformed keyword definition: %s') % d)
3453 3452
3454 3453 if ui.verbose:
3455 3454 aliases = ui.configitems('templatealias')
3456 3455 tree = templater.parse(tmpl)
3457 3456 ui.note(templater.prettyformat(tree), '\n')
3458 3457 newtree = templater.expandaliases(tree, aliases)
3459 3458 if newtree != tree:
3460 3459 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
3461 3460
3462 3461 mapfile = None
3463 3462 if revs is None:
3464 3463 k = 'debugtemplate'
3465 3464 t = formatter.maketemplater(ui, k, tmpl)
3466 3465 ui.write(templater.stringify(t(k, **props)))
3467 3466 else:
3468 3467 displayer = cmdutil.changeset_templater(ui, repo, None, opts, tmpl,
3469 3468 mapfile, buffered=False)
3470 3469 for r in revs:
3471 3470 displayer.show(repo[r], **props)
3472 3471 displayer.close()
3473 3472
3474 3473 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
3475 3474 def debugwalk(ui, repo, *pats, **opts):
3476 3475 """show how files match on given patterns"""
3477 3476 m = scmutil.match(repo[None], pats, opts)
3478 3477 items = list(repo.walk(m))
3479 3478 if not items:
3480 3479 return
3481 3480 f = lambda fn: fn
3482 3481 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
3483 3482 f = lambda fn: util.normpath(fn)
3484 3483 fmt = 'f %%-%ds %%-%ds %%s' % (
3485 3484 max([len(abs) for abs in items]),
3486 3485 max([len(m.rel(abs)) for abs in items]))
3487 3486 for abs in items:
3488 3487 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
3489 3488 ui.write("%s\n" % line.rstrip())
3490 3489
3491 3490 @command('debugwireargs',
3492 3491 [('', 'three', '', 'three'),
3493 3492 ('', 'four', '', 'four'),
3494 3493 ('', 'five', '', 'five'),
3495 3494 ] + remoteopts,
3496 3495 _('REPO [OPTIONS]... [ONE [TWO]]'),
3497 3496 norepo=True)
3498 3497 def debugwireargs(ui, repopath, *vals, **opts):
3499 3498 repo = hg.peer(ui, opts, repopath)
3500 3499 for opt in remoteopts:
3501 3500 del opts[opt[1]]
3502 3501 args = {}
3503 3502 for k, v in opts.iteritems():
3504 3503 if v:
3505 3504 args[k] = v
3506 3505 # run twice to check that we don't mess up the stream for the next command
3507 3506 res1 = repo.debugwireargs(*vals, **args)
3508 3507 res2 = repo.debugwireargs(*vals, **args)
3509 3508 ui.write("%s\n" % res1)
3510 3509 if res1 != res2:
3511 3510 ui.warn("%s\n" % res2)
3512 3511
3513 3512 @command('^diff',
3514 3513 [('r', 'rev', [], _('revision'), _('REV')),
3515 3514 ('c', 'change', '', _('change made by revision'), _('REV'))
3516 3515 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3517 3516 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3518 3517 inferrepo=True)
3519 3518 def diff(ui, repo, *pats, **opts):
3520 3519 """diff repository (or selected files)
3521 3520
3522 3521 Show differences between revisions for the specified files.
3523 3522
3524 3523 Differences between files are shown using the unified diff format.
3525 3524
3526 3525 .. note::
3527 3526
3528 3527 :hg:`diff` may generate unexpected results for merges, as it will
3529 3528 default to comparing against the working directory's first
3530 3529 parent changeset if no revisions are specified.
3531 3530
3532 3531 When two revision arguments are given, then changes are shown
3533 3532 between those revisions. If only one revision is specified then
3534 3533 that revision is compared to the working directory, and, when no
3535 3534 revisions are specified, the working directory files are compared
3536 3535 to its first parent.
3537 3536
3538 3537 Alternatively you can specify -c/--change with a revision to see
3539 3538 the changes in that changeset relative to its first parent.
3540 3539
3541 3540 Without the -a/--text option, diff will avoid generating diffs of
3542 3541 files it detects as binary. With -a, diff will generate a diff
3543 3542 anyway, probably with undesirable results.
3544 3543
3545 3544 Use the -g/--git option to generate diffs in the git extended diff
3546 3545 format. For more information, read :hg:`help diffs`.
3547 3546
3548 3547 .. container:: verbose
3549 3548
3550 3549 Examples:
3551 3550
3552 3551 - compare a file in the current working directory to its parent::
3553 3552
3554 3553 hg diff foo.c
3555 3554
3556 3555 - compare two historical versions of a directory, with rename info::
3557 3556
3558 3557 hg diff --git -r 1.0:1.2 lib/
3559 3558
3560 3559 - get change stats relative to the last change on some date::
3561 3560
3562 3561 hg diff --stat -r "date('may 2')"
3563 3562
3564 3563 - diff all newly-added files that contain a keyword::
3565 3564
3566 3565 hg diff "set:added() and grep(GNU)"
3567 3566
3568 3567 - compare a revision and its parents::
3569 3568
3570 3569 hg diff -c 9353 # compare against first parent
3571 3570 hg diff -r 9353^:9353 # same using revset syntax
3572 3571 hg diff -r 9353^2:9353 # compare against the second parent
3573 3572
3574 3573 Returns 0 on success.
3575 3574 """
3576 3575
3577 3576 revs = opts.get('rev')
3578 3577 change = opts.get('change')
3579 3578 stat = opts.get('stat')
3580 3579 reverse = opts.get('reverse')
3581 3580
3582 3581 if revs and change:
3583 3582 msg = _('cannot specify --rev and --change at the same time')
3584 3583 raise error.Abort(msg)
3585 3584 elif change:
3586 3585 node2 = scmutil.revsingle(repo, change, None).node()
3587 3586 node1 = repo[node2].p1().node()
3588 3587 else:
3589 3588 node1, node2 = scmutil.revpair(repo, revs)
3590 3589
3591 3590 if reverse:
3592 3591 node1, node2 = node2, node1
3593 3592
3594 3593 diffopts = patch.diffallopts(ui, opts)
3595 3594 m = scmutil.match(repo[node2], pats, opts)
3596 3595 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3597 3596 listsubrepos=opts.get('subrepos'),
3598 3597 root=opts.get('root'))
3599 3598
3600 3599 @command('^export',
3601 3600 [('o', 'output', '',
3602 3601 _('print output to file with formatted name'), _('FORMAT')),
3603 3602 ('', 'switch-parent', None, _('diff against the second parent')),
3604 3603 ('r', 'rev', [], _('revisions to export'), _('REV')),
3605 3604 ] + diffopts,
3606 3605 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3607 3606 def export(ui, repo, *changesets, **opts):
3608 3607 """dump the header and diffs for one or more changesets
3609 3608
3610 3609 Print the changeset header and diffs for one or more revisions.
3611 3610 If no revision is given, the parent of the working directory is used.
3612 3611
3613 3612 The information shown in the changeset header is: author, date,
3614 3613 branch name (if non-default), changeset hash, parent(s) and commit
3615 3614 comment.
3616 3615
3617 3616 .. note::
3618 3617
3619 3618 :hg:`export` may generate unexpected diff output for merge
3620 3619 changesets, as it will compare the merge changeset against its
3621 3620 first parent only.
3622 3621
3623 3622 Output may be to a file, in which case the name of the file is
3624 3623 given using a format string. The formatting rules are as follows:
3625 3624
3626 3625 :``%%``: literal "%" character
3627 3626 :``%H``: changeset hash (40 hexadecimal digits)
3628 3627 :``%N``: number of patches being generated
3629 3628 :``%R``: changeset revision number
3630 3629 :``%b``: basename of the exporting repository
3631 3630 :``%h``: short-form changeset hash (12 hexadecimal digits)
3632 3631 :``%m``: first line of the commit message (only alphanumeric characters)
3633 3632 :``%n``: zero-padded sequence number, starting at 1
3634 3633 :``%r``: zero-padded changeset revision number
3635 3634
3636 3635 Without the -a/--text option, export will avoid generating diffs
3637 3636 of files it detects as binary. With -a, export will generate a
3638 3637 diff anyway, probably with undesirable results.
3639 3638
3640 3639 Use the -g/--git option to generate diffs in the git extended diff
3641 3640 format. See :hg:`help diffs` for more information.
3642 3641
3643 3642 With the --switch-parent option, the diff will be against the
3644 3643 second parent. It can be useful to review a merge.
3645 3644
3646 3645 .. container:: verbose
3647 3646
3648 3647 Examples:
3649 3648
3650 3649 - use export and import to transplant a bugfix to the current
3651 3650 branch::
3652 3651
3653 3652 hg export -r 9353 | hg import -
3654 3653
3655 3654 - export all the changesets between two revisions to a file with
3656 3655 rename information::
3657 3656
3658 3657 hg export --git -r 123:150 > changes.txt
3659 3658
3660 3659 - split outgoing changes into a series of patches with
3661 3660 descriptive names::
3662 3661
3663 3662 hg export -r "outgoing()" -o "%n-%m.patch"
3664 3663
3665 3664 Returns 0 on success.
3666 3665 """
3667 3666 changesets += tuple(opts.get('rev', []))
3668 3667 if not changesets:
3669 3668 changesets = ['.']
3670 3669 revs = scmutil.revrange(repo, changesets)
3671 3670 if not revs:
3672 3671 raise error.Abort(_("export requires at least one changeset"))
3673 3672 if len(revs) > 1:
3674 3673 ui.note(_('exporting patches:\n'))
3675 3674 else:
3676 3675 ui.note(_('exporting patch:\n'))
3677 3676 cmdutil.export(repo, revs, template=opts.get('output'),
3678 3677 switch_parent=opts.get('switch_parent'),
3679 3678 opts=patch.diffallopts(ui, opts))
3680 3679
3681 3680 @command('files',
3682 3681 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3683 3682 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3684 3683 ] + walkopts + formatteropts + subrepoopts,
3685 3684 _('[OPTION]... [FILE]...'))
3686 3685 def files(ui, repo, *pats, **opts):
3687 3686 """list tracked files
3688 3687
3689 3688 Print files under Mercurial control in the working directory or
3690 3689 specified revision for given files (excluding removed files).
3691 3690 Files can be specified as filenames or filesets.
3692 3691
3693 3692 If no files are given to match, this command prints the names
3694 3693 of all files under Mercurial control.
3695 3694
3696 3695 .. container:: verbose
3697 3696
3698 3697 Examples:
3699 3698
3700 3699 - list all files under the current directory::
3701 3700
3702 3701 hg files .
3703 3702
3704 3703 - shows sizes and flags for current revision::
3705 3704
3706 3705 hg files -vr .
3707 3706
3708 3707 - list all files named README::
3709 3708
3710 3709 hg files -I "**/README"
3711 3710
3712 3711 - list all binary files::
3713 3712
3714 3713 hg files "set:binary()"
3715 3714
3716 3715 - find files containing a regular expression::
3717 3716
3718 3717 hg files "set:grep('bob')"
3719 3718
3720 3719 - search tracked file contents with xargs and grep::
3721 3720
3722 3721 hg files -0 | xargs -0 grep foo
3723 3722
3724 3723 See :hg:`help patterns` and :hg:`help filesets` for more information
3725 3724 on specifying file patterns.
3726 3725
3727 3726 Returns 0 if a match is found, 1 otherwise.
3728 3727
3729 3728 """
3730 3729 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3731 3730
3732 3731 end = '\n'
3733 3732 if opts.get('print0'):
3734 3733 end = '\0'
3735 3734 fmt = '%s' + end
3736 3735
3737 3736 m = scmutil.match(ctx, pats, opts)
3738 3737 with ui.formatter('files', opts) as fm:
3739 3738 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
3740 3739
3741 3740 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3742 3741 def forget(ui, repo, *pats, **opts):
3743 3742 """forget the specified files on the next commit
3744 3743
3745 3744 Mark the specified files so they will no longer be tracked
3746 3745 after the next commit.
3747 3746
3748 3747 This only removes files from the current branch, not from the
3749 3748 entire project history, and it does not delete them from the
3750 3749 working directory.
3751 3750
3752 3751 To delete the file from the working directory, see :hg:`remove`.
3753 3752
3754 3753 To undo a forget before the next commit, see :hg:`add`.
3755 3754
3756 3755 .. container:: verbose
3757 3756
3758 3757 Examples:
3759 3758
3760 3759 - forget newly-added binary files::
3761 3760
3762 3761 hg forget "set:added() and binary()"
3763 3762
3764 3763 - forget files that would be excluded by .hgignore::
3765 3764
3766 3765 hg forget "set:hgignore()"
3767 3766
3768 3767 Returns 0 on success.
3769 3768 """
3770 3769
3771 3770 if not pats:
3772 3771 raise error.Abort(_('no files specified'))
3773 3772
3774 3773 m = scmutil.match(repo[None], pats, opts)
3775 3774 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3776 3775 return rejected and 1 or 0
3777 3776
3778 3777 @command(
3779 3778 'graft',
3780 3779 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3781 3780 ('c', 'continue', False, _('resume interrupted graft')),
3782 3781 ('e', 'edit', False, _('invoke editor on commit messages')),
3783 3782 ('', 'log', None, _('append graft info to log message')),
3784 3783 ('f', 'force', False, _('force graft')),
3785 3784 ('D', 'currentdate', False,
3786 3785 _('record the current date as commit date')),
3787 3786 ('U', 'currentuser', False,
3788 3787 _('record the current user as committer'), _('DATE'))]
3789 3788 + commitopts2 + mergetoolopts + dryrunopts,
3790 3789 _('[OPTION]... [-r REV]... REV...'))
3791 3790 def graft(ui, repo, *revs, **opts):
3792 3791 '''copy changes from other branches onto the current branch
3793 3792
3794 3793 This command uses Mercurial's merge logic to copy individual
3795 3794 changes from other branches without merging branches in the
3796 3795 history graph. This is sometimes known as 'backporting' or
3797 3796 'cherry-picking'. By default, graft will copy user, date, and
3798 3797 description from the source changesets.
3799 3798
3800 3799 Changesets that are ancestors of the current revision, that have
3801 3800 already been grafted, or that are merges will be skipped.
3802 3801
3803 3802 If --log is specified, log messages will have a comment appended
3804 3803 of the form::
3805 3804
3806 3805 (grafted from CHANGESETHASH)
3807 3806
3808 3807 If --force is specified, revisions will be grafted even if they
3809 3808 are already ancestors of or have been grafted to the destination.
3810 3809 This is useful when the revisions have since been backed out.
3811 3810
3812 3811 If a graft merge results in conflicts, the graft process is
3813 3812 interrupted so that the current merge can be manually resolved.
3814 3813 Once all conflicts are addressed, the graft process can be
3815 3814 continued with the -c/--continue option.
3816 3815
3817 3816 .. note::
3818 3817
3819 3818 The -c/--continue option does not reapply earlier options, except
3820 3819 for --force.
3821 3820
3822 3821 .. container:: verbose
3823 3822
3824 3823 Examples:
3825 3824
3826 3825 - copy a single change to the stable branch and edit its description::
3827 3826
3828 3827 hg update stable
3829 3828 hg graft --edit 9393
3830 3829
3831 3830 - graft a range of changesets with one exception, updating dates::
3832 3831
3833 3832 hg graft -D "2085::2093 and not 2091"
3834 3833
3835 3834 - continue a graft after resolving conflicts::
3836 3835
3837 3836 hg graft -c
3838 3837
3839 3838 - show the source of a grafted changeset::
3840 3839
3841 3840 hg log --debug -r .
3842 3841
3843 3842 - show revisions sorted by date::
3844 3843
3845 3844 hg log -r "sort(all(), date)"
3846 3845
3847 3846 See :hg:`help revisions` and :hg:`help revsets` for more about
3848 3847 specifying revisions.
3849 3848
3850 3849 Returns 0 on successful completion.
3851 3850 '''
3852 3851 with repo.wlock():
3853 3852 return _dograft(ui, repo, *revs, **opts)
3854 3853
3855 3854 def _dograft(ui, repo, *revs, **opts):
3856 3855 if revs and opts.get('rev'):
3857 3856 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
3858 3857 'revision ordering!\n'))
3859 3858
3860 3859 revs = list(revs)
3861 3860 revs.extend(opts.get('rev'))
3862 3861
3863 3862 if not opts.get('user') and opts.get('currentuser'):
3864 3863 opts['user'] = ui.username()
3865 3864 if not opts.get('date') and opts.get('currentdate'):
3866 3865 opts['date'] = "%d %d" % util.makedate()
3867 3866
3868 3867 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3869 3868
3870 3869 cont = False
3871 3870 if opts.get('continue'):
3872 3871 cont = True
3873 3872 if revs:
3874 3873 raise error.Abort(_("can't specify --continue and revisions"))
3875 3874 # read in unfinished revisions
3876 3875 try:
3877 3876 nodes = repo.vfs.read('graftstate').splitlines()
3878 3877 revs = [repo[node].rev() for node in nodes]
3879 3878 except IOError as inst:
3880 3879 if inst.errno != errno.ENOENT:
3881 3880 raise
3882 3881 cmdutil.wrongtooltocontinue(repo, _('graft'))
3883 3882 else:
3884 3883 cmdutil.checkunfinished(repo)
3885 3884 cmdutil.bailifchanged(repo)
3886 3885 if not revs:
3887 3886 raise error.Abort(_('no revisions specified'))
3888 3887 revs = scmutil.revrange(repo, revs)
3889 3888
3890 3889 skipped = set()
3891 3890 # check for merges
3892 3891 for rev in repo.revs('%ld and merge()', revs):
3893 3892 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3894 3893 skipped.add(rev)
3895 3894 revs = [r for r in revs if r not in skipped]
3896 3895 if not revs:
3897 3896 return -1
3898 3897
3899 3898 # Don't check in the --continue case, in effect retaining --force across
3900 3899 # --continues. That's because without --force, any revisions we decided to
3901 3900 # skip would have been filtered out here, so they wouldn't have made their
3902 3901 # way to the graftstate. With --force, any revisions we would have otherwise
3903 3902 # skipped would not have been filtered out, and if they hadn't been applied
3904 3903 # already, they'd have been in the graftstate.
3905 3904 if not (cont or opts.get('force')):
3906 3905 # check for ancestors of dest branch
3907 3906 crev = repo['.'].rev()
3908 3907 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3909 3908 # XXX make this lazy in the future
3910 3909 # don't mutate while iterating, create a copy
3911 3910 for rev in list(revs):
3912 3911 if rev in ancestors:
3913 3912 ui.warn(_('skipping ancestor revision %d:%s\n') %
3914 3913 (rev, repo[rev]))
3915 3914 # XXX remove on list is slow
3916 3915 revs.remove(rev)
3917 3916 if not revs:
3918 3917 return -1
3919 3918
3920 3919 # analyze revs for earlier grafts
3921 3920 ids = {}
3922 3921 for ctx in repo.set("%ld", revs):
3923 3922 ids[ctx.hex()] = ctx.rev()
3924 3923 n = ctx.extra().get('source')
3925 3924 if n:
3926 3925 ids[n] = ctx.rev()
3927 3926
3928 3927 # check ancestors for earlier grafts
3929 3928 ui.debug('scanning for duplicate grafts\n')
3930 3929
3931 3930 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3932 3931 ctx = repo[rev]
3933 3932 n = ctx.extra().get('source')
3934 3933 if n in ids:
3935 3934 try:
3936 3935 r = repo[n].rev()
3937 3936 except error.RepoLookupError:
3938 3937 r = None
3939 3938 if r in revs:
3940 3939 ui.warn(_('skipping revision %d:%s '
3941 3940 '(already grafted to %d:%s)\n')
3942 3941 % (r, repo[r], rev, ctx))
3943 3942 revs.remove(r)
3944 3943 elif ids[n] in revs:
3945 3944 if r is None:
3946 3945 ui.warn(_('skipping already grafted revision %d:%s '
3947 3946 '(%d:%s also has unknown origin %s)\n')
3948 3947 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3949 3948 else:
3950 3949 ui.warn(_('skipping already grafted revision %d:%s '
3951 3950 '(%d:%s also has origin %d:%s)\n')
3952 3951 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3953 3952 revs.remove(ids[n])
3954 3953 elif ctx.hex() in ids:
3955 3954 r = ids[ctx.hex()]
3956 3955 ui.warn(_('skipping already grafted revision %d:%s '
3957 3956 '(was grafted from %d:%s)\n') %
3958 3957 (r, repo[r], rev, ctx))
3959 3958 revs.remove(r)
3960 3959 if not revs:
3961 3960 return -1
3962 3961
3963 3962 for pos, ctx in enumerate(repo.set("%ld", revs)):
3964 3963 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3965 3964 ctx.description().split('\n', 1)[0])
3966 3965 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3967 3966 if names:
3968 3967 desc += ' (%s)' % ' '.join(names)
3969 3968 ui.status(_('grafting %s\n') % desc)
3970 3969 if opts.get('dry_run'):
3971 3970 continue
3972 3971
3973 3972 source = ctx.extra().get('source')
3974 3973 extra = {}
3975 3974 if source:
3976 3975 extra['source'] = source
3977 3976 extra['intermediate-source'] = ctx.hex()
3978 3977 else:
3979 3978 extra['source'] = ctx.hex()
3980 3979 user = ctx.user()
3981 3980 if opts.get('user'):
3982 3981 user = opts['user']
3983 3982 date = ctx.date()
3984 3983 if opts.get('date'):
3985 3984 date = opts['date']
3986 3985 message = ctx.description()
3987 3986 if opts.get('log'):
3988 3987 message += '\n(grafted from %s)' % ctx.hex()
3989 3988
3990 3989 # we don't merge the first commit when continuing
3991 3990 if not cont:
3992 3991 # perform the graft merge with p1(rev) as 'ancestor'
3993 3992 try:
3994 3993 # ui.forcemerge is an internal variable, do not document
3995 3994 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3996 3995 'graft')
3997 3996 stats = mergemod.graft(repo, ctx, ctx.p1(),
3998 3997 ['local', 'graft'])
3999 3998 finally:
4000 3999 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
4001 4000 # report any conflicts
4002 4001 if stats and stats[3] > 0:
4003 4002 # write out state for --continue
4004 4003 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
4005 4004 repo.vfs.write('graftstate', ''.join(nodelines))
4006 4005 extra = ''
4007 4006 if opts.get('user'):
4008 4007 extra += ' --user %s' % util.shellquote(opts['user'])
4009 4008 if opts.get('date'):
4010 4009 extra += ' --date %s' % util.shellquote(opts['date'])
4011 4010 if opts.get('log'):
4012 4011 extra += ' --log'
4013 4012 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
4014 4013 raise error.Abort(
4015 4014 _("unresolved conflicts, can't continue"),
4016 4015 hint=hint)
4017 4016 else:
4018 4017 cont = False
4019 4018
4020 4019 # commit
4021 4020 node = repo.commit(text=message, user=user,
4022 4021 date=date, extra=extra, editor=editor)
4023 4022 if node is None:
4024 4023 ui.warn(
4025 4024 _('note: graft of %d:%s created no changes to commit\n') %
4026 4025 (ctx.rev(), ctx))
4027 4026
4028 4027 # remove state when we complete successfully
4029 4028 if not opts.get('dry_run'):
4030 4029 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
4031 4030
4032 4031 return 0
4033 4032
4034 4033 @command('grep',
4035 4034 [('0', 'print0', None, _('end fields with NUL')),
4036 4035 ('', 'all', None, _('print all revisions that match')),
4037 4036 ('a', 'text', None, _('treat all files as text')),
4038 4037 ('f', 'follow', None,
4039 4038 _('follow changeset history,'
4040 4039 ' or file history across copies and renames')),
4041 4040 ('i', 'ignore-case', None, _('ignore case when matching')),
4042 4041 ('l', 'files-with-matches', None,
4043 4042 _('print only filenames and revisions that match')),
4044 4043 ('n', 'line-number', None, _('print matching line numbers')),
4045 4044 ('r', 'rev', [],
4046 4045 _('only search files changed within revision range'), _('REV')),
4047 4046 ('u', 'user', None, _('list the author (long with -v)')),
4048 4047 ('d', 'date', None, _('list the date (short with -q)')),
4049 4048 ] + formatteropts + walkopts,
4050 4049 _('[OPTION]... PATTERN [FILE]...'),
4051 4050 inferrepo=True)
4052 4051 def grep(ui, repo, pattern, *pats, **opts):
4053 4052 """search revision history for a pattern in specified files
4054 4053
4055 4054 Search revision history for a regular expression in the specified
4056 4055 files or the entire project.
4057 4056
4058 4057 By default, grep prints the most recent revision number for each
4059 4058 file in which it finds a match. To get it to print every revision
4060 4059 that contains a change in match status ("-" for a match that becomes
4061 4060 a non-match, or "+" for a non-match that becomes a match), use the
4062 4061 --all flag.
4063 4062
4064 4063 PATTERN can be any Python (roughly Perl-compatible) regular
4065 4064 expression.
4066 4065
4067 4066 If no FILEs are specified (and -f/--follow isn't set), all files in
4068 4067 the repository are searched, including those that don't exist in the
4069 4068 current branch or have been deleted in a prior changeset.
4070 4069
4071 4070 Returns 0 if a match is found, 1 otherwise.
4072 4071 """
4073 4072 reflags = re.M
4074 4073 if opts.get('ignore_case'):
4075 4074 reflags |= re.I
4076 4075 try:
4077 4076 regexp = util.re.compile(pattern, reflags)
4078 4077 except re.error as inst:
4079 4078 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
4080 4079 return 1
4081 4080 sep, eol = ':', '\n'
4082 4081 if opts.get('print0'):
4083 4082 sep = eol = '\0'
4084 4083
4085 4084 getfile = util.lrucachefunc(repo.file)
4086 4085
4087 4086 def matchlines(body):
4088 4087 begin = 0
4089 4088 linenum = 0
4090 4089 while begin < len(body):
4091 4090 match = regexp.search(body, begin)
4092 4091 if not match:
4093 4092 break
4094 4093 mstart, mend = match.span()
4095 4094 linenum += body.count('\n', begin, mstart) + 1
4096 4095 lstart = body.rfind('\n', begin, mstart) + 1 or begin
4097 4096 begin = body.find('\n', mend) + 1 or len(body) + 1
4098 4097 lend = begin - 1
4099 4098 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
4100 4099
4101 4100 class linestate(object):
4102 4101 def __init__(self, line, linenum, colstart, colend):
4103 4102 self.line = line
4104 4103 self.linenum = linenum
4105 4104 self.colstart = colstart
4106 4105 self.colend = colend
4107 4106
4108 4107 def __hash__(self):
4109 4108 return hash((self.linenum, self.line))
4110 4109
4111 4110 def __eq__(self, other):
4112 4111 return self.line == other.line
4113 4112
4114 4113 def findpos(self):
4115 4114 """Iterate all (start, end) indices of matches"""
4116 4115 yield self.colstart, self.colend
4117 4116 p = self.colend
4118 4117 while p < len(self.line):
4119 4118 m = regexp.search(self.line, p)
4120 4119 if not m:
4121 4120 break
4122 4121 yield m.span()
4123 4122 p = m.end()
4124 4123
4125 4124 matches = {}
4126 4125 copies = {}
4127 4126 def grepbody(fn, rev, body):
4128 4127 matches[rev].setdefault(fn, [])
4129 4128 m = matches[rev][fn]
4130 4129 for lnum, cstart, cend, line in matchlines(body):
4131 4130 s = linestate(line, lnum, cstart, cend)
4132 4131 m.append(s)
4133 4132
4134 4133 def difflinestates(a, b):
4135 4134 sm = difflib.SequenceMatcher(None, a, b)
4136 4135 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
4137 4136 if tag == 'insert':
4138 4137 for i in xrange(blo, bhi):
4139 4138 yield ('+', b[i])
4140 4139 elif tag == 'delete':
4141 4140 for i in xrange(alo, ahi):
4142 4141 yield ('-', a[i])
4143 4142 elif tag == 'replace':
4144 4143 for i in xrange(alo, ahi):
4145 4144 yield ('-', a[i])
4146 4145 for i in xrange(blo, bhi):
4147 4146 yield ('+', b[i])
4148 4147
4149 4148 def display(fm, fn, ctx, pstates, states):
4150 4149 rev = ctx.rev()
4151 4150 if fm.isplain():
4152 4151 formatuser = ui.shortuser
4153 4152 else:
4154 4153 formatuser = str
4155 4154 if ui.quiet:
4156 4155 datefmt = '%Y-%m-%d'
4157 4156 else:
4158 4157 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
4159 4158 found = False
4160 4159 @util.cachefunc
4161 4160 def binary():
4162 4161 flog = getfile(fn)
4163 4162 return util.binary(flog.read(ctx.filenode(fn)))
4164 4163
4165 4164 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
4166 4165 if opts.get('all'):
4167 4166 iter = difflinestates(pstates, states)
4168 4167 else:
4169 4168 iter = [('', l) for l in states]
4170 4169 for change, l in iter:
4171 4170 fm.startitem()
4172 4171 fm.data(node=fm.hexfunc(ctx.node()))
4173 4172 cols = [
4174 4173 ('filename', fn, True),
4175 4174 ('rev', rev, True),
4176 4175 ('linenumber', l.linenum, opts.get('line_number')),
4177 4176 ]
4178 4177 if opts.get('all'):
4179 4178 cols.append(('change', change, True))
4180 4179 cols.extend([
4181 4180 ('user', formatuser(ctx.user()), opts.get('user')),
4182 4181 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
4183 4182 ])
4184 4183 lastcol = next(name for name, data, cond in reversed(cols) if cond)
4185 4184 for name, data, cond in cols:
4186 4185 field = fieldnamemap.get(name, name)
4187 4186 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
4188 4187 if cond and name != lastcol:
4189 4188 fm.plain(sep, label='grep.sep')
4190 4189 if not opts.get('files_with_matches'):
4191 4190 fm.plain(sep, label='grep.sep')
4192 4191 if not opts.get('text') and binary():
4193 4192 fm.plain(_(" Binary file matches"))
4194 4193 else:
4195 4194 displaymatches(fm.nested('texts'), l)
4196 4195 fm.plain(eol)
4197 4196 found = True
4198 4197 if opts.get('files_with_matches'):
4199 4198 break
4200 4199 return found
4201 4200
4202 4201 def displaymatches(fm, l):
4203 4202 p = 0
4204 4203 for s, e in l.findpos():
4205 4204 if p < s:
4206 4205 fm.startitem()
4207 4206 fm.write('text', '%s', l.line[p:s])
4208 4207 fm.data(matched=False)
4209 4208 fm.startitem()
4210 4209 fm.write('text', '%s', l.line[s:e], label='grep.match')
4211 4210 fm.data(matched=True)
4212 4211 p = e
4213 4212 if p < len(l.line):
4214 4213 fm.startitem()
4215 4214 fm.write('text', '%s', l.line[p:])
4216 4215 fm.data(matched=False)
4217 4216 fm.end()
4218 4217
4219 4218 skip = {}
4220 4219 revfiles = {}
4221 4220 matchfn = scmutil.match(repo[None], pats, opts)
4222 4221 found = False
4223 4222 follow = opts.get('follow')
4224 4223
4225 4224 def prep(ctx, fns):
4226 4225 rev = ctx.rev()
4227 4226 pctx = ctx.p1()
4228 4227 parent = pctx.rev()
4229 4228 matches.setdefault(rev, {})
4230 4229 matches.setdefault(parent, {})
4231 4230 files = revfiles.setdefault(rev, [])
4232 4231 for fn in fns:
4233 4232 flog = getfile(fn)
4234 4233 try:
4235 4234 fnode = ctx.filenode(fn)
4236 4235 except error.LookupError:
4237 4236 continue
4238 4237
4239 4238 copied = flog.renamed(fnode)
4240 4239 copy = follow and copied and copied[0]
4241 4240 if copy:
4242 4241 copies.setdefault(rev, {})[fn] = copy
4243 4242 if fn in skip:
4244 4243 if copy:
4245 4244 skip[copy] = True
4246 4245 continue
4247 4246 files.append(fn)
4248 4247
4249 4248 if fn not in matches[rev]:
4250 4249 grepbody(fn, rev, flog.read(fnode))
4251 4250
4252 4251 pfn = copy or fn
4253 4252 if pfn not in matches[parent]:
4254 4253 try:
4255 4254 fnode = pctx.filenode(pfn)
4256 4255 grepbody(pfn, parent, flog.read(fnode))
4257 4256 except error.LookupError:
4258 4257 pass
4259 4258
4260 4259 fm = ui.formatter('grep', opts)
4261 4260 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4262 4261 rev = ctx.rev()
4263 4262 parent = ctx.p1().rev()
4264 4263 for fn in sorted(revfiles.get(rev, [])):
4265 4264 states = matches[rev][fn]
4266 4265 copy = copies.get(rev, {}).get(fn)
4267 4266 if fn in skip:
4268 4267 if copy:
4269 4268 skip[copy] = True
4270 4269 continue
4271 4270 pstates = matches.get(parent, {}).get(copy or fn, [])
4272 4271 if pstates or states:
4273 4272 r = display(fm, fn, ctx, pstates, states)
4274 4273 found = found or r
4275 4274 if r and not opts.get('all'):
4276 4275 skip[fn] = True
4277 4276 if copy:
4278 4277 skip[copy] = True
4279 4278 del matches[rev]
4280 4279 del revfiles[rev]
4281 4280 fm.end()
4282 4281
4283 4282 return not found
4284 4283
4285 4284 @command('heads',
4286 4285 [('r', 'rev', '',
4287 4286 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
4288 4287 ('t', 'topo', False, _('show topological heads only')),
4289 4288 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
4290 4289 ('c', 'closed', False, _('show normal and closed branch heads')),
4291 4290 ] + templateopts,
4292 4291 _('[-ct] [-r STARTREV] [REV]...'))
4293 4292 def heads(ui, repo, *branchrevs, **opts):
4294 4293 """show branch heads
4295 4294
4296 4295 With no arguments, show all open branch heads in the repository.
4297 4296 Branch heads are changesets that have no descendants on the
4298 4297 same branch. They are where development generally takes place and
4299 4298 are the usual targets for update and merge operations.
4300 4299
4301 4300 If one or more REVs are given, only open branch heads on the
4302 4301 branches associated with the specified changesets are shown. This
4303 4302 means that you can use :hg:`heads .` to see the heads on the
4304 4303 currently checked-out branch.
4305 4304
4306 4305 If -c/--closed is specified, also show branch heads marked closed
4307 4306 (see :hg:`commit --close-branch`).
4308 4307
4309 4308 If STARTREV is specified, only those heads that are descendants of
4310 4309 STARTREV will be displayed.
4311 4310
4312 4311 If -t/--topo is specified, named branch mechanics will be ignored and only
4313 4312 topological heads (changesets with no children) will be shown.
4314 4313
4315 4314 Returns 0 if matching heads are found, 1 if not.
4316 4315 """
4317 4316
4318 4317 start = None
4319 4318 if 'rev' in opts:
4320 4319 start = scmutil.revsingle(repo, opts['rev'], None).node()
4321 4320
4322 4321 if opts.get('topo'):
4323 4322 heads = [repo[h] for h in repo.heads(start)]
4324 4323 else:
4325 4324 heads = []
4326 4325 for branch in repo.branchmap():
4327 4326 heads += repo.branchheads(branch, start, opts.get('closed'))
4328 4327 heads = [repo[h] for h in heads]
4329 4328
4330 4329 if branchrevs:
4331 4330 branches = set(repo[br].branch() for br in branchrevs)
4332 4331 heads = [h for h in heads if h.branch() in branches]
4333 4332
4334 4333 if opts.get('active') and branchrevs:
4335 4334 dagheads = repo.heads(start)
4336 4335 heads = [h for h in heads if h.node() in dagheads]
4337 4336
4338 4337 if branchrevs:
4339 4338 haveheads = set(h.branch() for h in heads)
4340 4339 if branches - haveheads:
4341 4340 headless = ', '.join(b for b in branches - haveheads)
4342 4341 msg = _('no open branch heads found on branches %s')
4343 4342 if opts.get('rev'):
4344 4343 msg += _(' (started at %s)') % opts['rev']
4345 4344 ui.warn((msg + '\n') % headless)
4346 4345
4347 4346 if not heads:
4348 4347 return 1
4349 4348
4350 4349 heads = sorted(heads, key=lambda x: -x.rev())
4351 4350 displayer = cmdutil.show_changeset(ui, repo, opts)
4352 4351 for ctx in heads:
4353 4352 displayer.show(ctx)
4354 4353 displayer.close()
4355 4354
4356 4355 @command('help',
4357 4356 [('e', 'extension', None, _('show only help for extensions')),
4358 4357 ('c', 'command', None, _('show only help for commands')),
4359 4358 ('k', 'keyword', None, _('show topics matching keyword')),
4360 4359 ('s', 'system', [], _('show help for specific platform(s)')),
4361 4360 ],
4362 4361 _('[-ecks] [TOPIC]'),
4363 4362 norepo=True)
4364 4363 def help_(ui, name=None, **opts):
4365 4364 """show help for a given topic or a help overview
4366 4365
4367 4366 With no arguments, print a list of commands with short help messages.
4368 4367
4369 4368 Given a topic, extension, or command name, print help for that
4370 4369 topic.
4371 4370
4372 4371 Returns 0 if successful.
4373 4372 """
4374 4373
4375 4374 textwidth = ui.configint('ui', 'textwidth', 78)
4376 4375 termwidth = ui.termwidth() - 2
4377 4376 if textwidth <= 0 or termwidth < textwidth:
4378 4377 textwidth = termwidth
4379 4378
4380 4379 keep = opts.get('system') or []
4381 4380 if len(keep) == 0:
4382 4381 if sys.platform.startswith('win'):
4383 4382 keep.append('windows')
4384 4383 elif sys.platform == 'OpenVMS':
4385 4384 keep.append('vms')
4386 4385 elif sys.platform == 'plan9':
4387 4386 keep.append('plan9')
4388 4387 else:
4389 4388 keep.append('unix')
4390 4389 keep.append(sys.platform.lower())
4391 4390 if ui.verbose:
4392 4391 keep.append('verbose')
4393 4392
4394 4393 section = None
4395 4394 subtopic = None
4396 4395 if name and '.' in name:
4397 4396 name, remaining = name.split('.', 1)
4398 4397 remaining = encoding.lower(remaining)
4399 4398 if '.' in remaining:
4400 4399 subtopic, section = remaining.split('.', 1)
4401 4400 else:
4402 4401 if name in help.subtopics:
4403 4402 subtopic = remaining
4404 4403 else:
4405 4404 section = remaining
4406 4405
4407 4406 text = help.help_(ui, name, subtopic=subtopic, **opts)
4408 4407
4409 4408 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4410 4409 section=section)
4411 4410
4412 4411 # We could have been given a weird ".foo" section without a name
4413 4412 # to look for, or we could have simply failed to found "foo.bar"
4414 4413 # because bar isn't a section of foo
4415 4414 if section and not (formatted and name):
4416 4415 raise error.Abort(_("help section not found"))
4417 4416
4418 4417 if 'verbose' in pruned:
4419 4418 keep.append('omitted')
4420 4419 else:
4421 4420 keep.append('notomitted')
4422 4421 formatted, pruned = minirst.format(text, textwidth, keep=keep,
4423 4422 section=section)
4424 4423 ui.write(formatted)
4425 4424
4426 4425
4427 4426 @command('identify|id',
4428 4427 [('r', 'rev', '',
4429 4428 _('identify the specified revision'), _('REV')),
4430 4429 ('n', 'num', None, _('show local revision number')),
4431 4430 ('i', 'id', None, _('show global revision id')),
4432 4431 ('b', 'branch', None, _('show branch')),
4433 4432 ('t', 'tags', None, _('show tags')),
4434 4433 ('B', 'bookmarks', None, _('show bookmarks')),
4435 4434 ] + remoteopts,
4436 4435 _('[-nibtB] [-r REV] [SOURCE]'),
4437 4436 optionalrepo=True)
4438 4437 def identify(ui, repo, source=None, rev=None,
4439 4438 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
4440 4439 """identify the working directory or specified revision
4441 4440
4442 4441 Print a summary identifying the repository state at REV using one or
4443 4442 two parent hash identifiers, followed by a "+" if the working
4444 4443 directory has uncommitted changes, the branch name (if not default),
4445 4444 a list of tags, and a list of bookmarks.
4446 4445
4447 4446 When REV is not given, print a summary of the current state of the
4448 4447 repository.
4449 4448
4450 4449 Specifying a path to a repository root or Mercurial bundle will
4451 4450 cause lookup to operate on that repository/bundle.
4452 4451
4453 4452 .. container:: verbose
4454 4453
4455 4454 Examples:
4456 4455
4457 4456 - generate a build identifier for the working directory::
4458 4457
4459 4458 hg id --id > build-id.dat
4460 4459
4461 4460 - find the revision corresponding to a tag::
4462 4461
4463 4462 hg id -n -r 1.3
4464 4463
4465 4464 - check the most recent revision of a remote repository::
4466 4465
4467 4466 hg id -r tip https://www.mercurial-scm.org/repo/hg/
4468 4467
4469 4468 See :hg:`log` for generating more information about specific revisions,
4470 4469 including full hash identifiers.
4471 4470
4472 4471 Returns 0 if successful.
4473 4472 """
4474 4473
4475 4474 if not repo and not source:
4476 4475 raise error.Abort(_("there is no Mercurial repository here "
4477 4476 "(.hg not found)"))
4478 4477
4479 4478 if ui.debugflag:
4480 4479 hexfunc = hex
4481 4480 else:
4482 4481 hexfunc = short
4483 4482 default = not (num or id or branch or tags or bookmarks)
4484 4483 output = []
4485 4484 revs = []
4486 4485
4487 4486 if source:
4488 4487 source, branches = hg.parseurl(ui.expandpath(source))
4489 4488 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
4490 4489 repo = peer.local()
4491 4490 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
4492 4491
4493 4492 if not repo:
4494 4493 if num or branch or tags:
4495 4494 raise error.Abort(
4496 4495 _("can't query remote revision number, branch, or tags"))
4497 4496 if not rev and revs:
4498 4497 rev = revs[0]
4499 4498 if not rev:
4500 4499 rev = "tip"
4501 4500
4502 4501 remoterev = peer.lookup(rev)
4503 4502 if default or id:
4504 4503 output = [hexfunc(remoterev)]
4505 4504
4506 4505 def getbms():
4507 4506 bms = []
4508 4507
4509 4508 if 'bookmarks' in peer.listkeys('namespaces'):
4510 4509 hexremoterev = hex(remoterev)
4511 4510 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
4512 4511 if bmr == hexremoterev]
4513 4512
4514 4513 return sorted(bms)
4515 4514
4516 4515 if bookmarks:
4517 4516 output.extend(getbms())
4518 4517 elif default and not ui.quiet:
4519 4518 # multiple bookmarks for a single parent separated by '/'
4520 4519 bm = '/'.join(getbms())
4521 4520 if bm:
4522 4521 output.append(bm)
4523 4522 else:
4524 4523 ctx = scmutil.revsingle(repo, rev, None)
4525 4524
4526 4525 if ctx.rev() is None:
4527 4526 ctx = repo[None]
4528 4527 parents = ctx.parents()
4529 4528 taglist = []
4530 4529 for p in parents:
4531 4530 taglist.extend(p.tags())
4532 4531
4533 4532 changed = ""
4534 4533 if default or id or num:
4535 4534 if (any(repo.status())
4536 4535 or any(ctx.sub(s).dirty() for s in ctx.substate)):
4537 4536 changed = '+'
4538 4537 if default or id:
4539 4538 output = ["%s%s" %
4540 4539 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
4541 4540 if num:
4542 4541 output.append("%s%s" %
4543 4542 ('+'.join([str(p.rev()) for p in parents]), changed))
4544 4543 else:
4545 4544 if default or id:
4546 4545 output = [hexfunc(ctx.node())]
4547 4546 if num:
4548 4547 output.append(str(ctx.rev()))
4549 4548 taglist = ctx.tags()
4550 4549
4551 4550 if default and not ui.quiet:
4552 4551 b = ctx.branch()
4553 4552 if b != 'default':
4554 4553 output.append("(%s)" % b)
4555 4554
4556 4555 # multiple tags for a single parent separated by '/'
4557 4556 t = '/'.join(taglist)
4558 4557 if t:
4559 4558 output.append(t)
4560 4559
4561 4560 # multiple bookmarks for a single parent separated by '/'
4562 4561 bm = '/'.join(ctx.bookmarks())
4563 4562 if bm:
4564 4563 output.append(bm)
4565 4564 else:
4566 4565 if branch:
4567 4566 output.append(ctx.branch())
4568 4567
4569 4568 if tags:
4570 4569 output.extend(taglist)
4571 4570
4572 4571 if bookmarks:
4573 4572 output.extend(ctx.bookmarks())
4574 4573
4575 4574 ui.write("%s\n" % ' '.join(output))
4576 4575
4577 4576 @command('import|patch',
4578 4577 [('p', 'strip', 1,
4579 4578 _('directory strip option for patch. This has the same '
4580 4579 'meaning as the corresponding patch option'), _('NUM')),
4581 4580 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4582 4581 ('e', 'edit', False, _('invoke editor on commit messages')),
4583 4582 ('f', 'force', None,
4584 4583 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4585 4584 ('', 'no-commit', None,
4586 4585 _("don't commit, just update the working directory")),
4587 4586 ('', 'bypass', None,
4588 4587 _("apply patch without touching the working directory")),
4589 4588 ('', 'partial', None,
4590 4589 _('commit even if some hunks fail')),
4591 4590 ('', 'exact', None,
4592 4591 _('abort if patch would apply lossily')),
4593 4592 ('', 'prefix', '',
4594 4593 _('apply patch to subdirectory'), _('DIR')),
4595 4594 ('', 'import-branch', None,
4596 4595 _('use any branch information in patch (implied by --exact)'))] +
4597 4596 commitopts + commitopts2 + similarityopts,
4598 4597 _('[OPTION]... PATCH...'))
4599 4598 def import_(ui, repo, patch1=None, *patches, **opts):
4600 4599 """import an ordered set of patches
4601 4600
4602 4601 Import a list of patches and commit them individually (unless
4603 4602 --no-commit is specified).
4604 4603
4605 4604 To read a patch from standard input, use "-" as the patch name. If
4606 4605 a URL is specified, the patch will be downloaded from there.
4607 4606
4608 4607 Import first applies changes to the working directory (unless
4609 4608 --bypass is specified), import will abort if there are outstanding
4610 4609 changes.
4611 4610
4612 4611 Use --bypass to apply and commit patches directly to the
4613 4612 repository, without affecting the working directory. Without
4614 4613 --exact, patches will be applied on top of the working directory
4615 4614 parent revision.
4616 4615
4617 4616 You can import a patch straight from a mail message. Even patches
4618 4617 as attachments work (to use the body part, it must have type
4619 4618 text/plain or text/x-patch). From and Subject headers of email
4620 4619 message are used as default committer and commit message. All
4621 4620 text/plain body parts before first diff are added to the commit
4622 4621 message.
4623 4622
4624 4623 If the imported patch was generated by :hg:`export`, user and
4625 4624 description from patch override values from message headers and
4626 4625 body. Values given on command line with -m/--message and -u/--user
4627 4626 override these.
4628 4627
4629 4628 If --exact is specified, import will set the working directory to
4630 4629 the parent of each patch before applying it, and will abort if the
4631 4630 resulting changeset has a different ID than the one recorded in
4632 4631 the patch. This will guard against various ways that portable
4633 4632 patch formats and mail systems might fail to transfer Mercurial
4634 4633 data or metadata. See :hg:`bundle` for lossless transmission.
4635 4634
4636 4635 Use --partial to ensure a changeset will be created from the patch
4637 4636 even if some hunks fail to apply. Hunks that fail to apply will be
4638 4637 written to a <target-file>.rej file. Conflicts can then be resolved
4639 4638 by hand before :hg:`commit --amend` is run to update the created
4640 4639 changeset. This flag exists to let people import patches that
4641 4640 partially apply without losing the associated metadata (author,
4642 4641 date, description, ...).
4643 4642
4644 4643 .. note::
4645 4644
4646 4645 When no hunks apply cleanly, :hg:`import --partial` will create
4647 4646 an empty changeset, importing only the patch metadata.
4648 4647
4649 4648 With -s/--similarity, hg will attempt to discover renames and
4650 4649 copies in the patch in the same way as :hg:`addremove`.
4651 4650
4652 4651 It is possible to use external patch programs to perform the patch
4653 4652 by setting the ``ui.patch`` configuration option. For the default
4654 4653 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4655 4654 See :hg:`help config` for more information about configuration
4656 4655 files and how to use these options.
4657 4656
4658 4657 See :hg:`help dates` for a list of formats valid for -d/--date.
4659 4658
4660 4659 .. container:: verbose
4661 4660
4662 4661 Examples:
4663 4662
4664 4663 - import a traditional patch from a website and detect renames::
4665 4664
4666 4665 hg import -s 80 http://example.com/bugfix.patch
4667 4666
4668 4667 - import a changeset from an hgweb server::
4669 4668
4670 4669 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4671 4670
4672 4671 - import all the patches in an Unix-style mbox::
4673 4672
4674 4673 hg import incoming-patches.mbox
4675 4674
4676 4675 - attempt to exactly restore an exported changeset (not always
4677 4676 possible)::
4678 4677
4679 4678 hg import --exact proposed-fix.patch
4680 4679
4681 4680 - use an external tool to apply a patch which is too fuzzy for
4682 4681 the default internal tool.
4683 4682
4684 4683 hg import --config ui.patch="patch --merge" fuzzy.patch
4685 4684
4686 4685 - change the default fuzzing from 2 to a less strict 7
4687 4686
4688 4687 hg import --config ui.fuzz=7 fuzz.patch
4689 4688
4690 4689 Returns 0 on success, 1 on partial success (see --partial).
4691 4690 """
4692 4691
4693 4692 if not patch1:
4694 4693 raise error.Abort(_('need at least one patch to import'))
4695 4694
4696 4695 patches = (patch1,) + patches
4697 4696
4698 4697 date = opts.get('date')
4699 4698 if date:
4700 4699 opts['date'] = util.parsedate(date)
4701 4700
4702 4701 exact = opts.get('exact')
4703 4702 update = not opts.get('bypass')
4704 4703 if not update and opts.get('no_commit'):
4705 4704 raise error.Abort(_('cannot use --no-commit with --bypass'))
4706 4705 try:
4707 4706 sim = float(opts.get('similarity') or 0)
4708 4707 except ValueError:
4709 4708 raise error.Abort(_('similarity must be a number'))
4710 4709 if sim < 0 or sim > 100:
4711 4710 raise error.Abort(_('similarity must be between 0 and 100'))
4712 4711 if sim and not update:
4713 4712 raise error.Abort(_('cannot use --similarity with --bypass'))
4714 4713 if exact:
4715 4714 if opts.get('edit'):
4716 4715 raise error.Abort(_('cannot use --exact with --edit'))
4717 4716 if opts.get('prefix'):
4718 4717 raise error.Abort(_('cannot use --exact with --prefix'))
4719 4718
4720 4719 base = opts["base"]
4721 4720 wlock = dsguard = lock = tr = None
4722 4721 msgs = []
4723 4722 ret = 0
4724 4723
4725 4724
4726 4725 try:
4727 4726 wlock = repo.wlock()
4728 4727
4729 4728 if update:
4730 4729 cmdutil.checkunfinished(repo)
4731 4730 if (exact or not opts.get('force')):
4732 4731 cmdutil.bailifchanged(repo)
4733 4732
4734 4733 if not opts.get('no_commit'):
4735 4734 lock = repo.lock()
4736 4735 tr = repo.transaction('import')
4737 4736 else:
4738 4737 dsguard = dirstateguard.dirstateguard(repo, 'import')
4739 4738 parents = repo[None].parents()
4740 4739 for patchurl in patches:
4741 4740 if patchurl == '-':
4742 4741 ui.status(_('applying patch from stdin\n'))
4743 4742 patchfile = ui.fin
4744 4743 patchurl = 'stdin' # for error message
4745 4744 else:
4746 4745 patchurl = os.path.join(base, patchurl)
4747 4746 ui.status(_('applying %s\n') % patchurl)
4748 4747 patchfile = hg.openpath(ui, patchurl)
4749 4748
4750 4749 haspatch = False
4751 4750 for hunk in patch.split(patchfile):
4752 4751 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4753 4752 parents, opts,
4754 4753 msgs, hg.clean)
4755 4754 if msg:
4756 4755 haspatch = True
4757 4756 ui.note(msg + '\n')
4758 4757 if update or exact:
4759 4758 parents = repo[None].parents()
4760 4759 else:
4761 4760 parents = [repo[node]]
4762 4761 if rej:
4763 4762 ui.write_err(_("patch applied partially\n"))
4764 4763 ui.write_err(_("(fix the .rej files and run "
4765 4764 "`hg commit --amend`)\n"))
4766 4765 ret = 1
4767 4766 break
4768 4767
4769 4768 if not haspatch:
4770 4769 raise error.Abort(_('%s: no diffs found') % patchurl)
4771 4770
4772 4771 if tr:
4773 4772 tr.close()
4774 4773 if msgs:
4775 4774 repo.savecommitmessage('\n* * *\n'.join(msgs))
4776 4775 if dsguard:
4777 4776 dsguard.close()
4778 4777 return ret
4779 4778 finally:
4780 4779 if tr:
4781 4780 tr.release()
4782 4781 release(lock, dsguard, wlock)
4783 4782
4784 4783 @command('incoming|in',
4785 4784 [('f', 'force', None,
4786 4785 _('run even if remote repository is unrelated')),
4787 4786 ('n', 'newest-first', None, _('show newest record first')),
4788 4787 ('', 'bundle', '',
4789 4788 _('file to store the bundles into'), _('FILE')),
4790 4789 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4791 4790 ('B', 'bookmarks', False, _("compare bookmarks")),
4792 4791 ('b', 'branch', [],
4793 4792 _('a specific branch you would like to pull'), _('BRANCH')),
4794 4793 ] + logopts + remoteopts + subrepoopts,
4795 4794 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4796 4795 def incoming(ui, repo, source="default", **opts):
4797 4796 """show new changesets found in source
4798 4797
4799 4798 Show new changesets found in the specified path/URL or the default
4800 4799 pull location. These are the changesets that would have been pulled
4801 4800 if a pull at the time you issued this command.
4802 4801
4803 4802 See pull for valid source format details.
4804 4803
4805 4804 .. container:: verbose
4806 4805
4807 4806 With -B/--bookmarks, the result of bookmark comparison between
4808 4807 local and remote repositories is displayed. With -v/--verbose,
4809 4808 status is also displayed for each bookmark like below::
4810 4809
4811 4810 BM1 01234567890a added
4812 4811 BM2 1234567890ab advanced
4813 4812 BM3 234567890abc diverged
4814 4813 BM4 34567890abcd changed
4815 4814
4816 4815 The action taken locally when pulling depends on the
4817 4816 status of each bookmark:
4818 4817
4819 4818 :``added``: pull will create it
4820 4819 :``advanced``: pull will update it
4821 4820 :``diverged``: pull will create a divergent bookmark
4822 4821 :``changed``: result depends on remote changesets
4823 4822
4824 4823 From the point of view of pulling behavior, bookmark
4825 4824 existing only in the remote repository are treated as ``added``,
4826 4825 even if it is in fact locally deleted.
4827 4826
4828 4827 .. container:: verbose
4829 4828
4830 4829 For remote repository, using --bundle avoids downloading the
4831 4830 changesets twice if the incoming is followed by a pull.
4832 4831
4833 4832 Examples:
4834 4833
4835 4834 - show incoming changes with patches and full description::
4836 4835
4837 4836 hg incoming -vp
4838 4837
4839 4838 - show incoming changes excluding merges, store a bundle::
4840 4839
4841 4840 hg in -vpM --bundle incoming.hg
4842 4841 hg pull incoming.hg
4843 4842
4844 4843 - briefly list changes inside a bundle::
4845 4844
4846 4845 hg in changes.hg -T "{desc|firstline}\\n"
4847 4846
4848 4847 Returns 0 if there are incoming changes, 1 otherwise.
4849 4848 """
4850 4849 if opts.get('graph'):
4851 4850 cmdutil.checkunsupportedgraphflags([], opts)
4852 4851 def display(other, chlist, displayer):
4853 4852 revdag = cmdutil.graphrevs(other, chlist, opts)
4854 4853 cmdutil.displaygraph(ui, repo, revdag, displayer,
4855 4854 graphmod.asciiedges)
4856 4855
4857 4856 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4858 4857 return 0
4859 4858
4860 4859 if opts.get('bundle') and opts.get('subrepos'):
4861 4860 raise error.Abort(_('cannot combine --bundle and --subrepos'))
4862 4861
4863 4862 if opts.get('bookmarks'):
4864 4863 source, branches = hg.parseurl(ui.expandpath(source),
4865 4864 opts.get('branch'))
4866 4865 other = hg.peer(repo, opts, source)
4867 4866 if 'bookmarks' not in other.listkeys('namespaces'):
4868 4867 ui.warn(_("remote doesn't support bookmarks\n"))
4869 4868 return 0
4870 4869 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4871 4870 return bookmarks.incoming(ui, repo, other)
4872 4871
4873 4872 repo._subtoppath = ui.expandpath(source)
4874 4873 try:
4875 4874 return hg.incoming(ui, repo, source, opts)
4876 4875 finally:
4877 4876 del repo._subtoppath
4878 4877
4879 4878
4880 4879 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4881 4880 norepo=True)
4882 4881 def init(ui, dest=".", **opts):
4883 4882 """create a new repository in the given directory
4884 4883
4885 4884 Initialize a new repository in the given directory. If the given
4886 4885 directory does not exist, it will be created.
4887 4886
4888 4887 If no directory is given, the current directory is used.
4889 4888
4890 4889 It is possible to specify an ``ssh://`` URL as the destination.
4891 4890 See :hg:`help urls` for more information.
4892 4891
4893 4892 Returns 0 on success.
4894 4893 """
4895 4894 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4896 4895
4897 4896 @command('locate',
4898 4897 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4899 4898 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4900 4899 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4901 4900 ] + walkopts,
4902 4901 _('[OPTION]... [PATTERN]...'))
4903 4902 def locate(ui, repo, *pats, **opts):
4904 4903 """locate files matching specific patterns (DEPRECATED)
4905 4904
4906 4905 Print files under Mercurial control in the working directory whose
4907 4906 names match the given patterns.
4908 4907
4909 4908 By default, this command searches all directories in the working
4910 4909 directory. To search just the current directory and its
4911 4910 subdirectories, use "--include .".
4912 4911
4913 4912 If no patterns are given to match, this command prints the names
4914 4913 of all files under Mercurial control in the working directory.
4915 4914
4916 4915 If you want to feed the output of this command into the "xargs"
4917 4916 command, use the -0 option to both this command and "xargs". This
4918 4917 will avoid the problem of "xargs" treating single filenames that
4919 4918 contain whitespace as multiple filenames.
4920 4919
4921 4920 See :hg:`help files` for a more versatile command.
4922 4921
4923 4922 Returns 0 if a match is found, 1 otherwise.
4924 4923 """
4925 4924 if opts.get('print0'):
4926 4925 end = '\0'
4927 4926 else:
4928 4927 end = '\n'
4929 4928 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4930 4929
4931 4930 ret = 1
4932 4931 ctx = repo[rev]
4933 4932 m = scmutil.match(ctx, pats, opts, default='relglob',
4934 4933 badfn=lambda x, y: False)
4935 4934
4936 4935 for abs in ctx.matches(m):
4937 4936 if opts.get('fullpath'):
4938 4937 ui.write(repo.wjoin(abs), end)
4939 4938 else:
4940 4939 ui.write(((pats and m.rel(abs)) or abs), end)
4941 4940 ret = 0
4942 4941
4943 4942 return ret
4944 4943
4945 4944 @command('^log|history',
4946 4945 [('f', 'follow', None,
4947 4946 _('follow changeset history, or file history across copies and renames')),
4948 4947 ('', 'follow-first', None,
4949 4948 _('only follow the first parent of merge changesets (DEPRECATED)')),
4950 4949 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4951 4950 ('C', 'copies', None, _('show copied files')),
4952 4951 ('k', 'keyword', [],
4953 4952 _('do case-insensitive search for a given text'), _('TEXT')),
4954 4953 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4955 4954 ('', 'removed', None, _('include revisions where files were removed')),
4956 4955 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4957 4956 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4958 4957 ('', 'only-branch', [],
4959 4958 _('show only changesets within the given named branch (DEPRECATED)'),
4960 4959 _('BRANCH')),
4961 4960 ('b', 'branch', [],
4962 4961 _('show changesets within the given named branch'), _('BRANCH')),
4963 4962 ('P', 'prune', [],
4964 4963 _('do not display revision or any of its ancestors'), _('REV')),
4965 4964 ] + logopts + walkopts,
4966 4965 _('[OPTION]... [FILE]'),
4967 4966 inferrepo=True)
4968 4967 def log(ui, repo, *pats, **opts):
4969 4968 """show revision history of entire repository or files
4970 4969
4971 4970 Print the revision history of the specified files or the entire
4972 4971 project.
4973 4972
4974 4973 If no revision range is specified, the default is ``tip:0`` unless
4975 4974 --follow is set, in which case the working directory parent is
4976 4975 used as the starting revision.
4977 4976
4978 4977 File history is shown without following rename or copy history of
4979 4978 files. Use -f/--follow with a filename to follow history across
4980 4979 renames and copies. --follow without a filename will only show
4981 4980 ancestors or descendants of the starting revision.
4982 4981
4983 4982 By default this command prints revision number and changeset id,
4984 4983 tags, non-trivial parents, user, date and time, and a summary for
4985 4984 each commit. When the -v/--verbose switch is used, the list of
4986 4985 changed files and full commit message are shown.
4987 4986
4988 4987 With --graph the revisions are shown as an ASCII art DAG with the most
4989 4988 recent changeset at the top.
4990 4989 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4991 4990 and '+' represents a fork where the changeset from the lines below is a
4992 4991 parent of the 'o' merge on the same line.
4993 4992
4994 4993 .. note::
4995 4994
4996 4995 :hg:`log --patch` may generate unexpected diff output for merge
4997 4996 changesets, as it will only compare the merge changeset against
4998 4997 its first parent. Also, only files different from BOTH parents
4999 4998 will appear in files:.
5000 4999
5001 5000 .. note::
5002 5001
5003 5002 For performance reasons, :hg:`log FILE` may omit duplicate changes
5004 5003 made on branches and will not show removals or mode changes. To
5005 5004 see all such changes, use the --removed switch.
5006 5005
5007 5006 .. container:: verbose
5008 5007
5009 5008 Some examples:
5010 5009
5011 5010 - changesets with full descriptions and file lists::
5012 5011
5013 5012 hg log -v
5014 5013
5015 5014 - changesets ancestral to the working directory::
5016 5015
5017 5016 hg log -f
5018 5017
5019 5018 - last 10 commits on the current branch::
5020 5019
5021 5020 hg log -l 10 -b .
5022 5021
5023 5022 - changesets showing all modifications of a file, including removals::
5024 5023
5025 5024 hg log --removed file.c
5026 5025
5027 5026 - all changesets that touch a directory, with diffs, excluding merges::
5028 5027
5029 5028 hg log -Mp lib/
5030 5029
5031 5030 - all revision numbers that match a keyword::
5032 5031
5033 5032 hg log -k bug --template "{rev}\\n"
5034 5033
5035 5034 - the full hash identifier of the working directory parent::
5036 5035
5037 5036 hg log -r . --template "{node}\\n"
5038 5037
5039 5038 - list available log templates::
5040 5039
5041 5040 hg log -T list
5042 5041
5043 5042 - check if a given changeset is included in a tagged release::
5044 5043
5045 5044 hg log -r "a21ccf and ancestor(1.9)"
5046 5045
5047 5046 - find all changesets by some user in a date range::
5048 5047
5049 5048 hg log -k alice -d "may 2008 to jul 2008"
5050 5049
5051 5050 - summary of all changesets after the last tag::
5052 5051
5053 5052 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
5054 5053
5055 5054 See :hg:`help dates` for a list of formats valid for -d/--date.
5056 5055
5057 5056 See :hg:`help revisions` and :hg:`help revsets` for more about
5058 5057 specifying and ordering revisions.
5059 5058
5060 5059 See :hg:`help templates` for more about pre-packaged styles and
5061 5060 specifying custom templates.
5062 5061
5063 5062 Returns 0 on success.
5064 5063
5065 5064 """
5066 5065 if opts.get('follow') and opts.get('rev'):
5067 5066 opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
5068 5067 del opts['follow']
5069 5068
5070 5069 if opts.get('graph'):
5071 5070 return cmdutil.graphlog(ui, repo, *pats, **opts)
5072 5071
5073 5072 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
5074 5073 limit = cmdutil.loglimit(opts)
5075 5074 count = 0
5076 5075
5077 5076 getrenamed = None
5078 5077 if opts.get('copies'):
5079 5078 endrev = None
5080 5079 if opts.get('rev'):
5081 5080 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
5082 5081 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
5083 5082
5084 5083 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5085 5084 for rev in revs:
5086 5085 if count == limit:
5087 5086 break
5088 5087 ctx = repo[rev]
5089 5088 copies = None
5090 5089 if getrenamed is not None and rev:
5091 5090 copies = []
5092 5091 for fn in ctx.files():
5093 5092 rename = getrenamed(fn, rev)
5094 5093 if rename:
5095 5094 copies.append((fn, rename[0]))
5096 5095 if filematcher:
5097 5096 revmatchfn = filematcher(ctx.rev())
5098 5097 else:
5099 5098 revmatchfn = None
5100 5099 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
5101 5100 if displayer.flush(ctx):
5102 5101 count += 1
5103 5102
5104 5103 displayer.close()
5105 5104
5106 5105 @command('manifest',
5107 5106 [('r', 'rev', '', _('revision to display'), _('REV')),
5108 5107 ('', 'all', False, _("list files from all revisions"))]
5109 5108 + formatteropts,
5110 5109 _('[-r REV]'))
5111 5110 def manifest(ui, repo, node=None, rev=None, **opts):
5112 5111 """output the current or given revision of the project manifest
5113 5112
5114 5113 Print a list of version controlled files for the given revision.
5115 5114 If no revision is given, the first parent of the working directory
5116 5115 is used, or the null revision if no revision is checked out.
5117 5116
5118 5117 With -v, print file permissions, symlink and executable bits.
5119 5118 With --debug, print file revision hashes.
5120 5119
5121 5120 If option --all is specified, the list of all files from all revisions
5122 5121 is printed. This includes deleted and renamed files.
5123 5122
5124 5123 Returns 0 on success.
5125 5124 """
5126 5125
5127 5126 fm = ui.formatter('manifest', opts)
5128 5127
5129 5128 if opts.get('all'):
5130 5129 if rev or node:
5131 5130 raise error.Abort(_("can't specify a revision with --all"))
5132 5131
5133 5132 res = []
5134 5133 prefix = "data/"
5135 5134 suffix = ".i"
5136 5135 plen = len(prefix)
5137 5136 slen = len(suffix)
5138 5137 with repo.lock():
5139 5138 for fn, b, size in repo.store.datafiles():
5140 5139 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
5141 5140 res.append(fn[plen:-slen])
5142 5141 for f in res:
5143 5142 fm.startitem()
5144 5143 fm.write("path", '%s\n', f)
5145 5144 fm.end()
5146 5145 return
5147 5146
5148 5147 if rev and node:
5149 5148 raise error.Abort(_("please specify just one revision"))
5150 5149
5151 5150 if not node:
5152 5151 node = rev
5153 5152
5154 5153 char = {'l': '@', 'x': '*', '': ''}
5155 5154 mode = {'l': '644', 'x': '755', '': '644'}
5156 5155 ctx = scmutil.revsingle(repo, node)
5157 5156 mf = ctx.manifest()
5158 5157 for f in ctx:
5159 5158 fm.startitem()
5160 5159 fl = ctx[f].flags()
5161 5160 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
5162 5161 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
5163 5162 fm.write('path', '%s\n', f)
5164 5163 fm.end()
5165 5164
5166 5165 @command('^merge',
5167 5166 [('f', 'force', None,
5168 5167 _('force a merge including outstanding changes (DEPRECATED)')),
5169 5168 ('r', 'rev', '', _('revision to merge'), _('REV')),
5170 5169 ('P', 'preview', None,
5171 5170 _('review revisions to merge (no merge is performed)'))
5172 5171 ] + mergetoolopts,
5173 5172 _('[-P] [[-r] REV]'))
5174 5173 def merge(ui, repo, node=None, **opts):
5175 5174 """merge another revision into working directory
5176 5175
5177 5176 The current working directory is updated with all changes made in
5178 5177 the requested revision since the last common predecessor revision.
5179 5178
5180 5179 Files that changed between either parent are marked as changed for
5181 5180 the next commit and a commit must be performed before any further
5182 5181 updates to the repository are allowed. The next commit will have
5183 5182 two parents.
5184 5183
5185 5184 ``--tool`` can be used to specify the merge tool used for file
5186 5185 merges. It overrides the HGMERGE environment variable and your
5187 5186 configuration files. See :hg:`help merge-tools` for options.
5188 5187
5189 5188 If no revision is specified, the working directory's parent is a
5190 5189 head revision, and the current branch contains exactly one other
5191 5190 head, the other head is merged with by default. Otherwise, an
5192 5191 explicit revision with which to merge with must be provided.
5193 5192
5194 5193 See :hg:`help resolve` for information on handling file conflicts.
5195 5194
5196 5195 To undo an uncommitted merge, use :hg:`update --clean .` which
5197 5196 will check out a clean copy of the original merge parent, losing
5198 5197 all changes.
5199 5198
5200 5199 Returns 0 on success, 1 if there are unresolved files.
5201 5200 """
5202 5201
5203 5202 if opts.get('rev') and node:
5204 5203 raise error.Abort(_("please specify just one revision"))
5205 5204 if not node:
5206 5205 node = opts.get('rev')
5207 5206
5208 5207 if node:
5209 5208 node = scmutil.revsingle(repo, node).node()
5210 5209
5211 5210 if not node:
5212 5211 node = repo[destutil.destmerge(repo)].node()
5213 5212
5214 5213 if opts.get('preview'):
5215 5214 # find nodes that are ancestors of p2 but not of p1
5216 5215 p1 = repo.lookup('.')
5217 5216 p2 = repo.lookup(node)
5218 5217 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
5219 5218
5220 5219 displayer = cmdutil.show_changeset(ui, repo, opts)
5221 5220 for node in nodes:
5222 5221 displayer.show(repo[node])
5223 5222 displayer.close()
5224 5223 return 0
5225 5224
5226 5225 try:
5227 5226 # ui.forcemerge is an internal variable, do not document
5228 5227 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
5229 5228 force = opts.get('force')
5230 5229 labels = ['working copy', 'merge rev']
5231 5230 return hg.merge(repo, node, force=force, mergeforce=force,
5232 5231 labels=labels)
5233 5232 finally:
5234 5233 ui.setconfig('ui', 'forcemerge', '', 'merge')
5235 5234
5236 5235 @command('outgoing|out',
5237 5236 [('f', 'force', None, _('run even when the destination is unrelated')),
5238 5237 ('r', 'rev', [],
5239 5238 _('a changeset intended to be included in the destination'), _('REV')),
5240 5239 ('n', 'newest-first', None, _('show newest record first')),
5241 5240 ('B', 'bookmarks', False, _('compare bookmarks')),
5242 5241 ('b', 'branch', [], _('a specific branch you would like to push'),
5243 5242 _('BRANCH')),
5244 5243 ] + logopts + remoteopts + subrepoopts,
5245 5244 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
5246 5245 def outgoing(ui, repo, dest=None, **opts):
5247 5246 """show changesets not found in the destination
5248 5247
5249 5248 Show changesets not found in the specified destination repository
5250 5249 or the default push location. These are the changesets that would
5251 5250 be pushed if a push was requested.
5252 5251
5253 5252 See pull for details of valid destination formats.
5254 5253
5255 5254 .. container:: verbose
5256 5255
5257 5256 With -B/--bookmarks, the result of bookmark comparison between
5258 5257 local and remote repositories is displayed. With -v/--verbose,
5259 5258 status is also displayed for each bookmark like below::
5260 5259
5261 5260 BM1 01234567890a added
5262 5261 BM2 deleted
5263 5262 BM3 234567890abc advanced
5264 5263 BM4 34567890abcd diverged
5265 5264 BM5 4567890abcde changed
5266 5265
5267 5266 The action taken when pushing depends on the
5268 5267 status of each bookmark:
5269 5268
5270 5269 :``added``: push with ``-B`` will create it
5271 5270 :``deleted``: push with ``-B`` will delete it
5272 5271 :``advanced``: push will update it
5273 5272 :``diverged``: push with ``-B`` will update it
5274 5273 :``changed``: push with ``-B`` will update it
5275 5274
5276 5275 From the point of view of pushing behavior, bookmarks
5277 5276 existing only in the remote repository are treated as
5278 5277 ``deleted``, even if it is in fact added remotely.
5279 5278
5280 5279 Returns 0 if there are outgoing changes, 1 otherwise.
5281 5280 """
5282 5281 if opts.get('graph'):
5283 5282 cmdutil.checkunsupportedgraphflags([], opts)
5284 5283 o, other = hg._outgoing(ui, repo, dest, opts)
5285 5284 if not o:
5286 5285 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5287 5286 return
5288 5287
5289 5288 revdag = cmdutil.graphrevs(repo, o, opts)
5290 5289 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
5291 5290 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
5292 5291 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5293 5292 return 0
5294 5293
5295 5294 if opts.get('bookmarks'):
5296 5295 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5297 5296 dest, branches = hg.parseurl(dest, opts.get('branch'))
5298 5297 other = hg.peer(repo, opts, dest)
5299 5298 if 'bookmarks' not in other.listkeys('namespaces'):
5300 5299 ui.warn(_("remote doesn't support bookmarks\n"))
5301 5300 return 0
5302 5301 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
5303 5302 return bookmarks.outgoing(ui, repo, other)
5304 5303
5305 5304 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
5306 5305 try:
5307 5306 return hg.outgoing(ui, repo, dest, opts)
5308 5307 finally:
5309 5308 del repo._subtoppath
5310 5309
5311 5310 @command('parents',
5312 5311 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
5313 5312 ] + templateopts,
5314 5313 _('[-r REV] [FILE]'),
5315 5314 inferrepo=True)
5316 5315 def parents(ui, repo, file_=None, **opts):
5317 5316 """show the parents of the working directory or revision (DEPRECATED)
5318 5317
5319 5318 Print the working directory's parent revisions. If a revision is
5320 5319 given via -r/--rev, the parent of that revision will be printed.
5321 5320 If a file argument is given, the revision in which the file was
5322 5321 last changed (before the working directory revision or the
5323 5322 argument to --rev if given) is printed.
5324 5323
5325 5324 This command is equivalent to::
5326 5325
5327 5326 hg log -r "p1()+p2()" or
5328 5327 hg log -r "p1(REV)+p2(REV)" or
5329 5328 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5330 5329 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5331 5330
5332 5331 See :hg:`summary` and :hg:`help revsets` for related information.
5333 5332
5334 5333 Returns 0 on success.
5335 5334 """
5336 5335
5337 5336 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
5338 5337
5339 5338 if file_:
5340 5339 m = scmutil.match(ctx, (file_,), opts)
5341 5340 if m.anypats() or len(m.files()) != 1:
5342 5341 raise error.Abort(_('can only specify an explicit filename'))
5343 5342 file_ = m.files()[0]
5344 5343 filenodes = []
5345 5344 for cp in ctx.parents():
5346 5345 if not cp:
5347 5346 continue
5348 5347 try:
5349 5348 filenodes.append(cp.filenode(file_))
5350 5349 except error.LookupError:
5351 5350 pass
5352 5351 if not filenodes:
5353 5352 raise error.Abort(_("'%s' not found in manifest!") % file_)
5354 5353 p = []
5355 5354 for fn in filenodes:
5356 5355 fctx = repo.filectx(file_, fileid=fn)
5357 5356 p.append(fctx.node())
5358 5357 else:
5359 5358 p = [cp.node() for cp in ctx.parents()]
5360 5359
5361 5360 displayer = cmdutil.show_changeset(ui, repo, opts)
5362 5361 for n in p:
5363 5362 if n != nullid:
5364 5363 displayer.show(repo[n])
5365 5364 displayer.close()
5366 5365
5367 5366 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
5368 5367 def paths(ui, repo, search=None, **opts):
5369 5368 """show aliases for remote repositories
5370 5369
5371 5370 Show definition of symbolic path name NAME. If no name is given,
5372 5371 show definition of all available names.
5373 5372
5374 5373 Option -q/--quiet suppresses all output when searching for NAME
5375 5374 and shows only the path names when listing all definitions.
5376 5375
5377 5376 Path names are defined in the [paths] section of your
5378 5377 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5379 5378 repository, ``.hg/hgrc`` is used, too.
5380 5379
5381 5380 The path names ``default`` and ``default-push`` have a special
5382 5381 meaning. When performing a push or pull operation, they are used
5383 5382 as fallbacks if no location is specified on the command-line.
5384 5383 When ``default-push`` is set, it will be used for push and
5385 5384 ``default`` will be used for pull; otherwise ``default`` is used
5386 5385 as the fallback for both. When cloning a repository, the clone
5387 5386 source is written as ``default`` in ``.hg/hgrc``.
5388 5387
5389 5388 .. note::
5390 5389
5391 5390 ``default`` and ``default-push`` apply to all inbound (e.g.
5392 5391 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5393 5392 and :hg:`bundle`) operations.
5394 5393
5395 5394 See :hg:`help urls` for more information.
5396 5395
5397 5396 Returns 0 on success.
5398 5397 """
5399 5398 if search:
5400 5399 pathitems = [(name, path) for name, path in ui.paths.iteritems()
5401 5400 if name == search]
5402 5401 else:
5403 5402 pathitems = sorted(ui.paths.iteritems())
5404 5403
5405 5404 fm = ui.formatter('paths', opts)
5406 5405 if fm.isplain():
5407 5406 hidepassword = util.hidepassword
5408 5407 else:
5409 5408 hidepassword = str
5410 5409 if ui.quiet:
5411 5410 namefmt = '%s\n'
5412 5411 else:
5413 5412 namefmt = '%s = '
5414 5413 showsubopts = not search and not ui.quiet
5415 5414
5416 5415 for name, path in pathitems:
5417 5416 fm.startitem()
5418 5417 fm.condwrite(not search, 'name', namefmt, name)
5419 5418 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
5420 5419 for subopt, value in sorted(path.suboptions.items()):
5421 5420 assert subopt not in ('name', 'url')
5422 5421 if showsubopts:
5423 5422 fm.plain('%s:%s = ' % (name, subopt))
5424 5423 fm.condwrite(showsubopts, subopt, '%s\n', value)
5425 5424
5426 5425 fm.end()
5427 5426
5428 5427 if search and not pathitems:
5429 5428 if not ui.quiet:
5430 5429 ui.warn(_("not found!\n"))
5431 5430 return 1
5432 5431 else:
5433 5432 return 0
5434 5433
5435 5434 @command('phase',
5436 5435 [('p', 'public', False, _('set changeset phase to public')),
5437 5436 ('d', 'draft', False, _('set changeset phase to draft')),
5438 5437 ('s', 'secret', False, _('set changeset phase to secret')),
5439 5438 ('f', 'force', False, _('allow to move boundary backward')),
5440 5439 ('r', 'rev', [], _('target revision'), _('REV')),
5441 5440 ],
5442 5441 _('[-p|-d|-s] [-f] [-r] [REV...]'))
5443 5442 def phase(ui, repo, *revs, **opts):
5444 5443 """set or show the current phase name
5445 5444
5446 5445 With no argument, show the phase name of the current revision(s).
5447 5446
5448 5447 With one of -p/--public, -d/--draft or -s/--secret, change the
5449 5448 phase value of the specified revisions.
5450 5449
5451 5450 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
5452 5451 lower phase to an higher phase. Phases are ordered as follows::
5453 5452
5454 5453 public < draft < secret
5455 5454
5456 5455 Returns 0 on success, 1 if some phases could not be changed.
5457 5456
5458 5457 (For more information about the phases concept, see :hg:`help phases`.)
5459 5458 """
5460 5459 # search for a unique phase argument
5461 5460 targetphase = None
5462 5461 for idx, name in enumerate(phases.phasenames):
5463 5462 if opts[name]:
5464 5463 if targetphase is not None:
5465 5464 raise error.Abort(_('only one phase can be specified'))
5466 5465 targetphase = idx
5467 5466
5468 5467 # look for specified revision
5469 5468 revs = list(revs)
5470 5469 revs.extend(opts['rev'])
5471 5470 if not revs:
5472 5471 # display both parents as the second parent phase can influence
5473 5472 # the phase of a merge commit
5474 5473 revs = [c.rev() for c in repo[None].parents()]
5475 5474
5476 5475 revs = scmutil.revrange(repo, revs)
5477 5476
5478 5477 lock = None
5479 5478 ret = 0
5480 5479 if targetphase is None:
5481 5480 # display
5482 5481 for r in revs:
5483 5482 ctx = repo[r]
5484 5483 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5485 5484 else:
5486 5485 tr = None
5487 5486 lock = repo.lock()
5488 5487 try:
5489 5488 tr = repo.transaction("phase")
5490 5489 # set phase
5491 5490 if not revs:
5492 5491 raise error.Abort(_('empty revision set'))
5493 5492 nodes = [repo[r].node() for r in revs]
5494 5493 # moving revision from public to draft may hide them
5495 5494 # We have to check result on an unfiltered repository
5496 5495 unfi = repo.unfiltered()
5497 5496 getphase = unfi._phasecache.phase
5498 5497 olddata = [getphase(unfi, r) for r in unfi]
5499 5498 phases.advanceboundary(repo, tr, targetphase, nodes)
5500 5499 if opts['force']:
5501 5500 phases.retractboundary(repo, tr, targetphase, nodes)
5502 5501 tr.close()
5503 5502 finally:
5504 5503 if tr is not None:
5505 5504 tr.release()
5506 5505 lock.release()
5507 5506 getphase = unfi._phasecache.phase
5508 5507 newdata = [getphase(unfi, r) for r in unfi]
5509 5508 changes = sum(newdata[r] != olddata[r] for r in unfi)
5510 5509 cl = unfi.changelog
5511 5510 rejected = [n for n in nodes
5512 5511 if newdata[cl.rev(n)] < targetphase]
5513 5512 if rejected:
5514 5513 ui.warn(_('cannot move %i changesets to a higher '
5515 5514 'phase, use --force\n') % len(rejected))
5516 5515 ret = 1
5517 5516 if changes:
5518 5517 msg = _('phase changed for %i changesets\n') % changes
5519 5518 if ret:
5520 5519 ui.status(msg)
5521 5520 else:
5522 5521 ui.note(msg)
5523 5522 else:
5524 5523 ui.warn(_('no phases changed\n'))
5525 5524 return ret
5526 5525
5527 5526 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5528 5527 """Run after a changegroup has been added via pull/unbundle
5529 5528
5530 5529 This takes arguments below:
5531 5530
5532 5531 :modheads: change of heads by pull/unbundle
5533 5532 :optupdate: updating working directory is needed or not
5534 5533 :checkout: update destination revision (or None to default destination)
5535 5534 :brev: a name, which might be a bookmark to be activated after updating
5536 5535 """
5537 5536 if modheads == 0:
5538 5537 return
5539 5538 if optupdate:
5540 5539 try:
5541 5540 return hg.updatetotally(ui, repo, checkout, brev)
5542 5541 except error.UpdateAbort as inst:
5543 5542 msg = _("not updating: %s") % str(inst)
5544 5543 hint = inst.hint
5545 5544 raise error.UpdateAbort(msg, hint=hint)
5546 5545 if modheads > 1:
5547 5546 currentbranchheads = len(repo.branchheads())
5548 5547 if currentbranchheads == modheads:
5549 5548 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
5550 5549 elif currentbranchheads > 1:
5551 5550 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
5552 5551 "merge)\n"))
5553 5552 else:
5554 5553 ui.status(_("(run 'hg heads' to see heads)\n"))
5555 5554 else:
5556 5555 ui.status(_("(run 'hg update' to get a working copy)\n"))
5557 5556
5558 5557 @command('^pull',
5559 5558 [('u', 'update', None,
5560 5559 _('update to new branch head if changesets were pulled')),
5561 5560 ('f', 'force', None, _('run even when remote repository is unrelated')),
5562 5561 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
5563 5562 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
5564 5563 ('b', 'branch', [], _('a specific branch you would like to pull'),
5565 5564 _('BRANCH')),
5566 5565 ] + remoteopts,
5567 5566 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
5568 5567 def pull(ui, repo, source="default", **opts):
5569 5568 """pull changes from the specified source
5570 5569
5571 5570 Pull changes from a remote repository to a local one.
5572 5571
5573 5572 This finds all changes from the repository at the specified path
5574 5573 or URL and adds them to a local repository (the current one unless
5575 5574 -R is specified). By default, this does not update the copy of the
5576 5575 project in the working directory.
5577 5576
5578 5577 Use :hg:`incoming` if you want to see what would have been added
5579 5578 by a pull at the time you issued this command. If you then decide
5580 5579 to add those changes to the repository, you should use :hg:`pull
5581 5580 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5582 5581
5583 5582 If SOURCE is omitted, the 'default' path will be used.
5584 5583 See :hg:`help urls` for more information.
5585 5584
5586 5585 Specifying bookmark as ``.`` is equivalent to specifying the active
5587 5586 bookmark's name.
5588 5587
5589 5588 Returns 0 on success, 1 if an update had unresolved files.
5590 5589 """
5591 5590 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
5592 5591 ui.status(_('pulling from %s\n') % util.hidepassword(source))
5593 5592 other = hg.peer(repo, opts, source)
5594 5593 try:
5595 5594 revs, checkout = hg.addbranchrevs(repo, other, branches,
5596 5595 opts.get('rev'))
5597 5596
5598 5597
5599 5598 pullopargs = {}
5600 5599 if opts.get('bookmark'):
5601 5600 if not revs:
5602 5601 revs = []
5603 5602 # The list of bookmark used here is not the one used to actually
5604 5603 # update the bookmark name. This can result in the revision pulled
5605 5604 # not ending up with the name of the bookmark because of a race
5606 5605 # condition on the server. (See issue 4689 for details)
5607 5606 remotebookmarks = other.listkeys('bookmarks')
5608 5607 pullopargs['remotebookmarks'] = remotebookmarks
5609 5608 for b in opts['bookmark']:
5610 5609 b = repo._bookmarks.expandname(b)
5611 5610 if b not in remotebookmarks:
5612 5611 raise error.Abort(_('remote bookmark %s not found!') % b)
5613 5612 revs.append(remotebookmarks[b])
5614 5613
5615 5614 if revs:
5616 5615 try:
5617 5616 # When 'rev' is a bookmark name, we cannot guarantee that it
5618 5617 # will be updated with that name because of a race condition
5619 5618 # server side. (See issue 4689 for details)
5620 5619 oldrevs = revs
5621 5620 revs = [] # actually, nodes
5622 5621 for r in oldrevs:
5623 5622 node = other.lookup(r)
5624 5623 revs.append(node)
5625 5624 if r == checkout:
5626 5625 checkout = node
5627 5626 except error.CapabilityError:
5628 5627 err = _("other repository doesn't support revision lookup, "
5629 5628 "so a rev cannot be specified.")
5630 5629 raise error.Abort(err)
5631 5630
5632 5631 pullopargs.update(opts.get('opargs', {}))
5633 5632 modheads = exchange.pull(repo, other, heads=revs,
5634 5633 force=opts.get('force'),
5635 5634 bookmarks=opts.get('bookmark', ()),
5636 5635 opargs=pullopargs).cgresult
5637 5636
5638 5637 # brev is a name, which might be a bookmark to be activated at
5639 5638 # the end of the update. In other words, it is an explicit
5640 5639 # destination of the update
5641 5640 brev = None
5642 5641
5643 5642 if checkout:
5644 5643 checkout = str(repo.changelog.rev(checkout))
5645 5644
5646 5645 # order below depends on implementation of
5647 5646 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5648 5647 # because 'checkout' is determined without it.
5649 5648 if opts.get('rev'):
5650 5649 brev = opts['rev'][0]
5651 5650 elif opts.get('branch'):
5652 5651 brev = opts['branch'][0]
5653 5652 else:
5654 5653 brev = branches[0]
5655 5654 repo._subtoppath = source
5656 5655 try:
5657 5656 ret = postincoming(ui, repo, modheads, opts.get('update'),
5658 5657 checkout, brev)
5659 5658
5660 5659 finally:
5661 5660 del repo._subtoppath
5662 5661
5663 5662 finally:
5664 5663 other.close()
5665 5664 return ret
5666 5665
5667 5666 @command('^push',
5668 5667 [('f', 'force', None, _('force push')),
5669 5668 ('r', 'rev', [],
5670 5669 _('a changeset intended to be included in the destination'),
5671 5670 _('REV')),
5672 5671 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5673 5672 ('b', 'branch', [],
5674 5673 _('a specific branch you would like to push'), _('BRANCH')),
5675 5674 ('', 'new-branch', False, _('allow pushing a new branch')),
5676 5675 ] + remoteopts,
5677 5676 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5678 5677 def push(ui, repo, dest=None, **opts):
5679 5678 """push changes to the specified destination
5680 5679
5681 5680 Push changesets from the local repository to the specified
5682 5681 destination.
5683 5682
5684 5683 This operation is symmetrical to pull: it is identical to a pull
5685 5684 in the destination repository from the current one.
5686 5685
5687 5686 By default, push will not allow creation of new heads at the
5688 5687 destination, since multiple heads would make it unclear which head
5689 5688 to use. In this situation, it is recommended to pull and merge
5690 5689 before pushing.
5691 5690
5692 5691 Use --new-branch if you want to allow push to create a new named
5693 5692 branch that is not present at the destination. This allows you to
5694 5693 only create a new branch without forcing other changes.
5695 5694
5696 5695 .. note::
5697 5696
5698 5697 Extra care should be taken with the -f/--force option,
5699 5698 which will push all new heads on all branches, an action which will
5700 5699 almost always cause confusion for collaborators.
5701 5700
5702 5701 If -r/--rev is used, the specified revision and all its ancestors
5703 5702 will be pushed to the remote repository.
5704 5703
5705 5704 If -B/--bookmark is used, the specified bookmarked revision, its
5706 5705 ancestors, and the bookmark will be pushed to the remote
5707 5706 repository. Specifying ``.`` is equivalent to specifying the active
5708 5707 bookmark's name.
5709 5708
5710 5709 Please see :hg:`help urls` for important details about ``ssh://``
5711 5710 URLs. If DESTINATION is omitted, a default path will be used.
5712 5711
5713 5712 Returns 0 if push was successful, 1 if nothing to push.
5714 5713 """
5715 5714
5716 5715 if opts.get('bookmark'):
5717 5716 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5718 5717 for b in opts['bookmark']:
5719 5718 # translate -B options to -r so changesets get pushed
5720 5719 b = repo._bookmarks.expandname(b)
5721 5720 if b in repo._bookmarks:
5722 5721 opts.setdefault('rev', []).append(b)
5723 5722 else:
5724 5723 # if we try to push a deleted bookmark, translate it to null
5725 5724 # this lets simultaneous -r, -b options continue working
5726 5725 opts.setdefault('rev', []).append("null")
5727 5726
5728 5727 path = ui.paths.getpath(dest, default=('default-push', 'default'))
5729 5728 if not path:
5730 5729 raise error.Abort(_('default repository not configured!'),
5731 5730 hint=_("see 'hg help config.paths'"))
5732 5731 dest = path.pushloc or path.loc
5733 5732 branches = (path.branch, opts.get('branch') or [])
5734 5733 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5735 5734 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5736 5735 other = hg.peer(repo, opts, dest)
5737 5736
5738 5737 if revs:
5739 5738 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5740 5739 if not revs:
5741 5740 raise error.Abort(_("specified revisions evaluate to an empty set"),
5742 5741 hint=_("use different revision arguments"))
5743 5742 elif path.pushrev:
5744 5743 # It doesn't make any sense to specify ancestor revisions. So limit
5745 5744 # to DAG heads to make discovery simpler.
5746 5745 expr = revset.formatspec('heads(%r)', path.pushrev)
5747 5746 revs = scmutil.revrange(repo, [expr])
5748 5747 revs = [repo[rev].node() for rev in revs]
5749 5748 if not revs:
5750 5749 raise error.Abort(_('default push revset for path evaluates to an '
5751 5750 'empty set'))
5752 5751
5753 5752 repo._subtoppath = dest
5754 5753 try:
5755 5754 # push subrepos depth-first for coherent ordering
5756 5755 c = repo['']
5757 5756 subs = c.substate # only repos that are committed
5758 5757 for s in sorted(subs):
5759 5758 result = c.sub(s).push(opts)
5760 5759 if result == 0:
5761 5760 return not result
5762 5761 finally:
5763 5762 del repo._subtoppath
5764 5763 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5765 5764 newbranch=opts.get('new_branch'),
5766 5765 bookmarks=opts.get('bookmark', ()),
5767 5766 opargs=opts.get('opargs'))
5768 5767
5769 5768 result = not pushop.cgresult
5770 5769
5771 5770 if pushop.bkresult is not None:
5772 5771 if pushop.bkresult == 2:
5773 5772 result = 2
5774 5773 elif not result and pushop.bkresult:
5775 5774 result = 2
5776 5775
5777 5776 return result
5778 5777
5779 5778 @command('recover', [])
5780 5779 def recover(ui, repo):
5781 5780 """roll back an interrupted transaction
5782 5781
5783 5782 Recover from an interrupted commit or pull.
5784 5783
5785 5784 This command tries to fix the repository status after an
5786 5785 interrupted operation. It should only be necessary when Mercurial
5787 5786 suggests it.
5788 5787
5789 5788 Returns 0 if successful, 1 if nothing to recover or verify fails.
5790 5789 """
5791 5790 if repo.recover():
5792 5791 return hg.verify(repo)
5793 5792 return 1
5794 5793
5795 5794 @command('^remove|rm',
5796 5795 [('A', 'after', None, _('record delete for missing files')),
5797 5796 ('f', 'force', None,
5798 5797 _('forget added files, delete modified files')),
5799 5798 ] + subrepoopts + walkopts,
5800 5799 _('[OPTION]... FILE...'),
5801 5800 inferrepo=True)
5802 5801 def remove(ui, repo, *pats, **opts):
5803 5802 """remove the specified files on the next commit
5804 5803
5805 5804 Schedule the indicated files for removal from the current branch.
5806 5805
5807 5806 This command schedules the files to be removed at the next commit.
5808 5807 To undo a remove before that, see :hg:`revert`. To undo added
5809 5808 files, see :hg:`forget`.
5810 5809
5811 5810 .. container:: verbose
5812 5811
5813 5812 -A/--after can be used to remove only files that have already
5814 5813 been deleted, -f/--force can be used to force deletion, and -Af
5815 5814 can be used to remove files from the next revision without
5816 5815 deleting them from the working directory.
5817 5816
5818 5817 The following table details the behavior of remove for different
5819 5818 file states (columns) and option combinations (rows). The file
5820 5819 states are Added [A], Clean [C], Modified [M] and Missing [!]
5821 5820 (as reported by :hg:`status`). The actions are Warn, Remove
5822 5821 (from branch) and Delete (from disk):
5823 5822
5824 5823 ========= == == == ==
5825 5824 opt/state A C M !
5826 5825 ========= == == == ==
5827 5826 none W RD W R
5828 5827 -f R RD RD R
5829 5828 -A W W W R
5830 5829 -Af R R R R
5831 5830 ========= == == == ==
5832 5831
5833 5832 .. note::
5834 5833
5835 5834 :hg:`remove` never deletes files in Added [A] state from the
5836 5835 working directory, not even if ``--force`` is specified.
5837 5836
5838 5837 Returns 0 on success, 1 if any warnings encountered.
5839 5838 """
5840 5839
5841 5840 after, force = opts.get('after'), opts.get('force')
5842 5841 if not pats and not after:
5843 5842 raise error.Abort(_('no files specified'))
5844 5843
5845 5844 m = scmutil.match(repo[None], pats, opts)
5846 5845 subrepos = opts.get('subrepos')
5847 5846 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5848 5847
5849 5848 @command('rename|move|mv',
5850 5849 [('A', 'after', None, _('record a rename that has already occurred')),
5851 5850 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5852 5851 ] + walkopts + dryrunopts,
5853 5852 _('[OPTION]... SOURCE... DEST'))
5854 5853 def rename(ui, repo, *pats, **opts):
5855 5854 """rename files; equivalent of copy + remove
5856 5855
5857 5856 Mark dest as copies of sources; mark sources for deletion. If dest
5858 5857 is a directory, copies are put in that directory. If dest is a
5859 5858 file, there can only be one source.
5860 5859
5861 5860 By default, this command copies the contents of files as they
5862 5861 exist in the working directory. If invoked with -A/--after, the
5863 5862 operation is recorded, but no copying is performed.
5864 5863
5865 5864 This command takes effect at the next commit. To undo a rename
5866 5865 before that, see :hg:`revert`.
5867 5866
5868 5867 Returns 0 on success, 1 if errors are encountered.
5869 5868 """
5870 5869 with repo.wlock(False):
5871 5870 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5872 5871
5873 5872 @command('resolve',
5874 5873 [('a', 'all', None, _('select all unresolved files')),
5875 5874 ('l', 'list', None, _('list state of files needing merge')),
5876 5875 ('m', 'mark', None, _('mark files as resolved')),
5877 5876 ('u', 'unmark', None, _('mark files as unresolved')),
5878 5877 ('n', 'no-status', None, _('hide status prefix'))]
5879 5878 + mergetoolopts + walkopts + formatteropts,
5880 5879 _('[OPTION]... [FILE]...'),
5881 5880 inferrepo=True)
5882 5881 def resolve(ui, repo, *pats, **opts):
5883 5882 """redo merges or set/view the merge status of files
5884 5883
5885 5884 Merges with unresolved conflicts are often the result of
5886 5885 non-interactive merging using the ``internal:merge`` configuration
5887 5886 setting, or a command-line merge tool like ``diff3``. The resolve
5888 5887 command is used to manage the files involved in a merge, after
5889 5888 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5890 5889 working directory must have two parents). See :hg:`help
5891 5890 merge-tools` for information on configuring merge tools.
5892 5891
5893 5892 The resolve command can be used in the following ways:
5894 5893
5895 5894 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5896 5895 files, discarding any previous merge attempts. Re-merging is not
5897 5896 performed for files already marked as resolved. Use ``--all/-a``
5898 5897 to select all unresolved files. ``--tool`` can be used to specify
5899 5898 the merge tool used for the given files. It overrides the HGMERGE
5900 5899 environment variable and your configuration files. Previous file
5901 5900 contents are saved with a ``.orig`` suffix.
5902 5901
5903 5902 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5904 5903 (e.g. after having manually fixed-up the files). The default is
5905 5904 to mark all unresolved files.
5906 5905
5907 5906 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5908 5907 default is to mark all resolved files.
5909 5908
5910 5909 - :hg:`resolve -l`: list files which had or still have conflicts.
5911 5910 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5912 5911
5913 5912 .. note::
5914 5913
5915 5914 Mercurial will not let you commit files with unresolved merge
5916 5915 conflicts. You must use :hg:`resolve -m ...` before you can
5917 5916 commit after a conflicting merge.
5918 5917
5919 5918 Returns 0 on success, 1 if any files fail a resolve attempt.
5920 5919 """
5921 5920
5922 5921 flaglist = 'all mark unmark list no_status'.split()
5923 5922 all, mark, unmark, show, nostatus = \
5924 5923 [opts.get(o) for o in flaglist]
5925 5924
5926 5925 if (show and (mark or unmark)) or (mark and unmark):
5927 5926 raise error.Abort(_("too many options specified"))
5928 5927 if pats and all:
5929 5928 raise error.Abort(_("can't specify --all and patterns"))
5930 5929 if not (all or pats or show or mark or unmark):
5931 5930 raise error.Abort(_('no files or directories specified'),
5932 5931 hint=('use --all to re-merge all unresolved files'))
5933 5932
5934 5933 if show:
5935 5934 fm = ui.formatter('resolve', opts)
5936 5935 ms = mergemod.mergestate.read(repo)
5937 5936 m = scmutil.match(repo[None], pats, opts)
5938 5937 for f in ms:
5939 5938 if not m(f):
5940 5939 continue
5941 5940 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
5942 5941 'd': 'driverresolved'}[ms[f]]
5943 5942 fm.startitem()
5944 5943 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
5945 5944 fm.write('path', '%s\n', f, label=l)
5946 5945 fm.end()
5947 5946 return 0
5948 5947
5949 5948 with repo.wlock():
5950 5949 ms = mergemod.mergestate.read(repo)
5951 5950
5952 5951 if not (ms.active() or repo.dirstate.p2() != nullid):
5953 5952 raise error.Abort(
5954 5953 _('resolve command not applicable when not merging'))
5955 5954
5956 5955 wctx = repo[None]
5957 5956
5958 5957 if ms.mergedriver and ms.mdstate() == 'u':
5959 5958 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5960 5959 ms.commit()
5961 5960 # allow mark and unmark to go through
5962 5961 if not mark and not unmark and not proceed:
5963 5962 return 1
5964 5963
5965 5964 m = scmutil.match(wctx, pats, opts)
5966 5965 ret = 0
5967 5966 didwork = False
5968 5967 runconclude = False
5969 5968
5970 5969 tocomplete = []
5971 5970 for f in ms:
5972 5971 if not m(f):
5973 5972 continue
5974 5973
5975 5974 didwork = True
5976 5975
5977 5976 # don't let driver-resolved files be marked, and run the conclude
5978 5977 # step if asked to resolve
5979 5978 if ms[f] == "d":
5980 5979 exact = m.exact(f)
5981 5980 if mark:
5982 5981 if exact:
5983 5982 ui.warn(_('not marking %s as it is driver-resolved\n')
5984 5983 % f)
5985 5984 elif unmark:
5986 5985 if exact:
5987 5986 ui.warn(_('not unmarking %s as it is driver-resolved\n')
5988 5987 % f)
5989 5988 else:
5990 5989 runconclude = True
5991 5990 continue
5992 5991
5993 5992 if mark:
5994 5993 ms.mark(f, "r")
5995 5994 elif unmark:
5996 5995 ms.mark(f, "u")
5997 5996 else:
5998 5997 # backup pre-resolve (merge uses .orig for its own purposes)
5999 5998 a = repo.wjoin(f)
6000 5999 try:
6001 6000 util.copyfile(a, a + ".resolve")
6002 6001 except (IOError, OSError) as inst:
6003 6002 if inst.errno != errno.ENOENT:
6004 6003 raise
6005 6004
6006 6005 try:
6007 6006 # preresolve file
6008 6007 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6009 6008 'resolve')
6010 6009 complete, r = ms.preresolve(f, wctx)
6011 6010 if not complete:
6012 6011 tocomplete.append(f)
6013 6012 elif r:
6014 6013 ret = 1
6015 6014 finally:
6016 6015 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6017 6016 ms.commit()
6018 6017
6019 6018 # replace filemerge's .orig file with our resolve file, but only
6020 6019 # for merges that are complete
6021 6020 if complete:
6022 6021 try:
6023 6022 util.rename(a + ".resolve",
6024 6023 scmutil.origpath(ui, repo, a))
6025 6024 except OSError as inst:
6026 6025 if inst.errno != errno.ENOENT:
6027 6026 raise
6028 6027
6029 6028 for f in tocomplete:
6030 6029 try:
6031 6030 # resolve file
6032 6031 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
6033 6032 'resolve')
6034 6033 r = ms.resolve(f, wctx)
6035 6034 if r:
6036 6035 ret = 1
6037 6036 finally:
6038 6037 ui.setconfig('ui', 'forcemerge', '', 'resolve')
6039 6038 ms.commit()
6040 6039
6041 6040 # replace filemerge's .orig file with our resolve file
6042 6041 a = repo.wjoin(f)
6043 6042 try:
6044 6043 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
6045 6044 except OSError as inst:
6046 6045 if inst.errno != errno.ENOENT:
6047 6046 raise
6048 6047
6049 6048 ms.commit()
6050 6049 ms.recordactions()
6051 6050
6052 6051 if not didwork and pats:
6053 6052 hint = None
6054 6053 if not any([p for p in pats if p.find(':') >= 0]):
6055 6054 pats = ['path:%s' % p for p in pats]
6056 6055 m = scmutil.match(wctx, pats, opts)
6057 6056 for f in ms:
6058 6057 if not m(f):
6059 6058 continue
6060 6059 flags = ''.join(['-%s ' % o[0] for o in flaglist
6061 6060 if opts.get(o)])
6062 6061 hint = _("(try: hg resolve %s%s)\n") % (
6063 6062 flags,
6064 6063 ' '.join(pats))
6065 6064 break
6066 6065 ui.warn(_("arguments do not match paths that need resolving\n"))
6067 6066 if hint:
6068 6067 ui.warn(hint)
6069 6068 elif ms.mergedriver and ms.mdstate() != 's':
6070 6069 # run conclude step when either a driver-resolved file is requested
6071 6070 # or there are no driver-resolved files
6072 6071 # we can't use 'ret' to determine whether any files are unresolved
6073 6072 # because we might not have tried to resolve some
6074 6073 if ((runconclude or not list(ms.driverresolved()))
6075 6074 and not list(ms.unresolved())):
6076 6075 proceed = mergemod.driverconclude(repo, ms, wctx)
6077 6076 ms.commit()
6078 6077 if not proceed:
6079 6078 return 1
6080 6079
6081 6080 # Nudge users into finishing an unfinished operation
6082 6081 unresolvedf = list(ms.unresolved())
6083 6082 driverresolvedf = list(ms.driverresolved())
6084 6083 if not unresolvedf and not driverresolvedf:
6085 6084 ui.status(_('(no more unresolved files)\n'))
6086 6085 cmdutil.checkafterresolved(repo)
6087 6086 elif not unresolvedf:
6088 6087 ui.status(_('(no more unresolved files -- '
6089 6088 'run "hg resolve --all" to conclude)\n'))
6090 6089
6091 6090 return ret
6092 6091
6093 6092 @command('revert',
6094 6093 [('a', 'all', None, _('revert all changes when no arguments given')),
6095 6094 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6096 6095 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
6097 6096 ('C', 'no-backup', None, _('do not save backup copies of files')),
6098 6097 ('i', 'interactive', None,
6099 6098 _('interactively select the changes (EXPERIMENTAL)')),
6100 6099 ] + walkopts + dryrunopts,
6101 6100 _('[OPTION]... [-r REV] [NAME]...'))
6102 6101 def revert(ui, repo, *pats, **opts):
6103 6102 """restore files to their checkout state
6104 6103
6105 6104 .. note::
6106 6105
6107 6106 To check out earlier revisions, you should use :hg:`update REV`.
6108 6107 To cancel an uncommitted merge (and lose your changes),
6109 6108 use :hg:`update --clean .`.
6110 6109
6111 6110 With no revision specified, revert the specified files or directories
6112 6111 to the contents they had in the parent of the working directory.
6113 6112 This restores the contents of files to an unmodified
6114 6113 state and unschedules adds, removes, copies, and renames. If the
6115 6114 working directory has two parents, you must explicitly specify a
6116 6115 revision.
6117 6116
6118 6117 Using the -r/--rev or -d/--date options, revert the given files or
6119 6118 directories to their states as of a specific revision. Because
6120 6119 revert does not change the working directory parents, this will
6121 6120 cause these files to appear modified. This can be helpful to "back
6122 6121 out" some or all of an earlier change. See :hg:`backout` for a
6123 6122 related method.
6124 6123
6125 6124 Modified files are saved with a .orig suffix before reverting.
6126 6125 To disable these backups, use --no-backup. It is possible to store
6127 6126 the backup files in a custom directory relative to the root of the
6128 6127 repository by setting the ``ui.origbackuppath`` configuration
6129 6128 option.
6130 6129
6131 6130 See :hg:`help dates` for a list of formats valid for -d/--date.
6132 6131
6133 6132 See :hg:`help backout` for a way to reverse the effect of an
6134 6133 earlier changeset.
6135 6134
6136 6135 Returns 0 on success.
6137 6136 """
6138 6137
6139 6138 if opts.get("date"):
6140 6139 if opts.get("rev"):
6141 6140 raise error.Abort(_("you can't specify a revision and a date"))
6142 6141 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
6143 6142
6144 6143 parent, p2 = repo.dirstate.parents()
6145 6144 if not opts.get('rev') and p2 != nullid:
6146 6145 # revert after merge is a trap for new users (issue2915)
6147 6146 raise error.Abort(_('uncommitted merge with no revision specified'),
6148 6147 hint=_("use 'hg update' or see 'hg help revert'"))
6149 6148
6150 6149 ctx = scmutil.revsingle(repo, opts.get('rev'))
6151 6150
6152 6151 if (not (pats or opts.get('include') or opts.get('exclude') or
6153 6152 opts.get('all') or opts.get('interactive'))):
6154 6153 msg = _("no files or directories specified")
6155 6154 if p2 != nullid:
6156 6155 hint = _("uncommitted merge, use --all to discard all changes,"
6157 6156 " or 'hg update -C .' to abort the merge")
6158 6157 raise error.Abort(msg, hint=hint)
6159 6158 dirty = any(repo.status())
6160 6159 node = ctx.node()
6161 6160 if node != parent:
6162 6161 if dirty:
6163 6162 hint = _("uncommitted changes, use --all to discard all"
6164 6163 " changes, or 'hg update %s' to update") % ctx.rev()
6165 6164 else:
6166 6165 hint = _("use --all to revert all files,"
6167 6166 " or 'hg update %s' to update") % ctx.rev()
6168 6167 elif dirty:
6169 6168 hint = _("uncommitted changes, use --all to discard all changes")
6170 6169 else:
6171 6170 hint = _("use --all to revert all files")
6172 6171 raise error.Abort(msg, hint=hint)
6173 6172
6174 6173 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
6175 6174
6176 6175 @command('rollback', dryrunopts +
6177 6176 [('f', 'force', False, _('ignore safety measures'))])
6178 6177 def rollback(ui, repo, **opts):
6179 6178 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6180 6179
6181 6180 Please use :hg:`commit --amend` instead of rollback to correct
6182 6181 mistakes in the last commit.
6183 6182
6184 6183 This command should be used with care. There is only one level of
6185 6184 rollback, and there is no way to undo a rollback. It will also
6186 6185 restore the dirstate at the time of the last transaction, losing
6187 6186 any dirstate changes since that time. This command does not alter
6188 6187 the working directory.
6189 6188
6190 6189 Transactions are used to encapsulate the effects of all commands
6191 6190 that create new changesets or propagate existing changesets into a
6192 6191 repository.
6193 6192
6194 6193 .. container:: verbose
6195 6194
6196 6195 For example, the following commands are transactional, and their
6197 6196 effects can be rolled back:
6198 6197
6199 6198 - commit
6200 6199 - import
6201 6200 - pull
6202 6201 - push (with this repository as the destination)
6203 6202 - unbundle
6204 6203
6205 6204 To avoid permanent data loss, rollback will refuse to rollback a
6206 6205 commit transaction if it isn't checked out. Use --force to
6207 6206 override this protection.
6208 6207
6209 6208 The rollback command can be entirely disabled by setting the
6210 6209 ``ui.rollback`` configuration setting to false. If you're here
6211 6210 because you want to use rollback and it's disabled, you can
6212 6211 re-enable the command by setting ``ui.rollback`` to true.
6213 6212
6214 6213 This command is not intended for use on public repositories. Once
6215 6214 changes are visible for pull by other users, rolling a transaction
6216 6215 back locally is ineffective (someone else may already have pulled
6217 6216 the changes). Furthermore, a race is possible with readers of the
6218 6217 repository; for example an in-progress pull from the repository
6219 6218 may fail if a rollback is performed.
6220 6219
6221 6220 Returns 0 on success, 1 if no rollback data is available.
6222 6221 """
6223 6222 if not ui.configbool('ui', 'rollback', True):
6224 6223 raise error.Abort(_('rollback is disabled because it is unsafe'),
6225 6224 hint=('see `hg help -v rollback` for information'))
6226 6225 return repo.rollback(dryrun=opts.get('dry_run'),
6227 6226 force=opts.get('force'))
6228 6227
6229 6228 @command('root', [])
6230 6229 def root(ui, repo):
6231 6230 """print the root (top) of the current working directory
6232 6231
6233 6232 Print the root directory of the current repository.
6234 6233
6235 6234 Returns 0 on success.
6236 6235 """
6237 6236 ui.write(repo.root + "\n")
6238 6237
6239 6238 @command('^serve',
6240 6239 [('A', 'accesslog', '', _('name of access log file to write to'),
6241 6240 _('FILE')),
6242 6241 ('d', 'daemon', None, _('run server in background')),
6243 6242 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
6244 6243 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
6245 6244 # use string type, then we can check if something was passed
6246 6245 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
6247 6246 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
6248 6247 _('ADDR')),
6249 6248 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
6250 6249 _('PREFIX')),
6251 6250 ('n', 'name', '',
6252 6251 _('name to show in web pages (default: working directory)'), _('NAME')),
6253 6252 ('', 'web-conf', '',
6254 6253 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
6255 6254 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
6256 6255 _('FILE')),
6257 6256 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
6258 6257 ('', 'stdio', None, _('for remote clients')),
6259 6258 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
6260 6259 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
6261 6260 ('', 'style', '', _('template style to use'), _('STYLE')),
6262 6261 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
6263 6262 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
6264 6263 _('[OPTION]...'),
6265 6264 optionalrepo=True)
6266 6265 def serve(ui, repo, **opts):
6267 6266 """start stand-alone webserver
6268 6267
6269 6268 Start a local HTTP repository browser and pull server. You can use
6270 6269 this for ad-hoc sharing and browsing of repositories. It is
6271 6270 recommended to use a real web server to serve a repository for
6272 6271 longer periods of time.
6273 6272
6274 6273 Please note that the server does not implement access control.
6275 6274 This means that, by default, anybody can read from the server and
6276 6275 nobody can write to it by default. Set the ``web.allow_push``
6277 6276 option to ``*`` to allow everybody to push to the server. You
6278 6277 should use a real web server if you need to authenticate users.
6279 6278
6280 6279 By default, the server logs accesses to stdout and errors to
6281 6280 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6282 6281 files.
6283 6282
6284 6283 To have the server choose a free port number to listen on, specify
6285 6284 a port number of 0; in this case, the server will print the port
6286 6285 number it uses.
6287 6286
6288 6287 Returns 0 on success.
6289 6288 """
6290 6289
6291 6290 if opts["stdio"] and opts["cmdserver"]:
6292 6291 raise error.Abort(_("cannot use --stdio with --cmdserver"))
6293 6292
6294 6293 if opts["stdio"]:
6295 6294 if repo is None:
6296 6295 raise error.RepoError(_("there is no Mercurial repository here"
6297 6296 " (.hg not found)"))
6298 6297 s = sshserver.sshserver(ui, repo)
6299 6298 s.serve_forever()
6300 6299
6301 6300 if opts["cmdserver"]:
6302 service = commandserver.createservice(ui, repo, opts)
6301 service = server.createcmdservice(ui, repo, opts)
6303 6302 else:
6304 6303 service = hgweb.createservice(ui, repo, opts)
6305 6304 return server.runservice(opts, initfn=service.init, runfn=service.run)
6306 6305
6307 6306 @command('^status|st',
6308 6307 [('A', 'all', None, _('show status of all files')),
6309 6308 ('m', 'modified', None, _('show only modified files')),
6310 6309 ('a', 'added', None, _('show only added files')),
6311 6310 ('r', 'removed', None, _('show only removed files')),
6312 6311 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
6313 6312 ('c', 'clean', None, _('show only files without changes')),
6314 6313 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
6315 6314 ('i', 'ignored', None, _('show only ignored files')),
6316 6315 ('n', 'no-status', None, _('hide status prefix')),
6317 6316 ('C', 'copies', None, _('show source of copied files')),
6318 6317 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
6319 6318 ('', 'rev', [], _('show difference from revision'), _('REV')),
6320 6319 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
6321 6320 ] + walkopts + subrepoopts + formatteropts,
6322 6321 _('[OPTION]... [FILE]...'),
6323 6322 inferrepo=True)
6324 6323 def status(ui, repo, *pats, **opts):
6325 6324 """show changed files in the working directory
6326 6325
6327 6326 Show status of files in the repository. If names are given, only
6328 6327 files that match are shown. Files that are clean or ignored or
6329 6328 the source of a copy/move operation, are not listed unless
6330 6329 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6331 6330 Unless options described with "show only ..." are given, the
6332 6331 options -mardu are used.
6333 6332
6334 6333 Option -q/--quiet hides untracked (unknown and ignored) files
6335 6334 unless explicitly requested with -u/--unknown or -i/--ignored.
6336 6335
6337 6336 .. note::
6338 6337
6339 6338 :hg:`status` may appear to disagree with diff if permissions have
6340 6339 changed or a merge has occurred. The standard diff format does
6341 6340 not report permission changes and diff only reports changes
6342 6341 relative to one merge parent.
6343 6342
6344 6343 If one revision is given, it is used as the base revision.
6345 6344 If two revisions are given, the differences between them are
6346 6345 shown. The --change option can also be used as a shortcut to list
6347 6346 the changed files of a revision from its first parent.
6348 6347
6349 6348 The codes used to show the status of files are::
6350 6349
6351 6350 M = modified
6352 6351 A = added
6353 6352 R = removed
6354 6353 C = clean
6355 6354 ! = missing (deleted by non-hg command, but still tracked)
6356 6355 ? = not tracked
6357 6356 I = ignored
6358 6357 = origin of the previous file (with --copies)
6359 6358
6360 6359 .. container:: verbose
6361 6360
6362 6361 Examples:
6363 6362
6364 6363 - show changes in the working directory relative to a
6365 6364 changeset::
6366 6365
6367 6366 hg status --rev 9353
6368 6367
6369 6368 - show changes in the working directory relative to the
6370 6369 current directory (see :hg:`help patterns` for more information)::
6371 6370
6372 6371 hg status re:
6373 6372
6374 6373 - show all changes including copies in an existing changeset::
6375 6374
6376 6375 hg status --copies --change 9353
6377 6376
6378 6377 - get a NUL separated list of added files, suitable for xargs::
6379 6378
6380 6379 hg status -an0
6381 6380
6382 6381 Returns 0 on success.
6383 6382 """
6384 6383
6385 6384 revs = opts.get('rev')
6386 6385 change = opts.get('change')
6387 6386
6388 6387 if revs and change:
6389 6388 msg = _('cannot specify --rev and --change at the same time')
6390 6389 raise error.Abort(msg)
6391 6390 elif change:
6392 6391 node2 = scmutil.revsingle(repo, change, None).node()
6393 6392 node1 = repo[node2].p1().node()
6394 6393 else:
6395 6394 node1, node2 = scmutil.revpair(repo, revs)
6396 6395
6397 6396 if pats:
6398 6397 cwd = repo.getcwd()
6399 6398 else:
6400 6399 cwd = ''
6401 6400
6402 6401 if opts.get('print0'):
6403 6402 end = '\0'
6404 6403 else:
6405 6404 end = '\n'
6406 6405 copy = {}
6407 6406 states = 'modified added removed deleted unknown ignored clean'.split()
6408 6407 show = [k for k in states if opts.get(k)]
6409 6408 if opts.get('all'):
6410 6409 show += ui.quiet and (states[:4] + ['clean']) or states
6411 6410 if not show:
6412 6411 if ui.quiet:
6413 6412 show = states[:4]
6414 6413 else:
6415 6414 show = states[:5]
6416 6415
6417 6416 m = scmutil.match(repo[node2], pats, opts)
6418 6417 stat = repo.status(node1, node2, m,
6419 6418 'ignored' in show, 'clean' in show, 'unknown' in show,
6420 6419 opts.get('subrepos'))
6421 6420 changestates = zip(states, 'MAR!?IC', stat)
6422 6421
6423 6422 if (opts.get('all') or opts.get('copies')
6424 6423 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
6425 6424 copy = copies.pathcopies(repo[node1], repo[node2], m)
6426 6425
6427 6426 fm = ui.formatter('status', opts)
6428 6427 fmt = '%s' + end
6429 6428 showchar = not opts.get('no_status')
6430 6429
6431 6430 for state, char, files in changestates:
6432 6431 if state in show:
6433 6432 label = 'status.' + state
6434 6433 for f in files:
6435 6434 fm.startitem()
6436 6435 fm.condwrite(showchar, 'status', '%s ', char, label=label)
6437 6436 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
6438 6437 if f in copy:
6439 6438 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
6440 6439 label='status.copied')
6441 6440 fm.end()
6442 6441
6443 6442 @command('^summary|sum',
6444 6443 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
6445 6444 def summary(ui, repo, **opts):
6446 6445 """summarize working directory state
6447 6446
6448 6447 This generates a brief summary of the working directory state,
6449 6448 including parents, branch, commit status, phase and available updates.
6450 6449
6451 6450 With the --remote option, this will check the default paths for
6452 6451 incoming and outgoing changes. This can be time-consuming.
6453 6452
6454 6453 Returns 0 on success.
6455 6454 """
6456 6455
6457 6456 ctx = repo[None]
6458 6457 parents = ctx.parents()
6459 6458 pnode = parents[0].node()
6460 6459 marks = []
6461 6460
6462 6461 ms = None
6463 6462 try:
6464 6463 ms = mergemod.mergestate.read(repo)
6465 6464 except error.UnsupportedMergeRecords as e:
6466 6465 s = ' '.join(e.recordtypes)
6467 6466 ui.warn(
6468 6467 _('warning: merge state has unsupported record types: %s\n') % s)
6469 6468 unresolved = 0
6470 6469 else:
6471 6470 unresolved = [f for f in ms if ms[f] == 'u']
6472 6471
6473 6472 for p in parents:
6474 6473 # label with log.changeset (instead of log.parent) since this
6475 6474 # shows a working directory parent *changeset*:
6476 6475 # i18n: column positioning for "hg summary"
6477 6476 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
6478 6477 label='log.changeset changeset.%s' % p.phasestr())
6479 6478 ui.write(' '.join(p.tags()), label='log.tag')
6480 6479 if p.bookmarks():
6481 6480 marks.extend(p.bookmarks())
6482 6481 if p.rev() == -1:
6483 6482 if not len(repo):
6484 6483 ui.write(_(' (empty repository)'))
6485 6484 else:
6486 6485 ui.write(_(' (no revision checked out)'))
6487 6486 ui.write('\n')
6488 6487 if p.description():
6489 6488 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
6490 6489 label='log.summary')
6491 6490
6492 6491 branch = ctx.branch()
6493 6492 bheads = repo.branchheads(branch)
6494 6493 # i18n: column positioning for "hg summary"
6495 6494 m = _('branch: %s\n') % branch
6496 6495 if branch != 'default':
6497 6496 ui.write(m, label='log.branch')
6498 6497 else:
6499 6498 ui.status(m, label='log.branch')
6500 6499
6501 6500 if marks:
6502 6501 active = repo._activebookmark
6503 6502 # i18n: column positioning for "hg summary"
6504 6503 ui.write(_('bookmarks:'), label='log.bookmark')
6505 6504 if active is not None:
6506 6505 if active in marks:
6507 6506 ui.write(' *' + active, label=activebookmarklabel)
6508 6507 marks.remove(active)
6509 6508 else:
6510 6509 ui.write(' [%s]' % active, label=activebookmarklabel)
6511 6510 for m in marks:
6512 6511 ui.write(' ' + m, label='log.bookmark')
6513 6512 ui.write('\n', label='log.bookmark')
6514 6513
6515 6514 status = repo.status(unknown=True)
6516 6515
6517 6516 c = repo.dirstate.copies()
6518 6517 copied, renamed = [], []
6519 6518 for d, s in c.iteritems():
6520 6519 if s in status.removed:
6521 6520 status.removed.remove(s)
6522 6521 renamed.append(d)
6523 6522 else:
6524 6523 copied.append(d)
6525 6524 if d in status.added:
6526 6525 status.added.remove(d)
6527 6526
6528 6527 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6529 6528
6530 6529 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
6531 6530 (ui.label(_('%d added'), 'status.added'), status.added),
6532 6531 (ui.label(_('%d removed'), 'status.removed'), status.removed),
6533 6532 (ui.label(_('%d renamed'), 'status.copied'), renamed),
6534 6533 (ui.label(_('%d copied'), 'status.copied'), copied),
6535 6534 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
6536 6535 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
6537 6536 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
6538 6537 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
6539 6538 t = []
6540 6539 for l, s in labels:
6541 6540 if s:
6542 6541 t.append(l % len(s))
6543 6542
6544 6543 t = ', '.join(t)
6545 6544 cleanworkdir = False
6546 6545
6547 6546 if repo.vfs.exists('graftstate'):
6548 6547 t += _(' (graft in progress)')
6549 6548 if repo.vfs.exists('updatestate'):
6550 6549 t += _(' (interrupted update)')
6551 6550 elif len(parents) > 1:
6552 6551 t += _(' (merge)')
6553 6552 elif branch != parents[0].branch():
6554 6553 t += _(' (new branch)')
6555 6554 elif (parents[0].closesbranch() and
6556 6555 pnode in repo.branchheads(branch, closed=True)):
6557 6556 t += _(' (head closed)')
6558 6557 elif not (status.modified or status.added or status.removed or renamed or
6559 6558 copied or subs):
6560 6559 t += _(' (clean)')
6561 6560 cleanworkdir = True
6562 6561 elif pnode not in bheads:
6563 6562 t += _(' (new branch head)')
6564 6563
6565 6564 if parents:
6566 6565 pendingphase = max(p.phase() for p in parents)
6567 6566 else:
6568 6567 pendingphase = phases.public
6569 6568
6570 6569 if pendingphase > phases.newcommitphase(ui):
6571 6570 t += ' (%s)' % phases.phasenames[pendingphase]
6572 6571
6573 6572 if cleanworkdir:
6574 6573 # i18n: column positioning for "hg summary"
6575 6574 ui.status(_('commit: %s\n') % t.strip())
6576 6575 else:
6577 6576 # i18n: column positioning for "hg summary"
6578 6577 ui.write(_('commit: %s\n') % t.strip())
6579 6578
6580 6579 # all ancestors of branch heads - all ancestors of parent = new csets
6581 6580 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
6582 6581 bheads))
6583 6582
6584 6583 if new == 0:
6585 6584 # i18n: column positioning for "hg summary"
6586 6585 ui.status(_('update: (current)\n'))
6587 6586 elif pnode not in bheads:
6588 6587 # i18n: column positioning for "hg summary"
6589 6588 ui.write(_('update: %d new changesets (update)\n') % new)
6590 6589 else:
6591 6590 # i18n: column positioning for "hg summary"
6592 6591 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
6593 6592 (new, len(bheads)))
6594 6593
6595 6594 t = []
6596 6595 draft = len(repo.revs('draft()'))
6597 6596 if draft:
6598 6597 t.append(_('%d draft') % draft)
6599 6598 secret = len(repo.revs('secret()'))
6600 6599 if secret:
6601 6600 t.append(_('%d secret') % secret)
6602 6601
6603 6602 if draft or secret:
6604 6603 ui.status(_('phases: %s\n') % ', '.join(t))
6605 6604
6606 6605 if obsolete.isenabled(repo, obsolete.createmarkersopt):
6607 6606 for trouble in ("unstable", "divergent", "bumped"):
6608 6607 numtrouble = len(repo.revs(trouble + "()"))
6609 6608 # We write all the possibilities to ease translation
6610 6609 troublemsg = {
6611 6610 "unstable": _("unstable: %d changesets"),
6612 6611 "divergent": _("divergent: %d changesets"),
6613 6612 "bumped": _("bumped: %d changesets"),
6614 6613 }
6615 6614 if numtrouble > 0:
6616 6615 ui.status(troublemsg[trouble] % numtrouble + "\n")
6617 6616
6618 6617 cmdutil.summaryhooks(ui, repo)
6619 6618
6620 6619 if opts.get('remote'):
6621 6620 needsincoming, needsoutgoing = True, True
6622 6621 else:
6623 6622 needsincoming, needsoutgoing = False, False
6624 6623 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
6625 6624 if i:
6626 6625 needsincoming = True
6627 6626 if o:
6628 6627 needsoutgoing = True
6629 6628 if not needsincoming and not needsoutgoing:
6630 6629 return
6631 6630
6632 6631 def getincoming():
6633 6632 source, branches = hg.parseurl(ui.expandpath('default'))
6634 6633 sbranch = branches[0]
6635 6634 try:
6636 6635 other = hg.peer(repo, {}, source)
6637 6636 except error.RepoError:
6638 6637 if opts.get('remote'):
6639 6638 raise
6640 6639 return source, sbranch, None, None, None
6641 6640 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
6642 6641 if revs:
6643 6642 revs = [other.lookup(rev) for rev in revs]
6644 6643 ui.debug('comparing with %s\n' % util.hidepassword(source))
6645 6644 repo.ui.pushbuffer()
6646 6645 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
6647 6646 repo.ui.popbuffer()
6648 6647 return source, sbranch, other, commoninc, commoninc[1]
6649 6648
6650 6649 if needsincoming:
6651 6650 source, sbranch, sother, commoninc, incoming = getincoming()
6652 6651 else:
6653 6652 source = sbranch = sother = commoninc = incoming = None
6654 6653
6655 6654 def getoutgoing():
6656 6655 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
6657 6656 dbranch = branches[0]
6658 6657 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
6659 6658 if source != dest:
6660 6659 try:
6661 6660 dother = hg.peer(repo, {}, dest)
6662 6661 except error.RepoError:
6663 6662 if opts.get('remote'):
6664 6663 raise
6665 6664 return dest, dbranch, None, None
6666 6665 ui.debug('comparing with %s\n' % util.hidepassword(dest))
6667 6666 elif sother is None:
6668 6667 # there is no explicit destination peer, but source one is invalid
6669 6668 return dest, dbranch, None, None
6670 6669 else:
6671 6670 dother = sother
6672 6671 if (source != dest or (sbranch is not None and sbranch != dbranch)):
6673 6672 common = None
6674 6673 else:
6675 6674 common = commoninc
6676 6675 if revs:
6677 6676 revs = [repo.lookup(rev) for rev in revs]
6678 6677 repo.ui.pushbuffer()
6679 6678 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
6680 6679 commoninc=common)
6681 6680 repo.ui.popbuffer()
6682 6681 return dest, dbranch, dother, outgoing
6683 6682
6684 6683 if needsoutgoing:
6685 6684 dest, dbranch, dother, outgoing = getoutgoing()
6686 6685 else:
6687 6686 dest = dbranch = dother = outgoing = None
6688 6687
6689 6688 if opts.get('remote'):
6690 6689 t = []
6691 6690 if incoming:
6692 6691 t.append(_('1 or more incoming'))
6693 6692 o = outgoing.missing
6694 6693 if o:
6695 6694 t.append(_('%d outgoing') % len(o))
6696 6695 other = dother or sother
6697 6696 if 'bookmarks' in other.listkeys('namespaces'):
6698 6697 counts = bookmarks.summary(repo, other)
6699 6698 if counts[0] > 0:
6700 6699 t.append(_('%d incoming bookmarks') % counts[0])
6701 6700 if counts[1] > 0:
6702 6701 t.append(_('%d outgoing bookmarks') % counts[1])
6703 6702
6704 6703 if t:
6705 6704 # i18n: column positioning for "hg summary"
6706 6705 ui.write(_('remote: %s\n') % (', '.join(t)))
6707 6706 else:
6708 6707 # i18n: column positioning for "hg summary"
6709 6708 ui.status(_('remote: (synced)\n'))
6710 6709
6711 6710 cmdutil.summaryremotehooks(ui, repo, opts,
6712 6711 ((source, sbranch, sother, commoninc),
6713 6712 (dest, dbranch, dother, outgoing)))
6714 6713
6715 6714 @command('tag',
6716 6715 [('f', 'force', None, _('force tag')),
6717 6716 ('l', 'local', None, _('make the tag local')),
6718 6717 ('r', 'rev', '', _('revision to tag'), _('REV')),
6719 6718 ('', 'remove', None, _('remove a tag')),
6720 6719 # -l/--local is already there, commitopts cannot be used
6721 6720 ('e', 'edit', None, _('invoke editor on commit messages')),
6722 6721 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
6723 6722 ] + commitopts2,
6724 6723 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
6725 6724 def tag(ui, repo, name1, *names, **opts):
6726 6725 """add one or more tags for the current or given revision
6727 6726
6728 6727 Name a particular revision using <name>.
6729 6728
6730 6729 Tags are used to name particular revisions of the repository and are
6731 6730 very useful to compare different revisions, to go back to significant
6732 6731 earlier versions or to mark branch points as releases, etc. Changing
6733 6732 an existing tag is normally disallowed; use -f/--force to override.
6734 6733
6735 6734 If no revision is given, the parent of the working directory is
6736 6735 used.
6737 6736
6738 6737 To facilitate version control, distribution, and merging of tags,
6739 6738 they are stored as a file named ".hgtags" which is managed similarly
6740 6739 to other project files and can be hand-edited if necessary. This
6741 6740 also means that tagging creates a new commit. The file
6742 6741 ".hg/localtags" is used for local tags (not shared among
6743 6742 repositories).
6744 6743
6745 6744 Tag commits are usually made at the head of a branch. If the parent
6746 6745 of the working directory is not a branch head, :hg:`tag` aborts; use
6747 6746 -f/--force to force the tag commit to be based on a non-head
6748 6747 changeset.
6749 6748
6750 6749 See :hg:`help dates` for a list of formats valid for -d/--date.
6751 6750
6752 6751 Since tag names have priority over branch names during revision
6753 6752 lookup, using an existing branch name as a tag name is discouraged.
6754 6753
6755 6754 Returns 0 on success.
6756 6755 """
6757 6756 wlock = lock = None
6758 6757 try:
6759 6758 wlock = repo.wlock()
6760 6759 lock = repo.lock()
6761 6760 rev_ = "."
6762 6761 names = [t.strip() for t in (name1,) + names]
6763 6762 if len(names) != len(set(names)):
6764 6763 raise error.Abort(_('tag names must be unique'))
6765 6764 for n in names:
6766 6765 scmutil.checknewlabel(repo, n, 'tag')
6767 6766 if not n:
6768 6767 raise error.Abort(_('tag names cannot consist entirely of '
6769 6768 'whitespace'))
6770 6769 if opts.get('rev') and opts.get('remove'):
6771 6770 raise error.Abort(_("--rev and --remove are incompatible"))
6772 6771 if opts.get('rev'):
6773 6772 rev_ = opts['rev']
6774 6773 message = opts.get('message')
6775 6774 if opts.get('remove'):
6776 6775 if opts.get('local'):
6777 6776 expectedtype = 'local'
6778 6777 else:
6779 6778 expectedtype = 'global'
6780 6779
6781 6780 for n in names:
6782 6781 if not repo.tagtype(n):
6783 6782 raise error.Abort(_("tag '%s' does not exist") % n)
6784 6783 if repo.tagtype(n) != expectedtype:
6785 6784 if expectedtype == 'global':
6786 6785 raise error.Abort(_("tag '%s' is not a global tag") % n)
6787 6786 else:
6788 6787 raise error.Abort(_("tag '%s' is not a local tag") % n)
6789 6788 rev_ = 'null'
6790 6789 if not message:
6791 6790 # we don't translate commit messages
6792 6791 message = 'Removed tag %s' % ', '.join(names)
6793 6792 elif not opts.get('force'):
6794 6793 for n in names:
6795 6794 if n in repo.tags():
6796 6795 raise error.Abort(_("tag '%s' already exists "
6797 6796 "(use -f to force)") % n)
6798 6797 if not opts.get('local'):
6799 6798 p1, p2 = repo.dirstate.parents()
6800 6799 if p2 != nullid:
6801 6800 raise error.Abort(_('uncommitted merge'))
6802 6801 bheads = repo.branchheads()
6803 6802 if not opts.get('force') and bheads and p1 not in bheads:
6804 6803 raise error.Abort(_('working directory is not at a branch head '
6805 6804 '(use -f to force)'))
6806 6805 r = scmutil.revsingle(repo, rev_).node()
6807 6806
6808 6807 if not message:
6809 6808 # we don't translate commit messages
6810 6809 message = ('Added tag %s for changeset %s' %
6811 6810 (', '.join(names), short(r)))
6812 6811
6813 6812 date = opts.get('date')
6814 6813 if date:
6815 6814 date = util.parsedate(date)
6816 6815
6817 6816 if opts.get('remove'):
6818 6817 editform = 'tag.remove'
6819 6818 else:
6820 6819 editform = 'tag.add'
6821 6820 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6822 6821
6823 6822 # don't allow tagging the null rev
6824 6823 if (not opts.get('remove') and
6825 6824 scmutil.revsingle(repo, rev_).rev() == nullrev):
6826 6825 raise error.Abort(_("cannot tag null revision"))
6827 6826
6828 6827 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6829 6828 editor=editor)
6830 6829 finally:
6831 6830 release(lock, wlock)
6832 6831
6833 6832 @command('tags', formatteropts, '')
6834 6833 def tags(ui, repo, **opts):
6835 6834 """list repository tags
6836 6835
6837 6836 This lists both regular and local tags. When the -v/--verbose
6838 6837 switch is used, a third column "local" is printed for local tags.
6839 6838 When the -q/--quiet switch is used, only the tag name is printed.
6840 6839
6841 6840 Returns 0 on success.
6842 6841 """
6843 6842
6844 6843 fm = ui.formatter('tags', opts)
6845 6844 hexfunc = fm.hexfunc
6846 6845 tagtype = ""
6847 6846
6848 6847 for t, n in reversed(repo.tagslist()):
6849 6848 hn = hexfunc(n)
6850 6849 label = 'tags.normal'
6851 6850 tagtype = ''
6852 6851 if repo.tagtype(t) == 'local':
6853 6852 label = 'tags.local'
6854 6853 tagtype = 'local'
6855 6854
6856 6855 fm.startitem()
6857 6856 fm.write('tag', '%s', t, label=label)
6858 6857 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6859 6858 fm.condwrite(not ui.quiet, 'rev node', fmt,
6860 6859 repo.changelog.rev(n), hn, label=label)
6861 6860 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6862 6861 tagtype, label=label)
6863 6862 fm.plain('\n')
6864 6863 fm.end()
6865 6864
6866 6865 @command('tip',
6867 6866 [('p', 'patch', None, _('show patch')),
6868 6867 ('g', 'git', None, _('use git extended diff format')),
6869 6868 ] + templateopts,
6870 6869 _('[-p] [-g]'))
6871 6870 def tip(ui, repo, **opts):
6872 6871 """show the tip revision (DEPRECATED)
6873 6872
6874 6873 The tip revision (usually just called the tip) is the changeset
6875 6874 most recently added to the repository (and therefore the most
6876 6875 recently changed head).
6877 6876
6878 6877 If you have just made a commit, that commit will be the tip. If
6879 6878 you have just pulled changes from another repository, the tip of
6880 6879 that repository becomes the current tip. The "tip" tag is special
6881 6880 and cannot be renamed or assigned to a different changeset.
6882 6881
6883 6882 This command is deprecated, please use :hg:`heads` instead.
6884 6883
6885 6884 Returns 0 on success.
6886 6885 """
6887 6886 displayer = cmdutil.show_changeset(ui, repo, opts)
6888 6887 displayer.show(repo['tip'])
6889 6888 displayer.close()
6890 6889
6891 6890 @command('unbundle',
6892 6891 [('u', 'update', None,
6893 6892 _('update to new branch head if changesets were unbundled'))],
6894 6893 _('[-u] FILE...'))
6895 6894 def unbundle(ui, repo, fname1, *fnames, **opts):
6896 6895 """apply one or more changegroup files
6897 6896
6898 6897 Apply one or more compressed changegroup files generated by the
6899 6898 bundle command.
6900 6899
6901 6900 Returns 0 on success, 1 if an update has unresolved files.
6902 6901 """
6903 6902 fnames = (fname1,) + fnames
6904 6903
6905 6904 with repo.lock():
6906 6905 for fname in fnames:
6907 6906 f = hg.openpath(ui, fname)
6908 6907 gen = exchange.readbundle(ui, f, fname)
6909 6908 if isinstance(gen, bundle2.unbundle20):
6910 6909 tr = repo.transaction('unbundle')
6911 6910 try:
6912 6911 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6913 6912 url='bundle:' + fname)
6914 6913 tr.close()
6915 6914 except error.BundleUnknownFeatureError as exc:
6916 6915 raise error.Abort(_('%s: unknown bundle feature, %s')
6917 6916 % (fname, exc),
6918 6917 hint=_("see https://mercurial-scm.org/"
6919 6918 "wiki/BundleFeature for more "
6920 6919 "information"))
6921 6920 finally:
6922 6921 if tr:
6923 6922 tr.release()
6924 6923 changes = [r.get('return', 0)
6925 6924 for r in op.records['changegroup']]
6926 6925 modheads = changegroup.combineresults(changes)
6927 6926 elif isinstance(gen, streamclone.streamcloneapplier):
6928 6927 raise error.Abort(
6929 6928 _('packed bundles cannot be applied with '
6930 6929 '"hg unbundle"'),
6931 6930 hint=_('use "hg debugapplystreamclonebundle"'))
6932 6931 else:
6933 6932 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
6934 6933
6935 6934 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
6936 6935
6937 6936 @command('^update|up|checkout|co',
6938 6937 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6939 6938 ('c', 'check', None, _('require clean working directory')),
6940 6939 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6941 6940 ('r', 'rev', '', _('revision'), _('REV'))
6942 6941 ] + mergetoolopts,
6943 6942 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6944 6943 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6945 6944 tool=None):
6946 6945 """update working directory (or switch revisions)
6947 6946
6948 6947 Update the repository's working directory to the specified
6949 6948 changeset. If no changeset is specified, update to the tip of the
6950 6949 current named branch and move the active bookmark (see :hg:`help
6951 6950 bookmarks`).
6952 6951
6953 6952 Update sets the working directory's parent revision to the specified
6954 6953 changeset (see :hg:`help parents`).
6955 6954
6956 6955 If the changeset is not a descendant or ancestor of the working
6957 6956 directory's parent, the update is aborted. With the -c/--check
6958 6957 option, the working directory is checked for uncommitted changes; if
6959 6958 none are found, the working directory is updated to the specified
6960 6959 changeset.
6961 6960
6962 6961 .. container:: verbose
6963 6962
6964 6963 The following rules apply when the working directory contains
6965 6964 uncommitted changes:
6966 6965
6967 6966 1. If neither -c/--check nor -C/--clean is specified, and if
6968 6967 the requested changeset is an ancestor or descendant of
6969 6968 the working directory's parent, the uncommitted changes
6970 6969 are merged into the requested changeset and the merged
6971 6970 result is left uncommitted. If the requested changeset is
6972 6971 not an ancestor or descendant (that is, it is on another
6973 6972 branch), the update is aborted and the uncommitted changes
6974 6973 are preserved.
6975 6974
6976 6975 2. With the -c/--check option, the update is aborted and the
6977 6976 uncommitted changes are preserved.
6978 6977
6979 6978 3. With the -C/--clean option, uncommitted changes are discarded and
6980 6979 the working directory is updated to the requested changeset.
6981 6980
6982 6981 To cancel an uncommitted merge (and lose your changes), use
6983 6982 :hg:`update --clean .`.
6984 6983
6985 6984 Use null as the changeset to remove the working directory (like
6986 6985 :hg:`clone -U`).
6987 6986
6988 6987 If you want to revert just one file to an older revision, use
6989 6988 :hg:`revert [-r REV] NAME`.
6990 6989
6991 6990 See :hg:`help dates` for a list of formats valid for -d/--date.
6992 6991
6993 6992 Returns 0 on success, 1 if there are unresolved files.
6994 6993 """
6995 6994 if rev and node:
6996 6995 raise error.Abort(_("please specify just one revision"))
6997 6996
6998 6997 if rev is None or rev == '':
6999 6998 rev = node
7000 6999
7001 7000 if date and rev is not None:
7002 7001 raise error.Abort(_("you can't specify a revision and a date"))
7003 7002
7004 7003 if check and clean:
7005 7004 raise error.Abort(_("cannot specify both -c/--check and -C/--clean"))
7006 7005
7007 7006 with repo.wlock():
7008 7007 cmdutil.clearunfinished(repo)
7009 7008
7010 7009 if date:
7011 7010 rev = cmdutil.finddate(ui, repo, date)
7012 7011
7013 7012 # if we defined a bookmark, we have to remember the original name
7014 7013 brev = rev
7015 7014 rev = scmutil.revsingle(repo, rev, rev).rev()
7016 7015
7017 7016 if check:
7018 7017 cmdutil.bailifchanged(repo, merge=False)
7019 7018
7020 7019 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
7021 7020
7022 7021 return hg.updatetotally(ui, repo, rev, brev, clean=clean, check=check)
7023 7022
7024 7023 @command('verify', [])
7025 7024 def verify(ui, repo):
7026 7025 """verify the integrity of the repository
7027 7026
7028 7027 Verify the integrity of the current repository.
7029 7028
7030 7029 This will perform an extensive check of the repository's
7031 7030 integrity, validating the hashes and checksums of each entry in
7032 7031 the changelog, manifest, and tracked files, as well as the
7033 7032 integrity of their crosslinks and indices.
7034 7033
7035 7034 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7036 7035 for more information about recovery from corruption of the
7037 7036 repository.
7038 7037
7039 7038 Returns 0 on success, 1 if errors are encountered.
7040 7039 """
7041 7040 return hg.verify(repo)
7042 7041
7043 7042 @command('version', [] + formatteropts, norepo=True)
7044 7043 def version_(ui, **opts):
7045 7044 """output version and copyright information"""
7046 7045 fm = ui.formatter("version", opts)
7047 7046 fm.startitem()
7048 7047 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
7049 7048 util.version())
7050 7049 license = _(
7051 7050 "(see https://mercurial-scm.org for more information)\n"
7052 7051 "\nCopyright (C) 2005-2016 Matt Mackall and others\n"
7053 7052 "This is free software; see the source for copying conditions. "
7054 7053 "There is NO\nwarranty; "
7055 7054 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7056 7055 )
7057 7056 if not ui.quiet:
7058 7057 fm.plain(license)
7059 7058
7060 7059 if ui.verbose:
7061 7060 fm.plain(_("\nEnabled extensions:\n\n"))
7062 7061 # format names and versions into columns
7063 7062 names = []
7064 7063 vers = []
7065 7064 isinternals = []
7066 7065 for name, module in extensions.extensions():
7067 7066 names.append(name)
7068 7067 vers.append(extensions.moduleversion(module) or None)
7069 7068 isinternals.append(extensions.ismoduleinternal(module))
7070 7069 fn = fm.nested("extensions")
7071 7070 if names:
7072 7071 namefmt = " %%-%ds " % max(len(n) for n in names)
7073 7072 places = [_("external"), _("internal")]
7074 7073 for n, v, p in zip(names, vers, isinternals):
7075 7074 fn.startitem()
7076 7075 fn.condwrite(ui.verbose, "name", namefmt, n)
7077 7076 if ui.verbose:
7078 7077 fn.plain("%s " % places[p])
7079 7078 fn.data(bundled=p)
7080 7079 fn.condwrite(ui.verbose and v, "ver", "%s", v)
7081 7080 if ui.verbose:
7082 7081 fn.plain("\n")
7083 7082 fn.end()
7084 7083 fm.end()
7085 7084
7086 7085 def loadcmdtable(ui, name, cmdtable):
7087 7086 """Load command functions from specified cmdtable
7088 7087 """
7089 7088 overrides = [cmd for cmd in cmdtable if cmd in table]
7090 7089 if overrides:
7091 7090 ui.warn(_("extension '%s' overrides commands: %s\n")
7092 7091 % (name, " ".join(overrides)))
7093 7092 table.update(cmdtable)
@@ -1,543 +1,531 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright 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 gc
12 12 import os
13 13 import random
14 14 import select
15 15 import signal
16 16 import socket
17 17 import struct
18 18 import traceback
19 19
20 20 from .i18n import _
21 21 from . import (
22 22 encoding,
23 23 error,
24 24 util,
25 25 )
26 26
27 27 logfile = None
28 28
29 29 def log(*args):
30 30 if not logfile:
31 31 return
32 32
33 33 for a in args:
34 34 logfile.write(str(a))
35 35
36 36 logfile.flush()
37 37
38 38 class channeledoutput(object):
39 39 """
40 40 Write data to out in the following format:
41 41
42 42 data length (unsigned int),
43 43 data
44 44 """
45 45 def __init__(self, out, channel):
46 46 self.out = out
47 47 self.channel = channel
48 48
49 49 @property
50 50 def name(self):
51 51 return '<%c-channel>' % self.channel
52 52
53 53 def write(self, data):
54 54 if not data:
55 55 return
56 56 # single write() to guarantee the same atomicity as the underlying file
57 57 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
58 58 self.out.flush()
59 59
60 60 def __getattr__(self, attr):
61 61 if attr in ('isatty', 'fileno', 'tell', 'seek'):
62 62 raise AttributeError(attr)
63 63 return getattr(self.out, attr)
64 64
65 65 class channeledinput(object):
66 66 """
67 67 Read data from in_.
68 68
69 69 Requests for input are written to out in the following format:
70 70 channel identifier - 'I' for plain input, 'L' line based (1 byte)
71 71 how many bytes to send at most (unsigned int),
72 72
73 73 The client replies with:
74 74 data length (unsigned int), 0 meaning EOF
75 75 data
76 76 """
77 77
78 78 maxchunksize = 4 * 1024
79 79
80 80 def __init__(self, in_, out, channel):
81 81 self.in_ = in_
82 82 self.out = out
83 83 self.channel = channel
84 84
85 85 @property
86 86 def name(self):
87 87 return '<%c-channel>' % self.channel
88 88
89 89 def read(self, size=-1):
90 90 if size < 0:
91 91 # if we need to consume all the clients input, ask for 4k chunks
92 92 # so the pipe doesn't fill up risking a deadlock
93 93 size = self.maxchunksize
94 94 s = self._read(size, self.channel)
95 95 buf = s
96 96 while s:
97 97 s = self._read(size, self.channel)
98 98 buf += s
99 99
100 100 return buf
101 101 else:
102 102 return self._read(size, self.channel)
103 103
104 104 def _read(self, size, channel):
105 105 if not size:
106 106 return ''
107 107 assert size > 0
108 108
109 109 # tell the client we need at most size bytes
110 110 self.out.write(struct.pack('>cI', channel, size))
111 111 self.out.flush()
112 112
113 113 length = self.in_.read(4)
114 114 length = struct.unpack('>I', length)[0]
115 115 if not length:
116 116 return ''
117 117 else:
118 118 return self.in_.read(length)
119 119
120 120 def readline(self, size=-1):
121 121 if size < 0:
122 122 size = self.maxchunksize
123 123 s = self._read(size, 'L')
124 124 buf = s
125 125 # keep asking for more until there's either no more or
126 126 # we got a full line
127 127 while s and s[-1] != '\n':
128 128 s = self._read(size, 'L')
129 129 buf += s
130 130
131 131 return buf
132 132 else:
133 133 return self._read(size, 'L')
134 134
135 135 def __iter__(self):
136 136 return self
137 137
138 138 def next(self):
139 139 l = self.readline()
140 140 if not l:
141 141 raise StopIteration
142 142 return l
143 143
144 144 def __getattr__(self, attr):
145 145 if attr in ('isatty', 'fileno', 'tell', 'seek'):
146 146 raise AttributeError(attr)
147 147 return getattr(self.in_, attr)
148 148
149 149 class server(object):
150 150 """
151 151 Listens for commands on fin, runs them and writes the output on a channel
152 152 based stream to fout.
153 153 """
154 154 def __init__(self, ui, repo, fin, fout):
155 155 self.cwd = os.getcwd()
156 156
157 157 # developer config: cmdserver.log
158 158 logpath = ui.config("cmdserver", "log", None)
159 159 if logpath:
160 160 global logfile
161 161 if logpath == '-':
162 162 # write log on a special 'd' (debug) channel
163 163 logfile = channeledoutput(fout, 'd')
164 164 else:
165 165 logfile = open(logpath, 'a')
166 166
167 167 if repo:
168 168 # the ui here is really the repo ui so take its baseui so we don't
169 169 # end up with its local configuration
170 170 self.ui = repo.baseui
171 171 self.repo = repo
172 172 self.repoui = repo.ui
173 173 else:
174 174 self.ui = ui
175 175 self.repo = self.repoui = None
176 176
177 177 self.cerr = channeledoutput(fout, 'e')
178 178 self.cout = channeledoutput(fout, 'o')
179 179 self.cin = channeledinput(fin, fout, 'I')
180 180 self.cresult = channeledoutput(fout, 'r')
181 181
182 182 self.client = fin
183 183
184 184 def cleanup(self):
185 185 """release and restore resources taken during server session"""
186 186 pass
187 187
188 188 def _read(self, size):
189 189 if not size:
190 190 return ''
191 191
192 192 data = self.client.read(size)
193 193
194 194 # is the other end closed?
195 195 if not data:
196 196 raise EOFError
197 197
198 198 return data
199 199
200 200 def _readstr(self):
201 201 """read a string from the channel
202 202
203 203 format:
204 204 data length (uint32), data
205 205 """
206 206 length = struct.unpack('>I', self._read(4))[0]
207 207 if not length:
208 208 return ''
209 209 return self._read(length)
210 210
211 211 def _readlist(self):
212 212 """read a list of NULL separated strings from the channel"""
213 213 s = self._readstr()
214 214 if s:
215 215 return s.split('\0')
216 216 else:
217 217 return []
218 218
219 219 def runcommand(self):
220 220 """ reads a list of \0 terminated arguments, executes
221 221 and writes the return code to the result channel """
222 222 from . import dispatch # avoid cycle
223 223
224 224 args = self._readlist()
225 225
226 226 # copy the uis so changes (e.g. --config or --verbose) don't
227 227 # persist between requests
228 228 copiedui = self.ui.copy()
229 229 uis = [copiedui]
230 230 if self.repo:
231 231 self.repo.baseui = copiedui
232 232 # clone ui without using ui.copy because this is protected
233 233 repoui = self.repoui.__class__(self.repoui)
234 234 repoui.copy = copiedui.copy # redo copy protection
235 235 uis.append(repoui)
236 236 self.repo.ui = self.repo.dirstate._ui = repoui
237 237 self.repo.invalidateall()
238 238
239 239 for ui in uis:
240 240 ui.resetstate()
241 241 # any kind of interaction must use server channels, but chg may
242 242 # replace channels by fully functional tty files. so nontty is
243 243 # enforced only if cin is a channel.
244 244 if not util.safehasattr(self.cin, 'fileno'):
245 245 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
246 246
247 247 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
248 248 self.cout, self.cerr)
249 249
250 250 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
251 251
252 252 # restore old cwd
253 253 if '--cwd' in args:
254 254 os.chdir(self.cwd)
255 255
256 256 self.cresult.write(struct.pack('>i', int(ret)))
257 257
258 258 def getencoding(self):
259 259 """ writes the current encoding to the result channel """
260 260 self.cresult.write(encoding.encoding)
261 261
262 262 def serveone(self):
263 263 cmd = self.client.readline()[:-1]
264 264 if cmd:
265 265 handler = self.capabilities.get(cmd)
266 266 if handler:
267 267 handler(self)
268 268 else:
269 269 # clients are expected to check what commands are supported by
270 270 # looking at the servers capabilities
271 271 raise error.Abort(_('unknown command %s') % cmd)
272 272
273 273 return cmd != ''
274 274
275 275 capabilities = {'runcommand' : runcommand,
276 276 'getencoding' : getencoding}
277 277
278 278 def serve(self):
279 279 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
280 280 hellomsg += '\n'
281 281 hellomsg += 'encoding: ' + encoding.encoding
282 282 hellomsg += '\n'
283 283 hellomsg += 'pid: %d' % util.getpid()
284 284 if util.safehasattr(os, 'getpgid'):
285 285 hellomsg += '\n'
286 286 hellomsg += 'pgid: %d' % os.getpgid(0)
287 287
288 288 # write the hello msg in -one- chunk
289 289 self.cout.write(hellomsg)
290 290
291 291 try:
292 292 while self.serveone():
293 293 pass
294 294 except EOFError:
295 295 # we'll get here if the client disconnected while we were reading
296 296 # its request
297 297 return 1
298 298
299 299 return 0
300 300
301 301 def _protectio(ui):
302 302 """ duplicates streams and redirect original to null if ui uses stdio """
303 303 ui.flush()
304 304 newfiles = []
305 305 nullfd = os.open(os.devnull, os.O_RDWR)
306 306 for f, sysf, mode in [(ui.fin, util.stdin, 'rb'),
307 307 (ui.fout, util.stdout, 'wb')]:
308 308 if f is sysf:
309 309 newfd = os.dup(f.fileno())
310 310 os.dup2(nullfd, f.fileno())
311 311 f = os.fdopen(newfd, mode)
312 312 newfiles.append(f)
313 313 os.close(nullfd)
314 314 return tuple(newfiles)
315 315
316 316 def _restoreio(ui, fin, fout):
317 317 """ restores streams from duplicated ones """
318 318 ui.flush()
319 319 for f, uif in [(fin, ui.fin), (fout, ui.fout)]:
320 320 if f is not uif:
321 321 os.dup2(f.fileno(), uif.fileno())
322 322 f.close()
323 323
324 324 class pipeservice(object):
325 325 def __init__(self, ui, repo, opts):
326 326 self.ui = ui
327 327 self.repo = repo
328 328
329 329 def init(self):
330 330 pass
331 331
332 332 def run(self):
333 333 ui = self.ui
334 334 # redirect stdio to null device so that broken extensions or in-process
335 335 # hooks will never cause corruption of channel protocol.
336 336 fin, fout = _protectio(ui)
337 337 try:
338 338 sv = server(ui, self.repo, fin, fout)
339 339 return sv.serve()
340 340 finally:
341 341 sv.cleanup()
342 342 _restoreio(ui, fin, fout)
343 343
344 344 def _initworkerprocess():
345 345 # use a different process group from the master process, in order to:
346 346 # 1. make the current process group no longer "orphaned" (because the
347 347 # parent of this process is in a different process group while
348 348 # remains in a same session)
349 349 # according to POSIX 2.2.2.52, orphaned process group will ignore
350 350 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
351 351 # cause trouble for things like ncurses.
352 352 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
353 353 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
354 354 # processes like ssh will be killed properly, without affecting
355 355 # unrelated processes.
356 356 os.setpgid(0, 0)
357 357 # change random state otherwise forked request handlers would have a
358 358 # same state inherited from parent.
359 359 random.seed()
360 360
361 361 def _serverequest(ui, repo, conn, createcmdserver):
362 362 fin = conn.makefile('rb')
363 363 fout = conn.makefile('wb')
364 364 sv = None
365 365 try:
366 366 sv = createcmdserver(repo, conn, fin, fout)
367 367 try:
368 368 sv.serve()
369 369 # handle exceptions that may be raised by command server. most of
370 370 # known exceptions are caught by dispatch.
371 371 except error.Abort as inst:
372 372 ui.warn(_('abort: %s\n') % inst)
373 373 except IOError as inst:
374 374 if inst.errno != errno.EPIPE:
375 375 raise
376 376 except KeyboardInterrupt:
377 377 pass
378 378 finally:
379 379 sv.cleanup()
380 380 except: # re-raises
381 381 # also write traceback to error channel. otherwise client cannot
382 382 # see it because it is written to server's stderr by default.
383 383 if sv:
384 384 cerr = sv.cerr
385 385 else:
386 386 cerr = channeledoutput(fout, 'e')
387 387 traceback.print_exc(file=cerr)
388 388 raise
389 389 finally:
390 390 fin.close()
391 391 try:
392 392 fout.close() # implicit flush() may cause another EPIPE
393 393 except IOError as inst:
394 394 if inst.errno != errno.EPIPE:
395 395 raise
396 396
397 397 class unixservicehandler(object):
398 398 """Set of pluggable operations for unix-mode services
399 399
400 400 Almost all methods except for createcmdserver() are called in the main
401 401 process. You can't pass mutable resource back from createcmdserver().
402 402 """
403 403
404 404 pollinterval = None
405 405
406 406 def __init__(self, ui):
407 407 self.ui = ui
408 408
409 409 def bindsocket(self, sock, address):
410 410 util.bindunixsocket(sock, address)
411 411
412 412 def unlinksocket(self, address):
413 413 os.unlink(address)
414 414
415 415 def printbanner(self, address):
416 416 self.ui.status(_('listening at %s\n') % address)
417 417 self.ui.flush() # avoid buffering of status message
418 418
419 419 def shouldexit(self):
420 420 """True if server should shut down; checked per pollinterval"""
421 421 return False
422 422
423 423 def newconnection(self):
424 424 """Called when main process notices new connection"""
425 425 pass
426 426
427 427 def createcmdserver(self, repo, conn, fin, fout):
428 428 """Create new command server instance; called in the process that
429 429 serves for the current connection"""
430 430 return server(self.ui, repo, fin, fout)
431 431
432 432 class unixforkingservice(object):
433 433 """
434 434 Listens on unix domain socket and forks server per connection
435 435 """
436 436
437 437 def __init__(self, ui, repo, opts, handler=None):
438 438 self.ui = ui
439 439 self.repo = repo
440 440 self.address = opts['address']
441 441 if not util.safehasattr(socket, 'AF_UNIX'):
442 442 raise error.Abort(_('unsupported platform'))
443 443 if not self.address:
444 444 raise error.Abort(_('no socket path specified with --address'))
445 445 self._servicehandler = handler or unixservicehandler(ui)
446 446 self._sock = None
447 447 self._oldsigchldhandler = None
448 448 self._workerpids = set() # updated by signal handler; do not iterate
449 449
450 450 def init(self):
451 451 self._sock = socket.socket(socket.AF_UNIX)
452 452 self._servicehandler.bindsocket(self._sock, self.address)
453 453 self._sock.listen(socket.SOMAXCONN)
454 454 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
455 455 self._oldsigchldhandler = o
456 456 self._servicehandler.printbanner(self.address)
457 457
458 458 def _cleanup(self):
459 459 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
460 460 self._sock.close()
461 461 self._servicehandler.unlinksocket(self.address)
462 462 # don't kill child processes as they have active clients, just wait
463 463 self._reapworkers(0)
464 464
465 465 def run(self):
466 466 try:
467 467 self._mainloop()
468 468 finally:
469 469 self._cleanup()
470 470
471 471 def _mainloop(self):
472 472 h = self._servicehandler
473 473 while not h.shouldexit():
474 474 try:
475 475 ready = select.select([self._sock], [], [], h.pollinterval)[0]
476 476 if not ready:
477 477 continue
478 478 conn, _addr = self._sock.accept()
479 479 except (select.error, socket.error) as inst:
480 480 if inst.args[0] == errno.EINTR:
481 481 continue
482 482 raise
483 483
484 484 pid = os.fork()
485 485 if pid:
486 486 try:
487 487 self.ui.debug('forked worker process (pid=%d)\n' % pid)
488 488 self._workerpids.add(pid)
489 489 h.newconnection()
490 490 finally:
491 491 conn.close() # release handle in parent process
492 492 else:
493 493 try:
494 494 self._runworker(conn)
495 495 conn.close()
496 496 os._exit(0)
497 497 except: # never return, hence no re-raises
498 498 try:
499 499 self.ui.traceback(force=True)
500 500 finally:
501 501 os._exit(255)
502 502
503 503 def _sigchldhandler(self, signal, frame):
504 504 self._reapworkers(os.WNOHANG)
505 505
506 506 def _reapworkers(self, options):
507 507 while self._workerpids:
508 508 try:
509 509 pid, _status = os.waitpid(-1, options)
510 510 except OSError as inst:
511 511 if inst.errno == errno.EINTR:
512 512 continue
513 513 if inst.errno != errno.ECHILD:
514 514 raise
515 515 # no child processes at all (reaped by other waitpid()?)
516 516 self._workerpids.clear()
517 517 return
518 518 if pid == 0:
519 519 # no waitable child processes
520 520 return
521 521 self.ui.debug('worker process exited (pid=%d)\n' % pid)
522 522 self._workerpids.discard(pid)
523 523
524 524 def _runworker(self, conn):
525 525 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
526 526 _initworkerprocess()
527 527 h = self._servicehandler
528 528 try:
529 529 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
530 530 finally:
531 531 gc.collect() # trigger __del__ since worker process uses os._exit
532
533 _servicemap = {
534 'pipe': pipeservice,
535 'unix': unixforkingservice,
536 }
537
538 def createservice(ui, repo, opts):
539 mode = opts['cmdserver']
540 try:
541 return _servicemap[mode](ui, repo, opts)
542 except KeyError:
543 raise error.Abort(_('unknown mode %s') % mode)
@@ -1,107 +1,120 b''
1 1 # server.py - utility and factory of server
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 sys
13 13 import tempfile
14 14
15 15 from .i18n import _
16 16
17 17 from . import (
18 commandserver,
18 19 error,
19 20 util,
20 21 )
21 22
22 23 def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
23 24 runargs=None, appendpid=False):
24 25 '''Run a command as a service.'''
25 26
26 27 def writepid(pid):
27 28 if opts['pid_file']:
28 29 if appendpid:
29 30 mode = 'a'
30 31 else:
31 32 mode = 'w'
32 33 fp = open(opts['pid_file'], mode)
33 34 fp.write(str(pid) + '\n')
34 35 fp.close()
35 36
36 37 if opts['daemon'] and not opts['daemon_postexec']:
37 38 # Signal child process startup with file removal
38 39 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
39 40 os.close(lockfd)
40 41 try:
41 42 if not runargs:
42 43 runargs = util.hgcmd() + sys.argv[1:]
43 44 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
44 45 # Don't pass --cwd to the child process, because we've already
45 46 # changed directory.
46 47 for i in xrange(1, len(runargs)):
47 48 if runargs[i].startswith('--cwd='):
48 49 del runargs[i]
49 50 break
50 51 elif runargs[i].startswith('--cwd'):
51 52 del runargs[i:i + 2]
52 53 break
53 54 def condfn():
54 55 return not os.path.exists(lockpath)
55 56 pid = util.rundetached(runargs, condfn)
56 57 if pid < 0:
57 58 raise error.Abort(_('child process failed to start'))
58 59 writepid(pid)
59 60 finally:
60 61 try:
61 62 os.unlink(lockpath)
62 63 except OSError as e:
63 64 if e.errno != errno.ENOENT:
64 65 raise
65 66 if parentfn:
66 67 return parentfn(pid)
67 68 else:
68 69 return
69 70
70 71 if initfn:
71 72 initfn()
72 73
73 74 if not opts['daemon']:
74 75 writepid(util.getpid())
75 76
76 77 if opts['daemon_postexec']:
77 78 try:
78 79 os.setsid()
79 80 except AttributeError:
80 81 pass
81 82 for inst in opts['daemon_postexec']:
82 83 if inst.startswith('unlink:'):
83 84 lockpath = inst[7:]
84 85 os.unlink(lockpath)
85 86 elif inst.startswith('chdir:'):
86 87 os.chdir(inst[6:])
87 88 elif inst != 'none':
88 89 raise error.Abort(_('invalid value for --daemon-postexec: %s')
89 90 % inst)
90 91 util.hidewindow()
91 92 util.stdout.flush()
92 93 util.stderr.flush()
93 94
94 95 nullfd = os.open(os.devnull, os.O_RDWR)
95 96 logfilefd = nullfd
96 97 if logfile:
97 98 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
98 99 os.dup2(nullfd, 0)
99 100 os.dup2(logfilefd, 1)
100 101 os.dup2(logfilefd, 2)
101 102 if nullfd not in (0, 1, 2):
102 103 os.close(nullfd)
103 104 if logfile and logfilefd not in (0, 1, 2):
104 105 os.close(logfilefd)
105 106
106 107 if runfn:
107 108 return runfn()
109
110 _cmdservicemap = {
111 'pipe': commandserver.pipeservice,
112 'unix': commandserver.unixforkingservice,
113 }
114
115 def createcmdservice(ui, repo, opts):
116 mode = opts['cmdserver']
117 try:
118 return _cmdservicemap[mode](ui, repo, opts)
119 except KeyError:
120 raise error.Abort(_('unknown mode %s') % mode)
General Comments 0
You need to be logged in to leave comments. Login now