##// END OF EJS Templates
help: pass commands module by argument...
Yuya Nishihara -
r32567:1b90036f default
parent child Browse files
Show More
@@ -1,5493 +1,5495 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 os
13 13 import re
14 import sys
14 15
15 16 from .i18n import _
16 17 from .node import (
17 18 hex,
18 19 nullid,
19 20 nullrev,
20 21 short,
21 22 )
22 23 from . import (
23 24 archival,
24 25 bookmarks,
25 26 bundle2,
26 27 changegroup,
27 28 cmdutil,
28 29 copies,
29 30 debugcommands as debugcommandsmod,
30 31 destutil,
31 32 dirstateguard,
32 33 discovery,
33 34 encoding,
34 35 error,
35 36 exchange,
36 37 extensions,
37 38 graphmod,
38 39 hbisect,
39 40 help,
40 41 hg,
41 42 lock as lockmod,
42 43 merge as mergemod,
43 44 obsolete,
44 45 patch,
45 46 phases,
46 47 pycompat,
47 48 rcutil,
48 49 registrar,
49 50 revsetlang,
50 51 scmutil,
51 52 server,
52 53 sshserver,
53 54 streamclone,
54 55 tags as tagsmod,
55 56 templatekw,
56 57 ui as uimod,
57 58 util,
58 59 )
59 60
60 61 release = lockmod.release
61 62
62 63 table = {}
63 64 table.update(debugcommandsmod.command._table)
64 65
65 66 command = registrar.command(table)
66 67
67 68 # label constants
68 69 # until 3.5, bookmarks.current was the advertised name, not
69 70 # bookmarks.active, so we must use both to avoid breaking old
70 71 # custom styles
71 72 activebookmarklabel = 'bookmarks.active bookmarks.current'
72 73
73 74 # common command options
74 75
75 76 globalopts = [
76 77 ('R', 'repository', '',
77 78 _('repository root directory or name of overlay bundle file'),
78 79 _('REPO')),
79 80 ('', 'cwd', '',
80 81 _('change working directory'), _('DIR')),
81 82 ('y', 'noninteractive', None,
82 83 _('do not prompt, automatically pick the first choice for all prompts')),
83 84 ('q', 'quiet', None, _('suppress output')),
84 85 ('v', 'verbose', None, _('enable additional output')),
85 86 ('', 'color', '',
86 87 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
87 88 # and should not be translated
88 89 _("when to colorize (boolean, always, auto, never, or debug)"),
89 90 _('TYPE')),
90 91 ('', 'config', [],
91 92 _('set/override config option (use \'section.name=value\')'),
92 93 _('CONFIG')),
93 94 ('', 'debug', None, _('enable debugging output')),
94 95 ('', 'debugger', None, _('start debugger')),
95 96 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
96 97 _('ENCODE')),
97 98 ('', 'encodingmode', encoding.encodingmode,
98 99 _('set the charset encoding mode'), _('MODE')),
99 100 ('', 'traceback', None, _('always print a traceback on exception')),
100 101 ('', 'time', None, _('time how long the command takes')),
101 102 ('', 'profile', None, _('print command execution profile')),
102 103 ('', 'version', None, _('output version information and exit')),
103 104 ('h', 'help', None, _('display help and exit')),
104 105 ('', 'hidden', False, _('consider hidden changesets')),
105 106 ('', 'pager', 'auto',
106 107 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
107 108 ]
108 109
109 110 dryrunopts = cmdutil.dryrunopts
110 111 remoteopts = cmdutil.remoteopts
111 112 walkopts = cmdutil.walkopts
112 113 commitopts = cmdutil.commitopts
113 114 commitopts2 = cmdutil.commitopts2
114 115 formatteropts = cmdutil.formatteropts
115 116 templateopts = cmdutil.templateopts
116 117 logopts = cmdutil.logopts
117 118 diffopts = cmdutil.diffopts
118 119 diffwsopts = cmdutil.diffwsopts
119 120 diffopts2 = cmdutil.diffopts2
120 121 mergetoolopts = cmdutil.mergetoolopts
121 122 similarityopts = cmdutil.similarityopts
122 123 subrepoopts = cmdutil.subrepoopts
123 124 debugrevlogopts = cmdutil.debugrevlogopts
124 125
125 126 # Commands start here, listed alphabetically
126 127
127 128 @command('^add',
128 129 walkopts + subrepoopts + dryrunopts,
129 130 _('[OPTION]... [FILE]...'),
130 131 inferrepo=True)
131 132 def add(ui, repo, *pats, **opts):
132 133 """add the specified files on the next commit
133 134
134 135 Schedule files to be version controlled and added to the
135 136 repository.
136 137
137 138 The files will be added to the repository at the next commit. To
138 139 undo an add before that, see :hg:`forget`.
139 140
140 141 If no names are given, add all files to the repository (except
141 142 files matching ``.hgignore``).
142 143
143 144 .. container:: verbose
144 145
145 146 Examples:
146 147
147 148 - New (unknown) files are added
148 149 automatically by :hg:`add`::
149 150
150 151 $ ls
151 152 foo.c
152 153 $ hg status
153 154 ? foo.c
154 155 $ hg add
155 156 adding foo.c
156 157 $ hg status
157 158 A foo.c
158 159
159 160 - Specific files to be added can be specified::
160 161
161 162 $ ls
162 163 bar.c foo.c
163 164 $ hg status
164 165 ? bar.c
165 166 ? foo.c
166 167 $ hg add bar.c
167 168 $ hg status
168 169 A bar.c
169 170 ? foo.c
170 171
171 172 Returns 0 if all files are successfully added.
172 173 """
173 174
174 175 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
175 176 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
176 177 return rejected and 1 or 0
177 178
178 179 @command('addremove',
179 180 similarityopts + subrepoopts + walkopts + dryrunopts,
180 181 _('[OPTION]... [FILE]...'),
181 182 inferrepo=True)
182 183 def addremove(ui, repo, *pats, **opts):
183 184 """add all new files, delete all missing files
184 185
185 186 Add all new files and remove all missing files from the
186 187 repository.
187 188
188 189 Unless names are given, new files are ignored if they match any of
189 190 the patterns in ``.hgignore``. As with add, these changes take
190 191 effect at the next commit.
191 192
192 193 Use the -s/--similarity option to detect renamed files. This
193 194 option takes a percentage between 0 (disabled) and 100 (files must
194 195 be identical) as its parameter. With a parameter greater than 0,
195 196 this compares every removed file with every added file and records
196 197 those similar enough as renames. Detecting renamed files this way
197 198 can be expensive. After using this option, :hg:`status -C` can be
198 199 used to check which files were identified as moved or renamed. If
199 200 not specified, -s/--similarity defaults to 100 and only renames of
200 201 identical files are detected.
201 202
202 203 .. container:: verbose
203 204
204 205 Examples:
205 206
206 207 - A number of files (bar.c and foo.c) are new,
207 208 while foobar.c has been removed (without using :hg:`remove`)
208 209 from the repository::
209 210
210 211 $ ls
211 212 bar.c foo.c
212 213 $ hg status
213 214 ! foobar.c
214 215 ? bar.c
215 216 ? foo.c
216 217 $ hg addremove
217 218 adding bar.c
218 219 adding foo.c
219 220 removing foobar.c
220 221 $ hg status
221 222 A bar.c
222 223 A foo.c
223 224 R foobar.c
224 225
225 226 - A file foobar.c was moved to foo.c without using :hg:`rename`.
226 227 Afterwards, it was edited slightly::
227 228
228 229 $ ls
229 230 foo.c
230 231 $ hg status
231 232 ! foobar.c
232 233 ? foo.c
233 234 $ hg addremove --similarity 90
234 235 removing foobar.c
235 236 adding foo.c
236 237 recording removal of foobar.c as rename to foo.c (94% similar)
237 238 $ hg status -C
238 239 A foo.c
239 240 foobar.c
240 241 R foobar.c
241 242
242 243 Returns 0 if all files are successfully added.
243 244 """
244 245 opts = pycompat.byteskwargs(opts)
245 246 try:
246 247 sim = float(opts.get('similarity') or 100)
247 248 except ValueError:
248 249 raise error.Abort(_('similarity must be a number'))
249 250 if sim < 0 or sim > 100:
250 251 raise error.Abort(_('similarity must be between 0 and 100'))
251 252 matcher = scmutil.match(repo[None], pats, opts)
252 253 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
253 254
254 255 @command('^annotate|blame',
255 256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
256 257 ('', 'follow', None,
257 258 _('follow copies/renames and list the filename (DEPRECATED)')),
258 259 ('', 'no-follow', None, _("don't follow copies and renames")),
259 260 ('a', 'text', None, _('treat all files as text')),
260 261 ('u', 'user', None, _('list the author (long with -v)')),
261 262 ('f', 'file', None, _('list the filename')),
262 263 ('d', 'date', None, _('list the date (short with -q)')),
263 264 ('n', 'number', None, _('list the revision number (default)')),
264 265 ('c', 'changeset', None, _('list the changeset')),
265 266 ('l', 'line-number', None, _('show line number at the first appearance')),
266 267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
267 268 ] + diffwsopts + walkopts + formatteropts,
268 269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
269 270 inferrepo=True)
270 271 def annotate(ui, repo, *pats, **opts):
271 272 """show changeset information by line for each file
272 273
273 274 List changes in files, showing the revision id responsible for
274 275 each line.
275 276
276 277 This command is useful for discovering when a change was made and
277 278 by whom.
278 279
279 280 If you include --file, --user, or --date, the revision number is
280 281 suppressed unless you also include --number.
281 282
282 283 Without the -a/--text option, annotate will avoid processing files
283 284 it detects as binary. With -a, annotate will annotate the file
284 285 anyway, although the results will probably be neither useful
285 286 nor desirable.
286 287
287 288 Returns 0 on success.
288 289 """
289 290 opts = pycompat.byteskwargs(opts)
290 291 if not pats:
291 292 raise error.Abort(_('at least one filename or pattern is required'))
292 293
293 294 if opts.get('follow'):
294 295 # --follow is deprecated and now just an alias for -f/--file
295 296 # to mimic the behavior of Mercurial before version 1.5
296 297 opts['file'] = True
297 298
298 299 ctx = scmutil.revsingle(repo, opts.get('rev'))
299 300
300 301 fm = ui.formatter('annotate', opts)
301 302 if ui.quiet:
302 303 datefunc = util.shortdate
303 304 else:
304 305 datefunc = util.datestr
305 306 if ctx.rev() is None:
306 307 def hexfn(node):
307 308 if node is None:
308 309 return None
309 310 else:
310 311 return fm.hexfunc(node)
311 312 if opts.get('changeset'):
312 313 # omit "+" suffix which is appended to node hex
313 314 def formatrev(rev):
314 315 if rev is None:
315 316 return '%d' % ctx.p1().rev()
316 317 else:
317 318 return '%d' % rev
318 319 else:
319 320 def formatrev(rev):
320 321 if rev is None:
321 322 return '%d+' % ctx.p1().rev()
322 323 else:
323 324 return '%d ' % rev
324 325 def formathex(hex):
325 326 if hex is None:
326 327 return '%s+' % fm.hexfunc(ctx.p1().node())
327 328 else:
328 329 return '%s ' % hex
329 330 else:
330 331 hexfn = fm.hexfunc
331 332 formatrev = formathex = str
332 333
333 334 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
334 335 ('number', ' ', lambda x: x[0].rev(), formatrev),
335 336 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
336 337 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
337 338 ('file', ' ', lambda x: x[0].path(), str),
338 339 ('line_number', ':', lambda x: x[1], str),
339 340 ]
340 341 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
341 342
342 343 if (not opts.get('user') and not opts.get('changeset')
343 344 and not opts.get('date') and not opts.get('file')):
344 345 opts['number'] = True
345 346
346 347 linenumber = opts.get('line_number') is not None
347 348 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
348 349 raise error.Abort(_('at least one of -n/-c is required for -l'))
349 350
350 351 ui.pager('annotate')
351 352
352 353 if fm.isplain():
353 354 def makefunc(get, fmt):
354 355 return lambda x: fmt(get(x))
355 356 else:
356 357 def makefunc(get, fmt):
357 358 return get
358 359 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
359 360 if opts.get(op)]
360 361 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
361 362 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
362 363 if opts.get(op))
363 364
364 365 def bad(x, y):
365 366 raise error.Abort("%s: %s" % (x, y))
366 367
367 368 m = scmutil.match(ctx, pats, opts, badfn=bad)
368 369
369 370 follow = not opts.get('no_follow')
370 371 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
371 372 whitespace=True)
372 373 skiprevs = opts.get('skip')
373 374 if skiprevs:
374 375 skiprevs = scmutil.revrange(repo, skiprevs)
375 376
376 377 for abs in ctx.walk(m):
377 378 fctx = ctx[abs]
378 379 if not opts.get('text') and fctx.isbinary():
379 380 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
380 381 continue
381 382
382 383 lines = fctx.annotate(follow=follow, linenumber=linenumber,
383 384 skiprevs=skiprevs, diffopts=diffopts)
384 385 if not lines:
385 386 continue
386 387 formats = []
387 388 pieces = []
388 389
389 390 for f, sep in funcmap:
390 391 l = [f(n) for n, dummy in lines]
391 392 if fm.isplain():
392 393 sizes = [encoding.colwidth(x) for x in l]
393 394 ml = max(sizes)
394 395 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
395 396 else:
396 397 formats.append(['%s' for x in l])
397 398 pieces.append(l)
398 399
399 400 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
400 401 fm.startitem()
401 402 fm.write(fields, "".join(f), *p)
402 403 fm.write('line', ": %s", l[1])
403 404
404 405 if not lines[-1][1].endswith('\n'):
405 406 fm.plain('\n')
406 407
407 408 fm.end()
408 409
409 410 @command('archive',
410 411 [('', 'no-decode', None, _('do not pass files through decoders')),
411 412 ('p', 'prefix', '', _('directory prefix for files in archive'),
412 413 _('PREFIX')),
413 414 ('r', 'rev', '', _('revision to distribute'), _('REV')),
414 415 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
415 416 ] + subrepoopts + walkopts,
416 417 _('[OPTION]... DEST'))
417 418 def archive(ui, repo, dest, **opts):
418 419 '''create an unversioned archive of a repository revision
419 420
420 421 By default, the revision used is the parent of the working
421 422 directory; use -r/--rev to specify a different revision.
422 423
423 424 The archive type is automatically detected based on file
424 425 extension (to override, use -t/--type).
425 426
426 427 .. container:: verbose
427 428
428 429 Examples:
429 430
430 431 - create a zip file containing the 1.0 release::
431 432
432 433 hg archive -r 1.0 project-1.0.zip
433 434
434 435 - create a tarball excluding .hg files::
435 436
436 437 hg archive project.tar.gz -X ".hg*"
437 438
438 439 Valid types are:
439 440
440 441 :``files``: a directory full of files (default)
441 442 :``tar``: tar archive, uncompressed
442 443 :``tbz2``: tar archive, compressed using bzip2
443 444 :``tgz``: tar archive, compressed using gzip
444 445 :``uzip``: zip archive, uncompressed
445 446 :``zip``: zip archive, compressed using deflate
446 447
447 448 The exact name of the destination archive or directory is given
448 449 using a format string; see :hg:`help export` for details.
449 450
450 451 Each member added to an archive file has a directory prefix
451 452 prepended. Use -p/--prefix to specify a format string for the
452 453 prefix. The default is the basename of the archive, with suffixes
453 454 removed.
454 455
455 456 Returns 0 on success.
456 457 '''
457 458
458 459 opts = pycompat.byteskwargs(opts)
459 460 ctx = scmutil.revsingle(repo, opts.get('rev'))
460 461 if not ctx:
461 462 raise error.Abort(_('no working directory: please specify a revision'))
462 463 node = ctx.node()
463 464 dest = cmdutil.makefilename(repo, dest, node)
464 465 if os.path.realpath(dest) == repo.root:
465 466 raise error.Abort(_('repository root cannot be destination'))
466 467
467 468 kind = opts.get('type') or archival.guesskind(dest) or 'files'
468 469 prefix = opts.get('prefix')
469 470
470 471 if dest == '-':
471 472 if kind == 'files':
472 473 raise error.Abort(_('cannot archive plain files to stdout'))
473 474 dest = cmdutil.makefileobj(repo, dest)
474 475 if not prefix:
475 476 prefix = os.path.basename(repo.root) + '-%h'
476 477
477 478 prefix = cmdutil.makefilename(repo, prefix, node)
478 479 matchfn = scmutil.match(ctx, [], opts)
479 480 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
480 481 matchfn, prefix, subrepos=opts.get('subrepos'))
481 482
482 483 @command('backout',
483 484 [('', 'merge', None, _('merge with old dirstate parent after backout')),
484 485 ('', 'commit', None,
485 486 _('commit if no conflicts were encountered (DEPRECATED)')),
486 487 ('', 'no-commit', None, _('do not commit')),
487 488 ('', 'parent', '',
488 489 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
489 490 ('r', 'rev', '', _('revision to backout'), _('REV')),
490 491 ('e', 'edit', False, _('invoke editor on commit messages')),
491 492 ] + mergetoolopts + walkopts + commitopts + commitopts2,
492 493 _('[OPTION]... [-r] REV'))
493 494 def backout(ui, repo, node=None, rev=None, **opts):
494 495 '''reverse effect of earlier changeset
495 496
496 497 Prepare a new changeset with the effect of REV undone in the
497 498 current working directory. If no conflicts were encountered,
498 499 it will be committed immediately.
499 500
500 501 If REV is the parent of the working directory, then this new changeset
501 502 is committed automatically (unless --no-commit is specified).
502 503
503 504 .. note::
504 505
505 506 :hg:`backout` cannot be used to fix either an unwanted or
506 507 incorrect merge.
507 508
508 509 .. container:: verbose
509 510
510 511 Examples:
511 512
512 513 - Reverse the effect of the parent of the working directory.
513 514 This backout will be committed immediately::
514 515
515 516 hg backout -r .
516 517
517 518 - Reverse the effect of previous bad revision 23::
518 519
519 520 hg backout -r 23
520 521
521 522 - Reverse the effect of previous bad revision 23 and
522 523 leave changes uncommitted::
523 524
524 525 hg backout -r 23 --no-commit
525 526 hg commit -m "Backout revision 23"
526 527
527 528 By default, the pending changeset will have one parent,
528 529 maintaining a linear history. With --merge, the pending
529 530 changeset will instead have two parents: the old parent of the
530 531 working directory and a new child of REV that simply undoes REV.
531 532
532 533 Before version 1.7, the behavior without --merge was equivalent
533 534 to specifying --merge followed by :hg:`update --clean .` to
534 535 cancel the merge and leave the child of REV as a head to be
535 536 merged separately.
536 537
537 538 See :hg:`help dates` for a list of formats valid for -d/--date.
538 539
539 540 See :hg:`help revert` for a way to restore files to the state
540 541 of another revision.
541 542
542 543 Returns 0 on success, 1 if nothing to backout or there are unresolved
543 544 files.
544 545 '''
545 546 wlock = lock = None
546 547 try:
547 548 wlock = repo.wlock()
548 549 lock = repo.lock()
549 550 return _dobackout(ui, repo, node, rev, **opts)
550 551 finally:
551 552 release(lock, wlock)
552 553
553 554 def _dobackout(ui, repo, node=None, rev=None, **opts):
554 555 opts = pycompat.byteskwargs(opts)
555 556 if opts.get('commit') and opts.get('no_commit'):
556 557 raise error.Abort(_("cannot use --commit with --no-commit"))
557 558 if opts.get('merge') and opts.get('no_commit'):
558 559 raise error.Abort(_("cannot use --merge with --no-commit"))
559 560
560 561 if rev and node:
561 562 raise error.Abort(_("please specify just one revision"))
562 563
563 564 if not rev:
564 565 rev = node
565 566
566 567 if not rev:
567 568 raise error.Abort(_("please specify a revision to backout"))
568 569
569 570 date = opts.get('date')
570 571 if date:
571 572 opts['date'] = util.parsedate(date)
572 573
573 574 cmdutil.checkunfinished(repo)
574 575 cmdutil.bailifchanged(repo)
575 576 node = scmutil.revsingle(repo, rev).node()
576 577
577 578 op1, op2 = repo.dirstate.parents()
578 579 if not repo.changelog.isancestor(node, op1):
579 580 raise error.Abort(_('cannot backout change that is not an ancestor'))
580 581
581 582 p1, p2 = repo.changelog.parents(node)
582 583 if p1 == nullid:
583 584 raise error.Abort(_('cannot backout a change with no parents'))
584 585 if p2 != nullid:
585 586 if not opts.get('parent'):
586 587 raise error.Abort(_('cannot backout a merge changeset'))
587 588 p = repo.lookup(opts['parent'])
588 589 if p not in (p1, p2):
589 590 raise error.Abort(_('%s is not a parent of %s') %
590 591 (short(p), short(node)))
591 592 parent = p
592 593 else:
593 594 if opts.get('parent'):
594 595 raise error.Abort(_('cannot use --parent on non-merge changeset'))
595 596 parent = p1
596 597
597 598 # the backout should appear on the same branch
598 599 branch = repo.dirstate.branch()
599 600 bheads = repo.branchheads(branch)
600 601 rctx = scmutil.revsingle(repo, hex(parent))
601 602 if not opts.get('merge') and op1 != node:
602 603 dsguard = dirstateguard.dirstateguard(repo, 'backout')
603 604 try:
604 605 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
605 606 'backout')
606 607 stats = mergemod.update(repo, parent, True, True, node, False)
607 608 repo.setparents(op1, op2)
608 609 dsguard.close()
609 610 hg._showstats(repo, stats)
610 611 if stats[3]:
611 612 repo.ui.status(_("use 'hg resolve' to retry unresolved "
612 613 "file merges\n"))
613 614 return 1
614 615 finally:
615 616 ui.setconfig('ui', 'forcemerge', '', '')
616 617 lockmod.release(dsguard)
617 618 else:
618 619 hg.clean(repo, node, show_stats=False)
619 620 repo.dirstate.setbranch(branch)
620 621 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
621 622
622 623 if opts.get('no_commit'):
623 624 msg = _("changeset %s backed out, "
624 625 "don't forget to commit.\n")
625 626 ui.status(msg % short(node))
626 627 return 0
627 628
628 629 def commitfunc(ui, repo, message, match, opts):
629 630 editform = 'backout'
630 631 e = cmdutil.getcommiteditor(editform=editform,
631 632 **pycompat.strkwargs(opts))
632 633 if not message:
633 634 # we don't translate commit messages
634 635 message = "Backed out changeset %s" % short(node)
635 636 e = cmdutil.getcommiteditor(edit=True, editform=editform)
636 637 return repo.commit(message, opts.get('user'), opts.get('date'),
637 638 match, editor=e)
638 639 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
639 640 if not newnode:
640 641 ui.status(_("nothing changed\n"))
641 642 return 1
642 643 cmdutil.commitstatus(repo, newnode, branch, bheads)
643 644
644 645 def nice(node):
645 646 return '%d:%s' % (repo.changelog.rev(node), short(node))
646 647 ui.status(_('changeset %s backs out changeset %s\n') %
647 648 (nice(repo.changelog.tip()), nice(node)))
648 649 if opts.get('merge') and op1 != node:
649 650 hg.clean(repo, op1, show_stats=False)
650 651 ui.status(_('merging with changeset %s\n')
651 652 % nice(repo.changelog.tip()))
652 653 try:
653 654 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
654 655 'backout')
655 656 return hg.merge(repo, hex(repo.changelog.tip()))
656 657 finally:
657 658 ui.setconfig('ui', 'forcemerge', '', '')
658 659 return 0
659 660
660 661 @command('bisect',
661 662 [('r', 'reset', False, _('reset bisect state')),
662 663 ('g', 'good', False, _('mark changeset good')),
663 664 ('b', 'bad', False, _('mark changeset bad')),
664 665 ('s', 'skip', False, _('skip testing changeset')),
665 666 ('e', 'extend', False, _('extend the bisect range')),
666 667 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
667 668 ('U', 'noupdate', False, _('do not update to target'))],
668 669 _("[-gbsr] [-U] [-c CMD] [REV]"))
669 670 def bisect(ui, repo, rev=None, extra=None, command=None,
670 671 reset=None, good=None, bad=None, skip=None, extend=None,
671 672 noupdate=None):
672 673 """subdivision search of changesets
673 674
674 675 This command helps to find changesets which introduce problems. To
675 676 use, mark the earliest changeset you know exhibits the problem as
676 677 bad, then mark the latest changeset which is free from the problem
677 678 as good. Bisect will update your working directory to a revision
678 679 for testing (unless the -U/--noupdate option is specified). Once
679 680 you have performed tests, mark the working directory as good or
680 681 bad, and bisect will either update to another candidate changeset
681 682 or announce that it has found the bad revision.
682 683
683 684 As a shortcut, you can also use the revision argument to mark a
684 685 revision as good or bad without checking it out first.
685 686
686 687 If you supply a command, it will be used for automatic bisection.
687 688 The environment variable HG_NODE will contain the ID of the
688 689 changeset being tested. The exit status of the command will be
689 690 used to mark revisions as good or bad: status 0 means good, 125
690 691 means to skip the revision, 127 (command not found) will abort the
691 692 bisection, and any other non-zero exit status means the revision
692 693 is bad.
693 694
694 695 .. container:: verbose
695 696
696 697 Some examples:
697 698
698 699 - start a bisection with known bad revision 34, and good revision 12::
699 700
700 701 hg bisect --bad 34
701 702 hg bisect --good 12
702 703
703 704 - advance the current bisection by marking current revision as good or
704 705 bad::
705 706
706 707 hg bisect --good
707 708 hg bisect --bad
708 709
709 710 - mark the current revision, or a known revision, to be skipped (e.g. if
710 711 that revision is not usable because of another issue)::
711 712
712 713 hg bisect --skip
713 714 hg bisect --skip 23
714 715
715 716 - skip all revisions that do not touch directories ``foo`` or ``bar``::
716 717
717 718 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
718 719
719 720 - forget the current bisection::
720 721
721 722 hg bisect --reset
722 723
723 724 - use 'make && make tests' to automatically find the first broken
724 725 revision::
725 726
726 727 hg bisect --reset
727 728 hg bisect --bad 34
728 729 hg bisect --good 12
729 730 hg bisect --command "make && make tests"
730 731
731 732 - see all changesets whose states are already known in the current
732 733 bisection::
733 734
734 735 hg log -r "bisect(pruned)"
735 736
736 737 - see the changeset currently being bisected (especially useful
737 738 if running with -U/--noupdate)::
738 739
739 740 hg log -r "bisect(current)"
740 741
741 742 - see all changesets that took part in the current bisection::
742 743
743 744 hg log -r "bisect(range)"
744 745
745 746 - you can even get a nice graph::
746 747
747 748 hg log --graph -r "bisect(range)"
748 749
749 750 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
750 751
751 752 Returns 0 on success.
752 753 """
753 754 # backward compatibility
754 755 if rev in "good bad reset init".split():
755 756 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
756 757 cmd, rev, extra = rev, extra, None
757 758 if cmd == "good":
758 759 good = True
759 760 elif cmd == "bad":
760 761 bad = True
761 762 else:
762 763 reset = True
763 764 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
764 765 raise error.Abort(_('incompatible arguments'))
765 766
766 767 if reset:
767 768 hbisect.resetstate(repo)
768 769 return
769 770
770 771 state = hbisect.load_state(repo)
771 772
772 773 # update state
773 774 if good or bad or skip:
774 775 if rev:
775 776 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
776 777 else:
777 778 nodes = [repo.lookup('.')]
778 779 if good:
779 780 state['good'] += nodes
780 781 elif bad:
781 782 state['bad'] += nodes
782 783 elif skip:
783 784 state['skip'] += nodes
784 785 hbisect.save_state(repo, state)
785 786 if not (state['good'] and state['bad']):
786 787 return
787 788
788 789 def mayupdate(repo, node, show_stats=True):
789 790 """common used update sequence"""
790 791 if noupdate:
791 792 return
792 793 cmdutil.checkunfinished(repo)
793 794 cmdutil.bailifchanged(repo)
794 795 return hg.clean(repo, node, show_stats=show_stats)
795 796
796 797 displayer = cmdutil.show_changeset(ui, repo, {})
797 798
798 799 if command:
799 800 changesets = 1
800 801 if noupdate:
801 802 try:
802 803 node = state['current'][0]
803 804 except LookupError:
804 805 raise error.Abort(_('current bisect revision is unknown - '
805 806 'start a new bisect to fix'))
806 807 else:
807 808 node, p2 = repo.dirstate.parents()
808 809 if p2 != nullid:
809 810 raise error.Abort(_('current bisect revision is a merge'))
810 811 if rev:
811 812 node = repo[scmutil.revsingle(repo, rev, node)].node()
812 813 try:
813 814 while changesets:
814 815 # update state
815 816 state['current'] = [node]
816 817 hbisect.save_state(repo, state)
817 818 status = ui.system(command, environ={'HG_NODE': hex(node)},
818 819 blockedtag='bisect_check')
819 820 if status == 125:
820 821 transition = "skip"
821 822 elif status == 0:
822 823 transition = "good"
823 824 # status < 0 means process was killed
824 825 elif status == 127:
825 826 raise error.Abort(_("failed to execute %s") % command)
826 827 elif status < 0:
827 828 raise error.Abort(_("%s killed") % command)
828 829 else:
829 830 transition = "bad"
830 831 state[transition].append(node)
831 832 ctx = repo[node]
832 833 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
833 834 hbisect.checkstate(state)
834 835 # bisect
835 836 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
836 837 # update to next check
837 838 node = nodes[0]
838 839 mayupdate(repo, node, show_stats=False)
839 840 finally:
840 841 state['current'] = [node]
841 842 hbisect.save_state(repo, state)
842 843 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
843 844 return
844 845
845 846 hbisect.checkstate(state)
846 847
847 848 # actually bisect
848 849 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
849 850 if extend:
850 851 if not changesets:
851 852 extendnode = hbisect.extendrange(repo, state, nodes, good)
852 853 if extendnode is not None:
853 854 ui.write(_("Extending search to changeset %d:%s\n")
854 855 % (extendnode.rev(), extendnode))
855 856 state['current'] = [extendnode.node()]
856 857 hbisect.save_state(repo, state)
857 858 return mayupdate(repo, extendnode.node())
858 859 raise error.Abort(_("nothing to extend"))
859 860
860 861 if changesets == 0:
861 862 hbisect.printresult(ui, repo, state, displayer, nodes, good)
862 863 else:
863 864 assert len(nodes) == 1 # only a single node can be tested next
864 865 node = nodes[0]
865 866 # compute the approximate number of remaining tests
866 867 tests, size = 0, 2
867 868 while size <= changesets:
868 869 tests, size = tests + 1, size * 2
869 870 rev = repo.changelog.rev(node)
870 871 ui.write(_("Testing changeset %d:%s "
871 872 "(%d changesets remaining, ~%d tests)\n")
872 873 % (rev, short(node), changesets, tests))
873 874 state['current'] = [node]
874 875 hbisect.save_state(repo, state)
875 876 return mayupdate(repo, node)
876 877
877 878 @command('bookmarks|bookmark',
878 879 [('f', 'force', False, _('force')),
879 880 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
880 881 ('d', 'delete', False, _('delete a given bookmark')),
881 882 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
882 883 ('i', 'inactive', False, _('mark a bookmark inactive')),
883 884 ] + formatteropts,
884 885 _('hg bookmarks [OPTIONS]... [NAME]...'))
885 886 def bookmark(ui, repo, *names, **opts):
886 887 '''create a new bookmark or list existing bookmarks
887 888
888 889 Bookmarks are labels on changesets to help track lines of development.
889 890 Bookmarks are unversioned and can be moved, renamed and deleted.
890 891 Deleting or moving a bookmark has no effect on the associated changesets.
891 892
892 893 Creating or updating to a bookmark causes it to be marked as 'active'.
893 894 The active bookmark is indicated with a '*'.
894 895 When a commit is made, the active bookmark will advance to the new commit.
895 896 A plain :hg:`update` will also advance an active bookmark, if possible.
896 897 Updating away from a bookmark will cause it to be deactivated.
897 898
898 899 Bookmarks can be pushed and pulled between repositories (see
899 900 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
900 901 diverged, a new 'divergent bookmark' of the form 'name@path' will
901 902 be created. Using :hg:`merge` will resolve the divergence.
902 903
903 904 A bookmark named '@' has the special property that :hg:`clone` will
904 905 check it out by default if it exists.
905 906
906 907 .. container:: verbose
907 908
908 909 Examples:
909 910
910 911 - create an active bookmark for a new line of development::
911 912
912 913 hg book new-feature
913 914
914 915 - create an inactive bookmark as a place marker::
915 916
916 917 hg book -i reviewed
917 918
918 919 - create an inactive bookmark on another changeset::
919 920
920 921 hg book -r .^ tested
921 922
922 923 - rename bookmark turkey to dinner::
923 924
924 925 hg book -m turkey dinner
925 926
926 927 - move the '@' bookmark from another branch::
927 928
928 929 hg book -f @
929 930 '''
930 931 opts = pycompat.byteskwargs(opts)
931 932 force = opts.get('force')
932 933 rev = opts.get('rev')
933 934 delete = opts.get('delete')
934 935 rename = opts.get('rename')
935 936 inactive = opts.get('inactive')
936 937
937 938 def checkformat(mark):
938 939 mark = mark.strip()
939 940 if not mark:
940 941 raise error.Abort(_("bookmark names cannot consist entirely of "
941 942 "whitespace"))
942 943 scmutil.checknewlabel(repo, mark, 'bookmark')
943 944 return mark
944 945
945 946 def checkconflict(repo, mark, cur, force=False, target=None):
946 947 if mark in marks and not force:
947 948 if target:
948 949 if marks[mark] == target and target == cur:
949 950 # re-activating a bookmark
950 951 return
951 952 anc = repo.changelog.ancestors([repo[target].rev()])
952 953 bmctx = repo[marks[mark]]
953 954 divs = [repo[b].node() for b in marks
954 955 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
955 956
956 957 # allow resolving a single divergent bookmark even if moving
957 958 # the bookmark across branches when a revision is specified
958 959 # that contains a divergent bookmark
959 960 if bmctx.rev() not in anc and target in divs:
960 961 bookmarks.deletedivergent(repo, [target], mark)
961 962 return
962 963
963 964 deletefrom = [b for b in divs
964 965 if repo[b].rev() in anc or b == target]
965 966 bookmarks.deletedivergent(repo, deletefrom, mark)
966 967 if bookmarks.validdest(repo, bmctx, repo[target]):
967 968 ui.status(_("moving bookmark '%s' forward from %s\n") %
968 969 (mark, short(bmctx.node())))
969 970 return
970 971 raise error.Abort(_("bookmark '%s' already exists "
971 972 "(use -f to force)") % mark)
972 973 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
973 974 and not force):
974 975 raise error.Abort(
975 976 _("a bookmark cannot have the name of an existing branch"))
976 977 if len(mark) > 3 and not force:
977 978 try:
978 979 shadowhash = (mark in repo)
979 980 except error.LookupError: # ambiguous identifier
980 981 shadowhash = False
981 982 if shadowhash:
982 983 repo.ui.warn(
983 984 _("bookmark %s matches a changeset hash\n"
984 985 "(did you leave a -r out of an 'hg bookmark' command?)\n")
985 986 % mark)
986 987
987 988 if delete and rename:
988 989 raise error.Abort(_("--delete and --rename are incompatible"))
989 990 if delete and rev:
990 991 raise error.Abort(_("--rev is incompatible with --delete"))
991 992 if rename and rev:
992 993 raise error.Abort(_("--rev is incompatible with --rename"))
993 994 if not names and (delete or rev):
994 995 raise error.Abort(_("bookmark name required"))
995 996
996 997 if delete or rename or names or inactive:
997 998 wlock = lock = tr = None
998 999 try:
999 1000 wlock = repo.wlock()
1000 1001 lock = repo.lock()
1001 1002 cur = repo.changectx('.').node()
1002 1003 marks = repo._bookmarks
1003 1004 if delete:
1004 1005 tr = repo.transaction('bookmark')
1005 1006 for mark in names:
1006 1007 if mark not in marks:
1007 1008 raise error.Abort(_("bookmark '%s' does not exist") %
1008 1009 mark)
1009 1010 if mark == repo._activebookmark:
1010 1011 bookmarks.deactivate(repo)
1011 1012 del marks[mark]
1012 1013
1013 1014 elif rename:
1014 1015 tr = repo.transaction('bookmark')
1015 1016 if not names:
1016 1017 raise error.Abort(_("new bookmark name required"))
1017 1018 elif len(names) > 1:
1018 1019 raise error.Abort(_("only one new bookmark name allowed"))
1019 1020 mark = checkformat(names[0])
1020 1021 if rename not in marks:
1021 1022 raise error.Abort(_("bookmark '%s' does not exist")
1022 1023 % rename)
1023 1024 checkconflict(repo, mark, cur, force)
1024 1025 marks[mark] = marks[rename]
1025 1026 if repo._activebookmark == rename and not inactive:
1026 1027 bookmarks.activate(repo, mark)
1027 1028 del marks[rename]
1028 1029 elif names:
1029 1030 tr = repo.transaction('bookmark')
1030 1031 newact = None
1031 1032 for mark in names:
1032 1033 mark = checkformat(mark)
1033 1034 if newact is None:
1034 1035 newact = mark
1035 1036 if inactive and mark == repo._activebookmark:
1036 1037 bookmarks.deactivate(repo)
1037 1038 return
1038 1039 tgt = cur
1039 1040 if rev:
1040 1041 tgt = scmutil.revsingle(repo, rev).node()
1041 1042 checkconflict(repo, mark, cur, force, tgt)
1042 1043 marks[mark] = tgt
1043 1044 if not inactive and cur == marks[newact] and not rev:
1044 1045 bookmarks.activate(repo, newact)
1045 1046 elif cur != tgt and newact == repo._activebookmark:
1046 1047 bookmarks.deactivate(repo)
1047 1048 elif inactive:
1048 1049 if len(marks) == 0:
1049 1050 ui.status(_("no bookmarks set\n"))
1050 1051 elif not repo._activebookmark:
1051 1052 ui.status(_("no active bookmark\n"))
1052 1053 else:
1053 1054 bookmarks.deactivate(repo)
1054 1055 if tr is not None:
1055 1056 marks.recordchange(tr)
1056 1057 tr.close()
1057 1058 finally:
1058 1059 lockmod.release(tr, lock, wlock)
1059 1060 else: # show bookmarks
1060 1061 fm = ui.formatter('bookmarks', opts)
1061 1062 hexfn = fm.hexfunc
1062 1063 marks = repo._bookmarks
1063 1064 if len(marks) == 0 and fm.isplain():
1064 1065 ui.status(_("no bookmarks set\n"))
1065 1066 for bmark, n in sorted(marks.iteritems()):
1066 1067 active = repo._activebookmark
1067 1068 if bmark == active:
1068 1069 prefix, label = '*', activebookmarklabel
1069 1070 else:
1070 1071 prefix, label = ' ', ''
1071 1072
1072 1073 fm.startitem()
1073 1074 if not ui.quiet:
1074 1075 fm.plain(' %s ' % prefix, label=label)
1075 1076 fm.write('bookmark', '%s', bmark, label=label)
1076 1077 pad = " " * (25 - encoding.colwidth(bmark))
1077 1078 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1078 1079 repo.changelog.rev(n), hexfn(n), label=label)
1079 1080 fm.data(active=(bmark == active))
1080 1081 fm.plain('\n')
1081 1082 fm.end()
1082 1083
1083 1084 @command('branch',
1084 1085 [('f', 'force', None,
1085 1086 _('set branch name even if it shadows an existing branch')),
1086 1087 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1087 1088 _('[-fC] [NAME]'))
1088 1089 def branch(ui, repo, label=None, **opts):
1089 1090 """set or show the current branch name
1090 1091
1091 1092 .. note::
1092 1093
1093 1094 Branch names are permanent and global. Use :hg:`bookmark` to create a
1094 1095 light-weight bookmark instead. See :hg:`help glossary` for more
1095 1096 information about named branches and bookmarks.
1096 1097
1097 1098 With no argument, show the current branch name. With one argument,
1098 1099 set the working directory branch name (the branch will not exist
1099 1100 in the repository until the next commit). Standard practice
1100 1101 recommends that primary development take place on the 'default'
1101 1102 branch.
1102 1103
1103 1104 Unless -f/--force is specified, branch will not let you set a
1104 1105 branch name that already exists.
1105 1106
1106 1107 Use -C/--clean to reset the working directory branch to that of
1107 1108 the parent of the working directory, negating a previous branch
1108 1109 change.
1109 1110
1110 1111 Use the command :hg:`update` to switch to an existing branch. Use
1111 1112 :hg:`commit --close-branch` to mark this branch head as closed.
1112 1113 When all heads of a branch are closed, the branch will be
1113 1114 considered closed.
1114 1115
1115 1116 Returns 0 on success.
1116 1117 """
1117 1118 opts = pycompat.byteskwargs(opts)
1118 1119 if label:
1119 1120 label = label.strip()
1120 1121
1121 1122 if not opts.get('clean') and not label:
1122 1123 ui.write("%s\n" % repo.dirstate.branch())
1123 1124 return
1124 1125
1125 1126 with repo.wlock():
1126 1127 if opts.get('clean'):
1127 1128 label = repo[None].p1().branch()
1128 1129 repo.dirstate.setbranch(label)
1129 1130 ui.status(_('reset working directory to branch %s\n') % label)
1130 1131 elif label:
1131 1132 if not opts.get('force') and label in repo.branchmap():
1132 1133 if label not in [p.branch() for p in repo[None].parents()]:
1133 1134 raise error.Abort(_('a branch of the same name already'
1134 1135 ' exists'),
1135 1136 # i18n: "it" refers to an existing branch
1136 1137 hint=_("use 'hg update' to switch to it"))
1137 1138 scmutil.checknewlabel(repo, label, 'branch')
1138 1139 repo.dirstate.setbranch(label)
1139 1140 ui.status(_('marked working directory as branch %s\n') % label)
1140 1141
1141 1142 # find any open named branches aside from default
1142 1143 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1143 1144 if n != "default" and not c]
1144 1145 if not others:
1145 1146 ui.status(_('(branches are permanent and global, '
1146 1147 'did you want a bookmark?)\n'))
1147 1148
1148 1149 @command('branches',
1149 1150 [('a', 'active', False,
1150 1151 _('show only branches that have unmerged heads (DEPRECATED)')),
1151 1152 ('c', 'closed', False, _('show normal and closed branches')),
1152 1153 ] + formatteropts,
1153 1154 _('[-c]'))
1154 1155 def branches(ui, repo, active=False, closed=False, **opts):
1155 1156 """list repository named branches
1156 1157
1157 1158 List the repository's named branches, indicating which ones are
1158 1159 inactive. If -c/--closed is specified, also list branches which have
1159 1160 been marked closed (see :hg:`commit --close-branch`).
1160 1161
1161 1162 Use the command :hg:`update` to switch to an existing branch.
1162 1163
1163 1164 Returns 0.
1164 1165 """
1165 1166
1166 1167 opts = pycompat.byteskwargs(opts)
1167 1168 ui.pager('branches')
1168 1169 fm = ui.formatter('branches', opts)
1169 1170 hexfunc = fm.hexfunc
1170 1171
1171 1172 allheads = set(repo.heads())
1172 1173 branches = []
1173 1174 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1174 1175 isactive = not isclosed and bool(set(heads) & allheads)
1175 1176 branches.append((tag, repo[tip], isactive, not isclosed))
1176 1177 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1177 1178 reverse=True)
1178 1179
1179 1180 for tag, ctx, isactive, isopen in branches:
1180 1181 if active and not isactive:
1181 1182 continue
1182 1183 if isactive:
1183 1184 label = 'branches.active'
1184 1185 notice = ''
1185 1186 elif not isopen:
1186 1187 if not closed:
1187 1188 continue
1188 1189 label = 'branches.closed'
1189 1190 notice = _(' (closed)')
1190 1191 else:
1191 1192 label = 'branches.inactive'
1192 1193 notice = _(' (inactive)')
1193 1194 current = (tag == repo.dirstate.branch())
1194 1195 if current:
1195 1196 label = 'branches.current'
1196 1197
1197 1198 fm.startitem()
1198 1199 fm.write('branch', '%s', tag, label=label)
1199 1200 rev = ctx.rev()
1200 1201 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1201 1202 fmt = ' ' * padsize + ' %d:%s'
1202 1203 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1203 1204 label='log.changeset changeset.%s' % ctx.phasestr())
1204 1205 fm.context(ctx=ctx)
1205 1206 fm.data(active=isactive, closed=not isopen, current=current)
1206 1207 if not ui.quiet:
1207 1208 fm.plain(notice)
1208 1209 fm.plain('\n')
1209 1210 fm.end()
1210 1211
1211 1212 @command('bundle',
1212 1213 [('f', 'force', None, _('run even when the destination is unrelated')),
1213 1214 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1214 1215 _('REV')),
1215 1216 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1216 1217 _('BRANCH')),
1217 1218 ('', 'base', [],
1218 1219 _('a base changeset assumed to be available at the destination'),
1219 1220 _('REV')),
1220 1221 ('a', 'all', None, _('bundle all changesets in the repository')),
1221 1222 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1222 1223 ] + remoteopts,
1223 1224 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1224 1225 def bundle(ui, repo, fname, dest=None, **opts):
1225 1226 """create a bundle file
1226 1227
1227 1228 Generate a bundle file containing data to be added to a repository.
1228 1229
1229 1230 To create a bundle containing all changesets, use -a/--all
1230 1231 (or --base null). Otherwise, hg assumes the destination will have
1231 1232 all the nodes you specify with --base parameters. Otherwise, hg
1232 1233 will assume the repository has all the nodes in destination, or
1233 1234 default-push/default if no destination is specified.
1234 1235
1235 1236 You can change bundle format with the -t/--type option. See
1236 1237 :hg:`help bundlespec` for documentation on this format. By default,
1237 1238 the most appropriate format is used and compression defaults to
1238 1239 bzip2.
1239 1240
1240 1241 The bundle file can then be transferred using conventional means
1241 1242 and applied to another repository with the unbundle or pull
1242 1243 command. This is useful when direct push and pull are not
1243 1244 available or when exporting an entire repository is undesirable.
1244 1245
1245 1246 Applying bundles preserves all changeset contents including
1246 1247 permissions, copy/rename information, and revision history.
1247 1248
1248 1249 Returns 0 on success, 1 if no changes found.
1249 1250 """
1250 1251 opts = pycompat.byteskwargs(opts)
1251 1252 revs = None
1252 1253 if 'rev' in opts:
1253 1254 revstrings = opts['rev']
1254 1255 revs = scmutil.revrange(repo, revstrings)
1255 1256 if revstrings and not revs:
1256 1257 raise error.Abort(_('no commits to bundle'))
1257 1258
1258 1259 bundletype = opts.get('type', 'bzip2').lower()
1259 1260 try:
1260 1261 bcompression, cgversion, params = exchange.parsebundlespec(
1261 1262 repo, bundletype, strict=False)
1262 1263 except error.UnsupportedBundleSpecification as e:
1263 1264 raise error.Abort(str(e),
1264 1265 hint=_("see 'hg help bundlespec' for supported "
1265 1266 "values for --type"))
1266 1267
1267 1268 # Packed bundles are a pseudo bundle format for now.
1268 1269 if cgversion == 's1':
1269 1270 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1270 1271 hint=_("use 'hg debugcreatestreamclonebundle'"))
1271 1272
1272 1273 if opts.get('all'):
1273 1274 if dest:
1274 1275 raise error.Abort(_("--all is incompatible with specifying "
1275 1276 "a destination"))
1276 1277 if opts.get('base'):
1277 1278 ui.warn(_("ignoring --base because --all was specified\n"))
1278 1279 base = ['null']
1279 1280 else:
1280 1281 base = scmutil.revrange(repo, opts.get('base'))
1281 1282 if cgversion not in changegroup.supportedoutgoingversions(repo):
1282 1283 raise error.Abort(_("repository does not support bundle version %s") %
1283 1284 cgversion)
1284 1285
1285 1286 if base:
1286 1287 if dest:
1287 1288 raise error.Abort(_("--base is incompatible with specifying "
1288 1289 "a destination"))
1289 1290 common = [repo.lookup(rev) for rev in base]
1290 1291 heads = revs and map(repo.lookup, revs) or None
1291 1292 outgoing = discovery.outgoing(repo, common, heads)
1292 1293 else:
1293 1294 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1294 1295 dest, branches = hg.parseurl(dest, opts.get('branch'))
1295 1296 other = hg.peer(repo, opts, dest)
1296 1297 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1297 1298 heads = revs and map(repo.lookup, revs) or revs
1298 1299 outgoing = discovery.findcommonoutgoing(repo, other,
1299 1300 onlyheads=heads,
1300 1301 force=opts.get('force'),
1301 1302 portable=True)
1302 1303
1303 1304 if not outgoing.missing:
1304 1305 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1305 1306 return 1
1306 1307
1307 1308 if cgversion == '01': #bundle1
1308 1309 if bcompression is None:
1309 1310 bcompression = 'UN'
1310 1311 bversion = 'HG10' + bcompression
1311 1312 bcompression = None
1312 1313 elif cgversion in ('02', '03'):
1313 1314 bversion = 'HG20'
1314 1315 else:
1315 1316 raise error.ProgrammingError(
1316 1317 'bundle: unexpected changegroup version %s' % cgversion)
1317 1318
1318 1319 # TODO compression options should be derived from bundlespec parsing.
1319 1320 # This is a temporary hack to allow adjusting bundle compression
1320 1321 # level without a) formalizing the bundlespec changes to declare it
1321 1322 # b) introducing a command flag.
1322 1323 compopts = {}
1323 1324 complevel = ui.configint('experimental', 'bundlecomplevel')
1324 1325 if complevel is not None:
1325 1326 compopts['level'] = complevel
1326 1327
1327 1328
1328 1329 contentopts = {'cg.version': cgversion}
1329 1330 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
1330 1331 contentopts['obsolescence'] = True
1331 1332 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1332 1333 contentopts, compression=bcompression,
1333 1334 compopts=compopts)
1334 1335
1335 1336 @command('cat',
1336 1337 [('o', 'output', '',
1337 1338 _('print output to file with formatted name'), _('FORMAT')),
1338 1339 ('r', 'rev', '', _('print the given revision'), _('REV')),
1339 1340 ('', 'decode', None, _('apply any matching decode filter')),
1340 1341 ] + walkopts,
1341 1342 _('[OPTION]... FILE...'),
1342 1343 inferrepo=True)
1343 1344 def cat(ui, repo, file1, *pats, **opts):
1344 1345 """output the current or given revision of files
1345 1346
1346 1347 Print the specified files as they were at the given revision. If
1347 1348 no revision is given, the parent of the working directory is used.
1348 1349
1349 1350 Output may be to a file, in which case the name of the file is
1350 1351 given using a format string. The formatting rules as follows:
1351 1352
1352 1353 :``%%``: literal "%" character
1353 1354 :``%s``: basename of file being printed
1354 1355 :``%d``: dirname of file being printed, or '.' if in repository root
1355 1356 :``%p``: root-relative path name of file being printed
1356 1357 :``%H``: changeset hash (40 hexadecimal digits)
1357 1358 :``%R``: changeset revision number
1358 1359 :``%h``: short-form changeset hash (12 hexadecimal digits)
1359 1360 :``%r``: zero-padded changeset revision number
1360 1361 :``%b``: basename of the exporting repository
1361 1362
1362 1363 Returns 0 on success.
1363 1364 """
1364 1365 ctx = scmutil.revsingle(repo, opts.get('rev'))
1365 1366 m = scmutil.match(ctx, (file1,) + pats, opts)
1366 1367 fntemplate = opts.pop('output', '')
1367 1368 if cmdutil.isstdiofilename(fntemplate):
1368 1369 fntemplate = ''
1369 1370
1370 1371 if not fntemplate:
1371 1372 ui.pager('cat')
1372 1373 return cmdutil.cat(ui, repo, ctx, m, fntemplate, '', **opts)
1373 1374
1374 1375 @command('^clone',
1375 1376 [('U', 'noupdate', None, _('the clone will include an empty working '
1376 1377 'directory (only a repository)')),
1377 1378 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1378 1379 _('REV')),
1379 1380 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1380 1381 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1381 1382 ('', 'pull', None, _('use pull protocol to copy metadata')),
1382 1383 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1383 1384 ] + remoteopts,
1384 1385 _('[OPTION]... SOURCE [DEST]'),
1385 1386 norepo=True)
1386 1387 def clone(ui, source, dest=None, **opts):
1387 1388 """make a copy of an existing repository
1388 1389
1389 1390 Create a copy of an existing repository in a new directory.
1390 1391
1391 1392 If no destination directory name is specified, it defaults to the
1392 1393 basename of the source.
1393 1394
1394 1395 The location of the source is added to the new repository's
1395 1396 ``.hg/hgrc`` file, as the default to be used for future pulls.
1396 1397
1397 1398 Only local paths and ``ssh://`` URLs are supported as
1398 1399 destinations. For ``ssh://`` destinations, no working directory or
1399 1400 ``.hg/hgrc`` will be created on the remote side.
1400 1401
1401 1402 If the source repository has a bookmark called '@' set, that
1402 1403 revision will be checked out in the new repository by default.
1403 1404
1404 1405 To check out a particular version, use -u/--update, or
1405 1406 -U/--noupdate to create a clone with no working directory.
1406 1407
1407 1408 To pull only a subset of changesets, specify one or more revisions
1408 1409 identifiers with -r/--rev or branches with -b/--branch. The
1409 1410 resulting clone will contain only the specified changesets and
1410 1411 their ancestors. These options (or 'clone src#rev dest') imply
1411 1412 --pull, even for local source repositories.
1412 1413
1413 1414 .. note::
1414 1415
1415 1416 Specifying a tag will include the tagged changeset but not the
1416 1417 changeset containing the tag.
1417 1418
1418 1419 .. container:: verbose
1419 1420
1420 1421 For efficiency, hardlinks are used for cloning whenever the
1421 1422 source and destination are on the same filesystem (note this
1422 1423 applies only to the repository data, not to the working
1423 1424 directory). Some filesystems, such as AFS, implement hardlinking
1424 1425 incorrectly, but do not report errors. In these cases, use the
1425 1426 --pull option to avoid hardlinking.
1426 1427
1427 1428 In some cases, you can clone repositories and the working
1428 1429 directory using full hardlinks with ::
1429 1430
1430 1431 $ cp -al REPO REPOCLONE
1431 1432
1432 1433 This is the fastest way to clone, but it is not always safe. The
1433 1434 operation is not atomic (making sure REPO is not modified during
1434 1435 the operation is up to you) and you have to make sure your
1435 1436 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1436 1437 so). Also, this is not compatible with certain extensions that
1437 1438 place their metadata under the .hg directory, such as mq.
1438 1439
1439 1440 Mercurial will update the working directory to the first applicable
1440 1441 revision from this list:
1441 1442
1442 1443 a) null if -U or the source repository has no changesets
1443 1444 b) if -u . and the source repository is local, the first parent of
1444 1445 the source repository's working directory
1445 1446 c) the changeset specified with -u (if a branch name, this means the
1446 1447 latest head of that branch)
1447 1448 d) the changeset specified with -r
1448 1449 e) the tipmost head specified with -b
1449 1450 f) the tipmost head specified with the url#branch source syntax
1450 1451 g) the revision marked with the '@' bookmark, if present
1451 1452 h) the tipmost head of the default branch
1452 1453 i) tip
1453 1454
1454 1455 When cloning from servers that support it, Mercurial may fetch
1455 1456 pre-generated data from a server-advertised URL. When this is done,
1456 1457 hooks operating on incoming changesets and changegroups may fire twice,
1457 1458 once for the bundle fetched from the URL and another for any additional
1458 1459 data not fetched from this URL. In addition, if an error occurs, the
1459 1460 repository may be rolled back to a partial clone. This behavior may
1460 1461 change in future releases. See :hg:`help -e clonebundles` for more.
1461 1462
1462 1463 Examples:
1463 1464
1464 1465 - clone a remote repository to a new directory named hg/::
1465 1466
1466 1467 hg clone https://www.mercurial-scm.org/repo/hg/
1467 1468
1468 1469 - create a lightweight local clone::
1469 1470
1470 1471 hg clone project/ project-feature/
1471 1472
1472 1473 - clone from an absolute path on an ssh server (note double-slash)::
1473 1474
1474 1475 hg clone ssh://user@server//home/projects/alpha/
1475 1476
1476 1477 - do a high-speed clone over a LAN while checking out a
1477 1478 specified version::
1478 1479
1479 1480 hg clone --uncompressed http://server/repo -u 1.5
1480 1481
1481 1482 - create a repository without changesets after a particular revision::
1482 1483
1483 1484 hg clone -r 04e544 experimental/ good/
1484 1485
1485 1486 - clone (and track) a particular named branch::
1486 1487
1487 1488 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1488 1489
1489 1490 See :hg:`help urls` for details on specifying URLs.
1490 1491
1491 1492 Returns 0 on success.
1492 1493 """
1493 1494 opts = pycompat.byteskwargs(opts)
1494 1495 if opts.get('noupdate') and opts.get('updaterev'):
1495 1496 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1496 1497
1497 1498 r = hg.clone(ui, opts, source, dest,
1498 1499 pull=opts.get('pull'),
1499 1500 stream=opts.get('uncompressed'),
1500 1501 rev=opts.get('rev'),
1501 1502 update=opts.get('updaterev') or not opts.get('noupdate'),
1502 1503 branch=opts.get('branch'),
1503 1504 shareopts=opts.get('shareopts'))
1504 1505
1505 1506 return r is None
1506 1507
1507 1508 @command('^commit|ci',
1508 1509 [('A', 'addremove', None,
1509 1510 _('mark new/missing files as added/removed before committing')),
1510 1511 ('', 'close-branch', None,
1511 1512 _('mark a branch head as closed')),
1512 1513 ('', 'amend', None, _('amend the parent of the working directory')),
1513 1514 ('s', 'secret', None, _('use the secret phase for committing')),
1514 1515 ('e', 'edit', None, _('invoke editor on commit messages')),
1515 1516 ('i', 'interactive', None, _('use interactive mode')),
1516 1517 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1517 1518 _('[OPTION]... [FILE]...'),
1518 1519 inferrepo=True)
1519 1520 def commit(ui, repo, *pats, **opts):
1520 1521 """commit the specified files or all outstanding changes
1521 1522
1522 1523 Commit changes to the given files into the repository. Unlike a
1523 1524 centralized SCM, this operation is a local operation. See
1524 1525 :hg:`push` for a way to actively distribute your changes.
1525 1526
1526 1527 If a list of files is omitted, all changes reported by :hg:`status`
1527 1528 will be committed.
1528 1529
1529 1530 If you are committing the result of a merge, do not provide any
1530 1531 filenames or -I/-X filters.
1531 1532
1532 1533 If no commit message is specified, Mercurial starts your
1533 1534 configured editor where you can enter a message. In case your
1534 1535 commit fails, you will find a backup of your message in
1535 1536 ``.hg/last-message.txt``.
1536 1537
1537 1538 The --close-branch flag can be used to mark the current branch
1538 1539 head closed. When all heads of a branch are closed, the branch
1539 1540 will be considered closed and no longer listed.
1540 1541
1541 1542 The --amend flag can be used to amend the parent of the
1542 1543 working directory with a new commit that contains the changes
1543 1544 in the parent in addition to those currently reported by :hg:`status`,
1544 1545 if there are any. The old commit is stored in a backup bundle in
1545 1546 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1546 1547 on how to restore it).
1547 1548
1548 1549 Message, user and date are taken from the amended commit unless
1549 1550 specified. When a message isn't specified on the command line,
1550 1551 the editor will open with the message of the amended commit.
1551 1552
1552 1553 It is not possible to amend public changesets (see :hg:`help phases`)
1553 1554 or changesets that have children.
1554 1555
1555 1556 See :hg:`help dates` for a list of formats valid for -d/--date.
1556 1557
1557 1558 Returns 0 on success, 1 if nothing changed.
1558 1559
1559 1560 .. container:: verbose
1560 1561
1561 1562 Examples:
1562 1563
1563 1564 - commit all files ending in .py::
1564 1565
1565 1566 hg commit --include "set:**.py"
1566 1567
1567 1568 - commit all non-binary files::
1568 1569
1569 1570 hg commit --exclude "set:binary()"
1570 1571
1571 1572 - amend the current commit and set the date to now::
1572 1573
1573 1574 hg commit --amend --date now
1574 1575 """
1575 1576 wlock = lock = None
1576 1577 try:
1577 1578 wlock = repo.wlock()
1578 1579 lock = repo.lock()
1579 1580 return _docommit(ui, repo, *pats, **opts)
1580 1581 finally:
1581 1582 release(lock, wlock)
1582 1583
1583 1584 def _docommit(ui, repo, *pats, **opts):
1584 1585 if opts.get(r'interactive'):
1585 1586 opts.pop(r'interactive')
1586 1587 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1587 1588 cmdutil.recordfilter, *pats,
1588 1589 **opts)
1589 1590 # ret can be 0 (no changes to record) or the value returned by
1590 1591 # commit(), 1 if nothing changed or None on success.
1591 1592 return 1 if ret == 0 else ret
1592 1593
1593 1594 opts = pycompat.byteskwargs(opts)
1594 1595 if opts.get('subrepos'):
1595 1596 if opts.get('amend'):
1596 1597 raise error.Abort(_('cannot amend with --subrepos'))
1597 1598 # Let --subrepos on the command line override config setting.
1598 1599 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1599 1600
1600 1601 cmdutil.checkunfinished(repo, commit=True)
1601 1602
1602 1603 branch = repo[None].branch()
1603 1604 bheads = repo.branchheads(branch)
1604 1605
1605 1606 extra = {}
1606 1607 if opts.get('close_branch'):
1607 1608 extra['close'] = 1
1608 1609
1609 1610 if not bheads:
1610 1611 raise error.Abort(_('can only close branch heads'))
1611 1612 elif opts.get('amend'):
1612 1613 if repo[None].parents()[0].p1().branch() != branch and \
1613 1614 repo[None].parents()[0].p2().branch() != branch:
1614 1615 raise error.Abort(_('can only close branch heads'))
1615 1616
1616 1617 if opts.get('amend'):
1617 1618 if ui.configbool('ui', 'commitsubrepos'):
1618 1619 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1619 1620
1620 1621 old = repo['.']
1621 1622 if not old.mutable():
1622 1623 raise error.Abort(_('cannot amend public changesets'))
1623 1624 if len(repo[None].parents()) > 1:
1624 1625 raise error.Abort(_('cannot amend while merging'))
1625 1626 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1626 1627 if not allowunstable and old.children():
1627 1628 raise error.Abort(_('cannot amend changeset with children'))
1628 1629
1629 1630 # Currently histedit gets confused if an amend happens while histedit
1630 1631 # is in progress. Since we have a checkunfinished command, we are
1631 1632 # temporarily honoring it.
1632 1633 #
1633 1634 # Note: eventually this guard will be removed. Please do not expect
1634 1635 # this behavior to remain.
1635 1636 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1636 1637 cmdutil.checkunfinished(repo)
1637 1638
1638 1639 # commitfunc is used only for temporary amend commit by cmdutil.amend
1639 1640 def commitfunc(ui, repo, message, match, opts):
1640 1641 return repo.commit(message,
1641 1642 opts.get('user') or old.user(),
1642 1643 opts.get('date') or old.date(),
1643 1644 match,
1644 1645 extra=extra)
1645 1646
1646 1647 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1647 1648 if node == old.node():
1648 1649 ui.status(_("nothing changed\n"))
1649 1650 return 1
1650 1651 else:
1651 1652 def commitfunc(ui, repo, message, match, opts):
1652 1653 overrides = {}
1653 1654 if opts.get('secret'):
1654 1655 overrides[('phases', 'new-commit')] = 'secret'
1655 1656
1656 1657 baseui = repo.baseui
1657 1658 with baseui.configoverride(overrides, 'commit'):
1658 1659 with ui.configoverride(overrides, 'commit'):
1659 1660 editform = cmdutil.mergeeditform(repo[None],
1660 1661 'commit.normal')
1661 1662 editor = cmdutil.getcommiteditor(
1662 1663 editform=editform, **pycompat.strkwargs(opts))
1663 1664 return repo.commit(message,
1664 1665 opts.get('user'),
1665 1666 opts.get('date'),
1666 1667 match,
1667 1668 editor=editor,
1668 1669 extra=extra)
1669 1670
1670 1671 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1671 1672
1672 1673 if not node:
1673 1674 stat = cmdutil.postcommitstatus(repo, pats, opts)
1674 1675 if stat[3]:
1675 1676 ui.status(_("nothing changed (%d missing files, see "
1676 1677 "'hg status')\n") % len(stat[3]))
1677 1678 else:
1678 1679 ui.status(_("nothing changed\n"))
1679 1680 return 1
1680 1681
1681 1682 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1682 1683
1683 1684 @command('config|showconfig|debugconfig',
1684 1685 [('u', 'untrusted', None, _('show untrusted configuration options')),
1685 1686 ('e', 'edit', None, _('edit user config')),
1686 1687 ('l', 'local', None, _('edit repository config')),
1687 1688 ('g', 'global', None, _('edit global config'))] + formatteropts,
1688 1689 _('[-u] [NAME]...'),
1689 1690 optionalrepo=True)
1690 1691 def config(ui, repo, *values, **opts):
1691 1692 """show combined config settings from all hgrc files
1692 1693
1693 1694 With no arguments, print names and values of all config items.
1694 1695
1695 1696 With one argument of the form section.name, print just the value
1696 1697 of that config item.
1697 1698
1698 1699 With multiple arguments, print names and values of all config
1699 1700 items with matching section names.
1700 1701
1701 1702 With --edit, start an editor on the user-level config file. With
1702 1703 --global, edit the system-wide config file. With --local, edit the
1703 1704 repository-level config file.
1704 1705
1705 1706 With --debug, the source (filename and line number) is printed
1706 1707 for each config item.
1707 1708
1708 1709 See :hg:`help config` for more information about config files.
1709 1710
1710 1711 Returns 0 on success, 1 if NAME does not exist.
1711 1712
1712 1713 """
1713 1714
1714 1715 opts = pycompat.byteskwargs(opts)
1715 1716 if opts.get('edit') or opts.get('local') or opts.get('global'):
1716 1717 if opts.get('local') and opts.get('global'):
1717 1718 raise error.Abort(_("can't use --local and --global together"))
1718 1719
1719 1720 if opts.get('local'):
1720 1721 if not repo:
1721 1722 raise error.Abort(_("can't use --local outside a repository"))
1722 1723 paths = [repo.vfs.join('hgrc')]
1723 1724 elif opts.get('global'):
1724 1725 paths = rcutil.systemrcpath()
1725 1726 else:
1726 1727 paths = rcutil.userrcpath()
1727 1728
1728 1729 for f in paths:
1729 1730 if os.path.exists(f):
1730 1731 break
1731 1732 else:
1732 1733 if opts.get('global'):
1733 1734 samplehgrc = uimod.samplehgrcs['global']
1734 1735 elif opts.get('local'):
1735 1736 samplehgrc = uimod.samplehgrcs['local']
1736 1737 else:
1737 1738 samplehgrc = uimod.samplehgrcs['user']
1738 1739
1739 1740 f = paths[0]
1740 1741 fp = open(f, "w")
1741 1742 fp.write(samplehgrc)
1742 1743 fp.close()
1743 1744
1744 1745 editor = ui.geteditor()
1745 1746 ui.system("%s \"%s\"" % (editor, f),
1746 1747 onerr=error.Abort, errprefix=_("edit failed"),
1747 1748 blockedtag='config_edit')
1748 1749 return
1749 1750 ui.pager('config')
1750 1751 fm = ui.formatter('config', opts)
1751 1752 for t, f in rcutil.rccomponents():
1752 1753 if t == 'path':
1753 1754 ui.debug('read config from: %s\n' % f)
1754 1755 elif t == 'items':
1755 1756 for section, name, value, source in f:
1756 1757 ui.debug('set config by: %s\n' % source)
1757 1758 else:
1758 1759 raise error.ProgrammingError('unknown rctype: %s' % t)
1759 1760 untrusted = bool(opts.get('untrusted'))
1760 1761 if values:
1761 1762 sections = [v for v in values if '.' not in v]
1762 1763 items = [v for v in values if '.' in v]
1763 1764 if len(items) > 1 or items and sections:
1764 1765 raise error.Abort(_('only one config item permitted'))
1765 1766 matched = False
1766 1767 for section, name, value in ui.walkconfig(untrusted=untrusted):
1767 1768 source = ui.configsource(section, name, untrusted)
1768 1769 value = pycompat.bytestr(value)
1769 1770 if fm.isplain():
1770 1771 source = source or 'none'
1771 1772 value = value.replace('\n', '\\n')
1772 1773 entryname = section + '.' + name
1773 1774 if values:
1774 1775 for v in values:
1775 1776 if v == section:
1776 1777 fm.startitem()
1777 1778 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1778 1779 fm.write('name value', '%s=%s\n', entryname, value)
1779 1780 matched = True
1780 1781 elif v == entryname:
1781 1782 fm.startitem()
1782 1783 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1783 1784 fm.write('value', '%s\n', value)
1784 1785 fm.data(name=entryname)
1785 1786 matched = True
1786 1787 else:
1787 1788 fm.startitem()
1788 1789 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1789 1790 fm.write('name value', '%s=%s\n', entryname, value)
1790 1791 matched = True
1791 1792 fm.end()
1792 1793 if matched:
1793 1794 return 0
1794 1795 return 1
1795 1796
1796 1797 @command('copy|cp',
1797 1798 [('A', 'after', None, _('record a copy that has already occurred')),
1798 1799 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1799 1800 ] + walkopts + dryrunopts,
1800 1801 _('[OPTION]... [SOURCE]... DEST'))
1801 1802 def copy(ui, repo, *pats, **opts):
1802 1803 """mark files as copied for the next commit
1803 1804
1804 1805 Mark dest as having copies of source files. If dest is a
1805 1806 directory, copies are put in that directory. If dest is a file,
1806 1807 the source must be a single file.
1807 1808
1808 1809 By default, this command copies the contents of files as they
1809 1810 exist in the working directory. If invoked with -A/--after, the
1810 1811 operation is recorded, but no copying is performed.
1811 1812
1812 1813 This command takes effect with the next commit. To undo a copy
1813 1814 before that, see :hg:`revert`.
1814 1815
1815 1816 Returns 0 on success, 1 if errors are encountered.
1816 1817 """
1817 1818 opts = pycompat.byteskwargs(opts)
1818 1819 with repo.wlock(False):
1819 1820 return cmdutil.copy(ui, repo, pats, opts)
1820 1821
1821 1822 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1822 1823 def debugcommands(ui, cmd='', *args):
1823 1824 """list all available commands and options"""
1824 1825 for cmd, vals in sorted(table.iteritems()):
1825 1826 cmd = cmd.split('|')[0].strip('^')
1826 1827 opts = ', '.join([i[1] for i in vals[1]])
1827 1828 ui.write('%s: %s\n' % (cmd, opts))
1828 1829
1829 1830 @command('debugcomplete',
1830 1831 [('o', 'options', None, _('show the command options'))],
1831 1832 _('[-o] CMD'),
1832 1833 norepo=True)
1833 1834 def debugcomplete(ui, cmd='', **opts):
1834 1835 """returns the completion list associated with the given command"""
1835 1836
1836 1837 if opts.get('options'):
1837 1838 options = []
1838 1839 otables = [globalopts]
1839 1840 if cmd:
1840 1841 aliases, entry = cmdutil.findcmd(cmd, table, False)
1841 1842 otables.append(entry[1])
1842 1843 for t in otables:
1843 1844 for o in t:
1844 1845 if "(DEPRECATED)" in o[3]:
1845 1846 continue
1846 1847 if o[0]:
1847 1848 options.append('-%s' % o[0])
1848 1849 options.append('--%s' % o[1])
1849 1850 ui.write("%s\n" % "\n".join(options))
1850 1851 return
1851 1852
1852 1853 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1853 1854 if ui.verbose:
1854 1855 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1855 1856 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1856 1857
1857 1858 @command('^diff',
1858 1859 [('r', 'rev', [], _('revision'), _('REV')),
1859 1860 ('c', 'change', '', _('change made by revision'), _('REV'))
1860 1861 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1861 1862 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1862 1863 inferrepo=True)
1863 1864 def diff(ui, repo, *pats, **opts):
1864 1865 """diff repository (or selected files)
1865 1866
1866 1867 Show differences between revisions for the specified files.
1867 1868
1868 1869 Differences between files are shown using the unified diff format.
1869 1870
1870 1871 .. note::
1871 1872
1872 1873 :hg:`diff` may generate unexpected results for merges, as it will
1873 1874 default to comparing against the working directory's first
1874 1875 parent changeset if no revisions are specified.
1875 1876
1876 1877 When two revision arguments are given, then changes are shown
1877 1878 between those revisions. If only one revision is specified then
1878 1879 that revision is compared to the working directory, and, when no
1879 1880 revisions are specified, the working directory files are compared
1880 1881 to its first parent.
1881 1882
1882 1883 Alternatively you can specify -c/--change with a revision to see
1883 1884 the changes in that changeset relative to its first parent.
1884 1885
1885 1886 Without the -a/--text option, diff will avoid generating diffs of
1886 1887 files it detects as binary. With -a, diff will generate a diff
1887 1888 anyway, probably with undesirable results.
1888 1889
1889 1890 Use the -g/--git option to generate diffs in the git extended diff
1890 1891 format. For more information, read :hg:`help diffs`.
1891 1892
1892 1893 .. container:: verbose
1893 1894
1894 1895 Examples:
1895 1896
1896 1897 - compare a file in the current working directory to its parent::
1897 1898
1898 1899 hg diff foo.c
1899 1900
1900 1901 - compare two historical versions of a directory, with rename info::
1901 1902
1902 1903 hg diff --git -r 1.0:1.2 lib/
1903 1904
1904 1905 - get change stats relative to the last change on some date::
1905 1906
1906 1907 hg diff --stat -r "date('may 2')"
1907 1908
1908 1909 - diff all newly-added files that contain a keyword::
1909 1910
1910 1911 hg diff "set:added() and grep(GNU)"
1911 1912
1912 1913 - compare a revision and its parents::
1913 1914
1914 1915 hg diff -c 9353 # compare against first parent
1915 1916 hg diff -r 9353^:9353 # same using revset syntax
1916 1917 hg diff -r 9353^2:9353 # compare against the second parent
1917 1918
1918 1919 Returns 0 on success.
1919 1920 """
1920 1921
1921 1922 opts = pycompat.byteskwargs(opts)
1922 1923 revs = opts.get('rev')
1923 1924 change = opts.get('change')
1924 1925 stat = opts.get('stat')
1925 1926 reverse = opts.get('reverse')
1926 1927
1927 1928 if revs and change:
1928 1929 msg = _('cannot specify --rev and --change at the same time')
1929 1930 raise error.Abort(msg)
1930 1931 elif change:
1931 1932 node2 = scmutil.revsingle(repo, change, None).node()
1932 1933 node1 = repo[node2].p1().node()
1933 1934 else:
1934 1935 node1, node2 = scmutil.revpair(repo, revs)
1935 1936
1936 1937 if reverse:
1937 1938 node1, node2 = node2, node1
1938 1939
1939 1940 diffopts = patch.diffallopts(ui, opts)
1940 1941 m = scmutil.match(repo[node2], pats, opts)
1941 1942 ui.pager('diff')
1942 1943 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1943 1944 listsubrepos=opts.get('subrepos'),
1944 1945 root=opts.get('root'))
1945 1946
1946 1947 @command('^export',
1947 1948 [('o', 'output', '',
1948 1949 _('print output to file with formatted name'), _('FORMAT')),
1949 1950 ('', 'switch-parent', None, _('diff against the second parent')),
1950 1951 ('r', 'rev', [], _('revisions to export'), _('REV')),
1951 1952 ] + diffopts,
1952 1953 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1953 1954 def export(ui, repo, *changesets, **opts):
1954 1955 """dump the header and diffs for one or more changesets
1955 1956
1956 1957 Print the changeset header and diffs for one or more revisions.
1957 1958 If no revision is given, the parent of the working directory is used.
1958 1959
1959 1960 The information shown in the changeset header is: author, date,
1960 1961 branch name (if non-default), changeset hash, parent(s) and commit
1961 1962 comment.
1962 1963
1963 1964 .. note::
1964 1965
1965 1966 :hg:`export` may generate unexpected diff output for merge
1966 1967 changesets, as it will compare the merge changeset against its
1967 1968 first parent only.
1968 1969
1969 1970 Output may be to a file, in which case the name of the file is
1970 1971 given using a format string. The formatting rules are as follows:
1971 1972
1972 1973 :``%%``: literal "%" character
1973 1974 :``%H``: changeset hash (40 hexadecimal digits)
1974 1975 :``%N``: number of patches being generated
1975 1976 :``%R``: changeset revision number
1976 1977 :``%b``: basename of the exporting repository
1977 1978 :``%h``: short-form changeset hash (12 hexadecimal digits)
1978 1979 :``%m``: first line of the commit message (only alphanumeric characters)
1979 1980 :``%n``: zero-padded sequence number, starting at 1
1980 1981 :``%r``: zero-padded changeset revision number
1981 1982
1982 1983 Without the -a/--text option, export will avoid generating diffs
1983 1984 of files it detects as binary. With -a, export will generate a
1984 1985 diff anyway, probably with undesirable results.
1985 1986
1986 1987 Use the -g/--git option to generate diffs in the git extended diff
1987 1988 format. See :hg:`help diffs` for more information.
1988 1989
1989 1990 With the --switch-parent option, the diff will be against the
1990 1991 second parent. It can be useful to review a merge.
1991 1992
1992 1993 .. container:: verbose
1993 1994
1994 1995 Examples:
1995 1996
1996 1997 - use export and import to transplant a bugfix to the current
1997 1998 branch::
1998 1999
1999 2000 hg export -r 9353 | hg import -
2000 2001
2001 2002 - export all the changesets between two revisions to a file with
2002 2003 rename information::
2003 2004
2004 2005 hg export --git -r 123:150 > changes.txt
2005 2006
2006 2007 - split outgoing changes into a series of patches with
2007 2008 descriptive names::
2008 2009
2009 2010 hg export -r "outgoing()" -o "%n-%m.patch"
2010 2011
2011 2012 Returns 0 on success.
2012 2013 """
2013 2014 opts = pycompat.byteskwargs(opts)
2014 2015 changesets += tuple(opts.get('rev', []))
2015 2016 if not changesets:
2016 2017 changesets = ['.']
2017 2018 revs = scmutil.revrange(repo, changesets)
2018 2019 if not revs:
2019 2020 raise error.Abort(_("export requires at least one changeset"))
2020 2021 if len(revs) > 1:
2021 2022 ui.note(_('exporting patches:\n'))
2022 2023 else:
2023 2024 ui.note(_('exporting patch:\n'))
2024 2025 ui.pager('export')
2025 2026 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
2026 2027 switch_parent=opts.get('switch_parent'),
2027 2028 opts=patch.diffallopts(ui, opts))
2028 2029
2029 2030 @command('files',
2030 2031 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2031 2032 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2032 2033 ] + walkopts + formatteropts + subrepoopts,
2033 2034 _('[OPTION]... [FILE]...'))
2034 2035 def files(ui, repo, *pats, **opts):
2035 2036 """list tracked files
2036 2037
2037 2038 Print files under Mercurial control in the working directory or
2038 2039 specified revision for given files (excluding removed files).
2039 2040 Files can be specified as filenames or filesets.
2040 2041
2041 2042 If no files are given to match, this command prints the names
2042 2043 of all files under Mercurial control.
2043 2044
2044 2045 .. container:: verbose
2045 2046
2046 2047 Examples:
2047 2048
2048 2049 - list all files under the current directory::
2049 2050
2050 2051 hg files .
2051 2052
2052 2053 - shows sizes and flags for current revision::
2053 2054
2054 2055 hg files -vr .
2055 2056
2056 2057 - list all files named README::
2057 2058
2058 2059 hg files -I "**/README"
2059 2060
2060 2061 - list all binary files::
2061 2062
2062 2063 hg files "set:binary()"
2063 2064
2064 2065 - find files containing a regular expression::
2065 2066
2066 2067 hg files "set:grep('bob')"
2067 2068
2068 2069 - search tracked file contents with xargs and grep::
2069 2070
2070 2071 hg files -0 | xargs -0 grep foo
2071 2072
2072 2073 See :hg:`help patterns` and :hg:`help filesets` for more information
2073 2074 on specifying file patterns.
2074 2075
2075 2076 Returns 0 if a match is found, 1 otherwise.
2076 2077
2077 2078 """
2078 2079
2079 2080 opts = pycompat.byteskwargs(opts)
2080 2081 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2081 2082
2082 2083 end = '\n'
2083 2084 if opts.get('print0'):
2084 2085 end = '\0'
2085 2086 fmt = '%s' + end
2086 2087
2087 2088 m = scmutil.match(ctx, pats, opts)
2088 2089 ui.pager('files')
2089 2090 with ui.formatter('files', opts) as fm:
2090 2091 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2091 2092
2092 2093 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2093 2094 def forget(ui, repo, *pats, **opts):
2094 2095 """forget the specified files on the next commit
2095 2096
2096 2097 Mark the specified files so they will no longer be tracked
2097 2098 after the next commit.
2098 2099
2099 2100 This only removes files from the current branch, not from the
2100 2101 entire project history, and it does not delete them from the
2101 2102 working directory.
2102 2103
2103 2104 To delete the file from the working directory, see :hg:`remove`.
2104 2105
2105 2106 To undo a forget before the next commit, see :hg:`add`.
2106 2107
2107 2108 .. container:: verbose
2108 2109
2109 2110 Examples:
2110 2111
2111 2112 - forget newly-added binary files::
2112 2113
2113 2114 hg forget "set:added() and binary()"
2114 2115
2115 2116 - forget files that would be excluded by .hgignore::
2116 2117
2117 2118 hg forget "set:hgignore()"
2118 2119
2119 2120 Returns 0 on success.
2120 2121 """
2121 2122
2122 2123 opts = pycompat.byteskwargs(opts)
2123 2124 if not pats:
2124 2125 raise error.Abort(_('no files specified'))
2125 2126
2126 2127 m = scmutil.match(repo[None], pats, opts)
2127 2128 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2128 2129 return rejected and 1 or 0
2129 2130
2130 2131 @command(
2131 2132 'graft',
2132 2133 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2133 2134 ('c', 'continue', False, _('resume interrupted graft')),
2134 2135 ('e', 'edit', False, _('invoke editor on commit messages')),
2135 2136 ('', 'log', None, _('append graft info to log message')),
2136 2137 ('f', 'force', False, _('force graft')),
2137 2138 ('D', 'currentdate', False,
2138 2139 _('record the current date as commit date')),
2139 2140 ('U', 'currentuser', False,
2140 2141 _('record the current user as committer'), _('DATE'))]
2141 2142 + commitopts2 + mergetoolopts + dryrunopts,
2142 2143 _('[OPTION]... [-r REV]... REV...'))
2143 2144 def graft(ui, repo, *revs, **opts):
2144 2145 '''copy changes from other branches onto the current branch
2145 2146
2146 2147 This command uses Mercurial's merge logic to copy individual
2147 2148 changes from other branches without merging branches in the
2148 2149 history graph. This is sometimes known as 'backporting' or
2149 2150 'cherry-picking'. By default, graft will copy user, date, and
2150 2151 description from the source changesets.
2151 2152
2152 2153 Changesets that are ancestors of the current revision, that have
2153 2154 already been grafted, or that are merges will be skipped.
2154 2155
2155 2156 If --log is specified, log messages will have a comment appended
2156 2157 of the form::
2157 2158
2158 2159 (grafted from CHANGESETHASH)
2159 2160
2160 2161 If --force is specified, revisions will be grafted even if they
2161 2162 are already ancestors of or have been grafted to the destination.
2162 2163 This is useful when the revisions have since been backed out.
2163 2164
2164 2165 If a graft merge results in conflicts, the graft process is
2165 2166 interrupted so that the current merge can be manually resolved.
2166 2167 Once all conflicts are addressed, the graft process can be
2167 2168 continued with the -c/--continue option.
2168 2169
2169 2170 .. note::
2170 2171
2171 2172 The -c/--continue option does not reapply earlier options, except
2172 2173 for --force.
2173 2174
2174 2175 .. container:: verbose
2175 2176
2176 2177 Examples:
2177 2178
2178 2179 - copy a single change to the stable branch and edit its description::
2179 2180
2180 2181 hg update stable
2181 2182 hg graft --edit 9393
2182 2183
2183 2184 - graft a range of changesets with one exception, updating dates::
2184 2185
2185 2186 hg graft -D "2085::2093 and not 2091"
2186 2187
2187 2188 - continue a graft after resolving conflicts::
2188 2189
2189 2190 hg graft -c
2190 2191
2191 2192 - show the source of a grafted changeset::
2192 2193
2193 2194 hg log --debug -r .
2194 2195
2195 2196 - show revisions sorted by date::
2196 2197
2197 2198 hg log -r "sort(all(), date)"
2198 2199
2199 2200 See :hg:`help revisions` for more about specifying revisions.
2200 2201
2201 2202 Returns 0 on successful completion.
2202 2203 '''
2203 2204 with repo.wlock():
2204 2205 return _dograft(ui, repo, *revs, **opts)
2205 2206
2206 2207 def _dograft(ui, repo, *revs, **opts):
2207 2208 opts = pycompat.byteskwargs(opts)
2208 2209 if revs and opts.get('rev'):
2209 2210 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2210 2211 'revision ordering!\n'))
2211 2212
2212 2213 revs = list(revs)
2213 2214 revs.extend(opts.get('rev'))
2214 2215
2215 2216 if not opts.get('user') and opts.get('currentuser'):
2216 2217 opts['user'] = ui.username()
2217 2218 if not opts.get('date') and opts.get('currentdate'):
2218 2219 opts['date'] = "%d %d" % util.makedate()
2219 2220
2220 2221 editor = cmdutil.getcommiteditor(editform='graft',
2221 2222 **pycompat.strkwargs(opts))
2222 2223
2223 2224 cont = False
2224 2225 if opts.get('continue'):
2225 2226 cont = True
2226 2227 if revs:
2227 2228 raise error.Abort(_("can't specify --continue and revisions"))
2228 2229 # read in unfinished revisions
2229 2230 try:
2230 2231 nodes = repo.vfs.read('graftstate').splitlines()
2231 2232 revs = [repo[node].rev() for node in nodes]
2232 2233 except IOError as inst:
2233 2234 if inst.errno != errno.ENOENT:
2234 2235 raise
2235 2236 cmdutil.wrongtooltocontinue(repo, _('graft'))
2236 2237 else:
2237 2238 cmdutil.checkunfinished(repo)
2238 2239 cmdutil.bailifchanged(repo)
2239 2240 if not revs:
2240 2241 raise error.Abort(_('no revisions specified'))
2241 2242 revs = scmutil.revrange(repo, revs)
2242 2243
2243 2244 skipped = set()
2244 2245 # check for merges
2245 2246 for rev in repo.revs('%ld and merge()', revs):
2246 2247 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2247 2248 skipped.add(rev)
2248 2249 revs = [r for r in revs if r not in skipped]
2249 2250 if not revs:
2250 2251 return -1
2251 2252
2252 2253 # Don't check in the --continue case, in effect retaining --force across
2253 2254 # --continues. That's because without --force, any revisions we decided to
2254 2255 # skip would have been filtered out here, so they wouldn't have made their
2255 2256 # way to the graftstate. With --force, any revisions we would have otherwise
2256 2257 # skipped would not have been filtered out, and if they hadn't been applied
2257 2258 # already, they'd have been in the graftstate.
2258 2259 if not (cont or opts.get('force')):
2259 2260 # check for ancestors of dest branch
2260 2261 crev = repo['.'].rev()
2261 2262 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2262 2263 # XXX make this lazy in the future
2263 2264 # don't mutate while iterating, create a copy
2264 2265 for rev in list(revs):
2265 2266 if rev in ancestors:
2266 2267 ui.warn(_('skipping ancestor revision %d:%s\n') %
2267 2268 (rev, repo[rev]))
2268 2269 # XXX remove on list is slow
2269 2270 revs.remove(rev)
2270 2271 if not revs:
2271 2272 return -1
2272 2273
2273 2274 # analyze revs for earlier grafts
2274 2275 ids = {}
2275 2276 for ctx in repo.set("%ld", revs):
2276 2277 ids[ctx.hex()] = ctx.rev()
2277 2278 n = ctx.extra().get('source')
2278 2279 if n:
2279 2280 ids[n] = ctx.rev()
2280 2281
2281 2282 # check ancestors for earlier grafts
2282 2283 ui.debug('scanning for duplicate grafts\n')
2283 2284
2284 2285 # The only changesets we can be sure doesn't contain grafts of any
2285 2286 # revs, are the ones that are common ancestors of *all* revs:
2286 2287 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2287 2288 ctx = repo[rev]
2288 2289 n = ctx.extra().get('source')
2289 2290 if n in ids:
2290 2291 try:
2291 2292 r = repo[n].rev()
2292 2293 except error.RepoLookupError:
2293 2294 r = None
2294 2295 if r in revs:
2295 2296 ui.warn(_('skipping revision %d:%s '
2296 2297 '(already grafted to %d:%s)\n')
2297 2298 % (r, repo[r], rev, ctx))
2298 2299 revs.remove(r)
2299 2300 elif ids[n] in revs:
2300 2301 if r is None:
2301 2302 ui.warn(_('skipping already grafted revision %d:%s '
2302 2303 '(%d:%s also has unknown origin %s)\n')
2303 2304 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2304 2305 else:
2305 2306 ui.warn(_('skipping already grafted revision %d:%s '
2306 2307 '(%d:%s also has origin %d:%s)\n')
2307 2308 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2308 2309 revs.remove(ids[n])
2309 2310 elif ctx.hex() in ids:
2310 2311 r = ids[ctx.hex()]
2311 2312 ui.warn(_('skipping already grafted revision %d:%s '
2312 2313 '(was grafted from %d:%s)\n') %
2313 2314 (r, repo[r], rev, ctx))
2314 2315 revs.remove(r)
2315 2316 if not revs:
2316 2317 return -1
2317 2318
2318 2319 for pos, ctx in enumerate(repo.set("%ld", revs)):
2319 2320 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2320 2321 ctx.description().split('\n', 1)[0])
2321 2322 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2322 2323 if names:
2323 2324 desc += ' (%s)' % ' '.join(names)
2324 2325 ui.status(_('grafting %s\n') % desc)
2325 2326 if opts.get('dry_run'):
2326 2327 continue
2327 2328
2328 2329 source = ctx.extra().get('source')
2329 2330 extra = {}
2330 2331 if source:
2331 2332 extra['source'] = source
2332 2333 extra['intermediate-source'] = ctx.hex()
2333 2334 else:
2334 2335 extra['source'] = ctx.hex()
2335 2336 user = ctx.user()
2336 2337 if opts.get('user'):
2337 2338 user = opts['user']
2338 2339 date = ctx.date()
2339 2340 if opts.get('date'):
2340 2341 date = opts['date']
2341 2342 message = ctx.description()
2342 2343 if opts.get('log'):
2343 2344 message += '\n(grafted from %s)' % ctx.hex()
2344 2345
2345 2346 # we don't merge the first commit when continuing
2346 2347 if not cont:
2347 2348 # perform the graft merge with p1(rev) as 'ancestor'
2348 2349 try:
2349 2350 # ui.forcemerge is an internal variable, do not document
2350 2351 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2351 2352 'graft')
2352 2353 stats = mergemod.graft(repo, ctx, ctx.p1(),
2353 2354 ['local', 'graft'])
2354 2355 finally:
2355 2356 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2356 2357 # report any conflicts
2357 2358 if stats and stats[3] > 0:
2358 2359 # write out state for --continue
2359 2360 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2360 2361 repo.vfs.write('graftstate', ''.join(nodelines))
2361 2362 extra = ''
2362 2363 if opts.get('user'):
2363 2364 extra += ' --user %s' % util.shellquote(opts['user'])
2364 2365 if opts.get('date'):
2365 2366 extra += ' --date %s' % util.shellquote(opts['date'])
2366 2367 if opts.get('log'):
2367 2368 extra += ' --log'
2368 2369 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2369 2370 raise error.Abort(
2370 2371 _("unresolved conflicts, can't continue"),
2371 2372 hint=hint)
2372 2373 else:
2373 2374 cont = False
2374 2375
2375 2376 # commit
2376 2377 node = repo.commit(text=message, user=user,
2377 2378 date=date, extra=extra, editor=editor)
2378 2379 if node is None:
2379 2380 ui.warn(
2380 2381 _('note: graft of %d:%s created no changes to commit\n') %
2381 2382 (ctx.rev(), ctx))
2382 2383
2383 2384 # remove state when we complete successfully
2384 2385 if not opts.get('dry_run'):
2385 2386 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2386 2387
2387 2388 return 0
2388 2389
2389 2390 @command('grep',
2390 2391 [('0', 'print0', None, _('end fields with NUL')),
2391 2392 ('', 'all', None, _('print all revisions that match')),
2392 2393 ('a', 'text', None, _('treat all files as text')),
2393 2394 ('f', 'follow', None,
2394 2395 _('follow changeset history,'
2395 2396 ' or file history across copies and renames')),
2396 2397 ('i', 'ignore-case', None, _('ignore case when matching')),
2397 2398 ('l', 'files-with-matches', None,
2398 2399 _('print only filenames and revisions that match')),
2399 2400 ('n', 'line-number', None, _('print matching line numbers')),
2400 2401 ('r', 'rev', [],
2401 2402 _('only search files changed within revision range'), _('REV')),
2402 2403 ('u', 'user', None, _('list the author (long with -v)')),
2403 2404 ('d', 'date', None, _('list the date (short with -q)')),
2404 2405 ] + formatteropts + walkopts,
2405 2406 _('[OPTION]... PATTERN [FILE]...'),
2406 2407 inferrepo=True)
2407 2408 def grep(ui, repo, pattern, *pats, **opts):
2408 2409 """search revision history for a pattern in specified files
2409 2410
2410 2411 Search revision history for a regular expression in the specified
2411 2412 files or the entire project.
2412 2413
2413 2414 By default, grep prints the most recent revision number for each
2414 2415 file in which it finds a match. To get it to print every revision
2415 2416 that contains a change in match status ("-" for a match that becomes
2416 2417 a non-match, or "+" for a non-match that becomes a match), use the
2417 2418 --all flag.
2418 2419
2419 2420 PATTERN can be any Python (roughly Perl-compatible) regular
2420 2421 expression.
2421 2422
2422 2423 If no FILEs are specified (and -f/--follow isn't set), all files in
2423 2424 the repository are searched, including those that don't exist in the
2424 2425 current branch or have been deleted in a prior changeset.
2425 2426
2426 2427 Returns 0 if a match is found, 1 otherwise.
2427 2428 """
2428 2429 opts = pycompat.byteskwargs(opts)
2429 2430 reflags = re.M
2430 2431 if opts.get('ignore_case'):
2431 2432 reflags |= re.I
2432 2433 try:
2433 2434 regexp = util.re.compile(pattern, reflags)
2434 2435 except re.error as inst:
2435 2436 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2436 2437 return 1
2437 2438 sep, eol = ':', '\n'
2438 2439 if opts.get('print0'):
2439 2440 sep = eol = '\0'
2440 2441
2441 2442 getfile = util.lrucachefunc(repo.file)
2442 2443
2443 2444 def matchlines(body):
2444 2445 begin = 0
2445 2446 linenum = 0
2446 2447 while begin < len(body):
2447 2448 match = regexp.search(body, begin)
2448 2449 if not match:
2449 2450 break
2450 2451 mstart, mend = match.span()
2451 2452 linenum += body.count('\n', begin, mstart) + 1
2452 2453 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2453 2454 begin = body.find('\n', mend) + 1 or len(body) + 1
2454 2455 lend = begin - 1
2455 2456 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2456 2457
2457 2458 class linestate(object):
2458 2459 def __init__(self, line, linenum, colstart, colend):
2459 2460 self.line = line
2460 2461 self.linenum = linenum
2461 2462 self.colstart = colstart
2462 2463 self.colend = colend
2463 2464
2464 2465 def __hash__(self):
2465 2466 return hash((self.linenum, self.line))
2466 2467
2467 2468 def __eq__(self, other):
2468 2469 return self.line == other.line
2469 2470
2470 2471 def findpos(self):
2471 2472 """Iterate all (start, end) indices of matches"""
2472 2473 yield self.colstart, self.colend
2473 2474 p = self.colend
2474 2475 while p < len(self.line):
2475 2476 m = regexp.search(self.line, p)
2476 2477 if not m:
2477 2478 break
2478 2479 yield m.span()
2479 2480 p = m.end()
2480 2481
2481 2482 matches = {}
2482 2483 copies = {}
2483 2484 def grepbody(fn, rev, body):
2484 2485 matches[rev].setdefault(fn, [])
2485 2486 m = matches[rev][fn]
2486 2487 for lnum, cstart, cend, line in matchlines(body):
2487 2488 s = linestate(line, lnum, cstart, cend)
2488 2489 m.append(s)
2489 2490
2490 2491 def difflinestates(a, b):
2491 2492 sm = difflib.SequenceMatcher(None, a, b)
2492 2493 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2493 2494 if tag == 'insert':
2494 2495 for i in xrange(blo, bhi):
2495 2496 yield ('+', b[i])
2496 2497 elif tag == 'delete':
2497 2498 for i in xrange(alo, ahi):
2498 2499 yield ('-', a[i])
2499 2500 elif tag == 'replace':
2500 2501 for i in xrange(alo, ahi):
2501 2502 yield ('-', a[i])
2502 2503 for i in xrange(blo, bhi):
2503 2504 yield ('+', b[i])
2504 2505
2505 2506 def display(fm, fn, ctx, pstates, states):
2506 2507 rev = ctx.rev()
2507 2508 if fm.isplain():
2508 2509 formatuser = ui.shortuser
2509 2510 else:
2510 2511 formatuser = str
2511 2512 if ui.quiet:
2512 2513 datefmt = '%Y-%m-%d'
2513 2514 else:
2514 2515 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2515 2516 found = False
2516 2517 @util.cachefunc
2517 2518 def binary():
2518 2519 flog = getfile(fn)
2519 2520 return util.binary(flog.read(ctx.filenode(fn)))
2520 2521
2521 2522 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2522 2523 if opts.get('all'):
2523 2524 iter = difflinestates(pstates, states)
2524 2525 else:
2525 2526 iter = [('', l) for l in states]
2526 2527 for change, l in iter:
2527 2528 fm.startitem()
2528 2529 fm.data(node=fm.hexfunc(ctx.node()))
2529 2530 cols = [
2530 2531 ('filename', fn, True),
2531 2532 ('rev', rev, True),
2532 2533 ('linenumber', l.linenum, opts.get('line_number')),
2533 2534 ]
2534 2535 if opts.get('all'):
2535 2536 cols.append(('change', change, True))
2536 2537 cols.extend([
2537 2538 ('user', formatuser(ctx.user()), opts.get('user')),
2538 2539 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2539 2540 ])
2540 2541 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2541 2542 for name, data, cond in cols:
2542 2543 field = fieldnamemap.get(name, name)
2543 2544 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2544 2545 if cond and name != lastcol:
2545 2546 fm.plain(sep, label='grep.sep')
2546 2547 if not opts.get('files_with_matches'):
2547 2548 fm.plain(sep, label='grep.sep')
2548 2549 if not opts.get('text') and binary():
2549 2550 fm.plain(_(" Binary file matches"))
2550 2551 else:
2551 2552 displaymatches(fm.nested('texts'), l)
2552 2553 fm.plain(eol)
2553 2554 found = True
2554 2555 if opts.get('files_with_matches'):
2555 2556 break
2556 2557 return found
2557 2558
2558 2559 def displaymatches(fm, l):
2559 2560 p = 0
2560 2561 for s, e in l.findpos():
2561 2562 if p < s:
2562 2563 fm.startitem()
2563 2564 fm.write('text', '%s', l.line[p:s])
2564 2565 fm.data(matched=False)
2565 2566 fm.startitem()
2566 2567 fm.write('text', '%s', l.line[s:e], label='grep.match')
2567 2568 fm.data(matched=True)
2568 2569 p = e
2569 2570 if p < len(l.line):
2570 2571 fm.startitem()
2571 2572 fm.write('text', '%s', l.line[p:])
2572 2573 fm.data(matched=False)
2573 2574 fm.end()
2574 2575
2575 2576 skip = {}
2576 2577 revfiles = {}
2577 2578 matchfn = scmutil.match(repo[None], pats, opts)
2578 2579 found = False
2579 2580 follow = opts.get('follow')
2580 2581
2581 2582 def prep(ctx, fns):
2582 2583 rev = ctx.rev()
2583 2584 pctx = ctx.p1()
2584 2585 parent = pctx.rev()
2585 2586 matches.setdefault(rev, {})
2586 2587 matches.setdefault(parent, {})
2587 2588 files = revfiles.setdefault(rev, [])
2588 2589 for fn in fns:
2589 2590 flog = getfile(fn)
2590 2591 try:
2591 2592 fnode = ctx.filenode(fn)
2592 2593 except error.LookupError:
2593 2594 continue
2594 2595
2595 2596 copied = flog.renamed(fnode)
2596 2597 copy = follow and copied and copied[0]
2597 2598 if copy:
2598 2599 copies.setdefault(rev, {})[fn] = copy
2599 2600 if fn in skip:
2600 2601 if copy:
2601 2602 skip[copy] = True
2602 2603 continue
2603 2604 files.append(fn)
2604 2605
2605 2606 if fn not in matches[rev]:
2606 2607 grepbody(fn, rev, flog.read(fnode))
2607 2608
2608 2609 pfn = copy or fn
2609 2610 if pfn not in matches[parent]:
2610 2611 try:
2611 2612 fnode = pctx.filenode(pfn)
2612 2613 grepbody(pfn, parent, flog.read(fnode))
2613 2614 except error.LookupError:
2614 2615 pass
2615 2616
2616 2617 ui.pager('grep')
2617 2618 fm = ui.formatter('grep', opts)
2618 2619 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2619 2620 rev = ctx.rev()
2620 2621 parent = ctx.p1().rev()
2621 2622 for fn in sorted(revfiles.get(rev, [])):
2622 2623 states = matches[rev][fn]
2623 2624 copy = copies.get(rev, {}).get(fn)
2624 2625 if fn in skip:
2625 2626 if copy:
2626 2627 skip[copy] = True
2627 2628 continue
2628 2629 pstates = matches.get(parent, {}).get(copy or fn, [])
2629 2630 if pstates or states:
2630 2631 r = display(fm, fn, ctx, pstates, states)
2631 2632 found = found or r
2632 2633 if r and not opts.get('all'):
2633 2634 skip[fn] = True
2634 2635 if copy:
2635 2636 skip[copy] = True
2636 2637 del matches[rev]
2637 2638 del revfiles[rev]
2638 2639 fm.end()
2639 2640
2640 2641 return not found
2641 2642
2642 2643 @command('heads',
2643 2644 [('r', 'rev', '',
2644 2645 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2645 2646 ('t', 'topo', False, _('show topological heads only')),
2646 2647 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2647 2648 ('c', 'closed', False, _('show normal and closed branch heads')),
2648 2649 ] + templateopts,
2649 2650 _('[-ct] [-r STARTREV] [REV]...'))
2650 2651 def heads(ui, repo, *branchrevs, **opts):
2651 2652 """show branch heads
2652 2653
2653 2654 With no arguments, show all open branch heads in the repository.
2654 2655 Branch heads are changesets that have no descendants on the
2655 2656 same branch. They are where development generally takes place and
2656 2657 are the usual targets for update and merge operations.
2657 2658
2658 2659 If one or more REVs are given, only open branch heads on the
2659 2660 branches associated with the specified changesets are shown. This
2660 2661 means that you can use :hg:`heads .` to see the heads on the
2661 2662 currently checked-out branch.
2662 2663
2663 2664 If -c/--closed is specified, also show branch heads marked closed
2664 2665 (see :hg:`commit --close-branch`).
2665 2666
2666 2667 If STARTREV is specified, only those heads that are descendants of
2667 2668 STARTREV will be displayed.
2668 2669
2669 2670 If -t/--topo is specified, named branch mechanics will be ignored and only
2670 2671 topological heads (changesets with no children) will be shown.
2671 2672
2672 2673 Returns 0 if matching heads are found, 1 if not.
2673 2674 """
2674 2675
2675 2676 opts = pycompat.byteskwargs(opts)
2676 2677 start = None
2677 2678 if 'rev' in opts:
2678 2679 start = scmutil.revsingle(repo, opts['rev'], None).node()
2679 2680
2680 2681 if opts.get('topo'):
2681 2682 heads = [repo[h] for h in repo.heads(start)]
2682 2683 else:
2683 2684 heads = []
2684 2685 for branch in repo.branchmap():
2685 2686 heads += repo.branchheads(branch, start, opts.get('closed'))
2686 2687 heads = [repo[h] for h in heads]
2687 2688
2688 2689 if branchrevs:
2689 2690 branches = set(repo[br].branch() for br in branchrevs)
2690 2691 heads = [h for h in heads if h.branch() in branches]
2691 2692
2692 2693 if opts.get('active') and branchrevs:
2693 2694 dagheads = repo.heads(start)
2694 2695 heads = [h for h in heads if h.node() in dagheads]
2695 2696
2696 2697 if branchrevs:
2697 2698 haveheads = set(h.branch() for h in heads)
2698 2699 if branches - haveheads:
2699 2700 headless = ', '.join(b for b in branches - haveheads)
2700 2701 msg = _('no open branch heads found on branches %s')
2701 2702 if opts.get('rev'):
2702 2703 msg += _(' (started at %s)') % opts['rev']
2703 2704 ui.warn((msg + '\n') % headless)
2704 2705
2705 2706 if not heads:
2706 2707 return 1
2707 2708
2708 2709 ui.pager('heads')
2709 2710 heads = sorted(heads, key=lambda x: -x.rev())
2710 2711 displayer = cmdutil.show_changeset(ui, repo, opts)
2711 2712 for ctx in heads:
2712 2713 displayer.show(ctx)
2713 2714 displayer.close()
2714 2715
2715 2716 @command('help',
2716 2717 [('e', 'extension', None, _('show only help for extensions')),
2717 2718 ('c', 'command', None, _('show only help for commands')),
2718 2719 ('k', 'keyword', None, _('show topics matching keyword')),
2719 2720 ('s', 'system', [], _('show help for specific platform(s)')),
2720 2721 ],
2721 2722 _('[-ecks] [TOPIC]'),
2722 2723 norepo=True)
2723 2724 def help_(ui, name=None, **opts):
2724 2725 """show help for a given topic or a help overview
2725 2726
2726 2727 With no arguments, print a list of commands with short help messages.
2727 2728
2728 2729 Given a topic, extension, or command name, print help for that
2729 2730 topic.
2730 2731
2731 2732 Returns 0 if successful.
2732 2733 """
2733 2734
2734 2735 keep = opts.get(r'system') or []
2735 2736 if len(keep) == 0:
2736 2737 if pycompat.sysplatform.startswith('win'):
2737 2738 keep.append('windows')
2738 2739 elif pycompat.sysplatform == 'OpenVMS':
2739 2740 keep.append('vms')
2740 2741 elif pycompat.sysplatform == 'plan9':
2741 2742 keep.append('plan9')
2742 2743 else:
2743 2744 keep.append('unix')
2744 2745 keep.append(pycompat.sysplatform.lower())
2745 2746 if ui.verbose:
2746 2747 keep.append('verbose')
2747 2748
2748 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2749 commands = sys.modules[__name__]
2750 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2749 2751 ui.pager('help')
2750 2752 ui.write(formatted)
2751 2753
2752 2754
2753 2755 @command('identify|id',
2754 2756 [('r', 'rev', '',
2755 2757 _('identify the specified revision'), _('REV')),
2756 2758 ('n', 'num', None, _('show local revision number')),
2757 2759 ('i', 'id', None, _('show global revision id')),
2758 2760 ('b', 'branch', None, _('show branch')),
2759 2761 ('t', 'tags', None, _('show tags')),
2760 2762 ('B', 'bookmarks', None, _('show bookmarks')),
2761 2763 ] + remoteopts,
2762 2764 _('[-nibtB] [-r REV] [SOURCE]'),
2763 2765 optionalrepo=True)
2764 2766 def identify(ui, repo, source=None, rev=None,
2765 2767 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2766 2768 """identify the working directory or specified revision
2767 2769
2768 2770 Print a summary identifying the repository state at REV using one or
2769 2771 two parent hash identifiers, followed by a "+" if the working
2770 2772 directory has uncommitted changes, the branch name (if not default),
2771 2773 a list of tags, and a list of bookmarks.
2772 2774
2773 2775 When REV is not given, print a summary of the current state of the
2774 2776 repository.
2775 2777
2776 2778 Specifying a path to a repository root or Mercurial bundle will
2777 2779 cause lookup to operate on that repository/bundle.
2778 2780
2779 2781 .. container:: verbose
2780 2782
2781 2783 Examples:
2782 2784
2783 2785 - generate a build identifier for the working directory::
2784 2786
2785 2787 hg id --id > build-id.dat
2786 2788
2787 2789 - find the revision corresponding to a tag::
2788 2790
2789 2791 hg id -n -r 1.3
2790 2792
2791 2793 - check the most recent revision of a remote repository::
2792 2794
2793 2795 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2794 2796
2795 2797 See :hg:`log` for generating more information about specific revisions,
2796 2798 including full hash identifiers.
2797 2799
2798 2800 Returns 0 if successful.
2799 2801 """
2800 2802
2801 2803 opts = pycompat.byteskwargs(opts)
2802 2804 if not repo and not source:
2803 2805 raise error.Abort(_("there is no Mercurial repository here "
2804 2806 "(.hg not found)"))
2805 2807
2806 2808 if ui.debugflag:
2807 2809 hexfunc = hex
2808 2810 else:
2809 2811 hexfunc = short
2810 2812 default = not (num or id or branch or tags or bookmarks)
2811 2813 output = []
2812 2814 revs = []
2813 2815
2814 2816 if source:
2815 2817 source, branches = hg.parseurl(ui.expandpath(source))
2816 2818 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2817 2819 repo = peer.local()
2818 2820 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2819 2821
2820 2822 if not repo:
2821 2823 if num or branch or tags:
2822 2824 raise error.Abort(
2823 2825 _("can't query remote revision number, branch, or tags"))
2824 2826 if not rev and revs:
2825 2827 rev = revs[0]
2826 2828 if not rev:
2827 2829 rev = "tip"
2828 2830
2829 2831 remoterev = peer.lookup(rev)
2830 2832 if default or id:
2831 2833 output = [hexfunc(remoterev)]
2832 2834
2833 2835 def getbms():
2834 2836 bms = []
2835 2837
2836 2838 if 'bookmarks' in peer.listkeys('namespaces'):
2837 2839 hexremoterev = hex(remoterev)
2838 2840 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2839 2841 if bmr == hexremoterev]
2840 2842
2841 2843 return sorted(bms)
2842 2844
2843 2845 if bookmarks:
2844 2846 output.extend(getbms())
2845 2847 elif default and not ui.quiet:
2846 2848 # multiple bookmarks for a single parent separated by '/'
2847 2849 bm = '/'.join(getbms())
2848 2850 if bm:
2849 2851 output.append(bm)
2850 2852 else:
2851 2853 ctx = scmutil.revsingle(repo, rev, None)
2852 2854
2853 2855 if ctx.rev() is None:
2854 2856 ctx = repo[None]
2855 2857 parents = ctx.parents()
2856 2858 taglist = []
2857 2859 for p in parents:
2858 2860 taglist.extend(p.tags())
2859 2861
2860 2862 changed = ""
2861 2863 if default or id or num:
2862 2864 if (any(repo.status())
2863 2865 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2864 2866 changed = '+'
2865 2867 if default or id:
2866 2868 output = ["%s%s" %
2867 2869 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2868 2870 if num:
2869 2871 output.append("%s%s" %
2870 2872 ('+'.join([str(p.rev()) for p in parents]), changed))
2871 2873 else:
2872 2874 if default or id:
2873 2875 output = [hexfunc(ctx.node())]
2874 2876 if num:
2875 2877 output.append(str(ctx.rev()))
2876 2878 taglist = ctx.tags()
2877 2879
2878 2880 if default and not ui.quiet:
2879 2881 b = ctx.branch()
2880 2882 if b != 'default':
2881 2883 output.append("(%s)" % b)
2882 2884
2883 2885 # multiple tags for a single parent separated by '/'
2884 2886 t = '/'.join(taglist)
2885 2887 if t:
2886 2888 output.append(t)
2887 2889
2888 2890 # multiple bookmarks for a single parent separated by '/'
2889 2891 bm = '/'.join(ctx.bookmarks())
2890 2892 if bm:
2891 2893 output.append(bm)
2892 2894 else:
2893 2895 if branch:
2894 2896 output.append(ctx.branch())
2895 2897
2896 2898 if tags:
2897 2899 output.extend(taglist)
2898 2900
2899 2901 if bookmarks:
2900 2902 output.extend(ctx.bookmarks())
2901 2903
2902 2904 ui.write("%s\n" % ' '.join(output))
2903 2905
2904 2906 @command('import|patch',
2905 2907 [('p', 'strip', 1,
2906 2908 _('directory strip option for patch. This has the same '
2907 2909 'meaning as the corresponding patch option'), _('NUM')),
2908 2910 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2909 2911 ('e', 'edit', False, _('invoke editor on commit messages')),
2910 2912 ('f', 'force', None,
2911 2913 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2912 2914 ('', 'no-commit', None,
2913 2915 _("don't commit, just update the working directory")),
2914 2916 ('', 'bypass', None,
2915 2917 _("apply patch without touching the working directory")),
2916 2918 ('', 'partial', None,
2917 2919 _('commit even if some hunks fail')),
2918 2920 ('', 'exact', None,
2919 2921 _('abort if patch would apply lossily')),
2920 2922 ('', 'prefix', '',
2921 2923 _('apply patch to subdirectory'), _('DIR')),
2922 2924 ('', 'import-branch', None,
2923 2925 _('use any branch information in patch (implied by --exact)'))] +
2924 2926 commitopts + commitopts2 + similarityopts,
2925 2927 _('[OPTION]... PATCH...'))
2926 2928 def import_(ui, repo, patch1=None, *patches, **opts):
2927 2929 """import an ordered set of patches
2928 2930
2929 2931 Import a list of patches and commit them individually (unless
2930 2932 --no-commit is specified).
2931 2933
2932 2934 To read a patch from standard input (stdin), use "-" as the patch
2933 2935 name. If a URL is specified, the patch will be downloaded from
2934 2936 there.
2935 2937
2936 2938 Import first applies changes to the working directory (unless
2937 2939 --bypass is specified), import will abort if there are outstanding
2938 2940 changes.
2939 2941
2940 2942 Use --bypass to apply and commit patches directly to the
2941 2943 repository, without affecting the working directory. Without
2942 2944 --exact, patches will be applied on top of the working directory
2943 2945 parent revision.
2944 2946
2945 2947 You can import a patch straight from a mail message. Even patches
2946 2948 as attachments work (to use the body part, it must have type
2947 2949 text/plain or text/x-patch). From and Subject headers of email
2948 2950 message are used as default committer and commit message. All
2949 2951 text/plain body parts before first diff are added to the commit
2950 2952 message.
2951 2953
2952 2954 If the imported patch was generated by :hg:`export`, user and
2953 2955 description from patch override values from message headers and
2954 2956 body. Values given on command line with -m/--message and -u/--user
2955 2957 override these.
2956 2958
2957 2959 If --exact is specified, import will set the working directory to
2958 2960 the parent of each patch before applying it, and will abort if the
2959 2961 resulting changeset has a different ID than the one recorded in
2960 2962 the patch. This will guard against various ways that portable
2961 2963 patch formats and mail systems might fail to transfer Mercurial
2962 2964 data or metadata. See :hg:`bundle` for lossless transmission.
2963 2965
2964 2966 Use --partial to ensure a changeset will be created from the patch
2965 2967 even if some hunks fail to apply. Hunks that fail to apply will be
2966 2968 written to a <target-file>.rej file. Conflicts can then be resolved
2967 2969 by hand before :hg:`commit --amend` is run to update the created
2968 2970 changeset. This flag exists to let people import patches that
2969 2971 partially apply without losing the associated metadata (author,
2970 2972 date, description, ...).
2971 2973
2972 2974 .. note::
2973 2975
2974 2976 When no hunks apply cleanly, :hg:`import --partial` will create
2975 2977 an empty changeset, importing only the patch metadata.
2976 2978
2977 2979 With -s/--similarity, hg will attempt to discover renames and
2978 2980 copies in the patch in the same way as :hg:`addremove`.
2979 2981
2980 2982 It is possible to use external patch programs to perform the patch
2981 2983 by setting the ``ui.patch`` configuration option. For the default
2982 2984 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2983 2985 See :hg:`help config` for more information about configuration
2984 2986 files and how to use these options.
2985 2987
2986 2988 See :hg:`help dates` for a list of formats valid for -d/--date.
2987 2989
2988 2990 .. container:: verbose
2989 2991
2990 2992 Examples:
2991 2993
2992 2994 - import a traditional patch from a website and detect renames::
2993 2995
2994 2996 hg import -s 80 http://example.com/bugfix.patch
2995 2997
2996 2998 - import a changeset from an hgweb server::
2997 2999
2998 3000 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2999 3001
3000 3002 - import all the patches in an Unix-style mbox::
3001 3003
3002 3004 hg import incoming-patches.mbox
3003 3005
3004 3006 - import patches from stdin::
3005 3007
3006 3008 hg import -
3007 3009
3008 3010 - attempt to exactly restore an exported changeset (not always
3009 3011 possible)::
3010 3012
3011 3013 hg import --exact proposed-fix.patch
3012 3014
3013 3015 - use an external tool to apply a patch which is too fuzzy for
3014 3016 the default internal tool.
3015 3017
3016 3018 hg import --config ui.patch="patch --merge" fuzzy.patch
3017 3019
3018 3020 - change the default fuzzing from 2 to a less strict 7
3019 3021
3020 3022 hg import --config ui.fuzz=7 fuzz.patch
3021 3023
3022 3024 Returns 0 on success, 1 on partial success (see --partial).
3023 3025 """
3024 3026
3025 3027 opts = pycompat.byteskwargs(opts)
3026 3028 if not patch1:
3027 3029 raise error.Abort(_('need at least one patch to import'))
3028 3030
3029 3031 patches = (patch1,) + patches
3030 3032
3031 3033 date = opts.get('date')
3032 3034 if date:
3033 3035 opts['date'] = util.parsedate(date)
3034 3036
3035 3037 exact = opts.get('exact')
3036 3038 update = not opts.get('bypass')
3037 3039 if not update and opts.get('no_commit'):
3038 3040 raise error.Abort(_('cannot use --no-commit with --bypass'))
3039 3041 try:
3040 3042 sim = float(opts.get('similarity') or 0)
3041 3043 except ValueError:
3042 3044 raise error.Abort(_('similarity must be a number'))
3043 3045 if sim < 0 or sim > 100:
3044 3046 raise error.Abort(_('similarity must be between 0 and 100'))
3045 3047 if sim and not update:
3046 3048 raise error.Abort(_('cannot use --similarity with --bypass'))
3047 3049 if exact:
3048 3050 if opts.get('edit'):
3049 3051 raise error.Abort(_('cannot use --exact with --edit'))
3050 3052 if opts.get('prefix'):
3051 3053 raise error.Abort(_('cannot use --exact with --prefix'))
3052 3054
3053 3055 base = opts["base"]
3054 3056 wlock = dsguard = lock = tr = None
3055 3057 msgs = []
3056 3058 ret = 0
3057 3059
3058 3060
3059 3061 try:
3060 3062 wlock = repo.wlock()
3061 3063
3062 3064 if update:
3063 3065 cmdutil.checkunfinished(repo)
3064 3066 if (exact or not opts.get('force')):
3065 3067 cmdutil.bailifchanged(repo)
3066 3068
3067 3069 if not opts.get('no_commit'):
3068 3070 lock = repo.lock()
3069 3071 tr = repo.transaction('import')
3070 3072 else:
3071 3073 dsguard = dirstateguard.dirstateguard(repo, 'import')
3072 3074 parents = repo[None].parents()
3073 3075 for patchurl in patches:
3074 3076 if patchurl == '-':
3075 3077 ui.status(_('applying patch from stdin\n'))
3076 3078 patchfile = ui.fin
3077 3079 patchurl = 'stdin' # for error message
3078 3080 else:
3079 3081 patchurl = os.path.join(base, patchurl)
3080 3082 ui.status(_('applying %s\n') % patchurl)
3081 3083 patchfile = hg.openpath(ui, patchurl)
3082 3084
3083 3085 haspatch = False
3084 3086 for hunk in patch.split(patchfile):
3085 3087 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3086 3088 parents, opts,
3087 3089 msgs, hg.clean)
3088 3090 if msg:
3089 3091 haspatch = True
3090 3092 ui.note(msg + '\n')
3091 3093 if update or exact:
3092 3094 parents = repo[None].parents()
3093 3095 else:
3094 3096 parents = [repo[node]]
3095 3097 if rej:
3096 3098 ui.write_err(_("patch applied partially\n"))
3097 3099 ui.write_err(_("(fix the .rej files and run "
3098 3100 "`hg commit --amend`)\n"))
3099 3101 ret = 1
3100 3102 break
3101 3103
3102 3104 if not haspatch:
3103 3105 raise error.Abort(_('%s: no diffs found') % patchurl)
3104 3106
3105 3107 if tr:
3106 3108 tr.close()
3107 3109 if msgs:
3108 3110 repo.savecommitmessage('\n* * *\n'.join(msgs))
3109 3111 if dsguard:
3110 3112 dsguard.close()
3111 3113 return ret
3112 3114 finally:
3113 3115 if tr:
3114 3116 tr.release()
3115 3117 release(lock, dsguard, wlock)
3116 3118
3117 3119 @command('incoming|in',
3118 3120 [('f', 'force', None,
3119 3121 _('run even if remote repository is unrelated')),
3120 3122 ('n', 'newest-first', None, _('show newest record first')),
3121 3123 ('', 'bundle', '',
3122 3124 _('file to store the bundles into'), _('FILE')),
3123 3125 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3124 3126 ('B', 'bookmarks', False, _("compare bookmarks")),
3125 3127 ('b', 'branch', [],
3126 3128 _('a specific branch you would like to pull'), _('BRANCH')),
3127 3129 ] + logopts + remoteopts + subrepoopts,
3128 3130 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3129 3131 def incoming(ui, repo, source="default", **opts):
3130 3132 """show new changesets found in source
3131 3133
3132 3134 Show new changesets found in the specified path/URL or the default
3133 3135 pull location. These are the changesets that would have been pulled
3134 3136 if a pull at the time you issued this command.
3135 3137
3136 3138 See pull for valid source format details.
3137 3139
3138 3140 .. container:: verbose
3139 3141
3140 3142 With -B/--bookmarks, the result of bookmark comparison between
3141 3143 local and remote repositories is displayed. With -v/--verbose,
3142 3144 status is also displayed for each bookmark like below::
3143 3145
3144 3146 BM1 01234567890a added
3145 3147 BM2 1234567890ab advanced
3146 3148 BM3 234567890abc diverged
3147 3149 BM4 34567890abcd changed
3148 3150
3149 3151 The action taken locally when pulling depends on the
3150 3152 status of each bookmark:
3151 3153
3152 3154 :``added``: pull will create it
3153 3155 :``advanced``: pull will update it
3154 3156 :``diverged``: pull will create a divergent bookmark
3155 3157 :``changed``: result depends on remote changesets
3156 3158
3157 3159 From the point of view of pulling behavior, bookmark
3158 3160 existing only in the remote repository are treated as ``added``,
3159 3161 even if it is in fact locally deleted.
3160 3162
3161 3163 .. container:: verbose
3162 3164
3163 3165 For remote repository, using --bundle avoids downloading the
3164 3166 changesets twice if the incoming is followed by a pull.
3165 3167
3166 3168 Examples:
3167 3169
3168 3170 - show incoming changes with patches and full description::
3169 3171
3170 3172 hg incoming -vp
3171 3173
3172 3174 - show incoming changes excluding merges, store a bundle::
3173 3175
3174 3176 hg in -vpM --bundle incoming.hg
3175 3177 hg pull incoming.hg
3176 3178
3177 3179 - briefly list changes inside a bundle::
3178 3180
3179 3181 hg in changes.hg -T "{desc|firstline}\\n"
3180 3182
3181 3183 Returns 0 if there are incoming changes, 1 otherwise.
3182 3184 """
3183 3185 opts = pycompat.byteskwargs(opts)
3184 3186 if opts.get('graph'):
3185 3187 cmdutil.checkunsupportedgraphflags([], opts)
3186 3188 def display(other, chlist, displayer):
3187 3189 revdag = cmdutil.graphrevs(other, chlist, opts)
3188 3190 cmdutil.displaygraph(ui, repo, revdag, displayer,
3189 3191 graphmod.asciiedges)
3190 3192
3191 3193 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3192 3194 return 0
3193 3195
3194 3196 if opts.get('bundle') and opts.get('subrepos'):
3195 3197 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3196 3198
3197 3199 if opts.get('bookmarks'):
3198 3200 source, branches = hg.parseurl(ui.expandpath(source),
3199 3201 opts.get('branch'))
3200 3202 other = hg.peer(repo, opts, source)
3201 3203 if 'bookmarks' not in other.listkeys('namespaces'):
3202 3204 ui.warn(_("remote doesn't support bookmarks\n"))
3203 3205 return 0
3204 3206 ui.pager('incoming')
3205 3207 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3206 3208 return bookmarks.incoming(ui, repo, other)
3207 3209
3208 3210 repo._subtoppath = ui.expandpath(source)
3209 3211 try:
3210 3212 return hg.incoming(ui, repo, source, opts)
3211 3213 finally:
3212 3214 del repo._subtoppath
3213 3215
3214 3216
3215 3217 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3216 3218 norepo=True)
3217 3219 def init(ui, dest=".", **opts):
3218 3220 """create a new repository in the given directory
3219 3221
3220 3222 Initialize a new repository in the given directory. If the given
3221 3223 directory does not exist, it will be created.
3222 3224
3223 3225 If no directory is given, the current directory is used.
3224 3226
3225 3227 It is possible to specify an ``ssh://`` URL as the destination.
3226 3228 See :hg:`help urls` for more information.
3227 3229
3228 3230 Returns 0 on success.
3229 3231 """
3230 3232 opts = pycompat.byteskwargs(opts)
3231 3233 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3232 3234
3233 3235 @command('locate',
3234 3236 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3235 3237 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3236 3238 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3237 3239 ] + walkopts,
3238 3240 _('[OPTION]... [PATTERN]...'))
3239 3241 def locate(ui, repo, *pats, **opts):
3240 3242 """locate files matching specific patterns (DEPRECATED)
3241 3243
3242 3244 Print files under Mercurial control in the working directory whose
3243 3245 names match the given patterns.
3244 3246
3245 3247 By default, this command searches all directories in the working
3246 3248 directory. To search just the current directory and its
3247 3249 subdirectories, use "--include .".
3248 3250
3249 3251 If no patterns are given to match, this command prints the names
3250 3252 of all files under Mercurial control in the working directory.
3251 3253
3252 3254 If you want to feed the output of this command into the "xargs"
3253 3255 command, use the -0 option to both this command and "xargs". This
3254 3256 will avoid the problem of "xargs" treating single filenames that
3255 3257 contain whitespace as multiple filenames.
3256 3258
3257 3259 See :hg:`help files` for a more versatile command.
3258 3260
3259 3261 Returns 0 if a match is found, 1 otherwise.
3260 3262 """
3261 3263 opts = pycompat.byteskwargs(opts)
3262 3264 if opts.get('print0'):
3263 3265 end = '\0'
3264 3266 else:
3265 3267 end = '\n'
3266 3268 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3267 3269
3268 3270 ret = 1
3269 3271 ctx = repo[rev]
3270 3272 m = scmutil.match(ctx, pats, opts, default='relglob',
3271 3273 badfn=lambda x, y: False)
3272 3274
3273 3275 ui.pager('locate')
3274 3276 for abs in ctx.matches(m):
3275 3277 if opts.get('fullpath'):
3276 3278 ui.write(repo.wjoin(abs), end)
3277 3279 else:
3278 3280 ui.write(((pats and m.rel(abs)) or abs), end)
3279 3281 ret = 0
3280 3282
3281 3283 return ret
3282 3284
3283 3285 @command('^log|history',
3284 3286 [('f', 'follow', None,
3285 3287 _('follow changeset history, or file history across copies and renames')),
3286 3288 ('', 'follow-first', None,
3287 3289 _('only follow the first parent of merge changesets (DEPRECATED)')),
3288 3290 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3289 3291 ('C', 'copies', None, _('show copied files')),
3290 3292 ('k', 'keyword', [],
3291 3293 _('do case-insensitive search for a given text'), _('TEXT')),
3292 3294 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3293 3295 ('', 'removed', None, _('include revisions where files were removed')),
3294 3296 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3295 3297 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3296 3298 ('', 'only-branch', [],
3297 3299 _('show only changesets within the given named branch (DEPRECATED)'),
3298 3300 _('BRANCH')),
3299 3301 ('b', 'branch', [],
3300 3302 _('show changesets within the given named branch'), _('BRANCH')),
3301 3303 ('P', 'prune', [],
3302 3304 _('do not display revision or any of its ancestors'), _('REV')),
3303 3305 ] + logopts + walkopts,
3304 3306 _('[OPTION]... [FILE]'),
3305 3307 inferrepo=True)
3306 3308 def log(ui, repo, *pats, **opts):
3307 3309 """show revision history of entire repository or files
3308 3310
3309 3311 Print the revision history of the specified files or the entire
3310 3312 project.
3311 3313
3312 3314 If no revision range is specified, the default is ``tip:0`` unless
3313 3315 --follow is set, in which case the working directory parent is
3314 3316 used as the starting revision.
3315 3317
3316 3318 File history is shown without following rename or copy history of
3317 3319 files. Use -f/--follow with a filename to follow history across
3318 3320 renames and copies. --follow without a filename will only show
3319 3321 ancestors or descendants of the starting revision.
3320 3322
3321 3323 By default this command prints revision number and changeset id,
3322 3324 tags, non-trivial parents, user, date and time, and a summary for
3323 3325 each commit. When the -v/--verbose switch is used, the list of
3324 3326 changed files and full commit message are shown.
3325 3327
3326 3328 With --graph the revisions are shown as an ASCII art DAG with the most
3327 3329 recent changeset at the top.
3328 3330 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3329 3331 and '+' represents a fork where the changeset from the lines below is a
3330 3332 parent of the 'o' merge on the same line.
3331 3333 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3332 3334 of a '|' indicates one or more revisions in a path are omitted.
3333 3335
3334 3336 .. note::
3335 3337
3336 3338 :hg:`log --patch` may generate unexpected diff output for merge
3337 3339 changesets, as it will only compare the merge changeset against
3338 3340 its first parent. Also, only files different from BOTH parents
3339 3341 will appear in files:.
3340 3342
3341 3343 .. note::
3342 3344
3343 3345 For performance reasons, :hg:`log FILE` may omit duplicate changes
3344 3346 made on branches and will not show removals or mode changes. To
3345 3347 see all such changes, use the --removed switch.
3346 3348
3347 3349 .. container:: verbose
3348 3350
3349 3351 Some examples:
3350 3352
3351 3353 - changesets with full descriptions and file lists::
3352 3354
3353 3355 hg log -v
3354 3356
3355 3357 - changesets ancestral to the working directory::
3356 3358
3357 3359 hg log -f
3358 3360
3359 3361 - last 10 commits on the current branch::
3360 3362
3361 3363 hg log -l 10 -b .
3362 3364
3363 3365 - changesets showing all modifications of a file, including removals::
3364 3366
3365 3367 hg log --removed file.c
3366 3368
3367 3369 - all changesets that touch a directory, with diffs, excluding merges::
3368 3370
3369 3371 hg log -Mp lib/
3370 3372
3371 3373 - all revision numbers that match a keyword::
3372 3374
3373 3375 hg log -k bug --template "{rev}\\n"
3374 3376
3375 3377 - the full hash identifier of the working directory parent::
3376 3378
3377 3379 hg log -r . --template "{node}\\n"
3378 3380
3379 3381 - list available log templates::
3380 3382
3381 3383 hg log -T list
3382 3384
3383 3385 - check if a given changeset is included in a tagged release::
3384 3386
3385 3387 hg log -r "a21ccf and ancestor(1.9)"
3386 3388
3387 3389 - find all changesets by some user in a date range::
3388 3390
3389 3391 hg log -k alice -d "may 2008 to jul 2008"
3390 3392
3391 3393 - summary of all changesets after the last tag::
3392 3394
3393 3395 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3394 3396
3395 3397 See :hg:`help dates` for a list of formats valid for -d/--date.
3396 3398
3397 3399 See :hg:`help revisions` for more about specifying and ordering
3398 3400 revisions.
3399 3401
3400 3402 See :hg:`help templates` for more about pre-packaged styles and
3401 3403 specifying custom templates.
3402 3404
3403 3405 Returns 0 on success.
3404 3406
3405 3407 """
3406 3408 opts = pycompat.byteskwargs(opts)
3407 3409 if opts.get('follow') and opts.get('rev'):
3408 3410 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3409 3411 del opts['follow']
3410 3412
3411 3413 if opts.get('graph'):
3412 3414 return cmdutil.graphlog(ui, repo, pats, opts)
3413 3415
3414 3416 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3415 3417 limit = cmdutil.loglimit(opts)
3416 3418 count = 0
3417 3419
3418 3420 getrenamed = None
3419 3421 if opts.get('copies'):
3420 3422 endrev = None
3421 3423 if opts.get('rev'):
3422 3424 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3423 3425 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3424 3426
3425 3427 ui.pager('log')
3426 3428 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3427 3429 for rev in revs:
3428 3430 if count == limit:
3429 3431 break
3430 3432 ctx = repo[rev]
3431 3433 copies = None
3432 3434 if getrenamed is not None and rev:
3433 3435 copies = []
3434 3436 for fn in ctx.files():
3435 3437 rename = getrenamed(fn, rev)
3436 3438 if rename:
3437 3439 copies.append((fn, rename[0]))
3438 3440 if filematcher:
3439 3441 revmatchfn = filematcher(ctx.rev())
3440 3442 else:
3441 3443 revmatchfn = None
3442 3444 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3443 3445 if displayer.flush(ctx):
3444 3446 count += 1
3445 3447
3446 3448 displayer.close()
3447 3449
3448 3450 @command('manifest',
3449 3451 [('r', 'rev', '', _('revision to display'), _('REV')),
3450 3452 ('', 'all', False, _("list files from all revisions"))]
3451 3453 + formatteropts,
3452 3454 _('[-r REV]'))
3453 3455 def manifest(ui, repo, node=None, rev=None, **opts):
3454 3456 """output the current or given revision of the project manifest
3455 3457
3456 3458 Print a list of version controlled files for the given revision.
3457 3459 If no revision is given, the first parent of the working directory
3458 3460 is used, or the null revision if no revision is checked out.
3459 3461
3460 3462 With -v, print file permissions, symlink and executable bits.
3461 3463 With --debug, print file revision hashes.
3462 3464
3463 3465 If option --all is specified, the list of all files from all revisions
3464 3466 is printed. This includes deleted and renamed files.
3465 3467
3466 3468 Returns 0 on success.
3467 3469 """
3468 3470 opts = pycompat.byteskwargs(opts)
3469 3471 fm = ui.formatter('manifest', opts)
3470 3472
3471 3473 if opts.get('all'):
3472 3474 if rev or node:
3473 3475 raise error.Abort(_("can't specify a revision with --all"))
3474 3476
3475 3477 res = []
3476 3478 prefix = "data/"
3477 3479 suffix = ".i"
3478 3480 plen = len(prefix)
3479 3481 slen = len(suffix)
3480 3482 with repo.lock():
3481 3483 for fn, b, size in repo.store.datafiles():
3482 3484 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3483 3485 res.append(fn[plen:-slen])
3484 3486 ui.pager('manifest')
3485 3487 for f in res:
3486 3488 fm.startitem()
3487 3489 fm.write("path", '%s\n', f)
3488 3490 fm.end()
3489 3491 return
3490 3492
3491 3493 if rev and node:
3492 3494 raise error.Abort(_("please specify just one revision"))
3493 3495
3494 3496 if not node:
3495 3497 node = rev
3496 3498
3497 3499 char = {'l': '@', 'x': '*', '': ''}
3498 3500 mode = {'l': '644', 'x': '755', '': '644'}
3499 3501 ctx = scmutil.revsingle(repo, node)
3500 3502 mf = ctx.manifest()
3501 3503 ui.pager('manifest')
3502 3504 for f in ctx:
3503 3505 fm.startitem()
3504 3506 fl = ctx[f].flags()
3505 3507 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3506 3508 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3507 3509 fm.write('path', '%s\n', f)
3508 3510 fm.end()
3509 3511
3510 3512 @command('^merge',
3511 3513 [('f', 'force', None,
3512 3514 _('force a merge including outstanding changes (DEPRECATED)')),
3513 3515 ('r', 'rev', '', _('revision to merge'), _('REV')),
3514 3516 ('P', 'preview', None,
3515 3517 _('review revisions to merge (no merge is performed)'))
3516 3518 ] + mergetoolopts,
3517 3519 _('[-P] [[-r] REV]'))
3518 3520 def merge(ui, repo, node=None, **opts):
3519 3521 """merge another revision into working directory
3520 3522
3521 3523 The current working directory is updated with all changes made in
3522 3524 the requested revision since the last common predecessor revision.
3523 3525
3524 3526 Files that changed between either parent are marked as changed for
3525 3527 the next commit and a commit must be performed before any further
3526 3528 updates to the repository are allowed. The next commit will have
3527 3529 two parents.
3528 3530
3529 3531 ``--tool`` can be used to specify the merge tool used for file
3530 3532 merges. It overrides the HGMERGE environment variable and your
3531 3533 configuration files. See :hg:`help merge-tools` for options.
3532 3534
3533 3535 If no revision is specified, the working directory's parent is a
3534 3536 head revision, and the current branch contains exactly one other
3535 3537 head, the other head is merged with by default. Otherwise, an
3536 3538 explicit revision with which to merge with must be provided.
3537 3539
3538 3540 See :hg:`help resolve` for information on handling file conflicts.
3539 3541
3540 3542 To undo an uncommitted merge, use :hg:`update --clean .` which
3541 3543 will check out a clean copy of the original merge parent, losing
3542 3544 all changes.
3543 3545
3544 3546 Returns 0 on success, 1 if there are unresolved files.
3545 3547 """
3546 3548
3547 3549 opts = pycompat.byteskwargs(opts)
3548 3550 if opts.get('rev') and node:
3549 3551 raise error.Abort(_("please specify just one revision"))
3550 3552 if not node:
3551 3553 node = opts.get('rev')
3552 3554
3553 3555 if node:
3554 3556 node = scmutil.revsingle(repo, node).node()
3555 3557
3556 3558 if not node:
3557 3559 node = repo[destutil.destmerge(repo)].node()
3558 3560
3559 3561 if opts.get('preview'):
3560 3562 # find nodes that are ancestors of p2 but not of p1
3561 3563 p1 = repo.lookup('.')
3562 3564 p2 = repo.lookup(node)
3563 3565 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3564 3566
3565 3567 displayer = cmdutil.show_changeset(ui, repo, opts)
3566 3568 for node in nodes:
3567 3569 displayer.show(repo[node])
3568 3570 displayer.close()
3569 3571 return 0
3570 3572
3571 3573 try:
3572 3574 # ui.forcemerge is an internal variable, do not document
3573 3575 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3574 3576 force = opts.get('force')
3575 3577 labels = ['working copy', 'merge rev']
3576 3578 return hg.merge(repo, node, force=force, mergeforce=force,
3577 3579 labels=labels)
3578 3580 finally:
3579 3581 ui.setconfig('ui', 'forcemerge', '', 'merge')
3580 3582
3581 3583 @command('outgoing|out',
3582 3584 [('f', 'force', None, _('run even when the destination is unrelated')),
3583 3585 ('r', 'rev', [],
3584 3586 _('a changeset intended to be included in the destination'), _('REV')),
3585 3587 ('n', 'newest-first', None, _('show newest record first')),
3586 3588 ('B', 'bookmarks', False, _('compare bookmarks')),
3587 3589 ('b', 'branch', [], _('a specific branch you would like to push'),
3588 3590 _('BRANCH')),
3589 3591 ] + logopts + remoteopts + subrepoopts,
3590 3592 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3591 3593 def outgoing(ui, repo, dest=None, **opts):
3592 3594 """show changesets not found in the destination
3593 3595
3594 3596 Show changesets not found in the specified destination repository
3595 3597 or the default push location. These are the changesets that would
3596 3598 be pushed if a push was requested.
3597 3599
3598 3600 See pull for details of valid destination formats.
3599 3601
3600 3602 .. container:: verbose
3601 3603
3602 3604 With -B/--bookmarks, the result of bookmark comparison between
3603 3605 local and remote repositories is displayed. With -v/--verbose,
3604 3606 status is also displayed for each bookmark like below::
3605 3607
3606 3608 BM1 01234567890a added
3607 3609 BM2 deleted
3608 3610 BM3 234567890abc advanced
3609 3611 BM4 34567890abcd diverged
3610 3612 BM5 4567890abcde changed
3611 3613
3612 3614 The action taken when pushing depends on the
3613 3615 status of each bookmark:
3614 3616
3615 3617 :``added``: push with ``-B`` will create it
3616 3618 :``deleted``: push with ``-B`` will delete it
3617 3619 :``advanced``: push will update it
3618 3620 :``diverged``: push with ``-B`` will update it
3619 3621 :``changed``: push with ``-B`` will update it
3620 3622
3621 3623 From the point of view of pushing behavior, bookmarks
3622 3624 existing only in the remote repository are treated as
3623 3625 ``deleted``, even if it is in fact added remotely.
3624 3626
3625 3627 Returns 0 if there are outgoing changes, 1 otherwise.
3626 3628 """
3627 3629 opts = pycompat.byteskwargs(opts)
3628 3630 if opts.get('graph'):
3629 3631 cmdutil.checkunsupportedgraphflags([], opts)
3630 3632 o, other = hg._outgoing(ui, repo, dest, opts)
3631 3633 if not o:
3632 3634 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3633 3635 return
3634 3636
3635 3637 revdag = cmdutil.graphrevs(repo, o, opts)
3636 3638 ui.pager('outgoing')
3637 3639 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3638 3640 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3639 3641 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3640 3642 return 0
3641 3643
3642 3644 if opts.get('bookmarks'):
3643 3645 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3644 3646 dest, branches = hg.parseurl(dest, opts.get('branch'))
3645 3647 other = hg.peer(repo, opts, dest)
3646 3648 if 'bookmarks' not in other.listkeys('namespaces'):
3647 3649 ui.warn(_("remote doesn't support bookmarks\n"))
3648 3650 return 0
3649 3651 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3650 3652 ui.pager('outgoing')
3651 3653 return bookmarks.outgoing(ui, repo, other)
3652 3654
3653 3655 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3654 3656 try:
3655 3657 return hg.outgoing(ui, repo, dest, opts)
3656 3658 finally:
3657 3659 del repo._subtoppath
3658 3660
3659 3661 @command('parents',
3660 3662 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3661 3663 ] + templateopts,
3662 3664 _('[-r REV] [FILE]'),
3663 3665 inferrepo=True)
3664 3666 def parents(ui, repo, file_=None, **opts):
3665 3667 """show the parents of the working directory or revision (DEPRECATED)
3666 3668
3667 3669 Print the working directory's parent revisions. If a revision is
3668 3670 given via -r/--rev, the parent of that revision will be printed.
3669 3671 If a file argument is given, the revision in which the file was
3670 3672 last changed (before the working directory revision or the
3671 3673 argument to --rev if given) is printed.
3672 3674
3673 3675 This command is equivalent to::
3674 3676
3675 3677 hg log -r "p1()+p2()" or
3676 3678 hg log -r "p1(REV)+p2(REV)" or
3677 3679 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3678 3680 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3679 3681
3680 3682 See :hg:`summary` and :hg:`help revsets` for related information.
3681 3683
3682 3684 Returns 0 on success.
3683 3685 """
3684 3686
3685 3687 opts = pycompat.byteskwargs(opts)
3686 3688 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3687 3689
3688 3690 if file_:
3689 3691 m = scmutil.match(ctx, (file_,), opts)
3690 3692 if m.anypats() or len(m.files()) != 1:
3691 3693 raise error.Abort(_('can only specify an explicit filename'))
3692 3694 file_ = m.files()[0]
3693 3695 filenodes = []
3694 3696 for cp in ctx.parents():
3695 3697 if not cp:
3696 3698 continue
3697 3699 try:
3698 3700 filenodes.append(cp.filenode(file_))
3699 3701 except error.LookupError:
3700 3702 pass
3701 3703 if not filenodes:
3702 3704 raise error.Abort(_("'%s' not found in manifest!") % file_)
3703 3705 p = []
3704 3706 for fn in filenodes:
3705 3707 fctx = repo.filectx(file_, fileid=fn)
3706 3708 p.append(fctx.node())
3707 3709 else:
3708 3710 p = [cp.node() for cp in ctx.parents()]
3709 3711
3710 3712 displayer = cmdutil.show_changeset(ui, repo, opts)
3711 3713 for n in p:
3712 3714 if n != nullid:
3713 3715 displayer.show(repo[n])
3714 3716 displayer.close()
3715 3717
3716 3718 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3717 3719 def paths(ui, repo, search=None, **opts):
3718 3720 """show aliases for remote repositories
3719 3721
3720 3722 Show definition of symbolic path name NAME. If no name is given,
3721 3723 show definition of all available names.
3722 3724
3723 3725 Option -q/--quiet suppresses all output when searching for NAME
3724 3726 and shows only the path names when listing all definitions.
3725 3727
3726 3728 Path names are defined in the [paths] section of your
3727 3729 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3728 3730 repository, ``.hg/hgrc`` is used, too.
3729 3731
3730 3732 The path names ``default`` and ``default-push`` have a special
3731 3733 meaning. When performing a push or pull operation, they are used
3732 3734 as fallbacks if no location is specified on the command-line.
3733 3735 When ``default-push`` is set, it will be used for push and
3734 3736 ``default`` will be used for pull; otherwise ``default`` is used
3735 3737 as the fallback for both. When cloning a repository, the clone
3736 3738 source is written as ``default`` in ``.hg/hgrc``.
3737 3739
3738 3740 .. note::
3739 3741
3740 3742 ``default`` and ``default-push`` apply to all inbound (e.g.
3741 3743 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3742 3744 and :hg:`bundle`) operations.
3743 3745
3744 3746 See :hg:`help urls` for more information.
3745 3747
3746 3748 Returns 0 on success.
3747 3749 """
3748 3750
3749 3751 opts = pycompat.byteskwargs(opts)
3750 3752 ui.pager('paths')
3751 3753 if search:
3752 3754 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3753 3755 if name == search]
3754 3756 else:
3755 3757 pathitems = sorted(ui.paths.iteritems())
3756 3758
3757 3759 fm = ui.formatter('paths', opts)
3758 3760 if fm.isplain():
3759 3761 hidepassword = util.hidepassword
3760 3762 else:
3761 3763 hidepassword = str
3762 3764 if ui.quiet:
3763 3765 namefmt = '%s\n'
3764 3766 else:
3765 3767 namefmt = '%s = '
3766 3768 showsubopts = not search and not ui.quiet
3767 3769
3768 3770 for name, path in pathitems:
3769 3771 fm.startitem()
3770 3772 fm.condwrite(not search, 'name', namefmt, name)
3771 3773 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3772 3774 for subopt, value in sorted(path.suboptions.items()):
3773 3775 assert subopt not in ('name', 'url')
3774 3776 if showsubopts:
3775 3777 fm.plain('%s:%s = ' % (name, subopt))
3776 3778 fm.condwrite(showsubopts, subopt, '%s\n', value)
3777 3779
3778 3780 fm.end()
3779 3781
3780 3782 if search and not pathitems:
3781 3783 if not ui.quiet:
3782 3784 ui.warn(_("not found!\n"))
3783 3785 return 1
3784 3786 else:
3785 3787 return 0
3786 3788
3787 3789 @command('phase',
3788 3790 [('p', 'public', False, _('set changeset phase to public')),
3789 3791 ('d', 'draft', False, _('set changeset phase to draft')),
3790 3792 ('s', 'secret', False, _('set changeset phase to secret')),
3791 3793 ('f', 'force', False, _('allow to move boundary backward')),
3792 3794 ('r', 'rev', [], _('target revision'), _('REV')),
3793 3795 ],
3794 3796 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3795 3797 def phase(ui, repo, *revs, **opts):
3796 3798 """set or show the current phase name
3797 3799
3798 3800 With no argument, show the phase name of the current revision(s).
3799 3801
3800 3802 With one of -p/--public, -d/--draft or -s/--secret, change the
3801 3803 phase value of the specified revisions.
3802 3804
3803 3805 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3804 3806 lower phase to an higher phase. Phases are ordered as follows::
3805 3807
3806 3808 public < draft < secret
3807 3809
3808 3810 Returns 0 on success, 1 if some phases could not be changed.
3809 3811
3810 3812 (For more information about the phases concept, see :hg:`help phases`.)
3811 3813 """
3812 3814 opts = pycompat.byteskwargs(opts)
3813 3815 # search for a unique phase argument
3814 3816 targetphase = None
3815 3817 for idx, name in enumerate(phases.phasenames):
3816 3818 if opts[name]:
3817 3819 if targetphase is not None:
3818 3820 raise error.Abort(_('only one phase can be specified'))
3819 3821 targetphase = idx
3820 3822
3821 3823 # look for specified revision
3822 3824 revs = list(revs)
3823 3825 revs.extend(opts['rev'])
3824 3826 if not revs:
3825 3827 # display both parents as the second parent phase can influence
3826 3828 # the phase of a merge commit
3827 3829 revs = [c.rev() for c in repo[None].parents()]
3828 3830
3829 3831 revs = scmutil.revrange(repo, revs)
3830 3832
3831 3833 lock = None
3832 3834 ret = 0
3833 3835 if targetphase is None:
3834 3836 # display
3835 3837 for r in revs:
3836 3838 ctx = repo[r]
3837 3839 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3838 3840 else:
3839 3841 tr = None
3840 3842 lock = repo.lock()
3841 3843 try:
3842 3844 tr = repo.transaction("phase")
3843 3845 # set phase
3844 3846 if not revs:
3845 3847 raise error.Abort(_('empty revision set'))
3846 3848 nodes = [repo[r].node() for r in revs]
3847 3849 # moving revision from public to draft may hide them
3848 3850 # We have to check result on an unfiltered repository
3849 3851 unfi = repo.unfiltered()
3850 3852 getphase = unfi._phasecache.phase
3851 3853 olddata = [getphase(unfi, r) for r in unfi]
3852 3854 phases.advanceboundary(repo, tr, targetphase, nodes)
3853 3855 if opts['force']:
3854 3856 phases.retractboundary(repo, tr, targetphase, nodes)
3855 3857 tr.close()
3856 3858 finally:
3857 3859 if tr is not None:
3858 3860 tr.release()
3859 3861 lock.release()
3860 3862 getphase = unfi._phasecache.phase
3861 3863 newdata = [getphase(unfi, r) for r in unfi]
3862 3864 changes = sum(newdata[r] != olddata[r] for r in unfi)
3863 3865 cl = unfi.changelog
3864 3866 rejected = [n for n in nodes
3865 3867 if newdata[cl.rev(n)] < targetphase]
3866 3868 if rejected:
3867 3869 ui.warn(_('cannot move %i changesets to a higher '
3868 3870 'phase, use --force\n') % len(rejected))
3869 3871 ret = 1
3870 3872 if changes:
3871 3873 msg = _('phase changed for %i changesets\n') % changes
3872 3874 if ret:
3873 3875 ui.status(msg)
3874 3876 else:
3875 3877 ui.note(msg)
3876 3878 else:
3877 3879 ui.warn(_('no phases changed\n'))
3878 3880 return ret
3879 3881
3880 3882 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3881 3883 """Run after a changegroup has been added via pull/unbundle
3882 3884
3883 3885 This takes arguments below:
3884 3886
3885 3887 :modheads: change of heads by pull/unbundle
3886 3888 :optupdate: updating working directory is needed or not
3887 3889 :checkout: update destination revision (or None to default destination)
3888 3890 :brev: a name, which might be a bookmark to be activated after updating
3889 3891 """
3890 3892 if modheads == 0:
3891 3893 return
3892 3894 if optupdate:
3893 3895 try:
3894 3896 return hg.updatetotally(ui, repo, checkout, brev)
3895 3897 except error.UpdateAbort as inst:
3896 3898 msg = _("not updating: %s") % str(inst)
3897 3899 hint = inst.hint
3898 3900 raise error.UpdateAbort(msg, hint=hint)
3899 3901 if modheads > 1:
3900 3902 currentbranchheads = len(repo.branchheads())
3901 3903 if currentbranchheads == modheads:
3902 3904 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3903 3905 elif currentbranchheads > 1:
3904 3906 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3905 3907 "merge)\n"))
3906 3908 else:
3907 3909 ui.status(_("(run 'hg heads' to see heads)\n"))
3908 3910 else:
3909 3911 ui.status(_("(run 'hg update' to get a working copy)\n"))
3910 3912
3911 3913 @command('^pull',
3912 3914 [('u', 'update', None,
3913 3915 _('update to new branch head if changesets were pulled')),
3914 3916 ('f', 'force', None, _('run even when remote repository is unrelated')),
3915 3917 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3916 3918 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3917 3919 ('b', 'branch', [], _('a specific branch you would like to pull'),
3918 3920 _('BRANCH')),
3919 3921 ] + remoteopts,
3920 3922 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3921 3923 def pull(ui, repo, source="default", **opts):
3922 3924 """pull changes from the specified source
3923 3925
3924 3926 Pull changes from a remote repository to a local one.
3925 3927
3926 3928 This finds all changes from the repository at the specified path
3927 3929 or URL and adds them to a local repository (the current one unless
3928 3930 -R is specified). By default, this does not update the copy of the
3929 3931 project in the working directory.
3930 3932
3931 3933 Use :hg:`incoming` if you want to see what would have been added
3932 3934 by a pull at the time you issued this command. If you then decide
3933 3935 to add those changes to the repository, you should use :hg:`pull
3934 3936 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3935 3937
3936 3938 If SOURCE is omitted, the 'default' path will be used.
3937 3939 See :hg:`help urls` for more information.
3938 3940
3939 3941 Specifying bookmark as ``.`` is equivalent to specifying the active
3940 3942 bookmark's name.
3941 3943
3942 3944 Returns 0 on success, 1 if an update had unresolved files.
3943 3945 """
3944 3946
3945 3947 opts = pycompat.byteskwargs(opts)
3946 3948 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3947 3949 msg = _('update destination required by configuration')
3948 3950 hint = _('use hg pull followed by hg update DEST')
3949 3951 raise error.Abort(msg, hint=hint)
3950 3952
3951 3953 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3952 3954 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3953 3955 other = hg.peer(repo, opts, source)
3954 3956 try:
3955 3957 revs, checkout = hg.addbranchrevs(repo, other, branches,
3956 3958 opts.get('rev'))
3957 3959
3958 3960
3959 3961 pullopargs = {}
3960 3962 if opts.get('bookmark'):
3961 3963 if not revs:
3962 3964 revs = []
3963 3965 # The list of bookmark used here is not the one used to actually
3964 3966 # update the bookmark name. This can result in the revision pulled
3965 3967 # not ending up with the name of the bookmark because of a race
3966 3968 # condition on the server. (See issue 4689 for details)
3967 3969 remotebookmarks = other.listkeys('bookmarks')
3968 3970 pullopargs['remotebookmarks'] = remotebookmarks
3969 3971 for b in opts['bookmark']:
3970 3972 b = repo._bookmarks.expandname(b)
3971 3973 if b not in remotebookmarks:
3972 3974 raise error.Abort(_('remote bookmark %s not found!') % b)
3973 3975 revs.append(remotebookmarks[b])
3974 3976
3975 3977 if revs:
3976 3978 try:
3977 3979 # When 'rev' is a bookmark name, we cannot guarantee that it
3978 3980 # will be updated with that name because of a race condition
3979 3981 # server side. (See issue 4689 for details)
3980 3982 oldrevs = revs
3981 3983 revs = [] # actually, nodes
3982 3984 for r in oldrevs:
3983 3985 node = other.lookup(r)
3984 3986 revs.append(node)
3985 3987 if r == checkout:
3986 3988 checkout = node
3987 3989 except error.CapabilityError:
3988 3990 err = _("other repository doesn't support revision lookup, "
3989 3991 "so a rev cannot be specified.")
3990 3992 raise error.Abort(err)
3991 3993
3992 3994 pullopargs.update(opts.get('opargs', {}))
3993 3995 modheads = exchange.pull(repo, other, heads=revs,
3994 3996 force=opts.get('force'),
3995 3997 bookmarks=opts.get('bookmark', ()),
3996 3998 opargs=pullopargs).cgresult
3997 3999
3998 4000 # brev is a name, which might be a bookmark to be activated at
3999 4001 # the end of the update. In other words, it is an explicit
4000 4002 # destination of the update
4001 4003 brev = None
4002 4004
4003 4005 if checkout:
4004 4006 checkout = str(repo.changelog.rev(checkout))
4005 4007
4006 4008 # order below depends on implementation of
4007 4009 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4008 4010 # because 'checkout' is determined without it.
4009 4011 if opts.get('rev'):
4010 4012 brev = opts['rev'][0]
4011 4013 elif opts.get('branch'):
4012 4014 brev = opts['branch'][0]
4013 4015 else:
4014 4016 brev = branches[0]
4015 4017 repo._subtoppath = source
4016 4018 try:
4017 4019 ret = postincoming(ui, repo, modheads, opts.get('update'),
4018 4020 checkout, brev)
4019 4021
4020 4022 finally:
4021 4023 del repo._subtoppath
4022 4024
4023 4025 finally:
4024 4026 other.close()
4025 4027 return ret
4026 4028
4027 4029 @command('^push',
4028 4030 [('f', 'force', None, _('force push')),
4029 4031 ('r', 'rev', [],
4030 4032 _('a changeset intended to be included in the destination'),
4031 4033 _('REV')),
4032 4034 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4033 4035 ('b', 'branch', [],
4034 4036 _('a specific branch you would like to push'), _('BRANCH')),
4035 4037 ('', 'new-branch', False, _('allow pushing a new branch')),
4036 4038 ] + remoteopts,
4037 4039 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4038 4040 def push(ui, repo, dest=None, **opts):
4039 4041 """push changes to the specified destination
4040 4042
4041 4043 Push changesets from the local repository to the specified
4042 4044 destination.
4043 4045
4044 4046 This operation is symmetrical to pull: it is identical to a pull
4045 4047 in the destination repository from the current one.
4046 4048
4047 4049 By default, push will not allow creation of new heads at the
4048 4050 destination, since multiple heads would make it unclear which head
4049 4051 to use. In this situation, it is recommended to pull and merge
4050 4052 before pushing.
4051 4053
4052 4054 Use --new-branch if you want to allow push to create a new named
4053 4055 branch that is not present at the destination. This allows you to
4054 4056 only create a new branch without forcing other changes.
4055 4057
4056 4058 .. note::
4057 4059
4058 4060 Extra care should be taken with the -f/--force option,
4059 4061 which will push all new heads on all branches, an action which will
4060 4062 almost always cause confusion for collaborators.
4061 4063
4062 4064 If -r/--rev is used, the specified revision and all its ancestors
4063 4065 will be pushed to the remote repository.
4064 4066
4065 4067 If -B/--bookmark is used, the specified bookmarked revision, its
4066 4068 ancestors, and the bookmark will be pushed to the remote
4067 4069 repository. Specifying ``.`` is equivalent to specifying the active
4068 4070 bookmark's name.
4069 4071
4070 4072 Please see :hg:`help urls` for important details about ``ssh://``
4071 4073 URLs. If DESTINATION is omitted, a default path will be used.
4072 4074
4073 4075 Returns 0 if push was successful, 1 if nothing to push.
4074 4076 """
4075 4077
4076 4078 opts = pycompat.byteskwargs(opts)
4077 4079 if opts.get('bookmark'):
4078 4080 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4079 4081 for b in opts['bookmark']:
4080 4082 # translate -B options to -r so changesets get pushed
4081 4083 b = repo._bookmarks.expandname(b)
4082 4084 if b in repo._bookmarks:
4083 4085 opts.setdefault('rev', []).append(b)
4084 4086 else:
4085 4087 # if we try to push a deleted bookmark, translate it to null
4086 4088 # this lets simultaneous -r, -b options continue working
4087 4089 opts.setdefault('rev', []).append("null")
4088 4090
4089 4091 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4090 4092 if not path:
4091 4093 raise error.Abort(_('default repository not configured!'),
4092 4094 hint=_("see 'hg help config.paths'"))
4093 4095 dest = path.pushloc or path.loc
4094 4096 branches = (path.branch, opts.get('branch') or [])
4095 4097 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4096 4098 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4097 4099 other = hg.peer(repo, opts, dest)
4098 4100
4099 4101 if revs:
4100 4102 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4101 4103 if not revs:
4102 4104 raise error.Abort(_("specified revisions evaluate to an empty set"),
4103 4105 hint=_("use different revision arguments"))
4104 4106 elif path.pushrev:
4105 4107 # It doesn't make any sense to specify ancestor revisions. So limit
4106 4108 # to DAG heads to make discovery simpler.
4107 4109 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4108 4110 revs = scmutil.revrange(repo, [expr])
4109 4111 revs = [repo[rev].node() for rev in revs]
4110 4112 if not revs:
4111 4113 raise error.Abort(_('default push revset for path evaluates to an '
4112 4114 'empty set'))
4113 4115
4114 4116 repo._subtoppath = dest
4115 4117 try:
4116 4118 # push subrepos depth-first for coherent ordering
4117 4119 c = repo['']
4118 4120 subs = c.substate # only repos that are committed
4119 4121 for s in sorted(subs):
4120 4122 result = c.sub(s).push(opts)
4121 4123 if result == 0:
4122 4124 return not result
4123 4125 finally:
4124 4126 del repo._subtoppath
4125 4127 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4126 4128 newbranch=opts.get('new_branch'),
4127 4129 bookmarks=opts.get('bookmark', ()),
4128 4130 opargs=opts.get('opargs'))
4129 4131
4130 4132 result = not pushop.cgresult
4131 4133
4132 4134 if pushop.bkresult is not None:
4133 4135 if pushop.bkresult == 2:
4134 4136 result = 2
4135 4137 elif not result and pushop.bkresult:
4136 4138 result = 2
4137 4139
4138 4140 return result
4139 4141
4140 4142 @command('recover', [])
4141 4143 def recover(ui, repo):
4142 4144 """roll back an interrupted transaction
4143 4145
4144 4146 Recover from an interrupted commit or pull.
4145 4147
4146 4148 This command tries to fix the repository status after an
4147 4149 interrupted operation. It should only be necessary when Mercurial
4148 4150 suggests it.
4149 4151
4150 4152 Returns 0 if successful, 1 if nothing to recover or verify fails.
4151 4153 """
4152 4154 if repo.recover():
4153 4155 return hg.verify(repo)
4154 4156 return 1
4155 4157
4156 4158 @command('^remove|rm',
4157 4159 [('A', 'after', None, _('record delete for missing files')),
4158 4160 ('f', 'force', None,
4159 4161 _('forget added files, delete modified files')),
4160 4162 ] + subrepoopts + walkopts,
4161 4163 _('[OPTION]... FILE...'),
4162 4164 inferrepo=True)
4163 4165 def remove(ui, repo, *pats, **opts):
4164 4166 """remove the specified files on the next commit
4165 4167
4166 4168 Schedule the indicated files for removal from the current branch.
4167 4169
4168 4170 This command schedules the files to be removed at the next commit.
4169 4171 To undo a remove before that, see :hg:`revert`. To undo added
4170 4172 files, see :hg:`forget`.
4171 4173
4172 4174 .. container:: verbose
4173 4175
4174 4176 -A/--after can be used to remove only files that have already
4175 4177 been deleted, -f/--force can be used to force deletion, and -Af
4176 4178 can be used to remove files from the next revision without
4177 4179 deleting them from the working directory.
4178 4180
4179 4181 The following table details the behavior of remove for different
4180 4182 file states (columns) and option combinations (rows). The file
4181 4183 states are Added [A], Clean [C], Modified [M] and Missing [!]
4182 4184 (as reported by :hg:`status`). The actions are Warn, Remove
4183 4185 (from branch) and Delete (from disk):
4184 4186
4185 4187 ========= == == == ==
4186 4188 opt/state A C M !
4187 4189 ========= == == == ==
4188 4190 none W RD W R
4189 4191 -f R RD RD R
4190 4192 -A W W W R
4191 4193 -Af R R R R
4192 4194 ========= == == == ==
4193 4195
4194 4196 .. note::
4195 4197
4196 4198 :hg:`remove` never deletes files in Added [A] state from the
4197 4199 working directory, not even if ``--force`` is specified.
4198 4200
4199 4201 Returns 0 on success, 1 if any warnings encountered.
4200 4202 """
4201 4203
4202 4204 opts = pycompat.byteskwargs(opts)
4203 4205 after, force = opts.get('after'), opts.get('force')
4204 4206 if not pats and not after:
4205 4207 raise error.Abort(_('no files specified'))
4206 4208
4207 4209 m = scmutil.match(repo[None], pats, opts)
4208 4210 subrepos = opts.get('subrepos')
4209 4211 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4210 4212
4211 4213 @command('rename|move|mv',
4212 4214 [('A', 'after', None, _('record a rename that has already occurred')),
4213 4215 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4214 4216 ] + walkopts + dryrunopts,
4215 4217 _('[OPTION]... SOURCE... DEST'))
4216 4218 def rename(ui, repo, *pats, **opts):
4217 4219 """rename files; equivalent of copy + remove
4218 4220
4219 4221 Mark dest as copies of sources; mark sources for deletion. If dest
4220 4222 is a directory, copies are put in that directory. If dest is a
4221 4223 file, there can only be one source.
4222 4224
4223 4225 By default, this command copies the contents of files as they
4224 4226 exist in the working directory. If invoked with -A/--after, the
4225 4227 operation is recorded, but no copying is performed.
4226 4228
4227 4229 This command takes effect at the next commit. To undo a rename
4228 4230 before that, see :hg:`revert`.
4229 4231
4230 4232 Returns 0 on success, 1 if errors are encountered.
4231 4233 """
4232 4234 opts = pycompat.byteskwargs(opts)
4233 4235 with repo.wlock(False):
4234 4236 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4235 4237
4236 4238 @command('resolve',
4237 4239 [('a', 'all', None, _('select all unresolved files')),
4238 4240 ('l', 'list', None, _('list state of files needing merge')),
4239 4241 ('m', 'mark', None, _('mark files as resolved')),
4240 4242 ('u', 'unmark', None, _('mark files as unresolved')),
4241 4243 ('n', 'no-status', None, _('hide status prefix'))]
4242 4244 + mergetoolopts + walkopts + formatteropts,
4243 4245 _('[OPTION]... [FILE]...'),
4244 4246 inferrepo=True)
4245 4247 def resolve(ui, repo, *pats, **opts):
4246 4248 """redo merges or set/view the merge status of files
4247 4249
4248 4250 Merges with unresolved conflicts are often the result of
4249 4251 non-interactive merging using the ``internal:merge`` configuration
4250 4252 setting, or a command-line merge tool like ``diff3``. The resolve
4251 4253 command is used to manage the files involved in a merge, after
4252 4254 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4253 4255 working directory must have two parents). See :hg:`help
4254 4256 merge-tools` for information on configuring merge tools.
4255 4257
4256 4258 The resolve command can be used in the following ways:
4257 4259
4258 4260 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4259 4261 files, discarding any previous merge attempts. Re-merging is not
4260 4262 performed for files already marked as resolved. Use ``--all/-a``
4261 4263 to select all unresolved files. ``--tool`` can be used to specify
4262 4264 the merge tool used for the given files. It overrides the HGMERGE
4263 4265 environment variable and your configuration files. Previous file
4264 4266 contents are saved with a ``.orig`` suffix.
4265 4267
4266 4268 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4267 4269 (e.g. after having manually fixed-up the files). The default is
4268 4270 to mark all unresolved files.
4269 4271
4270 4272 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4271 4273 default is to mark all resolved files.
4272 4274
4273 4275 - :hg:`resolve -l`: list files which had or still have conflicts.
4274 4276 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4275 4277 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4276 4278 the list. See :hg:`help filesets` for details.
4277 4279
4278 4280 .. note::
4279 4281
4280 4282 Mercurial will not let you commit files with unresolved merge
4281 4283 conflicts. You must use :hg:`resolve -m ...` before you can
4282 4284 commit after a conflicting merge.
4283 4285
4284 4286 Returns 0 on success, 1 if any files fail a resolve attempt.
4285 4287 """
4286 4288
4287 4289 opts = pycompat.byteskwargs(opts)
4288 4290 flaglist = 'all mark unmark list no_status'.split()
4289 4291 all, mark, unmark, show, nostatus = \
4290 4292 [opts.get(o) for o in flaglist]
4291 4293
4292 4294 if (show and (mark or unmark)) or (mark and unmark):
4293 4295 raise error.Abort(_("too many options specified"))
4294 4296 if pats and all:
4295 4297 raise error.Abort(_("can't specify --all and patterns"))
4296 4298 if not (all or pats or show or mark or unmark):
4297 4299 raise error.Abort(_('no files or directories specified'),
4298 4300 hint=('use --all to re-merge all unresolved files'))
4299 4301
4300 4302 if show:
4301 4303 ui.pager('resolve')
4302 4304 fm = ui.formatter('resolve', opts)
4303 4305 ms = mergemod.mergestate.read(repo)
4304 4306 m = scmutil.match(repo[None], pats, opts)
4305 4307 for f in ms:
4306 4308 if not m(f):
4307 4309 continue
4308 4310 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4309 4311 'd': 'driverresolved'}[ms[f]]
4310 4312 fm.startitem()
4311 4313 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4312 4314 fm.write('path', '%s\n', f, label=l)
4313 4315 fm.end()
4314 4316 return 0
4315 4317
4316 4318 with repo.wlock():
4317 4319 ms = mergemod.mergestate.read(repo)
4318 4320
4319 4321 if not (ms.active() or repo.dirstate.p2() != nullid):
4320 4322 raise error.Abort(
4321 4323 _('resolve command not applicable when not merging'))
4322 4324
4323 4325 wctx = repo[None]
4324 4326
4325 4327 if ms.mergedriver and ms.mdstate() == 'u':
4326 4328 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4327 4329 ms.commit()
4328 4330 # allow mark and unmark to go through
4329 4331 if not mark and not unmark and not proceed:
4330 4332 return 1
4331 4333
4332 4334 m = scmutil.match(wctx, pats, opts)
4333 4335 ret = 0
4334 4336 didwork = False
4335 4337 runconclude = False
4336 4338
4337 4339 tocomplete = []
4338 4340 for f in ms:
4339 4341 if not m(f):
4340 4342 continue
4341 4343
4342 4344 didwork = True
4343 4345
4344 4346 # don't let driver-resolved files be marked, and run the conclude
4345 4347 # step if asked to resolve
4346 4348 if ms[f] == "d":
4347 4349 exact = m.exact(f)
4348 4350 if mark:
4349 4351 if exact:
4350 4352 ui.warn(_('not marking %s as it is driver-resolved\n')
4351 4353 % f)
4352 4354 elif unmark:
4353 4355 if exact:
4354 4356 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4355 4357 % f)
4356 4358 else:
4357 4359 runconclude = True
4358 4360 continue
4359 4361
4360 4362 if mark:
4361 4363 ms.mark(f, "r")
4362 4364 elif unmark:
4363 4365 ms.mark(f, "u")
4364 4366 else:
4365 4367 # backup pre-resolve (merge uses .orig for its own purposes)
4366 4368 a = repo.wjoin(f)
4367 4369 try:
4368 4370 util.copyfile(a, a + ".resolve")
4369 4371 except (IOError, OSError) as inst:
4370 4372 if inst.errno != errno.ENOENT:
4371 4373 raise
4372 4374
4373 4375 try:
4374 4376 # preresolve file
4375 4377 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4376 4378 'resolve')
4377 4379 complete, r = ms.preresolve(f, wctx)
4378 4380 if not complete:
4379 4381 tocomplete.append(f)
4380 4382 elif r:
4381 4383 ret = 1
4382 4384 finally:
4383 4385 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4384 4386 ms.commit()
4385 4387
4386 4388 # replace filemerge's .orig file with our resolve file, but only
4387 4389 # for merges that are complete
4388 4390 if complete:
4389 4391 try:
4390 4392 util.rename(a + ".resolve",
4391 4393 scmutil.origpath(ui, repo, a))
4392 4394 except OSError as inst:
4393 4395 if inst.errno != errno.ENOENT:
4394 4396 raise
4395 4397
4396 4398 for f in tocomplete:
4397 4399 try:
4398 4400 # resolve file
4399 4401 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4400 4402 'resolve')
4401 4403 r = ms.resolve(f, wctx)
4402 4404 if r:
4403 4405 ret = 1
4404 4406 finally:
4405 4407 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4406 4408 ms.commit()
4407 4409
4408 4410 # replace filemerge's .orig file with our resolve file
4409 4411 a = repo.wjoin(f)
4410 4412 try:
4411 4413 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4412 4414 except OSError as inst:
4413 4415 if inst.errno != errno.ENOENT:
4414 4416 raise
4415 4417
4416 4418 ms.commit()
4417 4419 ms.recordactions()
4418 4420
4419 4421 if not didwork and pats:
4420 4422 hint = None
4421 4423 if not any([p for p in pats if p.find(':') >= 0]):
4422 4424 pats = ['path:%s' % p for p in pats]
4423 4425 m = scmutil.match(wctx, pats, opts)
4424 4426 for f in ms:
4425 4427 if not m(f):
4426 4428 continue
4427 4429 flags = ''.join(['-%s ' % o[0] for o in flaglist
4428 4430 if opts.get(o)])
4429 4431 hint = _("(try: hg resolve %s%s)\n") % (
4430 4432 flags,
4431 4433 ' '.join(pats))
4432 4434 break
4433 4435 ui.warn(_("arguments do not match paths that need resolving\n"))
4434 4436 if hint:
4435 4437 ui.warn(hint)
4436 4438 elif ms.mergedriver and ms.mdstate() != 's':
4437 4439 # run conclude step when either a driver-resolved file is requested
4438 4440 # or there are no driver-resolved files
4439 4441 # we can't use 'ret' to determine whether any files are unresolved
4440 4442 # because we might not have tried to resolve some
4441 4443 if ((runconclude or not list(ms.driverresolved()))
4442 4444 and not list(ms.unresolved())):
4443 4445 proceed = mergemod.driverconclude(repo, ms, wctx)
4444 4446 ms.commit()
4445 4447 if not proceed:
4446 4448 return 1
4447 4449
4448 4450 # Nudge users into finishing an unfinished operation
4449 4451 unresolvedf = list(ms.unresolved())
4450 4452 driverresolvedf = list(ms.driverresolved())
4451 4453 if not unresolvedf and not driverresolvedf:
4452 4454 ui.status(_('(no more unresolved files)\n'))
4453 4455 cmdutil.checkafterresolved(repo)
4454 4456 elif not unresolvedf:
4455 4457 ui.status(_('(no more unresolved files -- '
4456 4458 'run "hg resolve --all" to conclude)\n'))
4457 4459
4458 4460 return ret
4459 4461
4460 4462 @command('revert',
4461 4463 [('a', 'all', None, _('revert all changes when no arguments given')),
4462 4464 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4463 4465 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4464 4466 ('C', 'no-backup', None, _('do not save backup copies of files')),
4465 4467 ('i', 'interactive', None,
4466 4468 _('interactively select the changes (EXPERIMENTAL)')),
4467 4469 ] + walkopts + dryrunopts,
4468 4470 _('[OPTION]... [-r REV] [NAME]...'))
4469 4471 def revert(ui, repo, *pats, **opts):
4470 4472 """restore files to their checkout state
4471 4473
4472 4474 .. note::
4473 4475
4474 4476 To check out earlier revisions, you should use :hg:`update REV`.
4475 4477 To cancel an uncommitted merge (and lose your changes),
4476 4478 use :hg:`update --clean .`.
4477 4479
4478 4480 With no revision specified, revert the specified files or directories
4479 4481 to the contents they had in the parent of the working directory.
4480 4482 This restores the contents of files to an unmodified
4481 4483 state and unschedules adds, removes, copies, and renames. If the
4482 4484 working directory has two parents, you must explicitly specify a
4483 4485 revision.
4484 4486
4485 4487 Using the -r/--rev or -d/--date options, revert the given files or
4486 4488 directories to their states as of a specific revision. Because
4487 4489 revert does not change the working directory parents, this will
4488 4490 cause these files to appear modified. This can be helpful to "back
4489 4491 out" some or all of an earlier change. See :hg:`backout` for a
4490 4492 related method.
4491 4493
4492 4494 Modified files are saved with a .orig suffix before reverting.
4493 4495 To disable these backups, use --no-backup. It is possible to store
4494 4496 the backup files in a custom directory relative to the root of the
4495 4497 repository by setting the ``ui.origbackuppath`` configuration
4496 4498 option.
4497 4499
4498 4500 See :hg:`help dates` for a list of formats valid for -d/--date.
4499 4501
4500 4502 See :hg:`help backout` for a way to reverse the effect of an
4501 4503 earlier changeset.
4502 4504
4503 4505 Returns 0 on success.
4504 4506 """
4505 4507
4506 4508 if opts.get("date"):
4507 4509 if opts.get("rev"):
4508 4510 raise error.Abort(_("you can't specify a revision and a date"))
4509 4511 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4510 4512
4511 4513 parent, p2 = repo.dirstate.parents()
4512 4514 if not opts.get('rev') and p2 != nullid:
4513 4515 # revert after merge is a trap for new users (issue2915)
4514 4516 raise error.Abort(_('uncommitted merge with no revision specified'),
4515 4517 hint=_("use 'hg update' or see 'hg help revert'"))
4516 4518
4517 4519 ctx = scmutil.revsingle(repo, opts.get('rev'))
4518 4520
4519 4521 if (not (pats or opts.get('include') or opts.get('exclude') or
4520 4522 opts.get('all') or opts.get('interactive'))):
4521 4523 msg = _("no files or directories specified")
4522 4524 if p2 != nullid:
4523 4525 hint = _("uncommitted merge, use --all to discard all changes,"
4524 4526 " or 'hg update -C .' to abort the merge")
4525 4527 raise error.Abort(msg, hint=hint)
4526 4528 dirty = any(repo.status())
4527 4529 node = ctx.node()
4528 4530 if node != parent:
4529 4531 if dirty:
4530 4532 hint = _("uncommitted changes, use --all to discard all"
4531 4533 " changes, or 'hg update %s' to update") % ctx.rev()
4532 4534 else:
4533 4535 hint = _("use --all to revert all files,"
4534 4536 " or 'hg update %s' to update") % ctx.rev()
4535 4537 elif dirty:
4536 4538 hint = _("uncommitted changes, use --all to discard all changes")
4537 4539 else:
4538 4540 hint = _("use --all to revert all files")
4539 4541 raise error.Abort(msg, hint=hint)
4540 4542
4541 4543 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4542 4544
4543 4545 @command('rollback', dryrunopts +
4544 4546 [('f', 'force', False, _('ignore safety measures'))])
4545 4547 def rollback(ui, repo, **opts):
4546 4548 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4547 4549
4548 4550 Please use :hg:`commit --amend` instead of rollback to correct
4549 4551 mistakes in the last commit.
4550 4552
4551 4553 This command should be used with care. There is only one level of
4552 4554 rollback, and there is no way to undo a rollback. It will also
4553 4555 restore the dirstate at the time of the last transaction, losing
4554 4556 any dirstate changes since that time. This command does not alter
4555 4557 the working directory.
4556 4558
4557 4559 Transactions are used to encapsulate the effects of all commands
4558 4560 that create new changesets or propagate existing changesets into a
4559 4561 repository.
4560 4562
4561 4563 .. container:: verbose
4562 4564
4563 4565 For example, the following commands are transactional, and their
4564 4566 effects can be rolled back:
4565 4567
4566 4568 - commit
4567 4569 - import
4568 4570 - pull
4569 4571 - push (with this repository as the destination)
4570 4572 - unbundle
4571 4573
4572 4574 To avoid permanent data loss, rollback will refuse to rollback a
4573 4575 commit transaction if it isn't checked out. Use --force to
4574 4576 override this protection.
4575 4577
4576 4578 The rollback command can be entirely disabled by setting the
4577 4579 ``ui.rollback`` configuration setting to false. If you're here
4578 4580 because you want to use rollback and it's disabled, you can
4579 4581 re-enable the command by setting ``ui.rollback`` to true.
4580 4582
4581 4583 This command is not intended for use on public repositories. Once
4582 4584 changes are visible for pull by other users, rolling a transaction
4583 4585 back locally is ineffective (someone else may already have pulled
4584 4586 the changes). Furthermore, a race is possible with readers of the
4585 4587 repository; for example an in-progress pull from the repository
4586 4588 may fail if a rollback is performed.
4587 4589
4588 4590 Returns 0 on success, 1 if no rollback data is available.
4589 4591 """
4590 4592 if not ui.configbool('ui', 'rollback', True):
4591 4593 raise error.Abort(_('rollback is disabled because it is unsafe'),
4592 4594 hint=('see `hg help -v rollback` for information'))
4593 4595 return repo.rollback(dryrun=opts.get(r'dry_run'),
4594 4596 force=opts.get(r'force'))
4595 4597
4596 4598 @command('root', [])
4597 4599 def root(ui, repo):
4598 4600 """print the root (top) of the current working directory
4599 4601
4600 4602 Print the root directory of the current repository.
4601 4603
4602 4604 Returns 0 on success.
4603 4605 """
4604 4606 ui.write(repo.root + "\n")
4605 4607
4606 4608 @command('^serve',
4607 4609 [('A', 'accesslog', '', _('name of access log file to write to'),
4608 4610 _('FILE')),
4609 4611 ('d', 'daemon', None, _('run server in background')),
4610 4612 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4611 4613 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4612 4614 # use string type, then we can check if something was passed
4613 4615 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4614 4616 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4615 4617 _('ADDR')),
4616 4618 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4617 4619 _('PREFIX')),
4618 4620 ('n', 'name', '',
4619 4621 _('name to show in web pages (default: working directory)'), _('NAME')),
4620 4622 ('', 'web-conf', '',
4621 4623 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4622 4624 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4623 4625 _('FILE')),
4624 4626 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4625 4627 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4626 4628 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4627 4629 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4628 4630 ('', 'style', '', _('template style to use'), _('STYLE')),
4629 4631 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4630 4632 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4631 4633 + subrepoopts,
4632 4634 _('[OPTION]...'),
4633 4635 optionalrepo=True)
4634 4636 def serve(ui, repo, **opts):
4635 4637 """start stand-alone webserver
4636 4638
4637 4639 Start a local HTTP repository browser and pull server. You can use
4638 4640 this for ad-hoc sharing and browsing of repositories. It is
4639 4641 recommended to use a real web server to serve a repository for
4640 4642 longer periods of time.
4641 4643
4642 4644 Please note that the server does not implement access control.
4643 4645 This means that, by default, anybody can read from the server and
4644 4646 nobody can write to it by default. Set the ``web.allow_push``
4645 4647 option to ``*`` to allow everybody to push to the server. You
4646 4648 should use a real web server if you need to authenticate users.
4647 4649
4648 4650 By default, the server logs accesses to stdout and errors to
4649 4651 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4650 4652 files.
4651 4653
4652 4654 To have the server choose a free port number to listen on, specify
4653 4655 a port number of 0; in this case, the server will print the port
4654 4656 number it uses.
4655 4657
4656 4658 Returns 0 on success.
4657 4659 """
4658 4660
4659 4661 opts = pycompat.byteskwargs(opts)
4660 4662 if opts["stdio"] and opts["cmdserver"]:
4661 4663 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4662 4664
4663 4665 if opts["stdio"]:
4664 4666 if repo is None:
4665 4667 raise error.RepoError(_("there is no Mercurial repository here"
4666 4668 " (.hg not found)"))
4667 4669 s = sshserver.sshserver(ui, repo)
4668 4670 s.serve_forever()
4669 4671
4670 4672 service = server.createservice(ui, repo, opts)
4671 4673 return server.runservice(opts, initfn=service.init, runfn=service.run)
4672 4674
4673 4675 @command('^status|st',
4674 4676 [('A', 'all', None, _('show status of all files')),
4675 4677 ('m', 'modified', None, _('show only modified files')),
4676 4678 ('a', 'added', None, _('show only added files')),
4677 4679 ('r', 'removed', None, _('show only removed files')),
4678 4680 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4679 4681 ('c', 'clean', None, _('show only files without changes')),
4680 4682 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4681 4683 ('i', 'ignored', None, _('show only ignored files')),
4682 4684 ('n', 'no-status', None, _('hide status prefix')),
4683 4685 ('C', 'copies', None, _('show source of copied files')),
4684 4686 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4685 4687 ('', 'rev', [], _('show difference from revision'), _('REV')),
4686 4688 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4687 4689 ] + walkopts + subrepoopts + formatteropts,
4688 4690 _('[OPTION]... [FILE]...'),
4689 4691 inferrepo=True)
4690 4692 def status(ui, repo, *pats, **opts):
4691 4693 """show changed files in the working directory
4692 4694
4693 4695 Show status of files in the repository. If names are given, only
4694 4696 files that match are shown. Files that are clean or ignored or
4695 4697 the source of a copy/move operation, are not listed unless
4696 4698 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4697 4699 Unless options described with "show only ..." are given, the
4698 4700 options -mardu are used.
4699 4701
4700 4702 Option -q/--quiet hides untracked (unknown and ignored) files
4701 4703 unless explicitly requested with -u/--unknown or -i/--ignored.
4702 4704
4703 4705 .. note::
4704 4706
4705 4707 :hg:`status` may appear to disagree with diff if permissions have
4706 4708 changed or a merge has occurred. The standard diff format does
4707 4709 not report permission changes and diff only reports changes
4708 4710 relative to one merge parent.
4709 4711
4710 4712 If one revision is given, it is used as the base revision.
4711 4713 If two revisions are given, the differences between them are
4712 4714 shown. The --change option can also be used as a shortcut to list
4713 4715 the changed files of a revision from its first parent.
4714 4716
4715 4717 The codes used to show the status of files are::
4716 4718
4717 4719 M = modified
4718 4720 A = added
4719 4721 R = removed
4720 4722 C = clean
4721 4723 ! = missing (deleted by non-hg command, but still tracked)
4722 4724 ? = not tracked
4723 4725 I = ignored
4724 4726 = origin of the previous file (with --copies)
4725 4727
4726 4728 .. container:: verbose
4727 4729
4728 4730 Examples:
4729 4731
4730 4732 - show changes in the working directory relative to a
4731 4733 changeset::
4732 4734
4733 4735 hg status --rev 9353
4734 4736
4735 4737 - show changes in the working directory relative to the
4736 4738 current directory (see :hg:`help patterns` for more information)::
4737 4739
4738 4740 hg status re:
4739 4741
4740 4742 - show all changes including copies in an existing changeset::
4741 4743
4742 4744 hg status --copies --change 9353
4743 4745
4744 4746 - get a NUL separated list of added files, suitable for xargs::
4745 4747
4746 4748 hg status -an0
4747 4749
4748 4750 Returns 0 on success.
4749 4751 """
4750 4752
4751 4753 opts = pycompat.byteskwargs(opts)
4752 4754 revs = opts.get('rev')
4753 4755 change = opts.get('change')
4754 4756
4755 4757 if revs and change:
4756 4758 msg = _('cannot specify --rev and --change at the same time')
4757 4759 raise error.Abort(msg)
4758 4760 elif change:
4759 4761 node2 = scmutil.revsingle(repo, change, None).node()
4760 4762 node1 = repo[node2].p1().node()
4761 4763 else:
4762 4764 node1, node2 = scmutil.revpair(repo, revs)
4763 4765
4764 4766 if pats or ui.configbool('commands', 'status.relative'):
4765 4767 cwd = repo.getcwd()
4766 4768 else:
4767 4769 cwd = ''
4768 4770
4769 4771 if opts.get('print0'):
4770 4772 end = '\0'
4771 4773 else:
4772 4774 end = '\n'
4773 4775 copy = {}
4774 4776 states = 'modified added removed deleted unknown ignored clean'.split()
4775 4777 show = [k for k in states if opts.get(k)]
4776 4778 if opts.get('all'):
4777 4779 show += ui.quiet and (states[:4] + ['clean']) or states
4778 4780 if not show:
4779 4781 if ui.quiet:
4780 4782 show = states[:4]
4781 4783 else:
4782 4784 show = states[:5]
4783 4785
4784 4786 m = scmutil.match(repo[node2], pats, opts)
4785 4787 stat = repo.status(node1, node2, m,
4786 4788 'ignored' in show, 'clean' in show, 'unknown' in show,
4787 4789 opts.get('subrepos'))
4788 4790 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4789 4791
4790 4792 if (opts.get('all') or opts.get('copies')
4791 4793 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4792 4794 copy = copies.pathcopies(repo[node1], repo[node2], m)
4793 4795
4794 4796 ui.pager('status')
4795 4797 fm = ui.formatter('status', opts)
4796 4798 fmt = '%s' + end
4797 4799 showchar = not opts.get('no_status')
4798 4800
4799 4801 for state, char, files in changestates:
4800 4802 if state in show:
4801 4803 label = 'status.' + state
4802 4804 for f in files:
4803 4805 fm.startitem()
4804 4806 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4805 4807 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4806 4808 if f in copy:
4807 4809 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4808 4810 label='status.copied')
4809 4811 fm.end()
4810 4812
4811 4813 @command('^summary|sum',
4812 4814 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4813 4815 def summary(ui, repo, **opts):
4814 4816 """summarize working directory state
4815 4817
4816 4818 This generates a brief summary of the working directory state,
4817 4819 including parents, branch, commit status, phase and available updates.
4818 4820
4819 4821 With the --remote option, this will check the default paths for
4820 4822 incoming and outgoing changes. This can be time-consuming.
4821 4823
4822 4824 Returns 0 on success.
4823 4825 """
4824 4826
4825 4827 opts = pycompat.byteskwargs(opts)
4826 4828 ui.pager('summary')
4827 4829 ctx = repo[None]
4828 4830 parents = ctx.parents()
4829 4831 pnode = parents[0].node()
4830 4832 marks = []
4831 4833
4832 4834 ms = None
4833 4835 try:
4834 4836 ms = mergemod.mergestate.read(repo)
4835 4837 except error.UnsupportedMergeRecords as e:
4836 4838 s = ' '.join(e.recordtypes)
4837 4839 ui.warn(
4838 4840 _('warning: merge state has unsupported record types: %s\n') % s)
4839 4841 unresolved = 0
4840 4842 else:
4841 4843 unresolved = [f for f in ms if ms[f] == 'u']
4842 4844
4843 4845 for p in parents:
4844 4846 # label with log.changeset (instead of log.parent) since this
4845 4847 # shows a working directory parent *changeset*:
4846 4848 # i18n: column positioning for "hg summary"
4847 4849 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4848 4850 label=cmdutil._changesetlabels(p))
4849 4851 ui.write(' '.join(p.tags()), label='log.tag')
4850 4852 if p.bookmarks():
4851 4853 marks.extend(p.bookmarks())
4852 4854 if p.rev() == -1:
4853 4855 if not len(repo):
4854 4856 ui.write(_(' (empty repository)'))
4855 4857 else:
4856 4858 ui.write(_(' (no revision checked out)'))
4857 4859 if p.obsolete():
4858 4860 ui.write(_(' (obsolete)'))
4859 4861 if p.troubled():
4860 4862 ui.write(' ('
4861 4863 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4862 4864 for trouble in p.troubles())
4863 4865 + ')')
4864 4866 ui.write('\n')
4865 4867 if p.description():
4866 4868 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4867 4869 label='log.summary')
4868 4870
4869 4871 branch = ctx.branch()
4870 4872 bheads = repo.branchheads(branch)
4871 4873 # i18n: column positioning for "hg summary"
4872 4874 m = _('branch: %s\n') % branch
4873 4875 if branch != 'default':
4874 4876 ui.write(m, label='log.branch')
4875 4877 else:
4876 4878 ui.status(m, label='log.branch')
4877 4879
4878 4880 if marks:
4879 4881 active = repo._activebookmark
4880 4882 # i18n: column positioning for "hg summary"
4881 4883 ui.write(_('bookmarks:'), label='log.bookmark')
4882 4884 if active is not None:
4883 4885 if active in marks:
4884 4886 ui.write(' *' + active, label=activebookmarklabel)
4885 4887 marks.remove(active)
4886 4888 else:
4887 4889 ui.write(' [%s]' % active, label=activebookmarklabel)
4888 4890 for m in marks:
4889 4891 ui.write(' ' + m, label='log.bookmark')
4890 4892 ui.write('\n', label='log.bookmark')
4891 4893
4892 4894 status = repo.status(unknown=True)
4893 4895
4894 4896 c = repo.dirstate.copies()
4895 4897 copied, renamed = [], []
4896 4898 for d, s in c.iteritems():
4897 4899 if s in status.removed:
4898 4900 status.removed.remove(s)
4899 4901 renamed.append(d)
4900 4902 else:
4901 4903 copied.append(d)
4902 4904 if d in status.added:
4903 4905 status.added.remove(d)
4904 4906
4905 4907 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4906 4908
4907 4909 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4908 4910 (ui.label(_('%d added'), 'status.added'), status.added),
4909 4911 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4910 4912 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4911 4913 (ui.label(_('%d copied'), 'status.copied'), copied),
4912 4914 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4913 4915 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4914 4916 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4915 4917 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4916 4918 t = []
4917 4919 for l, s in labels:
4918 4920 if s:
4919 4921 t.append(l % len(s))
4920 4922
4921 4923 t = ', '.join(t)
4922 4924 cleanworkdir = False
4923 4925
4924 4926 if repo.vfs.exists('graftstate'):
4925 4927 t += _(' (graft in progress)')
4926 4928 if repo.vfs.exists('updatestate'):
4927 4929 t += _(' (interrupted update)')
4928 4930 elif len(parents) > 1:
4929 4931 t += _(' (merge)')
4930 4932 elif branch != parents[0].branch():
4931 4933 t += _(' (new branch)')
4932 4934 elif (parents[0].closesbranch() and
4933 4935 pnode in repo.branchheads(branch, closed=True)):
4934 4936 t += _(' (head closed)')
4935 4937 elif not (status.modified or status.added or status.removed or renamed or
4936 4938 copied or subs):
4937 4939 t += _(' (clean)')
4938 4940 cleanworkdir = True
4939 4941 elif pnode not in bheads:
4940 4942 t += _(' (new branch head)')
4941 4943
4942 4944 if parents:
4943 4945 pendingphase = max(p.phase() for p in parents)
4944 4946 else:
4945 4947 pendingphase = phases.public
4946 4948
4947 4949 if pendingphase > phases.newcommitphase(ui):
4948 4950 t += ' (%s)' % phases.phasenames[pendingphase]
4949 4951
4950 4952 if cleanworkdir:
4951 4953 # i18n: column positioning for "hg summary"
4952 4954 ui.status(_('commit: %s\n') % t.strip())
4953 4955 else:
4954 4956 # i18n: column positioning for "hg summary"
4955 4957 ui.write(_('commit: %s\n') % t.strip())
4956 4958
4957 4959 # all ancestors of branch heads - all ancestors of parent = new csets
4958 4960 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4959 4961 bheads))
4960 4962
4961 4963 if new == 0:
4962 4964 # i18n: column positioning for "hg summary"
4963 4965 ui.status(_('update: (current)\n'))
4964 4966 elif pnode not in bheads:
4965 4967 # i18n: column positioning for "hg summary"
4966 4968 ui.write(_('update: %d new changesets (update)\n') % new)
4967 4969 else:
4968 4970 # i18n: column positioning for "hg summary"
4969 4971 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4970 4972 (new, len(bheads)))
4971 4973
4972 4974 t = []
4973 4975 draft = len(repo.revs('draft()'))
4974 4976 if draft:
4975 4977 t.append(_('%d draft') % draft)
4976 4978 secret = len(repo.revs('secret()'))
4977 4979 if secret:
4978 4980 t.append(_('%d secret') % secret)
4979 4981
4980 4982 if draft or secret:
4981 4983 ui.status(_('phases: %s\n') % ', '.join(t))
4982 4984
4983 4985 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4984 4986 for trouble in ("unstable", "divergent", "bumped"):
4985 4987 numtrouble = len(repo.revs(trouble + "()"))
4986 4988 # We write all the possibilities to ease translation
4987 4989 troublemsg = {
4988 4990 "unstable": _("unstable: %d changesets"),
4989 4991 "divergent": _("divergent: %d changesets"),
4990 4992 "bumped": _("bumped: %d changesets"),
4991 4993 }
4992 4994 if numtrouble > 0:
4993 4995 ui.status(troublemsg[trouble] % numtrouble + "\n")
4994 4996
4995 4997 cmdutil.summaryhooks(ui, repo)
4996 4998
4997 4999 if opts.get('remote'):
4998 5000 needsincoming, needsoutgoing = True, True
4999 5001 else:
5000 5002 needsincoming, needsoutgoing = False, False
5001 5003 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5002 5004 if i:
5003 5005 needsincoming = True
5004 5006 if o:
5005 5007 needsoutgoing = True
5006 5008 if not needsincoming and not needsoutgoing:
5007 5009 return
5008 5010
5009 5011 def getincoming():
5010 5012 source, branches = hg.parseurl(ui.expandpath('default'))
5011 5013 sbranch = branches[0]
5012 5014 try:
5013 5015 other = hg.peer(repo, {}, source)
5014 5016 except error.RepoError:
5015 5017 if opts.get('remote'):
5016 5018 raise
5017 5019 return source, sbranch, None, None, None
5018 5020 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5019 5021 if revs:
5020 5022 revs = [other.lookup(rev) for rev in revs]
5021 5023 ui.debug('comparing with %s\n' % util.hidepassword(source))
5022 5024 repo.ui.pushbuffer()
5023 5025 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5024 5026 repo.ui.popbuffer()
5025 5027 return source, sbranch, other, commoninc, commoninc[1]
5026 5028
5027 5029 if needsincoming:
5028 5030 source, sbranch, sother, commoninc, incoming = getincoming()
5029 5031 else:
5030 5032 source = sbranch = sother = commoninc = incoming = None
5031 5033
5032 5034 def getoutgoing():
5033 5035 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5034 5036 dbranch = branches[0]
5035 5037 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5036 5038 if source != dest:
5037 5039 try:
5038 5040 dother = hg.peer(repo, {}, dest)
5039 5041 except error.RepoError:
5040 5042 if opts.get('remote'):
5041 5043 raise
5042 5044 return dest, dbranch, None, None
5043 5045 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5044 5046 elif sother is None:
5045 5047 # there is no explicit destination peer, but source one is invalid
5046 5048 return dest, dbranch, None, None
5047 5049 else:
5048 5050 dother = sother
5049 5051 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5050 5052 common = None
5051 5053 else:
5052 5054 common = commoninc
5053 5055 if revs:
5054 5056 revs = [repo.lookup(rev) for rev in revs]
5055 5057 repo.ui.pushbuffer()
5056 5058 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5057 5059 commoninc=common)
5058 5060 repo.ui.popbuffer()
5059 5061 return dest, dbranch, dother, outgoing
5060 5062
5061 5063 if needsoutgoing:
5062 5064 dest, dbranch, dother, outgoing = getoutgoing()
5063 5065 else:
5064 5066 dest = dbranch = dother = outgoing = None
5065 5067
5066 5068 if opts.get('remote'):
5067 5069 t = []
5068 5070 if incoming:
5069 5071 t.append(_('1 or more incoming'))
5070 5072 o = outgoing.missing
5071 5073 if o:
5072 5074 t.append(_('%d outgoing') % len(o))
5073 5075 other = dother or sother
5074 5076 if 'bookmarks' in other.listkeys('namespaces'):
5075 5077 counts = bookmarks.summary(repo, other)
5076 5078 if counts[0] > 0:
5077 5079 t.append(_('%d incoming bookmarks') % counts[0])
5078 5080 if counts[1] > 0:
5079 5081 t.append(_('%d outgoing bookmarks') % counts[1])
5080 5082
5081 5083 if t:
5082 5084 # i18n: column positioning for "hg summary"
5083 5085 ui.write(_('remote: %s\n') % (', '.join(t)))
5084 5086 else:
5085 5087 # i18n: column positioning for "hg summary"
5086 5088 ui.status(_('remote: (synced)\n'))
5087 5089
5088 5090 cmdutil.summaryremotehooks(ui, repo, opts,
5089 5091 ((source, sbranch, sother, commoninc),
5090 5092 (dest, dbranch, dother, outgoing)))
5091 5093
5092 5094 @command('tag',
5093 5095 [('f', 'force', None, _('force tag')),
5094 5096 ('l', 'local', None, _('make the tag local')),
5095 5097 ('r', 'rev', '', _('revision to tag'), _('REV')),
5096 5098 ('', 'remove', None, _('remove a tag')),
5097 5099 # -l/--local is already there, commitopts cannot be used
5098 5100 ('e', 'edit', None, _('invoke editor on commit messages')),
5099 5101 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5100 5102 ] + commitopts2,
5101 5103 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5102 5104 def tag(ui, repo, name1, *names, **opts):
5103 5105 """add one or more tags for the current or given revision
5104 5106
5105 5107 Name a particular revision using <name>.
5106 5108
5107 5109 Tags are used to name particular revisions of the repository and are
5108 5110 very useful to compare different revisions, to go back to significant
5109 5111 earlier versions or to mark branch points as releases, etc. Changing
5110 5112 an existing tag is normally disallowed; use -f/--force to override.
5111 5113
5112 5114 If no revision is given, the parent of the working directory is
5113 5115 used.
5114 5116
5115 5117 To facilitate version control, distribution, and merging of tags,
5116 5118 they are stored as a file named ".hgtags" which is managed similarly
5117 5119 to other project files and can be hand-edited if necessary. This
5118 5120 also means that tagging creates a new commit. The file
5119 5121 ".hg/localtags" is used for local tags (not shared among
5120 5122 repositories).
5121 5123
5122 5124 Tag commits are usually made at the head of a branch. If the parent
5123 5125 of the working directory is not a branch head, :hg:`tag` aborts; use
5124 5126 -f/--force to force the tag commit to be based on a non-head
5125 5127 changeset.
5126 5128
5127 5129 See :hg:`help dates` for a list of formats valid for -d/--date.
5128 5130
5129 5131 Since tag names have priority over branch names during revision
5130 5132 lookup, using an existing branch name as a tag name is discouraged.
5131 5133
5132 5134 Returns 0 on success.
5133 5135 """
5134 5136 opts = pycompat.byteskwargs(opts)
5135 5137 wlock = lock = None
5136 5138 try:
5137 5139 wlock = repo.wlock()
5138 5140 lock = repo.lock()
5139 5141 rev_ = "."
5140 5142 names = [t.strip() for t in (name1,) + names]
5141 5143 if len(names) != len(set(names)):
5142 5144 raise error.Abort(_('tag names must be unique'))
5143 5145 for n in names:
5144 5146 scmutil.checknewlabel(repo, n, 'tag')
5145 5147 if not n:
5146 5148 raise error.Abort(_('tag names cannot consist entirely of '
5147 5149 'whitespace'))
5148 5150 if opts.get('rev') and opts.get('remove'):
5149 5151 raise error.Abort(_("--rev and --remove are incompatible"))
5150 5152 if opts.get('rev'):
5151 5153 rev_ = opts['rev']
5152 5154 message = opts.get('message')
5153 5155 if opts.get('remove'):
5154 5156 if opts.get('local'):
5155 5157 expectedtype = 'local'
5156 5158 else:
5157 5159 expectedtype = 'global'
5158 5160
5159 5161 for n in names:
5160 5162 if not repo.tagtype(n):
5161 5163 raise error.Abort(_("tag '%s' does not exist") % n)
5162 5164 if repo.tagtype(n) != expectedtype:
5163 5165 if expectedtype == 'global':
5164 5166 raise error.Abort(_("tag '%s' is not a global tag") % n)
5165 5167 else:
5166 5168 raise error.Abort(_("tag '%s' is not a local tag") % n)
5167 5169 rev_ = 'null'
5168 5170 if not message:
5169 5171 # we don't translate commit messages
5170 5172 message = 'Removed tag %s' % ', '.join(names)
5171 5173 elif not opts.get('force'):
5172 5174 for n in names:
5173 5175 if n in repo.tags():
5174 5176 raise error.Abort(_("tag '%s' already exists "
5175 5177 "(use -f to force)") % n)
5176 5178 if not opts.get('local'):
5177 5179 p1, p2 = repo.dirstate.parents()
5178 5180 if p2 != nullid:
5179 5181 raise error.Abort(_('uncommitted merge'))
5180 5182 bheads = repo.branchheads()
5181 5183 if not opts.get('force') and bheads and p1 not in bheads:
5182 5184 raise error.Abort(_('working directory is not at a branch head '
5183 5185 '(use -f to force)'))
5184 5186 r = scmutil.revsingle(repo, rev_).node()
5185 5187
5186 5188 if not message:
5187 5189 # we don't translate commit messages
5188 5190 message = ('Added tag %s for changeset %s' %
5189 5191 (', '.join(names), short(r)))
5190 5192
5191 5193 date = opts.get('date')
5192 5194 if date:
5193 5195 date = util.parsedate(date)
5194 5196
5195 5197 if opts.get('remove'):
5196 5198 editform = 'tag.remove'
5197 5199 else:
5198 5200 editform = 'tag.add'
5199 5201 editor = cmdutil.getcommiteditor(editform=editform,
5200 5202 **pycompat.strkwargs(opts))
5201 5203
5202 5204 # don't allow tagging the null rev
5203 5205 if (not opts.get('remove') and
5204 5206 scmutil.revsingle(repo, rev_).rev() == nullrev):
5205 5207 raise error.Abort(_("cannot tag null revision"))
5206 5208
5207 5209 tagsmod.tag(repo, names, r, message, opts.get('local'),
5208 5210 opts.get('user'), date, editor=editor)
5209 5211 finally:
5210 5212 release(lock, wlock)
5211 5213
5212 5214 @command('tags', formatteropts, '')
5213 5215 def tags(ui, repo, **opts):
5214 5216 """list repository tags
5215 5217
5216 5218 This lists both regular and local tags. When the -v/--verbose
5217 5219 switch is used, a third column "local" is printed for local tags.
5218 5220 When the -q/--quiet switch is used, only the tag name is printed.
5219 5221
5220 5222 Returns 0 on success.
5221 5223 """
5222 5224
5223 5225 opts = pycompat.byteskwargs(opts)
5224 5226 ui.pager('tags')
5225 5227 fm = ui.formatter('tags', opts)
5226 5228 hexfunc = fm.hexfunc
5227 5229 tagtype = ""
5228 5230
5229 5231 for t, n in reversed(repo.tagslist()):
5230 5232 hn = hexfunc(n)
5231 5233 label = 'tags.normal'
5232 5234 tagtype = ''
5233 5235 if repo.tagtype(t) == 'local':
5234 5236 label = 'tags.local'
5235 5237 tagtype = 'local'
5236 5238
5237 5239 fm.startitem()
5238 5240 fm.write('tag', '%s', t, label=label)
5239 5241 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5240 5242 fm.condwrite(not ui.quiet, 'rev node', fmt,
5241 5243 repo.changelog.rev(n), hn, label=label)
5242 5244 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5243 5245 tagtype, label=label)
5244 5246 fm.plain('\n')
5245 5247 fm.end()
5246 5248
5247 5249 @command('tip',
5248 5250 [('p', 'patch', None, _('show patch')),
5249 5251 ('g', 'git', None, _('use git extended diff format')),
5250 5252 ] + templateopts,
5251 5253 _('[-p] [-g]'))
5252 5254 def tip(ui, repo, **opts):
5253 5255 """show the tip revision (DEPRECATED)
5254 5256
5255 5257 The tip revision (usually just called the tip) is the changeset
5256 5258 most recently added to the repository (and therefore the most
5257 5259 recently changed head).
5258 5260
5259 5261 If you have just made a commit, that commit will be the tip. If
5260 5262 you have just pulled changes from another repository, the tip of
5261 5263 that repository becomes the current tip. The "tip" tag is special
5262 5264 and cannot be renamed or assigned to a different changeset.
5263 5265
5264 5266 This command is deprecated, please use :hg:`heads` instead.
5265 5267
5266 5268 Returns 0 on success.
5267 5269 """
5268 5270 opts = pycompat.byteskwargs(opts)
5269 5271 displayer = cmdutil.show_changeset(ui, repo, opts)
5270 5272 displayer.show(repo['tip'])
5271 5273 displayer.close()
5272 5274
5273 5275 @command('unbundle',
5274 5276 [('u', 'update', None,
5275 5277 _('update to new branch head if changesets were unbundled'))],
5276 5278 _('[-u] FILE...'))
5277 5279 def unbundle(ui, repo, fname1, *fnames, **opts):
5278 5280 """apply one or more bundle files
5279 5281
5280 5282 Apply one or more bundle files generated by :hg:`bundle`.
5281 5283
5282 5284 Returns 0 on success, 1 if an update has unresolved files.
5283 5285 """
5284 5286 fnames = (fname1,) + fnames
5285 5287
5286 5288 with repo.lock():
5287 5289 for fname in fnames:
5288 5290 f = hg.openpath(ui, fname)
5289 5291 gen = exchange.readbundle(ui, f, fname)
5290 5292 if isinstance(gen, bundle2.unbundle20):
5291 5293 tr = repo.transaction('unbundle')
5292 5294 try:
5293 5295 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5294 5296 url='bundle:' + fname)
5295 5297 tr.close()
5296 5298 except error.BundleUnknownFeatureError as exc:
5297 5299 raise error.Abort(_('%s: unknown bundle feature, %s')
5298 5300 % (fname, exc),
5299 5301 hint=_("see https://mercurial-scm.org/"
5300 5302 "wiki/BundleFeature for more "
5301 5303 "information"))
5302 5304 finally:
5303 5305 if tr:
5304 5306 tr.release()
5305 5307 changes = [r.get('return', 0)
5306 5308 for r in op.records['changegroup']]
5307 5309 modheads = changegroup.combineresults(changes)
5308 5310 elif isinstance(gen, streamclone.streamcloneapplier):
5309 5311 raise error.Abort(
5310 5312 _('packed bundles cannot be applied with '
5311 5313 '"hg unbundle"'),
5312 5314 hint=_('use "hg debugapplystreamclonebundle"'))
5313 5315 else:
5314 5316 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5315 5317
5316 5318 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5317 5319
5318 5320 @command('^update|up|checkout|co',
5319 5321 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5320 5322 ('c', 'check', None, _('require clean working directory')),
5321 5323 ('m', 'merge', None, _('merge uncommitted changes')),
5322 5324 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5323 5325 ('r', 'rev', '', _('revision'), _('REV'))
5324 5326 ] + mergetoolopts,
5325 5327 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5326 5328 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5327 5329 merge=None, tool=None):
5328 5330 """update working directory (or switch revisions)
5329 5331
5330 5332 Update the repository's working directory to the specified
5331 5333 changeset. If no changeset is specified, update to the tip of the
5332 5334 current named branch and move the active bookmark (see :hg:`help
5333 5335 bookmarks`).
5334 5336
5335 5337 Update sets the working directory's parent revision to the specified
5336 5338 changeset (see :hg:`help parents`).
5337 5339
5338 5340 If the changeset is not a descendant or ancestor of the working
5339 5341 directory's parent and there are uncommitted changes, the update is
5340 5342 aborted. With the -c/--check option, the working directory is checked
5341 5343 for uncommitted changes; if none are found, the working directory is
5342 5344 updated to the specified changeset.
5343 5345
5344 5346 .. container:: verbose
5345 5347
5346 5348 The -C/--clean, -c/--check, and -m/--merge options control what
5347 5349 happens if the working directory contains uncommitted changes.
5348 5350 At most of one of them can be specified.
5349 5351
5350 5352 1. If no option is specified, and if
5351 5353 the requested changeset is an ancestor or descendant of
5352 5354 the working directory's parent, the uncommitted changes
5353 5355 are merged into the requested changeset and the merged
5354 5356 result is left uncommitted. If the requested changeset is
5355 5357 not an ancestor or descendant (that is, it is on another
5356 5358 branch), the update is aborted and the uncommitted changes
5357 5359 are preserved.
5358 5360
5359 5361 2. With the -m/--merge option, the update is allowed even if the
5360 5362 requested changeset is not an ancestor or descendant of
5361 5363 the working directory's parent.
5362 5364
5363 5365 3. With the -c/--check option, the update is aborted and the
5364 5366 uncommitted changes are preserved.
5365 5367
5366 5368 4. With the -C/--clean option, uncommitted changes are discarded and
5367 5369 the working directory is updated to the requested changeset.
5368 5370
5369 5371 To cancel an uncommitted merge (and lose your changes), use
5370 5372 :hg:`update --clean .`.
5371 5373
5372 5374 Use null as the changeset to remove the working directory (like
5373 5375 :hg:`clone -U`).
5374 5376
5375 5377 If you want to revert just one file to an older revision, use
5376 5378 :hg:`revert [-r REV] NAME`.
5377 5379
5378 5380 See :hg:`help dates` for a list of formats valid for -d/--date.
5379 5381
5380 5382 Returns 0 on success, 1 if there are unresolved files.
5381 5383 """
5382 5384 if rev and node:
5383 5385 raise error.Abort(_("please specify just one revision"))
5384 5386
5385 5387 if ui.configbool('commands', 'update.requiredest'):
5386 5388 if not node and not rev and not date:
5387 5389 raise error.Abort(_('you must specify a destination'),
5388 5390 hint=_('for example: hg update ".::"'))
5389 5391
5390 5392 if rev is None or rev == '':
5391 5393 rev = node
5392 5394
5393 5395 if date and rev is not None:
5394 5396 raise error.Abort(_("you can't specify a revision and a date"))
5395 5397
5396 5398 if len([x for x in (clean, check, merge) if x]) > 1:
5397 5399 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5398 5400 "or -m/merge"))
5399 5401
5400 5402 updatecheck = None
5401 5403 if check:
5402 5404 updatecheck = 'abort'
5403 5405 elif merge:
5404 5406 updatecheck = 'none'
5405 5407
5406 5408 with repo.wlock():
5407 5409 cmdutil.clearunfinished(repo)
5408 5410
5409 5411 if date:
5410 5412 rev = cmdutil.finddate(ui, repo, date)
5411 5413
5412 5414 # if we defined a bookmark, we have to remember the original name
5413 5415 brev = rev
5414 5416 rev = scmutil.revsingle(repo, rev, rev).rev()
5415 5417
5416 5418 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5417 5419
5418 5420 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5419 5421 updatecheck=updatecheck)
5420 5422
5421 5423 @command('verify', [])
5422 5424 def verify(ui, repo):
5423 5425 """verify the integrity of the repository
5424 5426
5425 5427 Verify the integrity of the current repository.
5426 5428
5427 5429 This will perform an extensive check of the repository's
5428 5430 integrity, validating the hashes and checksums of each entry in
5429 5431 the changelog, manifest, and tracked files, as well as the
5430 5432 integrity of their crosslinks and indices.
5431 5433
5432 5434 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5433 5435 for more information about recovery from corruption of the
5434 5436 repository.
5435 5437
5436 5438 Returns 0 on success, 1 if errors are encountered.
5437 5439 """
5438 5440 return hg.verify(repo)
5439 5441
5440 5442 @command('version', [] + formatteropts, norepo=True)
5441 5443 def version_(ui, **opts):
5442 5444 """output version and copyright information"""
5443 5445 opts = pycompat.byteskwargs(opts)
5444 5446 if ui.verbose:
5445 5447 ui.pager('version')
5446 5448 fm = ui.formatter("version", opts)
5447 5449 fm.startitem()
5448 5450 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5449 5451 util.version())
5450 5452 license = _(
5451 5453 "(see https://mercurial-scm.org for more information)\n"
5452 5454 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5453 5455 "This is free software; see the source for copying conditions. "
5454 5456 "There is NO\nwarranty; "
5455 5457 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5456 5458 )
5457 5459 if not ui.quiet:
5458 5460 fm.plain(license)
5459 5461
5460 5462 if ui.verbose:
5461 5463 fm.plain(_("\nEnabled extensions:\n\n"))
5462 5464 # format names and versions into columns
5463 5465 names = []
5464 5466 vers = []
5465 5467 isinternals = []
5466 5468 for name, module in extensions.extensions():
5467 5469 names.append(name)
5468 5470 vers.append(extensions.moduleversion(module) or None)
5469 5471 isinternals.append(extensions.ismoduleinternal(module))
5470 5472 fn = fm.nested("extensions")
5471 5473 if names:
5472 5474 namefmt = " %%-%ds " % max(len(n) for n in names)
5473 5475 places = [_("external"), _("internal")]
5474 5476 for n, v, p in zip(names, vers, isinternals):
5475 5477 fn.startitem()
5476 5478 fn.condwrite(ui.verbose, "name", namefmt, n)
5477 5479 if ui.verbose:
5478 5480 fn.plain("%s " % places[p])
5479 5481 fn.data(bundled=p)
5480 5482 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5481 5483 if ui.verbose:
5482 5484 fn.plain("\n")
5483 5485 fn.end()
5484 5486 fm.end()
5485 5487
5486 5488 def loadcmdtable(ui, name, cmdtable):
5487 5489 """Load command functions from specified cmdtable
5488 5490 """
5489 5491 overrides = [cmd for cmd in cmdtable if cmd in table]
5490 5492 if overrides:
5491 5493 ui.warn(_("extension '%s' overrides commands: %s\n")
5492 5494 % (name, " ".join(overrides)))
5493 5495 table.update(cmdtable)
@@ -1,1005 +1,1006 b''
1 1 # dispatch.py - command dispatching 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, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 fileset,
34 34 help,
35 35 hg,
36 36 hook,
37 37 profiling,
38 38 pycompat,
39 39 revset,
40 40 scmutil,
41 41 templatefilters,
42 42 templatekw,
43 43 templater,
44 44 ui as uimod,
45 45 util,
46 46 )
47 47
48 48 class request(object):
49 49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 50 ferr=None, prereposetups=None):
51 51 self.args = args
52 52 self.ui = ui
53 53 self.repo = repo
54 54
55 55 # input/output/error streams
56 56 self.fin = fin
57 57 self.fout = fout
58 58 self.ferr = ferr
59 59
60 60 # reposetups which run before extensions, useful for chg to pre-fill
61 61 # low-level repo state (for example, changelog) before extensions.
62 62 self.prereposetups = prereposetups or []
63 63
64 64 def _runexithandlers(self):
65 65 exc = None
66 66 handlers = self.ui._exithandlers
67 67 try:
68 68 while handlers:
69 69 func, args, kwargs = handlers.pop()
70 70 try:
71 71 func(*args, **kwargs)
72 72 except: # re-raises below
73 73 if exc is None:
74 74 exc = sys.exc_info()[1]
75 75 self.ui.warn(('error in exit handlers:\n'))
76 76 self.ui.traceback(force=True)
77 77 finally:
78 78 if exc is not None:
79 79 raise exc
80 80
81 81 def run():
82 82 "run the command in sys.argv"
83 83 req = request(pycompat.sysargv[1:])
84 84 err = None
85 85 try:
86 86 status = (dispatch(req) or 0) & 255
87 87 except error.StdioError as err:
88 88 status = -1
89 89 if util.safehasattr(req.ui, 'fout'):
90 90 try:
91 91 req.ui.fout.close()
92 92 except IOError as err:
93 93 status = -1
94 94 if util.safehasattr(req.ui, 'ferr'):
95 95 if err is not None and err.errno != errno.EPIPE:
96 96 req.ui.ferr.write('abort: %s\n' % err.strerror)
97 97 req.ui.ferr.close()
98 98 sys.exit(status & 255)
99 99
100 100 def _getsimilar(symbols, value):
101 101 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
102 102 # The cutoff for similarity here is pretty arbitrary. It should
103 103 # probably be investigated and tweaked.
104 104 return [s for s in symbols if sim(s) > 0.6]
105 105
106 106 def _reportsimilar(write, similar):
107 107 if len(similar) == 1:
108 108 write(_("(did you mean %s?)\n") % similar[0])
109 109 elif similar:
110 110 ss = ", ".join(sorted(similar))
111 111 write(_("(did you mean one of %s?)\n") % ss)
112 112
113 113 def _formatparse(write, inst):
114 114 similar = []
115 115 if isinstance(inst, error.UnknownIdentifier):
116 116 # make sure to check fileset first, as revset can invoke fileset
117 117 similar = _getsimilar(inst.symbols, inst.function)
118 118 if len(inst.args) > 1:
119 119 write(_("hg: parse error at %s: %s\n") %
120 120 (inst.args[1], inst.args[0]))
121 121 if (inst.args[0][0] == ' '):
122 122 write(_("unexpected leading whitespace\n"))
123 123 else:
124 124 write(_("hg: parse error: %s\n") % inst.args[0])
125 125 _reportsimilar(write, similar)
126 126 if inst.hint:
127 127 write(_("(%s)\n") % inst.hint)
128 128
129 129 def _formatargs(args):
130 130 return ' '.join(util.shellquote(a) for a in args)
131 131
132 132 def dispatch(req):
133 133 "run the command specified in req.args"
134 134 if req.ferr:
135 135 ferr = req.ferr
136 136 elif req.ui:
137 137 ferr = req.ui.ferr
138 138 else:
139 139 ferr = util.stderr
140 140
141 141 try:
142 142 if not req.ui:
143 143 req.ui = uimod.ui.load()
144 144 if '--traceback' in req.args:
145 145 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
146 146
147 147 # set ui streams from the request
148 148 if req.fin:
149 149 req.ui.fin = req.fin
150 150 if req.fout:
151 151 req.ui.fout = req.fout
152 152 if req.ferr:
153 153 req.ui.ferr = req.ferr
154 154 except error.Abort as inst:
155 155 ferr.write(_("abort: %s\n") % inst)
156 156 if inst.hint:
157 157 ferr.write(_("(%s)\n") % inst.hint)
158 158 return -1
159 159 except error.ParseError as inst:
160 160 _formatparse(ferr.write, inst)
161 161 return -1
162 162
163 163 msg = _formatargs(req.args)
164 164 starttime = util.timer()
165 165 ret = None
166 166 try:
167 167 ret = _runcatch(req)
168 168 except error.ProgrammingError as inst:
169 169 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
170 170 if inst.hint:
171 171 req.ui.warn(_('** (%s)\n') % inst.hint)
172 172 raise
173 173 except KeyboardInterrupt as inst:
174 174 try:
175 175 if isinstance(inst, error.SignalInterrupt):
176 176 msg = _("killed!\n")
177 177 else:
178 178 msg = _("interrupted!\n")
179 179 req.ui.warn(msg)
180 180 except error.SignalInterrupt:
181 181 # maybe pager would quit without consuming all the output, and
182 182 # SIGPIPE was raised. we cannot print anything in this case.
183 183 pass
184 184 except IOError as inst:
185 185 if inst.errno != errno.EPIPE:
186 186 raise
187 187 ret = -1
188 188 finally:
189 189 duration = util.timer() - starttime
190 190 req.ui.flush()
191 191 if req.ui.logblockedtimes:
192 192 req.ui._blockedtimes['command_duration'] = duration * 1000
193 193 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
194 194 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
195 195 msg, ret or 0, duration)
196 196 try:
197 197 req._runexithandlers()
198 198 except: # exiting, so no re-raises
199 199 ret = ret or -1
200 200 return ret
201 201
202 202 def _runcatch(req):
203 203 def catchterm(*args):
204 204 raise error.SignalInterrupt
205 205
206 206 ui = req.ui
207 207 try:
208 208 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
209 209 num = getattr(signal, name, None)
210 210 if num:
211 211 signal.signal(num, catchterm)
212 212 except ValueError:
213 213 pass # happens if called in a thread
214 214
215 215 def _runcatchfunc():
216 216 realcmd = None
217 217 try:
218 218 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
219 219 cmd = cmdargs[0]
220 220 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
221 221 realcmd = aliases[0]
222 222 except (error.UnknownCommand, error.AmbiguousCommand,
223 223 IndexError, getopt.GetoptError):
224 224 # Don't handle this here. We know the command is
225 225 # invalid, but all we're worried about for now is that
226 226 # it's not a command that server operators expect to
227 227 # be safe to offer to users in a sandbox.
228 228 pass
229 229 if realcmd == 'serve' and '--stdio' in cmdargs:
230 230 # We want to constrain 'hg serve --stdio' instances pretty
231 231 # closely, as many shared-ssh access tools want to grant
232 232 # access to run *only* 'hg -R $repo serve --stdio'. We
233 233 # restrict to exactly that set of arguments, and prohibit
234 234 # any repo name that starts with '--' to prevent
235 235 # shenanigans wherein a user does something like pass
236 236 # --debugger or --config=ui.debugger=1 as a repo
237 237 # name. This used to actually run the debugger.
238 238 if (len(req.args) != 4 or
239 239 req.args[0] != '-R' or
240 240 req.args[1].startswith('--') or
241 241 req.args[2] != 'serve' or
242 242 req.args[3] != '--stdio'):
243 243 raise error.Abort(
244 244 _('potentially unsafe serve --stdio invocation: %r') %
245 245 (req.args,))
246 246
247 247 try:
248 248 debugger = 'pdb'
249 249 debugtrace = {
250 250 'pdb' : pdb.set_trace
251 251 }
252 252 debugmortem = {
253 253 'pdb' : pdb.post_mortem
254 254 }
255 255
256 256 # read --config before doing anything else
257 257 # (e.g. to change trust settings for reading .hg/hgrc)
258 258 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
259 259
260 260 if req.repo:
261 261 # copy configs that were passed on the cmdline (--config) to
262 262 # the repo ui
263 263 for sec, name, val in cfgs:
264 264 req.repo.ui.setconfig(sec, name, val, source='--config')
265 265
266 266 # developer config: ui.debugger
267 267 debugger = ui.config("ui", "debugger")
268 268 debugmod = pdb
269 269 if not debugger or ui.plain():
270 270 # if we are in HGPLAIN mode, then disable custom debugging
271 271 debugger = 'pdb'
272 272 elif '--debugger' in req.args:
273 273 # This import can be slow for fancy debuggers, so only
274 274 # do it when absolutely necessary, i.e. when actual
275 275 # debugging has been requested
276 276 with demandimport.deactivated():
277 277 try:
278 278 debugmod = __import__(debugger)
279 279 except ImportError:
280 280 pass # Leave debugmod = pdb
281 281
282 282 debugtrace[debugger] = debugmod.set_trace
283 283 debugmortem[debugger] = debugmod.post_mortem
284 284
285 285 # enter the debugger before command execution
286 286 if '--debugger' in req.args:
287 287 ui.warn(_("entering debugger - "
288 288 "type c to continue starting hg or h for help\n"))
289 289
290 290 if (debugger != 'pdb' and
291 291 debugtrace[debugger] == debugtrace['pdb']):
292 292 ui.warn(_("%s debugger specified "
293 293 "but its module was not found\n") % debugger)
294 294 with demandimport.deactivated():
295 295 debugtrace[debugger]()
296 296 try:
297 297 return _dispatch(req)
298 298 finally:
299 299 ui.flush()
300 300 except: # re-raises
301 301 # enter the debugger when we hit an exception
302 302 if '--debugger' in req.args:
303 303 traceback.print_exc()
304 304 debugmortem[debugger](sys.exc_info()[2])
305 305 raise
306 306
307 307 return _callcatch(ui, _runcatchfunc)
308 308
309 309 def _callcatch(ui, func):
310 310 """like scmutil.callcatch but handles more high-level exceptions about
311 311 config parsing and commands. besides, use handlecommandexception to handle
312 312 uncaught exceptions.
313 313 """
314 314 try:
315 315 return scmutil.callcatch(ui, func)
316 316 except error.AmbiguousCommand as inst:
317 317 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
318 318 (inst.args[0], " ".join(inst.args[1])))
319 319 except error.CommandError as inst:
320 320 if inst.args[0]:
321 321 ui.pager('help')
322 322 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
323 323 commands.help_(ui, inst.args[0], full=False, command=True)
324 324 else:
325 325 ui.pager('help')
326 326 ui.warn(_("hg: %s\n") % inst.args[1])
327 327 commands.help_(ui, 'shortlist')
328 328 except error.ParseError as inst:
329 329 _formatparse(ui.warn, inst)
330 330 return -1
331 331 except error.UnknownCommand as inst:
332 332 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
333 333 try:
334 334 # check if the command is in a disabled extension
335 335 # (but don't check for extensions themselves)
336 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
336 formatted = help.formattedhelp(ui, commands, inst.args[0],
337 unknowncmd=True)
337 338 ui.warn(nocmdmsg)
338 339 ui.write(formatted)
339 340 except (error.UnknownCommand, error.Abort):
340 341 suggested = False
341 342 if len(inst.args) == 2:
342 343 sim = _getsimilar(inst.args[1], inst.args[0])
343 344 if sim:
344 345 ui.warn(nocmdmsg)
345 346 _reportsimilar(ui.warn, sim)
346 347 suggested = True
347 348 if not suggested:
348 349 ui.pager('help')
349 350 ui.warn(nocmdmsg)
350 351 commands.help_(ui, 'shortlist')
351 352 except IOError:
352 353 raise
353 354 except KeyboardInterrupt:
354 355 raise
355 356 except: # probably re-raises
356 357 if not handlecommandexception(ui):
357 358 raise
358 359
359 360 return -1
360 361
361 362 def aliasargs(fn, givenargs):
362 363 args = getattr(fn, 'args', [])
363 364 if args:
364 365 cmd = ' '.join(map(util.shellquote, args))
365 366
366 367 nums = []
367 368 def replacer(m):
368 369 num = int(m.group(1)) - 1
369 370 nums.append(num)
370 371 if num < len(givenargs):
371 372 return givenargs[num]
372 373 raise error.Abort(_('too few arguments for command alias'))
373 374 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
374 375 givenargs = [x for i, x in enumerate(givenargs)
375 376 if i not in nums]
376 377 args = pycompat.shlexsplit(cmd)
377 378 return args + givenargs
378 379
379 380 def aliasinterpolate(name, args, cmd):
380 381 '''interpolate args into cmd for shell aliases
381 382
382 383 This also handles $0, $@ and "$@".
383 384 '''
384 385 # util.interpolate can't deal with "$@" (with quotes) because it's only
385 386 # built to match prefix + patterns.
386 387 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
387 388 replacemap['$0'] = name
388 389 replacemap['$$'] = '$'
389 390 replacemap['$@'] = ' '.join(args)
390 391 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
391 392 # parameters, separated out into words. Emulate the same behavior here by
392 393 # quoting the arguments individually. POSIX shells will then typically
393 394 # tokenize each argument into exactly one word.
394 395 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
395 396 # escape '\$' for regex
396 397 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
397 398 r = re.compile(regex)
398 399 return r.sub(lambda x: replacemap[x.group()], cmd)
399 400
400 401 class cmdalias(object):
401 402 def __init__(self, name, definition, cmdtable, source):
402 403 self.name = self.cmd = name
403 404 self.cmdname = ''
404 405 self.definition = definition
405 406 self.fn = None
406 407 self.givenargs = []
407 408 self.opts = []
408 409 self.help = ''
409 410 self.badalias = None
410 411 self.unknowncmd = False
411 412 self.source = source
412 413
413 414 try:
414 415 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
415 416 for alias, e in cmdtable.iteritems():
416 417 if e is entry:
417 418 self.cmd = alias
418 419 break
419 420 self.shadows = True
420 421 except error.UnknownCommand:
421 422 self.shadows = False
422 423
423 424 if not self.definition:
424 425 self.badalias = _("no definition for alias '%s'") % self.name
425 426 return
426 427
427 428 if self.definition.startswith('!'):
428 429 self.shell = True
429 430 def fn(ui, *args):
430 431 env = {'HG_ARGS': ' '.join((self.name,) + args)}
431 432 def _checkvar(m):
432 433 if m.groups()[0] == '$':
433 434 return m.group()
434 435 elif int(m.groups()[0]) <= len(args):
435 436 return m.group()
436 437 else:
437 438 ui.debug("No argument found for substitution "
438 439 "of %i variable in alias '%s' definition."
439 440 % (int(m.groups()[0]), self.name))
440 441 return ''
441 442 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
442 443 cmd = aliasinterpolate(self.name, args, cmd)
443 444 return ui.system(cmd, environ=env,
444 445 blockedtag='alias_%s' % self.name)
445 446 self.fn = fn
446 447 return
447 448
448 449 try:
449 450 args = pycompat.shlexsplit(self.definition)
450 451 except ValueError as inst:
451 452 self.badalias = (_("error in definition for alias '%s': %s")
452 453 % (self.name, inst))
453 454 return
454 455 self.cmdname = cmd = args.pop(0)
455 456 self.givenargs = args
456 457
457 458 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
458 459 if _earlygetopt([invalidarg], args):
459 460 self.badalias = (_("error in definition for alias '%s': %s may "
460 461 "only be given on the command line")
461 462 % (self.name, invalidarg))
462 463 return
463 464
464 465 try:
465 466 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
466 467 if len(tableentry) > 2:
467 468 self.fn, self.opts, self.help = tableentry
468 469 else:
469 470 self.fn, self.opts = tableentry
470 471
471 472 if self.help.startswith("hg " + cmd):
472 473 # drop prefix in old-style help lines so hg shows the alias
473 474 self.help = self.help[4 + len(cmd):]
474 475 self.__doc__ = self.fn.__doc__
475 476
476 477 except error.UnknownCommand:
477 478 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
478 479 % (self.name, cmd))
479 480 self.unknowncmd = True
480 481 except error.AmbiguousCommand:
481 482 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
482 483 % (self.name, cmd))
483 484
484 485 @property
485 486 def args(self):
486 487 args = pycompat.maplist(util.expandpath, self.givenargs)
487 488 return aliasargs(self.fn, args)
488 489
489 490 def __getattr__(self, name):
490 491 adefaults = {r'norepo': True,
491 492 r'optionalrepo': False, r'inferrepo': False}
492 493 if name not in adefaults:
493 494 raise AttributeError(name)
494 495 if self.badalias or util.safehasattr(self, 'shell'):
495 496 return adefaults[name]
496 497 return getattr(self.fn, name)
497 498
498 499 def __call__(self, ui, *args, **opts):
499 500 if self.badalias:
500 501 hint = None
501 502 if self.unknowncmd:
502 503 try:
503 504 # check if the command is in a disabled extension
504 505 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
505 506 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
506 507 except error.UnknownCommand:
507 508 pass
508 509 raise error.Abort(self.badalias, hint=hint)
509 510 if self.shadows:
510 511 ui.debug("alias '%s' shadows command '%s'\n" %
511 512 (self.name, self.cmdname))
512 513
513 514 ui.log('commandalias', "alias '%s' expands to '%s'\n",
514 515 self.name, self.definition)
515 516 if util.safehasattr(self, 'shell'):
516 517 return self.fn(ui, *args, **opts)
517 518 else:
518 519 try:
519 520 return util.checksignature(self.fn)(ui, *args, **opts)
520 521 except error.SignatureError:
521 522 args = ' '.join([self.cmdname] + self.args)
522 523 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
523 524 raise
524 525
525 526 def addaliases(ui, cmdtable):
526 527 # aliases are processed after extensions have been loaded, so they
527 528 # may use extension commands. Aliases can also use other alias definitions,
528 529 # but only if they have been defined prior to the current definition.
529 530 for alias, definition in ui.configitems('alias'):
530 531 source = ui.configsource('alias', alias)
531 532 aliasdef = cmdalias(alias, definition, cmdtable, source)
532 533
533 534 try:
534 535 olddef = cmdtable[aliasdef.cmd][0]
535 536 if olddef.definition == aliasdef.definition:
536 537 continue
537 538 except (KeyError, AttributeError):
538 539 # definition might not exist or it might not be a cmdalias
539 540 pass
540 541
541 542 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
542 543
543 544 def _parse(ui, args):
544 545 options = {}
545 546 cmdoptions = {}
546 547
547 548 try:
548 549 args = fancyopts.fancyopts(args, commands.globalopts, options)
549 550 except getopt.GetoptError as inst:
550 551 raise error.CommandError(None, inst)
551 552
552 553 if args:
553 554 cmd, args = args[0], args[1:]
554 555 aliases, entry = cmdutil.findcmd(cmd, commands.table,
555 556 ui.configbool("ui", "strict"))
556 557 cmd = aliases[0]
557 558 args = aliasargs(entry[0], args)
558 559 defaults = ui.config("defaults", cmd)
559 560 if defaults:
560 561 args = pycompat.maplist(
561 562 util.expandpath, pycompat.shlexsplit(defaults)) + args
562 563 c = list(entry[1])
563 564 else:
564 565 cmd = None
565 566 c = []
566 567
567 568 # combine global options into local
568 569 for o in commands.globalopts:
569 570 c.append((o[0], o[1], options[o[1]], o[3]))
570 571
571 572 try:
572 573 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
573 574 except getopt.GetoptError as inst:
574 575 raise error.CommandError(cmd, inst)
575 576
576 577 # separate global options back out
577 578 for o in commands.globalopts:
578 579 n = o[1]
579 580 options[n] = cmdoptions[n]
580 581 del cmdoptions[n]
581 582
582 583 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
583 584
584 585 def _parseconfig(ui, config):
585 586 """parse the --config options from the command line"""
586 587 configs = []
587 588
588 589 for cfg in config:
589 590 try:
590 591 name, value = [cfgelem.strip()
591 592 for cfgelem in cfg.split('=', 1)]
592 593 section, name = name.split('.', 1)
593 594 if not section or not name:
594 595 raise IndexError
595 596 ui.setconfig(section, name, value, '--config')
596 597 configs.append((section, name, value))
597 598 except (IndexError, ValueError):
598 599 raise error.Abort(_('malformed --config option: %r '
599 600 '(use --config section.name=value)') % cfg)
600 601
601 602 return configs
602 603
603 604 def _earlygetopt(aliases, args):
604 605 """Return list of values for an option (or aliases).
605 606
606 607 The values are listed in the order they appear in args.
607 608 The options and values are removed from args.
608 609
609 610 >>> args = ['x', '--cwd', 'foo', 'y']
610 611 >>> _earlygetopt(['--cwd'], args), args
611 612 (['foo'], ['x', 'y'])
612 613
613 614 >>> args = ['x', '--cwd=bar', 'y']
614 615 >>> _earlygetopt(['--cwd'], args), args
615 616 (['bar'], ['x', 'y'])
616 617
617 618 >>> args = ['x', '-R', 'foo', 'y']
618 619 >>> _earlygetopt(['-R'], args), args
619 620 (['foo'], ['x', 'y'])
620 621
621 622 >>> args = ['x', '-Rbar', 'y']
622 623 >>> _earlygetopt(['-R'], args), args
623 624 (['bar'], ['x', 'y'])
624 625 """
625 626 try:
626 627 argcount = args.index("--")
627 628 except ValueError:
628 629 argcount = len(args)
629 630 shortopts = [opt for opt in aliases if len(opt) == 2]
630 631 values = []
631 632 pos = 0
632 633 while pos < argcount:
633 634 fullarg = arg = args[pos]
634 635 equals = arg.find('=')
635 636 if equals > -1:
636 637 arg = arg[:equals]
637 638 if arg in aliases:
638 639 del args[pos]
639 640 if equals > -1:
640 641 values.append(fullarg[equals + 1:])
641 642 argcount -= 1
642 643 else:
643 644 if pos + 1 >= argcount:
644 645 # ignore and let getopt report an error if there is no value
645 646 break
646 647 values.append(args.pop(pos))
647 648 argcount -= 2
648 649 elif arg[:2] in shortopts:
649 650 # short option can have no following space, e.g. hg log -Rfoo
650 651 values.append(args.pop(pos)[2:])
651 652 argcount -= 1
652 653 else:
653 654 pos += 1
654 655 return values
655 656
656 657 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
657 658 # run pre-hook, and abort if it fails
658 659 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
659 660 pats=cmdpats, opts=cmdoptions)
660 661 try:
661 662 ret = _runcommand(ui, options, cmd, d)
662 663 # run post-hook, passing command result
663 664 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
664 665 result=ret, pats=cmdpats, opts=cmdoptions)
665 666 except Exception:
666 667 # run failure hook and re-raise
667 668 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
668 669 pats=cmdpats, opts=cmdoptions)
669 670 raise
670 671 return ret
671 672
672 673 def _getlocal(ui, rpath, wd=None):
673 674 """Return (path, local ui object) for the given target path.
674 675
675 676 Takes paths in [cwd]/.hg/hgrc into account."
676 677 """
677 678 if wd is None:
678 679 try:
679 680 wd = pycompat.getcwd()
680 681 except OSError as e:
681 682 raise error.Abort(_("error getting current working directory: %s") %
682 683 e.strerror)
683 684 path = cmdutil.findrepo(wd) or ""
684 685 if not path:
685 686 lui = ui
686 687 else:
687 688 lui = ui.copy()
688 689 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
689 690
690 691 if rpath and rpath[-1]:
691 692 path = lui.expandpath(rpath[-1])
692 693 lui = ui.copy()
693 694 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
694 695
695 696 return path, lui
696 697
697 698 def _checkshellalias(lui, ui, args):
698 699 """Return the function to run the shell alias, if it is required"""
699 700 options = {}
700 701
701 702 try:
702 703 args = fancyopts.fancyopts(args, commands.globalopts, options)
703 704 except getopt.GetoptError:
704 705 return
705 706
706 707 if not args:
707 708 return
708 709
709 710 cmdtable = commands.table
710 711
711 712 cmd = args[0]
712 713 try:
713 714 strict = ui.configbool("ui", "strict")
714 715 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
715 716 except (error.AmbiguousCommand, error.UnknownCommand):
716 717 return
717 718
718 719 cmd = aliases[0]
719 720 fn = entry[0]
720 721
721 722 if cmd and util.safehasattr(fn, 'shell'):
722 723 d = lambda: fn(ui, *args[1:])
723 724 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
724 725 [], {})
725 726
726 727 _loaded = set()
727 728
728 729 # list of (objname, loadermod, loadername) tuple:
729 730 # - objname is the name of an object in extension module, from which
730 731 # extra information is loaded
731 732 # - loadermod is the module where loader is placed
732 733 # - loadername is the name of the function, which takes (ui, extensionname,
733 734 # extraobj) arguments
734 735 extraloaders = [
735 736 ('cmdtable', commands, 'loadcmdtable'),
736 737 ('colortable', color, 'loadcolortable'),
737 738 ('filesetpredicate', fileset, 'loadpredicate'),
738 739 ('revsetpredicate', revset, 'loadpredicate'),
739 740 ('templatefilter', templatefilters, 'loadfilter'),
740 741 ('templatefunc', templater, 'loadfunction'),
741 742 ('templatekeyword', templatekw, 'loadkeyword'),
742 743 ]
743 744
744 745 def _dispatch(req):
745 746 args = req.args
746 747 ui = req.ui
747 748
748 749 # check for cwd
749 750 cwd = _earlygetopt(['--cwd'], args)
750 751 if cwd:
751 752 os.chdir(cwd[-1])
752 753
753 754 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
754 755 path, lui = _getlocal(ui, rpath)
755 756
756 757 uis = {ui, lui}
757 758
758 759 if req.repo:
759 760 uis.add(req.repo.ui)
760 761
761 762 if '--profile' in args:
762 763 for ui_ in uis:
763 764 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
764 765
765 766 with profiling.maybeprofile(lui):
766 767 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
767 768 # reposetup. Programs like TortoiseHg will call _dispatch several
768 769 # times so we keep track of configured extensions in _loaded.
769 770 extensions.loadall(lui)
770 771 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
771 772 # Propagate any changes to lui.__class__ by extensions
772 773 ui.__class__ = lui.__class__
773 774
774 775 # (uisetup and extsetup are handled in extensions.loadall)
775 776
776 777 for name, module in exts:
777 778 for objname, loadermod, loadername in extraloaders:
778 779 extraobj = getattr(module, objname, None)
779 780 if extraobj is not None:
780 781 getattr(loadermod, loadername)(ui, name, extraobj)
781 782 _loaded.add(name)
782 783
783 784 # (reposetup is handled in hg.repository)
784 785
785 786 addaliases(lui, commands.table)
786 787
787 788 # All aliases and commands are completely defined, now.
788 789 # Check abbreviation/ambiguity of shell alias.
789 790 shellaliasfn = _checkshellalias(lui, ui, args)
790 791 if shellaliasfn:
791 792 return shellaliasfn()
792 793
793 794 # check for fallback encoding
794 795 fallback = lui.config('ui', 'fallbackencoding')
795 796 if fallback:
796 797 encoding.fallbackencoding = fallback
797 798
798 799 fullargs = args
799 800 cmd, func, args, options, cmdoptions = _parse(lui, args)
800 801
801 802 if options["config"]:
802 803 raise error.Abort(_("option --config may not be abbreviated!"))
803 804 if options["cwd"]:
804 805 raise error.Abort(_("option --cwd may not be abbreviated!"))
805 806 if options["repository"]:
806 807 raise error.Abort(_(
807 808 "option -R has to be separated from other options (e.g. not "
808 809 "-qR) and --repository may only be abbreviated as --repo!"))
809 810
810 811 if options["encoding"]:
811 812 encoding.encoding = options["encoding"]
812 813 if options["encodingmode"]:
813 814 encoding.encodingmode = options["encodingmode"]
814 815 if options["time"]:
815 816 def get_times():
816 817 t = os.times()
817 818 if t[4] == 0.0:
818 819 # Windows leaves this as zero, so use time.clock()
819 820 t = (t[0], t[1], t[2], t[3], time.clock())
820 821 return t
821 822 s = get_times()
822 823 def print_time():
823 824 t = get_times()
824 825 ui.warn(
825 826 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
826 827 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
827 828 ui.atexit(print_time)
828 829
829 830 if options['verbose'] or options['debug'] or options['quiet']:
830 831 for opt in ('verbose', 'debug', 'quiet'):
831 832 val = str(bool(options[opt]))
832 833 if pycompat.ispy3:
833 834 val = val.encode('ascii')
834 835 for ui_ in uis:
835 836 ui_.setconfig('ui', opt, val, '--' + opt)
836 837
837 838 if options['traceback']:
838 839 for ui_ in uis:
839 840 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
840 841
841 842 if options['noninteractive']:
842 843 for ui_ in uis:
843 844 ui_.setconfig('ui', 'interactive', 'off', '-y')
844 845
845 846 if util.parsebool(options['pager']):
846 847 ui.pager('internal-always-' + cmd)
847 848 elif options['pager'] != 'auto':
848 849 ui.disablepager()
849 850
850 851 if cmdoptions.get('insecure', False):
851 852 for ui_ in uis:
852 853 ui_.insecureconnections = True
853 854
854 855 # setup color handling
855 856 coloropt = options['color']
856 857 for ui_ in uis:
857 858 if coloropt:
858 859 ui_.setconfig('ui', 'color', coloropt, '--color')
859 860 color.setup(ui_)
860 861
861 862 if options['version']:
862 863 return commands.version_(ui)
863 864 if options['help']:
864 865 return commands.help_(ui, cmd, command=cmd is not None)
865 866 elif not cmd:
866 867 return commands.help_(ui, 'shortlist')
867 868
868 869 repo = None
869 870 cmdpats = args[:]
870 871 if not func.norepo:
871 872 # use the repo from the request only if we don't have -R
872 873 if not rpath and not cwd:
873 874 repo = req.repo
874 875
875 876 if repo:
876 877 # set the descriptors of the repo ui to those of ui
877 878 repo.ui.fin = ui.fin
878 879 repo.ui.fout = ui.fout
879 880 repo.ui.ferr = ui.ferr
880 881 else:
881 882 try:
882 883 repo = hg.repository(ui, path=path,
883 884 presetupfuncs=req.prereposetups)
884 885 if not repo.local():
885 886 raise error.Abort(_("repository '%s' is not local")
886 887 % path)
887 888 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
888 889 'repo')
889 890 except error.RequirementError:
890 891 raise
891 892 except error.RepoError:
892 893 if rpath and rpath[-1]: # invalid -R path
893 894 raise
894 895 if not func.optionalrepo:
895 896 if func.inferrepo and args and not path:
896 897 # try to infer -R from command args
897 898 repos = map(cmdutil.findrepo, args)
898 899 guess = repos[0]
899 900 if guess and repos.count(guess) == len(repos):
900 901 req.args = ['--repository', guess] + fullargs
901 902 return _dispatch(req)
902 903 if not path:
903 904 raise error.RepoError(_("no repository found in"
904 905 " '%s' (.hg not found)")
905 906 % pycompat.getcwd())
906 907 raise
907 908 if repo:
908 909 ui = repo.ui
909 910 if options['hidden']:
910 911 repo = repo.unfiltered()
911 912 args.insert(0, repo)
912 913 elif rpath:
913 914 ui.warn(_("warning: --repository ignored\n"))
914 915
915 916 msg = _formatargs(fullargs)
916 917 ui.log("command", '%s\n', msg)
917 918 strcmdopt = pycompat.strkwargs(cmdoptions)
918 919 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
919 920 try:
920 921 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
921 922 cmdpats, cmdoptions)
922 923 finally:
923 924 if repo and repo != req.repo:
924 925 repo.close()
925 926
926 927 def _runcommand(ui, options, cmd, cmdfunc):
927 928 """Run a command function, possibly with profiling enabled."""
928 929 try:
929 930 return cmdfunc()
930 931 except error.SignatureError:
931 932 raise error.CommandError(cmd, _('invalid arguments'))
932 933
933 934 def _exceptionwarning(ui):
934 935 """Produce a warning message for the current active exception"""
935 936
936 937 # For compatibility checking, we discard the portion of the hg
937 938 # version after the + on the assumption that if a "normal
938 939 # user" is running a build with a + in it the packager
939 940 # probably built from fairly close to a tag and anyone with a
940 941 # 'make local' copy of hg (where the version number can be out
941 942 # of date) will be clueful enough to notice the implausible
942 943 # version number and try updating.
943 944 ct = util.versiontuple(n=2)
944 945 worst = None, ct, ''
945 946 if ui.config('ui', 'supportcontact', None) is None:
946 947 for name, mod in extensions.extensions():
947 948 testedwith = getattr(mod, 'testedwith', '')
948 949 if pycompat.ispy3 and isinstance(testedwith, str):
949 950 testedwith = testedwith.encode(u'utf-8')
950 951 report = getattr(mod, 'buglink', _('the extension author.'))
951 952 if not testedwith.strip():
952 953 # We found an untested extension. It's likely the culprit.
953 954 worst = name, 'unknown', report
954 955 break
955 956
956 957 # Never blame on extensions bundled with Mercurial.
957 958 if extensions.ismoduleinternal(mod):
958 959 continue
959 960
960 961 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
961 962 if ct in tested:
962 963 continue
963 964
964 965 lower = [t for t in tested if t < ct]
965 966 nearest = max(lower or tested)
966 967 if worst[0] is None or nearest < worst[1]:
967 968 worst = name, nearest, report
968 969 if worst[0] is not None:
969 970 name, testedwith, report = worst
970 971 if not isinstance(testedwith, (bytes, str)):
971 972 testedwith = '.'.join([str(c) for c in testedwith])
972 973 warning = (_('** Unknown exception encountered with '
973 974 'possibly-broken third-party extension %s\n'
974 975 '** which supports versions %s of Mercurial.\n'
975 976 '** Please disable %s and try your action again.\n'
976 977 '** If that fixes the bug please report it to %s\n')
977 978 % (name, testedwith, name, report))
978 979 else:
979 980 bugtracker = ui.config('ui', 'supportcontact', None)
980 981 if bugtracker is None:
981 982 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
982 983 warning = (_("** unknown exception encountered, "
983 984 "please report by visiting\n** ") + bugtracker + '\n')
984 985 if pycompat.ispy3:
985 986 sysversion = sys.version.encode(u'utf-8')
986 987 else:
987 988 sysversion = sys.version
988 989 sysversion = sysversion.replace('\n', '')
989 990 warning += ((_("** Python %s\n") % sysversion) +
990 991 (_("** Mercurial Distributed SCM (version %s)\n") %
991 992 util.version()) +
992 993 (_("** Extensions loaded: %s\n") %
993 994 ", ".join([x[0] for x in extensions.extensions()])))
994 995 return warning
995 996
996 997 def handlecommandexception(ui):
997 998 """Produce a warning message for broken commands
998 999
999 1000 Called when handling an exception; the exception is reraised if
1000 1001 this function returns False, ignored otherwise.
1001 1002 """
1002 1003 warning = _exceptionwarning(ui)
1003 1004 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1004 1005 ui.warn(warning)
1005 1006 return False # re-raise the exception
@@ -1,667 +1,667 b''
1 1 # help.py - help data for mercurial
2 2 #
3 3 # Copyright 2006 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 itertools
11 11 import os
12 12 import textwrap
13 13
14 14 from .i18n import (
15 15 _,
16 16 gettext,
17 17 )
18 18 from . import (
19 19 cmdutil,
20 20 encoding,
21 21 error,
22 22 extensions,
23 23 filemerge,
24 24 fileset,
25 25 minirst,
26 26 pycompat,
27 27 revset,
28 28 templatefilters,
29 29 templatekw,
30 30 templater,
31 31 util,
32 32 )
33 33 from .hgweb import (
34 34 webcommands,
35 35 )
36 36
37 37 _exclkeywords = {
38 38 "(ADVANCED)",
39 39 "(DEPRECATED)",
40 40 "(EXPERIMENTAL)",
41 41 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
42 42 _("(ADVANCED)"),
43 43 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
44 44 _("(DEPRECATED)"),
45 45 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
46 46 _("(EXPERIMENTAL)"),
47 47 }
48 48
49 49 def listexts(header, exts, indent=1, showdeprecated=False):
50 50 '''return a text listing of the given extensions'''
51 51 rst = []
52 52 if exts:
53 53 for name, desc in sorted(exts.iteritems()):
54 54 if not showdeprecated and any(w in desc for w in _exclkeywords):
55 55 continue
56 56 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
57 57 if rst:
58 58 rst.insert(0, '\n%s\n\n' % header)
59 59 return rst
60 60
61 61 def extshelp(ui):
62 62 rst = loaddoc('extensions')(ui).splitlines(True)
63 63 rst.extend(listexts(
64 64 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
65 65 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
66 66 doc = ''.join(rst)
67 67 return doc
68 68
69 69 def optrst(header, options, verbose):
70 70 data = []
71 71 multioccur = False
72 72 for option in options:
73 73 if len(option) == 5:
74 74 shortopt, longopt, default, desc, optlabel = option
75 75 else:
76 76 shortopt, longopt, default, desc = option
77 77 optlabel = _("VALUE") # default label
78 78
79 79 if not verbose and any(w in desc for w in _exclkeywords):
80 80 continue
81 81
82 82 so = ''
83 83 if shortopt:
84 84 so = '-' + shortopt
85 85 lo = '--' + longopt
86 86 if default:
87 87 desc += _(" (default: %s)") % default
88 88
89 89 if isinstance(default, list):
90 90 lo += " %s [+]" % optlabel
91 91 multioccur = True
92 92 elif (default is not None) and not isinstance(default, bool):
93 93 lo += " %s" % optlabel
94 94
95 95 data.append((so, lo, desc))
96 96
97 97 if multioccur:
98 98 header += (_(" ([+] can be repeated)"))
99 99
100 100 rst = ['\n%s:\n\n' % header]
101 101 rst.extend(minirst.maketable(data, 1))
102 102
103 103 return ''.join(rst)
104 104
105 105 def indicateomitted(rst, omitted, notomitted=None):
106 106 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
107 107 if notomitted:
108 108 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
109 109
110 110 def filtercmd(ui, cmd, kw, doc):
111 111 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
112 112 return True
113 113 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
114 114 return True
115 115 return False
116 116
117 def topicmatch(ui, kw):
117 def topicmatch(ui, commands, kw):
118 118 """Return help topics matching kw.
119 119
120 120 Returns {'section': [(name, summary), ...], ...} where section is
121 121 one of topics, commands, extensions, or extensioncommands.
122 122 """
123 123 kw = encoding.lower(kw)
124 124 def lowercontains(container):
125 125 return kw in encoding.lower(container) # translated in helptable
126 126 results = {'topics': [],
127 127 'commands': [],
128 128 'extensions': [],
129 129 'extensioncommands': [],
130 130 }
131 131 for names, header, doc in helptable:
132 132 # Old extensions may use a str as doc.
133 133 if (sum(map(lowercontains, names))
134 134 or lowercontains(header)
135 135 or (callable(doc) and lowercontains(doc(ui)))):
136 136 results['topics'].append((names[0], header))
137 from . import commands # avoid cycle
138 137 for cmd, entry in commands.table.iteritems():
139 138 if len(entry) == 3:
140 139 summary = entry[2]
141 140 else:
142 141 summary = ''
143 142 # translate docs *before* searching there
144 143 docs = _(getattr(entry[0], '__doc__', None)) or ''
145 144 if kw in cmd or lowercontains(summary) or lowercontains(docs):
146 145 doclines = docs.splitlines()
147 146 if doclines:
148 147 summary = doclines[0]
149 148 cmdname = cmd.partition('|')[0].lstrip('^')
150 149 if filtercmd(ui, cmdname, kw, docs):
151 150 continue
152 151 results['commands'].append((cmdname, summary))
153 152 for name, docs in itertools.chain(
154 153 extensions.enabled(False).iteritems(),
155 154 extensions.disabled().iteritems()):
156 155 if not docs:
157 156 continue
158 157 mod = extensions.load(ui, name, '')
159 158 name = name.rpartition('.')[-1]
160 159 if lowercontains(name) or lowercontains(docs):
161 160 # extension docs are already translated
162 161 results['extensions'].append((name, docs.splitlines()[0]))
163 162 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
164 163 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
165 164 cmdname = cmd.partition('|')[0].lstrip('^')
166 165 if entry[0].__doc__:
167 166 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
168 167 else:
169 168 cmddoc = _('(no help text available)')
170 169 if filtercmd(ui, cmdname, kw, cmddoc):
171 170 continue
172 171 results['extensioncommands'].append((cmdname, cmddoc))
173 172 return results
174 173
175 174 def loaddoc(topic, subdir=None):
176 175 """Return a delayed loader for help/topic.txt."""
177 176
178 177 def loader(ui):
179 178 docdir = os.path.join(util.datapath, 'help')
180 179 if subdir:
181 180 docdir = os.path.join(docdir, subdir)
182 181 path = os.path.join(docdir, topic + ".txt")
183 182 doc = gettext(util.readfile(path))
184 183 for rewriter in helphooks.get(topic, []):
185 184 doc = rewriter(ui, topic, doc)
186 185 return doc
187 186
188 187 return loader
189 188
190 189 internalstable = sorted([
191 190 (['bundles'], _('Bundles'),
192 191 loaddoc('bundles', subdir='internals')),
193 192 (['censor'], _('Censor'),
194 193 loaddoc('censor', subdir='internals')),
195 194 (['changegroups'], _('Changegroups'),
196 195 loaddoc('changegroups', subdir='internals')),
197 196 (['requirements'], _('Repository Requirements'),
198 197 loaddoc('requirements', subdir='internals')),
199 198 (['revlogs'], _('Revision Logs'),
200 199 loaddoc('revlogs', subdir='internals')),
201 200 (['wireprotocol'], _('Wire Protocol'),
202 201 loaddoc('wireprotocol', subdir='internals')),
203 202 ])
204 203
205 204 def internalshelp(ui):
206 205 """Generate the index for the "internals" topic."""
207 206 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
208 207 '\n']
209 208 for names, header, doc in internalstable:
210 209 lines.append(' :%s: %s\n' % (names[0], header))
211 210
212 211 return ''.join(lines)
213 212
214 213 helptable = sorted([
215 214 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
216 215 (['color'], _("Colorizing Outputs"), loaddoc('color')),
217 216 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
218 217 (["dates"], _("Date Formats"), loaddoc('dates')),
219 218 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
220 219 (['environment', 'env'], _('Environment Variables'),
221 220 loaddoc('environment')),
222 221 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
223 222 _('Specifying Revisions'), loaddoc('revisions')),
224 223 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
225 224 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
226 225 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
227 226 loaddoc('merge-tools')),
228 227 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
229 228 loaddoc('templates')),
230 229 (['urls'], _('URL Paths'), loaddoc('urls')),
231 230 (["extensions"], _("Using Additional Features"), extshelp),
232 231 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
233 232 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
234 233 (["glossary"], _("Glossary"), loaddoc('glossary')),
235 234 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
236 235 loaddoc('hgignore')),
237 236 (["phases"], _("Working with Phases"), loaddoc('phases')),
238 237 (['scripting'], _('Using Mercurial from scripts and automation'),
239 238 loaddoc('scripting')),
240 239 (['internals'], _("Technical implementation topics"),
241 240 internalshelp),
242 241 (['pager'], _("Pager Support"), loaddoc('pager')),
243 242 ])
244 243
245 244 # Maps topics with sub-topics to a list of their sub-topics.
246 245 subtopics = {
247 246 'internals': internalstable,
248 247 }
249 248
250 249 # Map topics to lists of callable taking the current topic help and
251 250 # returning the updated version
252 251 helphooks = {}
253 252
254 253 def addtopichook(topic, rewriter):
255 254 helphooks.setdefault(topic, []).append(rewriter)
256 255
257 256 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
258 257 """Extract docstring from the items key to function mapping, build a
259 258 single documentation block and use it to overwrite the marker in doc.
260 259 """
261 260 entries = []
262 261 for name in sorted(items):
263 262 text = (items[name].__doc__ or '').rstrip()
264 263 if (not text
265 264 or not ui.verbose and any(w in text for w in _exclkeywords)):
266 265 continue
267 266 text = gettext(text)
268 267 if dedent:
269 268 # Abuse latin1 to use textwrap.dedent() on bytes.
270 269 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
271 270 lines = text.splitlines()
272 271 doclines = [(lines[0])]
273 272 for l in lines[1:]:
274 273 # Stop once we find some Python doctest
275 274 if l.strip().startswith('>>>'):
276 275 break
277 276 if dedent:
278 277 doclines.append(l.rstrip())
279 278 else:
280 279 doclines.append(' ' + l.strip())
281 280 entries.append('\n'.join(doclines))
282 281 entries = '\n\n'.join(entries)
283 282 return doc.replace(marker, entries)
284 283
285 284 def addtopicsymbols(topic, marker, symbols, dedent=False):
286 285 def add(ui, topic, doc):
287 286 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
288 287 addtopichook(topic, add)
289 288
290 289 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
291 290 util.bundlecompressiontopics())
292 291 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
293 292 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
294 293 filemerge.internalsdoc)
295 294 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
296 295 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
297 296 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
298 297 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
299 298 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
300 299 dedent=True)
301 300
302 def help_(ui, name, unknowncmd=False, full=True, subtopic=None, **opts):
301 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
302 **opts):
303 303 '''
304 304 Generate the help for 'name' as unformatted restructured text. If
305 305 'name' is None, describe the commands available.
306 306 '''
307 307
308 from . import commands # avoid cycle
309 308 opts = pycompat.byteskwargs(opts)
310 309
311 310 def helpcmd(name, subtopic=None):
312 311 try:
313 312 aliases, entry = cmdutil.findcmd(name, commands.table,
314 313 strict=unknowncmd)
315 314 except error.AmbiguousCommand as inst:
316 315 # py3k fix: except vars can't be used outside the scope of the
317 316 # except block, nor can be used inside a lambda. python issue4617
318 317 prefix = inst.args[0]
319 318 select = lambda c: c.lstrip('^').startswith(prefix)
320 319 rst = helplist(select)
321 320 return rst
322 321
323 322 rst = []
324 323
325 324 # check if it's an invalid alias and display its error if it is
326 325 if getattr(entry[0], 'badalias', None):
327 326 rst.append(entry[0].badalias + '\n')
328 327 if entry[0].unknowncmd:
329 328 try:
330 329 rst.extend(helpextcmd(entry[0].cmdname))
331 330 except error.UnknownCommand:
332 331 pass
333 332 return rst
334 333
335 334 # synopsis
336 335 if len(entry) > 2:
337 336 if entry[2].startswith('hg'):
338 337 rst.append("%s\n" % entry[2])
339 338 else:
340 339 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
341 340 else:
342 341 rst.append('hg %s\n' % aliases[0])
343 342 # aliases
344 343 if full and not ui.quiet and len(aliases) > 1:
345 344 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
346 345 rst.append('\n')
347 346
348 347 # description
349 348 doc = gettext(entry[0].__doc__)
350 349 if not doc:
351 350 doc = _("(no help text available)")
352 351 if util.safehasattr(entry[0], 'definition'): # aliased command
353 352 source = entry[0].source
354 353 if entry[0].definition.startswith('!'): # shell alias
355 354 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
356 355 (entry[0].definition[1:], source))
357 356 else:
358 357 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
359 358 (entry[0].definition, doc, source))
360 359 doc = doc.splitlines(True)
361 360 if ui.quiet or not full:
362 361 rst.append(doc[0])
363 362 else:
364 363 rst.extend(doc)
365 364 rst.append('\n')
366 365
367 366 # check if this command shadows a non-trivial (multi-line)
368 367 # extension help text
369 368 try:
370 369 mod = extensions.find(name)
371 370 doc = gettext(mod.__doc__) or ''
372 371 if '\n' in doc.strip():
373 372 msg = _("(use 'hg help -e %s' to show help for "
374 373 "the %s extension)") % (name, name)
375 374 rst.append('\n%s\n' % msg)
376 375 except KeyError:
377 376 pass
378 377
379 378 # options
380 379 if not ui.quiet and entry[1]:
381 380 rst.append(optrst(_("options"), entry[1], ui.verbose))
382 381
383 382 if ui.verbose:
384 383 rst.append(optrst(_("global options"),
385 384 commands.globalopts, ui.verbose))
386 385
387 386 if not ui.verbose:
388 387 if not full:
389 388 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
390 389 % name)
391 390 elif not ui.quiet:
392 391 rst.append(_('\n(some details hidden, use --verbose '
393 392 'to show complete help)'))
394 393
395 394 return rst
396 395
397 396
398 397 def helplist(select=None, **opts):
399 398 # list of commands
400 399 if name == "shortlist":
401 400 header = _('basic commands:\n\n')
402 401 elif name == "debug":
403 402 header = _('debug commands (internal and unsupported):\n\n')
404 403 else:
405 404 header = _('list of commands:\n\n')
406 405
407 406 h = {}
408 407 cmds = {}
409 408 for c, e in commands.table.iteritems():
410 409 f = c.partition("|")[0]
411 410 if select and not select(f):
412 411 continue
413 412 if (not select and name != 'shortlist' and
414 413 e[0].__module__ != commands.__name__):
415 414 continue
416 415 if name == "shortlist" and not f.startswith("^"):
417 416 continue
418 417 f = f.lstrip("^")
419 418 doc = e[0].__doc__
420 419 if filtercmd(ui, f, name, doc):
421 420 continue
422 421 doc = gettext(doc)
423 422 if not doc:
424 423 doc = _("(no help text available)")
425 424 h[f] = doc.splitlines()[0].rstrip()
426 425 cmds[f] = c.lstrip("^")
427 426
428 427 rst = []
429 428 if not h:
430 429 if not ui.quiet:
431 430 rst.append(_('no commands defined\n'))
432 431 return rst
433 432
434 433 if not ui.quiet:
435 434 rst.append(header)
436 435 fns = sorted(h)
437 436 for f in fns:
438 437 if ui.verbose:
439 438 commacmds = cmds[f].replace("|",", ")
440 439 rst.append(" :%s: %s\n" % (commacmds, h[f]))
441 440 else:
442 441 rst.append(' :%s: %s\n' % (f, h[f]))
443 442
444 443 ex = opts.get
445 444 anyopts = (ex('keyword') or not (ex('command') or ex('extension')))
446 445 if not name and anyopts:
447 446 exts = listexts(_('enabled extensions:'), extensions.enabled())
448 447 if exts:
449 448 rst.append('\n')
450 449 rst.extend(exts)
451 450
452 451 rst.append(_("\nadditional help topics:\n\n"))
453 452 topics = []
454 453 for names, header, doc in helptable:
455 454 topics.append((names[0], header))
456 455 for t, desc in topics:
457 456 rst.append(" :%s: %s\n" % (t, desc))
458 457
459 458 if ui.quiet:
460 459 pass
461 460 elif ui.verbose:
462 461 rst.append('\n%s\n' % optrst(_("global options"),
463 462 commands.globalopts, ui.verbose))
464 463 if name == 'shortlist':
465 464 rst.append(_("\n(use 'hg help' for the full list "
466 465 "of commands)\n"))
467 466 else:
468 467 if name == 'shortlist':
469 468 rst.append(_("\n(use 'hg help' for the full list of commands "
470 469 "or 'hg -v' for details)\n"))
471 470 elif name and not full:
472 471 rst.append(_("\n(use 'hg help %s' to show the full help "
473 472 "text)\n") % name)
474 473 elif name and cmds and name in cmds.keys():
475 474 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
476 475 "aliases and global options)\n") % name)
477 476 else:
478 477 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
479 478 "and global options)\n")
480 479 % (name and " " + name or ""))
481 480 return rst
482 481
483 482 def helptopic(name, subtopic=None):
484 483 # Look for sub-topic entry first.
485 484 header, doc = None, None
486 485 if subtopic and name in subtopics:
487 486 for names, header, doc in subtopics[name]:
488 487 if subtopic in names:
489 488 break
490 489
491 490 if not header:
492 491 for names, header, doc in helptable:
493 492 if name in names:
494 493 break
495 494 else:
496 495 raise error.UnknownCommand(name)
497 496
498 497 rst = [minirst.section(header)]
499 498
500 499 # description
501 500 if not doc:
502 501 rst.append(" %s\n" % _("(no help text available)"))
503 502 if callable(doc):
504 503 rst += [" %s\n" % l for l in doc(ui).splitlines()]
505 504
506 505 if not ui.verbose:
507 506 omitted = _('(some details hidden, use --verbose'
508 507 ' to show complete help)')
509 508 indicateomitted(rst, omitted)
510 509
511 510 try:
512 511 cmdutil.findcmd(name, commands.table)
513 512 rst.append(_("\nuse 'hg help -c %s' to see help for "
514 513 "the %s command\n") % (name, name))
515 514 except error.UnknownCommand:
516 515 pass
517 516 return rst
518 517
519 518 def helpext(name, subtopic=None):
520 519 try:
521 520 mod = extensions.find(name)
522 521 doc = gettext(mod.__doc__) or _('no help text available')
523 522 except KeyError:
524 523 mod = None
525 524 doc = extensions.disabledext(name)
526 525 if not doc:
527 526 raise error.UnknownCommand(name)
528 527
529 528 if '\n' not in doc:
530 529 head, tail = doc, ""
531 530 else:
532 531 head, tail = doc.split('\n', 1)
533 532 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
534 533 if tail:
535 534 rst.extend(tail.splitlines(True))
536 535 rst.append('\n')
537 536
538 537 if not ui.verbose:
539 538 omitted = _('(some details hidden, use --verbose'
540 539 ' to show complete help)')
541 540 indicateomitted(rst, omitted)
542 541
543 542 if mod:
544 543 try:
545 544 ct = mod.cmdtable
546 545 except AttributeError:
547 546 ct = {}
548 547 modcmds = set([c.partition('|')[0] for c in ct])
549 548 rst.extend(helplist(modcmds.__contains__))
550 549 else:
551 550 rst.append(_("(use 'hg help extensions' for information on enabling"
552 551 " extensions)\n"))
553 552 return rst
554 553
555 554 def helpextcmd(name, subtopic=None):
556 555 cmd, ext, mod = extensions.disabledcmd(ui, name,
557 556 ui.configbool('ui', 'strict'))
558 557 doc = gettext(mod.__doc__).splitlines()[0]
559 558
560 559 rst = listexts(_("'%s' is provided by the following "
561 560 "extension:") % cmd, {ext: doc}, indent=4,
562 561 showdeprecated=True)
563 562 rst.append('\n')
564 563 rst.append(_("(use 'hg help extensions' for information on enabling "
565 564 "extensions)\n"))
566 565 return rst
567 566
568 567
569 568 rst = []
570 569 kw = opts.get('keyword')
571 570 if kw or name is None and any(opts[o] for o in opts):
572 matches = topicmatch(ui, name or '')
571 matches = topicmatch(ui, commands, name or '')
573 572 helpareas = []
574 573 if opts.get('extension'):
575 574 helpareas += [('extensions', _('Extensions'))]
576 575 if opts.get('command'):
577 576 helpareas += [('commands', _('Commands'))]
578 577 if not helpareas:
579 578 helpareas = [('topics', _('Topics')),
580 579 ('commands', _('Commands')),
581 580 ('extensions', _('Extensions')),
582 581 ('extensioncommands', _('Extension Commands'))]
583 582 for t, title in helpareas:
584 583 if matches[t]:
585 584 rst.append('%s:\n\n' % title)
586 585 rst.extend(minirst.maketable(sorted(matches[t]), 1))
587 586 rst.append('\n')
588 587 if not rst:
589 588 msg = _('no matches')
590 589 hint = _("try 'hg help' for a list of topics")
591 590 raise error.Abort(msg, hint=hint)
592 591 elif name and name != 'shortlist':
593 592 queries = []
594 593 if unknowncmd:
595 594 queries += [helpextcmd]
596 595 if opts.get('extension'):
597 596 queries += [helpext]
598 597 if opts.get('command'):
599 598 queries += [helpcmd]
600 599 if not queries:
601 600 queries = (helptopic, helpcmd, helpext, helpextcmd)
602 601 for f in queries:
603 602 try:
604 603 rst = f(name, subtopic)
605 604 break
606 605 except error.UnknownCommand:
607 606 pass
608 607 else:
609 608 if unknowncmd:
610 609 raise error.UnknownCommand(name)
611 610 else:
612 611 msg = _('no such help topic: %s') % name
613 612 hint = _("try 'hg help --keyword %s'") % name
614 613 raise error.Abort(msg, hint=hint)
615 614 else:
616 615 # program name
617 616 if not ui.quiet:
618 617 rst = [_("Mercurial Distributed SCM\n"), '\n']
619 618 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
620 619
621 620 return ''.join(rst)
622 621
623 def formattedhelp(ui, name, keep=None, unknowncmd=False, full=True, **opts):
622 def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
623 **opts):
624 624 """get help for a given topic (as a dotted name) as rendered rst
625 625
626 626 Either returns the rendered help text or raises an exception.
627 627 """
628 628 if keep is None:
629 629 keep = []
630 630 else:
631 631 keep = list(keep) # make a copy so we can mutate this later
632 632 fullname = name
633 633 section = None
634 634 subtopic = None
635 635 if name and '.' in name:
636 636 name, remaining = name.split('.', 1)
637 637 remaining = encoding.lower(remaining)
638 638 if '.' in remaining:
639 639 subtopic, section = remaining.split('.', 1)
640 640 else:
641 641 if name in subtopics:
642 642 subtopic = remaining
643 643 else:
644 644 section = remaining
645 645 textwidth = ui.configint('ui', 'textwidth', 78)
646 646 termwidth = ui.termwidth() - 2
647 647 if textwidth <= 0 or termwidth < textwidth:
648 648 textwidth = termwidth
649 text = help_(ui, name,
649 text = help_(ui, commands, name,
650 650 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
651 651
652 652 formatted, pruned = minirst.format(text, textwidth, keep=keep,
653 653 section=section)
654 654
655 655 # We could have been given a weird ".foo" section without a name
656 656 # to look for, or we could have simply failed to found "foo.bar"
657 657 # because bar isn't a section of foo
658 658 if section and not (formatted and name):
659 659 raise error.Abort(_("help section not found: %s") % fullname)
660 660
661 661 if 'verbose' in pruned:
662 662 keep.append('omitted')
663 663 else:
664 664 keep.append('notomitted')
665 665 formatted, pruned = minirst.format(text, textwidth, keep=keep,
666 666 section=section)
667 667 return formatted
@@ -1,1383 +1,1383 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
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 cgi
11 11 import copy
12 12 import mimetypes
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, short
18 18
19 19 from .common import (
20 20 ErrorResponse,
21 21 HTTP_FORBIDDEN,
22 22 HTTP_NOT_FOUND,
23 23 HTTP_OK,
24 24 get_contact,
25 25 paritygen,
26 26 staticfile,
27 27 )
28 28
29 29 from .. import (
30 30 archival,
31 31 context,
32 32 encoding,
33 33 error,
34 34 graphmod,
35 35 revset,
36 36 revsetlang,
37 37 scmutil,
38 38 smartset,
39 39 templatefilters,
40 40 templater,
41 41 util,
42 42 )
43 43
44 44 from . import (
45 45 webutil,
46 46 )
47 47
48 48 __all__ = []
49 49 commands = {}
50 50
51 51 class webcommand(object):
52 52 """Decorator used to register a web command handler.
53 53
54 54 The decorator takes as its positional arguments the name/path the
55 55 command should be accessible under.
56 56
57 57 Usage:
58 58
59 59 @webcommand('mycommand')
60 60 def mycommand(web, req, tmpl):
61 61 pass
62 62 """
63 63
64 64 def __init__(self, name):
65 65 self.name = name
66 66
67 67 def __call__(self, func):
68 68 __all__.append(self.name)
69 69 commands[self.name] = func
70 70 return func
71 71
72 72 @webcommand('log')
73 73 def log(web, req, tmpl):
74 74 """
75 75 /log[/{revision}[/{path}]]
76 76 --------------------------
77 77
78 78 Show repository or file history.
79 79
80 80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
81 81 the specified changeset identifier is shown. If ``{revision}`` is not
82 82 defined, the default is ``tip``. This form is equivalent to the
83 83 ``changelog`` handler.
84 84
85 85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
86 86 file will be shown. This form is equivalent to the ``filelog`` handler.
87 87 """
88 88
89 89 if 'file' in req.form and req.form['file'][0]:
90 90 return filelog(web, req, tmpl)
91 91 else:
92 92 return changelog(web, req, tmpl)
93 93
94 94 @webcommand('rawfile')
95 95 def rawfile(web, req, tmpl):
96 96 guessmime = web.configbool('web', 'guessmime', False)
97 97
98 98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 99 if not path:
100 100 content = manifest(web, req, tmpl)
101 101 req.respond(HTTP_OK, web.ctype)
102 102 return content
103 103
104 104 try:
105 105 fctx = webutil.filectx(web.repo, req)
106 106 except error.LookupError as inst:
107 107 try:
108 108 content = manifest(web, req, tmpl)
109 109 req.respond(HTTP_OK, web.ctype)
110 110 return content
111 111 except ErrorResponse:
112 112 raise inst
113 113
114 114 path = fctx.path()
115 115 text = fctx.data()
116 116 mt = 'application/binary'
117 117 if guessmime:
118 118 mt = mimetypes.guess_type(path)[0]
119 119 if mt is None:
120 120 if util.binary(text):
121 121 mt = 'application/binary'
122 122 else:
123 123 mt = 'text/plain'
124 124 if mt.startswith('text/'):
125 125 mt += '; charset="%s"' % encoding.encoding
126 126
127 127 req.respond(HTTP_OK, mt, path, body=text)
128 128 return []
129 129
130 130 def _filerevision(web, req, tmpl, fctx):
131 131 f = fctx.path()
132 132 text = fctx.data()
133 133 parity = paritygen(web.stripecount)
134 134 ishead = fctx.filerev() in fctx.filelog().headrevs()
135 135
136 136 if util.binary(text):
137 137 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
138 138 text = '(binary:%s)' % mt
139 139
140 140 def lines():
141 141 for lineno, t in enumerate(text.splitlines(True)):
142 142 yield {"line": t,
143 143 "lineid": "l%d" % (lineno + 1),
144 144 "linenumber": "% 6d" % (lineno + 1),
145 145 "parity": next(parity)}
146 146
147 147 return tmpl("filerevision",
148 148 file=f,
149 149 path=webutil.up(f),
150 150 text=lines(),
151 151 symrev=webutil.symrevorshortnode(req, fctx),
152 152 rename=webutil.renamelink(fctx),
153 153 permissions=fctx.manifest().flags(f),
154 154 ishead=int(ishead),
155 155 **webutil.commonentry(web.repo, fctx))
156 156
157 157 @webcommand('file')
158 158 def file(web, req, tmpl):
159 159 """
160 160 /file/{revision}[/{path}]
161 161 -------------------------
162 162
163 163 Show information about a directory or file in the repository.
164 164
165 165 Info about the ``path`` given as a URL parameter will be rendered.
166 166
167 167 If ``path`` is a directory, information about the entries in that
168 168 directory will be rendered. This form is equivalent to the ``manifest``
169 169 handler.
170 170
171 171 If ``path`` is a file, information about that file will be shown via
172 172 the ``filerevision`` template.
173 173
174 174 If ``path`` is not defined, information about the root directory will
175 175 be rendered.
176 176 """
177 177 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
178 178 if not path:
179 179 return manifest(web, req, tmpl)
180 180 try:
181 181 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
182 182 except error.LookupError as inst:
183 183 try:
184 184 return manifest(web, req, tmpl)
185 185 except ErrorResponse:
186 186 raise inst
187 187
188 188 def _search(web, req, tmpl):
189 189 MODE_REVISION = 'rev'
190 190 MODE_KEYWORD = 'keyword'
191 191 MODE_REVSET = 'revset'
192 192
193 193 def revsearch(ctx):
194 194 yield ctx
195 195
196 196 def keywordsearch(query):
197 197 lower = encoding.lower
198 198 qw = lower(query).split()
199 199
200 200 def revgen():
201 201 cl = web.repo.changelog
202 202 for i in xrange(len(web.repo) - 1, 0, -100):
203 203 l = []
204 204 for j in cl.revs(max(0, i - 99), i):
205 205 ctx = web.repo[j]
206 206 l.append(ctx)
207 207 l.reverse()
208 208 for e in l:
209 209 yield e
210 210
211 211 for ctx in revgen():
212 212 miss = 0
213 213 for q in qw:
214 214 if not (q in lower(ctx.user()) or
215 215 q in lower(ctx.description()) or
216 216 q in lower(" ".join(ctx.files()))):
217 217 miss = 1
218 218 break
219 219 if miss:
220 220 continue
221 221
222 222 yield ctx
223 223
224 224 def revsetsearch(revs):
225 225 for r in revs:
226 226 yield web.repo[r]
227 227
228 228 searchfuncs = {
229 229 MODE_REVISION: (revsearch, 'exact revision search'),
230 230 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
231 231 MODE_REVSET: (revsetsearch, 'revset expression search'),
232 232 }
233 233
234 234 def getsearchmode(query):
235 235 try:
236 236 ctx = web.repo[query]
237 237 except (error.RepoError, error.LookupError):
238 238 # query is not an exact revision pointer, need to
239 239 # decide if it's a revset expression or keywords
240 240 pass
241 241 else:
242 242 return MODE_REVISION, ctx
243 243
244 244 revdef = 'reverse(%s)' % query
245 245 try:
246 246 tree = revsetlang.parse(revdef)
247 247 except error.ParseError:
248 248 # can't parse to a revset tree
249 249 return MODE_KEYWORD, query
250 250
251 251 if revsetlang.depth(tree) <= 2:
252 252 # no revset syntax used
253 253 return MODE_KEYWORD, query
254 254
255 255 if any((token, (value or '')[:3]) == ('string', 're:')
256 256 for token, value, pos in revsetlang.tokenize(revdef)):
257 257 return MODE_KEYWORD, query
258 258
259 259 funcsused = revsetlang.funcsused(tree)
260 260 if not funcsused.issubset(revset.safesymbols):
261 261 return MODE_KEYWORD, query
262 262
263 263 mfunc = revset.match(web.repo.ui, revdef)
264 264 try:
265 265 revs = mfunc(web.repo)
266 266 return MODE_REVSET, revs
267 267 # ParseError: wrongly placed tokens, wrongs arguments, etc
268 268 # RepoLookupError: no such revision, e.g. in 'revision:'
269 269 # Abort: bookmark/tag not exists
270 270 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
271 271 except (error.ParseError, error.RepoLookupError, error.Abort,
272 272 LookupError):
273 273 return MODE_KEYWORD, query
274 274
275 275 def changelist(**map):
276 276 count = 0
277 277
278 278 for ctx in searchfunc[0](funcarg):
279 279 count += 1
280 280 n = ctx.node()
281 281 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
282 282 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
283 283
284 284 yield tmpl('searchentry',
285 285 parity=next(parity),
286 286 changelogtag=showtags,
287 287 files=files,
288 288 **webutil.commonentry(web.repo, ctx))
289 289
290 290 if count >= revcount:
291 291 break
292 292
293 293 query = req.form['rev'][0]
294 294 revcount = web.maxchanges
295 295 if 'revcount' in req.form:
296 296 try:
297 297 revcount = int(req.form.get('revcount', [revcount])[0])
298 298 revcount = max(revcount, 1)
299 299 tmpl.defaults['sessionvars']['revcount'] = revcount
300 300 except ValueError:
301 301 pass
302 302
303 303 lessvars = copy.copy(tmpl.defaults['sessionvars'])
304 304 lessvars['revcount'] = max(revcount / 2, 1)
305 305 lessvars['rev'] = query
306 306 morevars = copy.copy(tmpl.defaults['sessionvars'])
307 307 morevars['revcount'] = revcount * 2
308 308 morevars['rev'] = query
309 309
310 310 mode, funcarg = getsearchmode(query)
311 311
312 312 if 'forcekw' in req.form:
313 313 showforcekw = ''
314 314 showunforcekw = searchfuncs[mode][1]
315 315 mode = MODE_KEYWORD
316 316 funcarg = query
317 317 else:
318 318 if mode != MODE_KEYWORD:
319 319 showforcekw = searchfuncs[MODE_KEYWORD][1]
320 320 else:
321 321 showforcekw = ''
322 322 showunforcekw = ''
323 323
324 324 searchfunc = searchfuncs[mode]
325 325
326 326 tip = web.repo['tip']
327 327 parity = paritygen(web.stripecount)
328 328
329 329 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
330 330 entries=changelist, archives=web.archivelist("tip"),
331 331 morevars=morevars, lessvars=lessvars,
332 332 modedesc=searchfunc[1],
333 333 showforcekw=showforcekw, showunforcekw=showunforcekw)
334 334
335 335 @webcommand('changelog')
336 336 def changelog(web, req, tmpl, shortlog=False):
337 337 """
338 338 /changelog[/{revision}]
339 339 -----------------------
340 340
341 341 Show information about multiple changesets.
342 342
343 343 If the optional ``revision`` URL argument is absent, information about
344 344 all changesets starting at ``tip`` will be rendered. If the ``revision``
345 345 argument is present, changesets will be shown starting from the specified
346 346 revision.
347 347
348 348 If ``revision`` is absent, the ``rev`` query string argument may be
349 349 defined. This will perform a search for changesets.
350 350
351 351 The argument for ``rev`` can be a single revision, a revision set,
352 352 or a literal keyword to search for in changeset data (equivalent to
353 353 :hg:`log -k`).
354 354
355 355 The ``revcount`` query string argument defines the maximum numbers of
356 356 changesets to render.
357 357
358 358 For non-searches, the ``changelog`` template will be rendered.
359 359 """
360 360
361 361 query = ''
362 362 if 'node' in req.form:
363 363 ctx = webutil.changectx(web.repo, req)
364 364 symrev = webutil.symrevorshortnode(req, ctx)
365 365 elif 'rev' in req.form:
366 366 return _search(web, req, tmpl)
367 367 else:
368 368 ctx = web.repo['tip']
369 369 symrev = 'tip'
370 370
371 371 def changelist():
372 372 revs = []
373 373 if pos != -1:
374 374 revs = web.repo.changelog.revs(pos, 0)
375 375 curcount = 0
376 376 for rev in revs:
377 377 curcount += 1
378 378 if curcount > revcount + 1:
379 379 break
380 380
381 381 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
382 382 entry['parity'] = next(parity)
383 383 yield entry
384 384
385 385 if shortlog:
386 386 revcount = web.maxshortchanges
387 387 else:
388 388 revcount = web.maxchanges
389 389
390 390 if 'revcount' in req.form:
391 391 try:
392 392 revcount = int(req.form.get('revcount', [revcount])[0])
393 393 revcount = max(revcount, 1)
394 394 tmpl.defaults['sessionvars']['revcount'] = revcount
395 395 except ValueError:
396 396 pass
397 397
398 398 lessvars = copy.copy(tmpl.defaults['sessionvars'])
399 399 lessvars['revcount'] = max(revcount / 2, 1)
400 400 morevars = copy.copy(tmpl.defaults['sessionvars'])
401 401 morevars['revcount'] = revcount * 2
402 402
403 403 count = len(web.repo)
404 404 pos = ctx.rev()
405 405 parity = paritygen(web.stripecount)
406 406
407 407 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
408 408
409 409 entries = list(changelist())
410 410 latestentry = entries[:1]
411 411 if len(entries) > revcount:
412 412 nextentry = entries[-1:]
413 413 entries = entries[:-1]
414 414 else:
415 415 nextentry = []
416 416
417 417 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
418 418 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
419 419 entries=entries,
420 420 latestentry=latestentry, nextentry=nextentry,
421 421 archives=web.archivelist("tip"), revcount=revcount,
422 422 morevars=morevars, lessvars=lessvars, query=query)
423 423
424 424 @webcommand('shortlog')
425 425 def shortlog(web, req, tmpl):
426 426 """
427 427 /shortlog
428 428 ---------
429 429
430 430 Show basic information about a set of changesets.
431 431
432 432 This accepts the same parameters as the ``changelog`` handler. The only
433 433 difference is the ``shortlog`` template will be rendered instead of the
434 434 ``changelog`` template.
435 435 """
436 436 return changelog(web, req, tmpl, shortlog=True)
437 437
438 438 @webcommand('changeset')
439 439 def changeset(web, req, tmpl):
440 440 """
441 441 /changeset[/{revision}]
442 442 -----------------------
443 443
444 444 Show information about a single changeset.
445 445
446 446 A URL path argument is the changeset identifier to show. See ``hg help
447 447 revisions`` for possible values. If not defined, the ``tip`` changeset
448 448 will be shown.
449 449
450 450 The ``changeset`` template is rendered. Contents of the ``changesettag``,
451 451 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
452 452 templates related to diffs may all be used to produce the output.
453 453 """
454 454 ctx = webutil.changectx(web.repo, req)
455 455
456 456 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
457 457
458 458 rev = webcommand('rev')(changeset)
459 459
460 460 def decodepath(path):
461 461 """Hook for mapping a path in the repository to a path in the
462 462 working copy.
463 463
464 464 Extensions (e.g., largefiles) can override this to remap files in
465 465 the virtual file system presented by the manifest command below."""
466 466 return path
467 467
468 468 @webcommand('manifest')
469 469 def manifest(web, req, tmpl):
470 470 """
471 471 /manifest[/{revision}[/{path}]]
472 472 -------------------------------
473 473
474 474 Show information about a directory.
475 475
476 476 If the URL path arguments are omitted, information about the root
477 477 directory for the ``tip`` changeset will be shown.
478 478
479 479 Because this handler can only show information for directories, it
480 480 is recommended to use the ``file`` handler instead, as it can handle both
481 481 directories and files.
482 482
483 483 The ``manifest`` template will be rendered for this handler.
484 484 """
485 485 if 'node' in req.form:
486 486 ctx = webutil.changectx(web.repo, req)
487 487 symrev = webutil.symrevorshortnode(req, ctx)
488 488 else:
489 489 ctx = web.repo['tip']
490 490 symrev = 'tip'
491 491 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
492 492 mf = ctx.manifest()
493 493 node = ctx.node()
494 494
495 495 files = {}
496 496 dirs = {}
497 497 parity = paritygen(web.stripecount)
498 498
499 499 if path and path[-1] != "/":
500 500 path += "/"
501 501 l = len(path)
502 502 abspath = "/" + path
503 503
504 504 for full, n in mf.iteritems():
505 505 # the virtual path (working copy path) used for the full
506 506 # (repository) path
507 507 f = decodepath(full)
508 508
509 509 if f[:l] != path:
510 510 continue
511 511 remain = f[l:]
512 512 elements = remain.split('/')
513 513 if len(elements) == 1:
514 514 files[remain] = full
515 515 else:
516 516 h = dirs # need to retain ref to dirs (root)
517 517 for elem in elements[0:-1]:
518 518 if elem not in h:
519 519 h[elem] = {}
520 520 h = h[elem]
521 521 if len(h) > 1:
522 522 break
523 523 h[None] = None # denotes files present
524 524
525 525 if mf and not files and not dirs:
526 526 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
527 527
528 528 def filelist(**map):
529 529 for f in sorted(files):
530 530 full = files[f]
531 531
532 532 fctx = ctx.filectx(full)
533 533 yield {"file": full,
534 534 "parity": next(parity),
535 535 "basename": f,
536 536 "date": fctx.date(),
537 537 "size": fctx.size(),
538 538 "permissions": mf.flags(full)}
539 539
540 540 def dirlist(**map):
541 541 for d in sorted(dirs):
542 542
543 543 emptydirs = []
544 544 h = dirs[d]
545 545 while isinstance(h, dict) and len(h) == 1:
546 546 k, v = h.items()[0]
547 547 if v:
548 548 emptydirs.append(k)
549 549 h = v
550 550
551 551 path = "%s%s" % (abspath, d)
552 552 yield {"parity": next(parity),
553 553 "path": path,
554 554 "emptydirs": "/".join(emptydirs),
555 555 "basename": d}
556 556
557 557 return tmpl("manifest",
558 558 symrev=symrev,
559 559 path=abspath,
560 560 up=webutil.up(abspath),
561 561 upparity=next(parity),
562 562 fentries=filelist,
563 563 dentries=dirlist,
564 564 archives=web.archivelist(hex(node)),
565 565 **webutil.commonentry(web.repo, ctx))
566 566
567 567 @webcommand('tags')
568 568 def tags(web, req, tmpl):
569 569 """
570 570 /tags
571 571 -----
572 572
573 573 Show information about tags.
574 574
575 575 No arguments are accepted.
576 576
577 577 The ``tags`` template is rendered.
578 578 """
579 579 i = list(reversed(web.repo.tagslist()))
580 580 parity = paritygen(web.stripecount)
581 581
582 582 def entries(notip, latestonly, **map):
583 583 t = i
584 584 if notip:
585 585 t = [(k, n) for k, n in i if k != "tip"]
586 586 if latestonly:
587 587 t = t[:1]
588 588 for k, n in t:
589 589 yield {"parity": next(parity),
590 590 "tag": k,
591 591 "date": web.repo[n].date(),
592 592 "node": hex(n)}
593 593
594 594 return tmpl("tags",
595 595 node=hex(web.repo.changelog.tip()),
596 596 entries=lambda **x: entries(False, False, **x),
597 597 entriesnotip=lambda **x: entries(True, False, **x),
598 598 latestentry=lambda **x: entries(True, True, **x))
599 599
600 600 @webcommand('bookmarks')
601 601 def bookmarks(web, req, tmpl):
602 602 """
603 603 /bookmarks
604 604 ----------
605 605
606 606 Show information about bookmarks.
607 607
608 608 No arguments are accepted.
609 609
610 610 The ``bookmarks`` template is rendered.
611 611 """
612 612 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
613 613 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
614 614 i = sorted(i, key=sortkey, reverse=True)
615 615 parity = paritygen(web.stripecount)
616 616
617 617 def entries(latestonly, **map):
618 618 t = i
619 619 if latestonly:
620 620 t = i[:1]
621 621 for k, n in t:
622 622 yield {"parity": next(parity),
623 623 "bookmark": k,
624 624 "date": web.repo[n].date(),
625 625 "node": hex(n)}
626 626
627 627 if i:
628 628 latestrev = i[0][1]
629 629 else:
630 630 latestrev = -1
631 631
632 632 return tmpl("bookmarks",
633 633 node=hex(web.repo.changelog.tip()),
634 634 lastchange=[{"date": web.repo[latestrev].date()}],
635 635 entries=lambda **x: entries(latestonly=False, **x),
636 636 latestentry=lambda **x: entries(latestonly=True, **x))
637 637
638 638 @webcommand('branches')
639 639 def branches(web, req, tmpl):
640 640 """
641 641 /branches
642 642 ---------
643 643
644 644 Show information about branches.
645 645
646 646 All known branches are contained in the output, even closed branches.
647 647
648 648 No arguments are accepted.
649 649
650 650 The ``branches`` template is rendered.
651 651 """
652 652 entries = webutil.branchentries(web.repo, web.stripecount)
653 653 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
654 654 return tmpl('branches', node=hex(web.repo.changelog.tip()),
655 655 entries=entries, latestentry=latestentry)
656 656
657 657 @webcommand('summary')
658 658 def summary(web, req, tmpl):
659 659 """
660 660 /summary
661 661 --------
662 662
663 663 Show a summary of repository state.
664 664
665 665 Information about the latest changesets, bookmarks, tags, and branches
666 666 is captured by this handler.
667 667
668 668 The ``summary`` template is rendered.
669 669 """
670 670 i = reversed(web.repo.tagslist())
671 671
672 672 def tagentries(**map):
673 673 parity = paritygen(web.stripecount)
674 674 count = 0
675 675 for k, n in i:
676 676 if k == "tip": # skip tip
677 677 continue
678 678
679 679 count += 1
680 680 if count > 10: # limit to 10 tags
681 681 break
682 682
683 683 yield tmpl("tagentry",
684 684 parity=next(parity),
685 685 tag=k,
686 686 node=hex(n),
687 687 date=web.repo[n].date())
688 688
689 689 def bookmarks(**map):
690 690 parity = paritygen(web.stripecount)
691 691 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
692 692 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
693 693 marks = sorted(marks, key=sortkey, reverse=True)
694 694 for k, n in marks[:10]: # limit to 10 bookmarks
695 695 yield {'parity': next(parity),
696 696 'bookmark': k,
697 697 'date': web.repo[n].date(),
698 698 'node': hex(n)}
699 699
700 700 def changelist(**map):
701 701 parity = paritygen(web.stripecount, offset=start - end)
702 702 l = [] # build a list in forward order for efficiency
703 703 revs = []
704 704 if start < end:
705 705 revs = web.repo.changelog.revs(start, end - 1)
706 706 for i in revs:
707 707 ctx = web.repo[i]
708 708
709 709 l.append(tmpl(
710 710 'shortlogentry',
711 711 parity=next(parity),
712 712 **webutil.commonentry(web.repo, ctx)))
713 713
714 714 for entry in reversed(l):
715 715 yield entry
716 716
717 717 tip = web.repo['tip']
718 718 count = len(web.repo)
719 719 start = max(0, count - web.maxchanges)
720 720 end = min(count, start + web.maxchanges)
721 721
722 722 return tmpl("summary",
723 723 desc=web.config("web", "description", "unknown"),
724 724 owner=get_contact(web.config) or "unknown",
725 725 lastchange=tip.date(),
726 726 tags=tagentries,
727 727 bookmarks=bookmarks,
728 728 branches=webutil.branchentries(web.repo, web.stripecount, 10),
729 729 shortlog=changelist,
730 730 node=tip.hex(),
731 731 symrev='tip',
732 732 archives=web.archivelist("tip"),
733 733 labels=web.configlist('web', 'labels'))
734 734
735 735 @webcommand('filediff')
736 736 def filediff(web, req, tmpl):
737 737 """
738 738 /diff/{revision}/{path}
739 739 -----------------------
740 740
741 741 Show how a file changed in a particular commit.
742 742
743 743 The ``filediff`` template is rendered.
744 744
745 745 This handler is registered under both the ``/diff`` and ``/filediff``
746 746 paths. ``/diff`` is used in modern code.
747 747 """
748 748 fctx, ctx = None, None
749 749 try:
750 750 fctx = webutil.filectx(web.repo, req)
751 751 except LookupError:
752 752 ctx = webutil.changectx(web.repo, req)
753 753 path = webutil.cleanpath(web.repo, req.form['file'][0])
754 754 if path not in ctx.files():
755 755 raise
756 756
757 757 if fctx is not None:
758 758 path = fctx.path()
759 759 ctx = fctx.changectx()
760 760 basectx = ctx.p1()
761 761
762 762 style = web.config('web', 'style', 'paper')
763 763 if 'style' in req.form:
764 764 style = req.form['style'][0]
765 765
766 766 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
767 767 if fctx is not None:
768 768 rename = webutil.renamelink(fctx)
769 769 ctx = fctx
770 770 else:
771 771 rename = []
772 772 ctx = ctx
773 773 return tmpl("filediff",
774 774 file=path,
775 775 symrev=webutil.symrevorshortnode(req, ctx),
776 776 rename=rename,
777 777 diff=diffs,
778 778 **webutil.commonentry(web.repo, ctx))
779 779
780 780 diff = webcommand('diff')(filediff)
781 781
782 782 @webcommand('comparison')
783 783 def comparison(web, req, tmpl):
784 784 """
785 785 /comparison/{revision}/{path}
786 786 -----------------------------
787 787
788 788 Show a comparison between the old and new versions of a file from changes
789 789 made on a particular revision.
790 790
791 791 This is similar to the ``diff`` handler. However, this form features
792 792 a split or side-by-side diff rather than a unified diff.
793 793
794 794 The ``context`` query string argument can be used to control the lines of
795 795 context in the diff.
796 796
797 797 The ``filecomparison`` template is rendered.
798 798 """
799 799 ctx = webutil.changectx(web.repo, req)
800 800 if 'file' not in req.form:
801 801 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
802 802 path = webutil.cleanpath(web.repo, req.form['file'][0])
803 803
804 804 parsecontext = lambda v: v == 'full' and -1 or int(v)
805 805 if 'context' in req.form:
806 806 context = parsecontext(req.form['context'][0])
807 807 else:
808 808 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
809 809
810 810 def filelines(f):
811 811 if f.isbinary():
812 812 mt = mimetypes.guess_type(f.path())[0]
813 813 if not mt:
814 814 mt = 'application/octet-stream'
815 815 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
816 816 return f.data().splitlines()
817 817
818 818 fctx = None
819 819 parent = ctx.p1()
820 820 leftrev = parent.rev()
821 821 leftnode = parent.node()
822 822 rightrev = ctx.rev()
823 823 rightnode = ctx.node()
824 824 if path in ctx:
825 825 fctx = ctx[path]
826 826 rightlines = filelines(fctx)
827 827 if path not in parent:
828 828 leftlines = ()
829 829 else:
830 830 pfctx = parent[path]
831 831 leftlines = filelines(pfctx)
832 832 else:
833 833 rightlines = ()
834 834 pfctx = ctx.parents()[0][path]
835 835 leftlines = filelines(pfctx)
836 836
837 837 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
838 838 if fctx is not None:
839 839 rename = webutil.renamelink(fctx)
840 840 ctx = fctx
841 841 else:
842 842 rename = []
843 843 ctx = ctx
844 844 return tmpl('filecomparison',
845 845 file=path,
846 846 symrev=webutil.symrevorshortnode(req, ctx),
847 847 rename=rename,
848 848 leftrev=leftrev,
849 849 leftnode=hex(leftnode),
850 850 rightrev=rightrev,
851 851 rightnode=hex(rightnode),
852 852 comparison=comparison,
853 853 **webutil.commonentry(web.repo, ctx))
854 854
855 855 @webcommand('annotate')
856 856 def annotate(web, req, tmpl):
857 857 """
858 858 /annotate/{revision}/{path}
859 859 ---------------------------
860 860
861 861 Show changeset information for each line in a file.
862 862
863 863 The ``fileannotate`` template is rendered.
864 864 """
865 865 fctx = webutil.filectx(web.repo, req)
866 866 f = fctx.path()
867 867 parity = paritygen(web.stripecount)
868 868
869 869 # parents() is called once per line and several lines likely belong to
870 870 # same revision. So it is worth caching.
871 871 # TODO there are still redundant operations within basefilectx.parents()
872 872 # and from the fctx.annotate() call itself that could be cached.
873 873 parentscache = {}
874 874 def parents(f):
875 875 rev = f.rev()
876 876 if rev not in parentscache:
877 877 parentscache[rev] = []
878 878 for p in f.parents():
879 879 entry = {
880 880 'node': p.hex(),
881 881 'rev': p.rev(),
882 882 }
883 883 parentscache[rev].append(entry)
884 884
885 885 for p in parentscache[rev]:
886 886 yield p
887 887
888 888 def annotate(**map):
889 889 if fctx.isbinary():
890 890 mt = (mimetypes.guess_type(fctx.path())[0]
891 891 or 'application/octet-stream')
892 892 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
893 893 else:
894 894 lines = webutil.annotate(fctx, web.repo.ui)
895 895
896 896 previousrev = None
897 897 blockparitygen = paritygen(1)
898 898 for lineno, ((f, targetline), l) in enumerate(lines):
899 899 rev = f.rev()
900 900 if rev != previousrev:
901 901 blockhead = True
902 902 blockparity = next(blockparitygen)
903 903 else:
904 904 blockhead = None
905 905 previousrev = rev
906 906 yield {"parity": next(parity),
907 907 "node": f.hex(),
908 908 "rev": rev,
909 909 "author": f.user(),
910 910 "parents": parents(f),
911 911 "desc": f.description(),
912 912 "extra": f.extra(),
913 913 "file": f.path(),
914 914 "blockhead": blockhead,
915 915 "blockparity": blockparity,
916 916 "targetline": targetline,
917 917 "line": l,
918 918 "lineno": lineno + 1,
919 919 "lineid": "l%d" % (lineno + 1),
920 920 "linenumber": "% 6d" % (lineno + 1),
921 921 "revdate": f.date()}
922 922
923 923 return tmpl("fileannotate",
924 924 file=f,
925 925 annotate=annotate,
926 926 path=webutil.up(f),
927 927 symrev=webutil.symrevorshortnode(req, fctx),
928 928 rename=webutil.renamelink(fctx),
929 929 permissions=fctx.manifest().flags(f),
930 930 **webutil.commonentry(web.repo, fctx))
931 931
932 932 @webcommand('filelog')
933 933 def filelog(web, req, tmpl):
934 934 """
935 935 /filelog/{revision}/{path}
936 936 --------------------------
937 937
938 938 Show information about the history of a file in the repository.
939 939
940 940 The ``revcount`` query string argument can be defined to control the
941 941 maximum number of entries to show.
942 942
943 943 The ``filelog`` template will be rendered.
944 944 """
945 945
946 946 try:
947 947 fctx = webutil.filectx(web.repo, req)
948 948 f = fctx.path()
949 949 fl = fctx.filelog()
950 950 except error.LookupError:
951 951 f = webutil.cleanpath(web.repo, req.form['file'][0])
952 952 fl = web.repo.file(f)
953 953 numrevs = len(fl)
954 954 if not numrevs: # file doesn't exist at all
955 955 raise
956 956 rev = webutil.changectx(web.repo, req).rev()
957 957 first = fl.linkrev(0)
958 958 if rev < first: # current rev is from before file existed
959 959 raise
960 960 frev = numrevs - 1
961 961 while fl.linkrev(frev) > rev:
962 962 frev -= 1
963 963 fctx = web.repo.filectx(f, fl.linkrev(frev))
964 964
965 965 revcount = web.maxshortchanges
966 966 if 'revcount' in req.form:
967 967 try:
968 968 revcount = int(req.form.get('revcount', [revcount])[0])
969 969 revcount = max(revcount, 1)
970 970 tmpl.defaults['sessionvars']['revcount'] = revcount
971 971 except ValueError:
972 972 pass
973 973
974 974 lrange = webutil.linerange(req)
975 975
976 976 lessvars = copy.copy(tmpl.defaults['sessionvars'])
977 977 lessvars['revcount'] = max(revcount / 2, 1)
978 978 morevars = copy.copy(tmpl.defaults['sessionvars'])
979 979 morevars['revcount'] = revcount * 2
980 980
981 981 patch = 'patch' in req.form
982 982 if patch:
983 983 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
984 984 descend = 'descend' in req.form
985 985 if descend:
986 986 lessvars['descend'] = morevars['descend'] = req.form['descend'][0]
987 987
988 988 count = fctx.filerev() + 1
989 989 start = max(0, count - revcount) # first rev on this page
990 990 end = min(count, start + revcount) # last rev on this page
991 991 parity = paritygen(web.stripecount, offset=start - end)
992 992
993 993 repo = web.repo
994 994 revs = fctx.filelog().revs(start, end - 1)
995 995 entries = []
996 996
997 997 diffstyle = web.config('web', 'style', 'paper')
998 998 if 'style' in req.form:
999 999 diffstyle = req.form['style'][0]
1000 1000
1001 1001 def diff(fctx, linerange=None):
1002 1002 ctx = fctx.changectx()
1003 1003 basectx = ctx.p1()
1004 1004 path = fctx.path()
1005 1005 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle,
1006 1006 linerange=linerange,
1007 1007 lineidprefix='%s-' % ctx.hex()[:12])
1008 1008
1009 1009 linerange = None
1010 1010 if lrange is not None:
1011 1011 linerange = webutil.formatlinerange(*lrange)
1012 1012 # deactivate numeric nav links when linerange is specified as this
1013 1013 # would required a dedicated "revnav" class
1014 1014 nav = None
1015 1015 if descend:
1016 1016 it = context.blockdescendants(fctx, *lrange)
1017 1017 else:
1018 1018 it = context.blockancestors(fctx, *lrange)
1019 1019 for i, (c, lr) in enumerate(it, 1):
1020 1020 diffs = None
1021 1021 if patch:
1022 1022 diffs = diff(c, linerange=lr)
1023 1023 # follow renames accross filtered (not in range) revisions
1024 1024 path = c.path()
1025 1025 entries.append(dict(
1026 1026 parity=next(parity),
1027 1027 filerev=c.rev(),
1028 1028 file=path,
1029 1029 diff=diffs,
1030 1030 linerange=webutil.formatlinerange(*lr),
1031 1031 **webutil.commonentry(repo, c)))
1032 1032 if i == revcount:
1033 1033 break
1034 1034 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1035 1035 morevars['linerange'] = lessvars['linerange']
1036 1036 else:
1037 1037 for i in revs:
1038 1038 iterfctx = fctx.filectx(i)
1039 1039 diffs = None
1040 1040 if patch:
1041 1041 diffs = diff(iterfctx)
1042 1042 entries.append(dict(
1043 1043 parity=next(parity),
1044 1044 filerev=i,
1045 1045 file=f,
1046 1046 diff=diffs,
1047 1047 rename=webutil.renamelink(iterfctx),
1048 1048 **webutil.commonentry(repo, iterfctx)))
1049 1049 entries.reverse()
1050 1050 revnav = webutil.filerevnav(web.repo, fctx.path())
1051 1051 nav = revnav.gen(end - 1, revcount, count)
1052 1052
1053 1053 latestentry = entries[:1]
1054 1054
1055 1055 return tmpl("filelog",
1056 1056 file=f,
1057 1057 nav=nav,
1058 1058 symrev=webutil.symrevorshortnode(req, fctx),
1059 1059 entries=entries,
1060 1060 descend=descend,
1061 1061 patch=patch,
1062 1062 latestentry=latestentry,
1063 1063 linerange=linerange,
1064 1064 revcount=revcount,
1065 1065 morevars=morevars,
1066 1066 lessvars=lessvars,
1067 1067 **webutil.commonentry(web.repo, fctx))
1068 1068
1069 1069 @webcommand('archive')
1070 1070 def archive(web, req, tmpl):
1071 1071 """
1072 1072 /archive/{revision}.{format}[/{path}]
1073 1073 -------------------------------------
1074 1074
1075 1075 Obtain an archive of repository content.
1076 1076
1077 1077 The content and type of the archive is defined by a URL path parameter.
1078 1078 ``format`` is the file extension of the archive type to be generated. e.g.
1079 1079 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1080 1080 server configuration.
1081 1081
1082 1082 The optional ``path`` URL parameter controls content to include in the
1083 1083 archive. If omitted, every file in the specified revision is present in the
1084 1084 archive. If included, only the specified file or contents of the specified
1085 1085 directory will be included in the archive.
1086 1086
1087 1087 No template is used for this handler. Raw, binary content is generated.
1088 1088 """
1089 1089
1090 1090 type_ = req.form.get('type', [None])[0]
1091 1091 allowed = web.configlist("web", "allow_archive")
1092 1092 key = req.form['node'][0]
1093 1093
1094 1094 if type_ not in web.archivespecs:
1095 1095 msg = 'Unsupported archive type: %s' % type_
1096 1096 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1097 1097
1098 1098 if not ((type_ in allowed or
1099 1099 web.configbool("web", "allow" + type_, False))):
1100 1100 msg = 'Archive type not allowed: %s' % type_
1101 1101 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1102 1102
1103 1103 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1104 1104 cnode = web.repo.lookup(key)
1105 1105 arch_version = key
1106 1106 if cnode == key or key == 'tip':
1107 1107 arch_version = short(cnode)
1108 1108 name = "%s-%s" % (reponame, arch_version)
1109 1109
1110 1110 ctx = webutil.changectx(web.repo, req)
1111 1111 pats = []
1112 1112 matchfn = scmutil.match(ctx, [])
1113 1113 file = req.form.get('file', None)
1114 1114 if file:
1115 1115 pats = ['path:' + file[0]]
1116 1116 matchfn = scmutil.match(ctx, pats, default='path')
1117 1117 if pats:
1118 1118 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1119 1119 if not files:
1120 1120 raise ErrorResponse(HTTP_NOT_FOUND,
1121 1121 'file(s) not found: %s' % file[0])
1122 1122
1123 1123 mimetype, artype, extension, encoding = web.archivespecs[type_]
1124 1124 headers = [
1125 1125 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1126 1126 ]
1127 1127 if encoding:
1128 1128 headers.append(('Content-Encoding', encoding))
1129 1129 req.headers.extend(headers)
1130 1130 req.respond(HTTP_OK, mimetype)
1131 1131
1132 1132 archival.archive(web.repo, req, cnode, artype, prefix=name,
1133 1133 matchfn=matchfn,
1134 1134 subrepos=web.configbool("web", "archivesubrepos"))
1135 1135 return []
1136 1136
1137 1137
1138 1138 @webcommand('static')
1139 1139 def static(web, req, tmpl):
1140 1140 fname = req.form['file'][0]
1141 1141 # a repo owner may set web.static in .hg/hgrc to get any file
1142 1142 # readable by the user running the CGI script
1143 1143 static = web.config("web", "static", None, untrusted=False)
1144 1144 if not static:
1145 1145 tp = web.templatepath or templater.templatepaths()
1146 1146 if isinstance(tp, str):
1147 1147 tp = [tp]
1148 1148 static = [os.path.join(p, 'static') for p in tp]
1149 1149 staticfile(static, fname, req)
1150 1150 return []
1151 1151
1152 1152 @webcommand('graph')
1153 1153 def graph(web, req, tmpl):
1154 1154 """
1155 1155 /graph[/{revision}]
1156 1156 -------------------
1157 1157
1158 1158 Show information about the graphical topology of the repository.
1159 1159
1160 1160 Information rendered by this handler can be used to create visual
1161 1161 representations of repository topology.
1162 1162
1163 1163 The ``revision`` URL parameter controls the starting changeset.
1164 1164
1165 1165 The ``revcount`` query string argument can define the number of changesets
1166 1166 to show information for.
1167 1167
1168 1168 This handler will render the ``graph`` template.
1169 1169 """
1170 1170
1171 1171 if 'node' in req.form:
1172 1172 ctx = webutil.changectx(web.repo, req)
1173 1173 symrev = webutil.symrevorshortnode(req, ctx)
1174 1174 else:
1175 1175 ctx = web.repo['tip']
1176 1176 symrev = 'tip'
1177 1177 rev = ctx.rev()
1178 1178
1179 1179 bg_height = 39
1180 1180 revcount = web.maxshortchanges
1181 1181 if 'revcount' in req.form:
1182 1182 try:
1183 1183 revcount = int(req.form.get('revcount', [revcount])[0])
1184 1184 revcount = max(revcount, 1)
1185 1185 tmpl.defaults['sessionvars']['revcount'] = revcount
1186 1186 except ValueError:
1187 1187 pass
1188 1188
1189 1189 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1190 1190 lessvars['revcount'] = max(revcount / 2, 1)
1191 1191 morevars = copy.copy(tmpl.defaults['sessionvars'])
1192 1192 morevars['revcount'] = revcount * 2
1193 1193
1194 1194 count = len(web.repo)
1195 1195 pos = rev
1196 1196
1197 1197 uprev = min(max(0, count - 1), rev + revcount)
1198 1198 downrev = max(0, rev - revcount)
1199 1199 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1200 1200
1201 1201 tree = []
1202 1202 if pos != -1:
1203 1203 allrevs = web.repo.changelog.revs(pos, 0)
1204 1204 revs = []
1205 1205 for i in allrevs:
1206 1206 revs.append(i)
1207 1207 if len(revs) >= revcount:
1208 1208 break
1209 1209
1210 1210 # We have to feed a baseset to dagwalker as it is expecting smartset
1211 1211 # object. This does not have a big impact on hgweb performance itself
1212 1212 # since hgweb graphing code is not itself lazy yet.
1213 1213 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1214 1214 # As we said one line above... not lazy.
1215 1215 tree = list(graphmod.colored(dag, web.repo))
1216 1216
1217 1217 def getcolumns(tree):
1218 1218 cols = 0
1219 1219 for (id, type, ctx, vtx, edges) in tree:
1220 1220 if type != graphmod.CHANGESET:
1221 1221 continue
1222 1222 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1223 1223 max([edge[1] for edge in edges] or [0]))
1224 1224 return cols
1225 1225
1226 1226 def graphdata(usetuples, encodestr):
1227 1227 data = []
1228 1228
1229 1229 row = 0
1230 1230 for (id, type, ctx, vtx, edges) in tree:
1231 1231 if type != graphmod.CHANGESET:
1232 1232 continue
1233 1233 node = str(ctx)
1234 1234 age = encodestr(templatefilters.age(ctx.date()))
1235 1235 desc = templatefilters.firstline(encodestr(ctx.description()))
1236 1236 desc = cgi.escape(templatefilters.nonempty(desc))
1237 1237 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1238 1238 branch = cgi.escape(encodestr(ctx.branch()))
1239 1239 try:
1240 1240 branchnode = web.repo.branchtip(branch)
1241 1241 except error.RepoLookupError:
1242 1242 branchnode = None
1243 1243 branch = branch, branchnode == ctx.node()
1244 1244
1245 1245 if usetuples:
1246 1246 data.append((node, vtx, edges, desc, user, age, branch,
1247 1247 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1248 1248 [cgi.escape(encodestr(x))
1249 1249 for x in ctx.bookmarks()]))
1250 1250 else:
1251 1251 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1252 1252 'color': (edge[2] - 1) % 6 + 1,
1253 1253 'width': edge[3], 'bcolor': edge[4]}
1254 1254 for edge in edges]
1255 1255
1256 1256 data.append(
1257 1257 {'node': node,
1258 1258 'col': vtx[0],
1259 1259 'color': (vtx[1] - 1) % 6 + 1,
1260 1260 'edges': edgedata,
1261 1261 'row': row,
1262 1262 'nextrow': row + 1,
1263 1263 'desc': desc,
1264 1264 'user': user,
1265 1265 'age': age,
1266 1266 'bookmarks': webutil.nodebookmarksdict(
1267 1267 web.repo, ctx.node()),
1268 1268 'branches': webutil.nodebranchdict(web.repo, ctx),
1269 1269 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1270 1270 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1271 1271
1272 1272 row += 1
1273 1273
1274 1274 return data
1275 1275
1276 1276 cols = getcolumns(tree)
1277 1277 rows = len(tree)
1278 1278 canvasheight = (rows + 1) * bg_height - 27
1279 1279
1280 1280 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1281 1281 uprev=uprev,
1282 1282 lessvars=lessvars, morevars=morevars, downrev=downrev,
1283 1283 cols=cols, rows=rows,
1284 1284 canvaswidth=(cols + 1) * bg_height,
1285 1285 truecanvasheight=rows * bg_height,
1286 1286 canvasheight=canvasheight, bg_height=bg_height,
1287 1287 # {jsdata} will be passed to |json, so it must be in utf-8
1288 1288 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1289 1289 nodes=lambda **x: graphdata(False, str),
1290 1290 node=ctx.hex(), changenav=changenav)
1291 1291
1292 1292 def _getdoc(e):
1293 1293 doc = e[0].__doc__
1294 1294 if doc:
1295 1295 doc = _(doc).partition('\n')[0]
1296 1296 else:
1297 1297 doc = _('(no help text available)')
1298 1298 return doc
1299 1299
1300 1300 @webcommand('help')
1301 1301 def help(web, req, tmpl):
1302 1302 """
1303 1303 /help[/{topic}]
1304 1304 ---------------
1305 1305
1306 1306 Render help documentation.
1307 1307
1308 1308 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1309 1309 is defined, that help topic will be rendered. If not, an index of
1310 1310 available help topics will be rendered.
1311 1311
1312 1312 The ``help`` template will be rendered when requesting help for a topic.
1313 1313 ``helptopics`` will be rendered for the index of help topics.
1314 1314 """
1315 1315 from .. import commands, help as helpmod # avoid cycle
1316 1316
1317 1317 topicname = req.form.get('node', [None])[0]
1318 1318 if not topicname:
1319 1319 def topics(**map):
1320 1320 for entries, summary, _doc in helpmod.helptable:
1321 1321 yield {'topic': entries[0], 'summary': summary}
1322 1322
1323 1323 early, other = [], []
1324 1324 primary = lambda s: s.partition('|')[0]
1325 1325 for c, e in commands.table.iteritems():
1326 1326 doc = _getdoc(e)
1327 1327 if 'DEPRECATED' in doc or c.startswith('debug'):
1328 1328 continue
1329 1329 cmd = primary(c)
1330 1330 if cmd.startswith('^'):
1331 1331 early.append((cmd[1:], doc))
1332 1332 else:
1333 1333 other.append((cmd, doc))
1334 1334
1335 1335 early.sort()
1336 1336 other.sort()
1337 1337
1338 1338 def earlycommands(**map):
1339 1339 for c, doc in early:
1340 1340 yield {'topic': c, 'summary': doc}
1341 1341
1342 1342 def othercommands(**map):
1343 1343 for c, doc in other:
1344 1344 yield {'topic': c, 'summary': doc}
1345 1345
1346 1346 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1347 1347 othercommands=othercommands, title='Index')
1348 1348
1349 1349 # Render an index of sub-topics.
1350 1350 if topicname in helpmod.subtopics:
1351 1351 topics = []
1352 1352 for entries, summary, _doc in helpmod.subtopics[topicname]:
1353 1353 topics.append({
1354 1354 'topic': '%s.%s' % (topicname, entries[0]),
1355 1355 'basename': entries[0],
1356 1356 'summary': summary,
1357 1357 })
1358 1358
1359 1359 return tmpl('helptopics', topics=topics, title=topicname,
1360 1360 subindex=True)
1361 1361
1362 1362 u = webutil.wsgiui.load()
1363 1363 u.verbose = True
1364 1364
1365 1365 # Render a page from a sub-topic.
1366 1366 if '.' in topicname:
1367 1367 # TODO implement support for rendering sections, like
1368 1368 # `hg help` works.
1369 1369 topic, subtopic = topicname.split('.', 1)
1370 1370 if topic not in helpmod.subtopics:
1371 1371 raise ErrorResponse(HTTP_NOT_FOUND)
1372 1372 else:
1373 1373 topic = topicname
1374 1374 subtopic = None
1375 1375
1376 1376 try:
1377 doc = helpmod.help_(u, topic, subtopic=subtopic)
1377 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1378 1378 except error.UnknownCommand:
1379 1379 raise ErrorResponse(HTTP_NOT_FOUND)
1380 1380 return tmpl('help', topic=topicname, doc=doc)
1381 1381
1382 1382 # tell hggettext to extract docstrings from these functions:
1383 1383 i18nfunctions = commands.values()
General Comments 0
You need to be logged in to leave comments. Login now