##// END OF EJS Templates
push: add --publish flag to change phase of pushed changesets...
av6 -
r40722:9b8d1ad8 default
parent child Browse files
Show More
@@ -1,6204 +1,6206 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 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 hbisect,
44 44 help,
45 45 hg,
46 46 logcmdutil,
47 47 merge as mergemod,
48 48 narrowspec,
49 49 obsolete,
50 50 obsutil,
51 51 patch,
52 52 phases,
53 53 pycompat,
54 54 rcutil,
55 55 registrar,
56 56 repair,
57 57 revsetlang,
58 58 rewriteutil,
59 59 scmutil,
60 60 server,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 templatekw,
65 65 ui as uimod,
66 66 util,
67 67 wireprotoserver,
68 68 )
69 69 from .utils import (
70 70 dateutil,
71 71 stringutil,
72 72 )
73 73
74 74 table = {}
75 75 table.update(debugcommandsmod.command._table)
76 76
77 77 command = registrar.command(table)
78 78 INTENT_READONLY = registrar.INTENT_READONLY
79 79
80 80 # common command options
81 81
82 82 globalopts = [
83 83 ('R', 'repository', '',
84 84 _('repository root directory or name of overlay bundle file'),
85 85 _('REPO')),
86 86 ('', 'cwd', '',
87 87 _('change working directory'), _('DIR')),
88 88 ('y', 'noninteractive', None,
89 89 _('do not prompt, automatically pick the first choice for all prompts')),
90 90 ('q', 'quiet', None, _('suppress output')),
91 91 ('v', 'verbose', None, _('enable additional output')),
92 92 ('', 'color', '',
93 93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 94 # and should not be translated
95 95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 96 _('TYPE')),
97 97 ('', 'config', [],
98 98 _('set/override config option (use \'section.name=value\')'),
99 99 _('CONFIG')),
100 100 ('', 'debug', None, _('enable debugging output')),
101 101 ('', 'debugger', None, _('start debugger')),
102 102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 103 _('ENCODE')),
104 104 ('', 'encodingmode', encoding.encodingmode,
105 105 _('set the charset encoding mode'), _('MODE')),
106 106 ('', 'traceback', None, _('always print a traceback on exception')),
107 107 ('', 'time', None, _('time how long the command takes')),
108 108 ('', 'profile', None, _('print command execution profile')),
109 109 ('', 'version', None, _('output version information and exit')),
110 110 ('h', 'help', None, _('display help and exit')),
111 111 ('', 'hidden', False, _('consider hidden changesets')),
112 112 ('', 'pager', 'auto',
113 113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 114 ]
115 115
116 116 dryrunopts = cmdutil.dryrunopts
117 117 remoteopts = cmdutil.remoteopts
118 118 walkopts = cmdutil.walkopts
119 119 commitopts = cmdutil.commitopts
120 120 commitopts2 = cmdutil.commitopts2
121 121 formatteropts = cmdutil.formatteropts
122 122 templateopts = cmdutil.templateopts
123 123 logopts = cmdutil.logopts
124 124 diffopts = cmdutil.diffopts
125 125 diffwsopts = cmdutil.diffwsopts
126 126 diffopts2 = cmdutil.diffopts2
127 127 mergetoolopts = cmdutil.mergetoolopts
128 128 similarityopts = cmdutil.similarityopts
129 129 subrepoopts = cmdutil.subrepoopts
130 130 debugrevlogopts = cmdutil.debugrevlogopts
131 131
132 132 # Commands start here, listed alphabetically
133 133
134 134 @command('add',
135 135 walkopts + subrepoopts + dryrunopts,
136 136 _('[OPTION]... [FILE]...'),
137 137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 138 helpbasic=True, inferrepo=True)
139 139 def add(ui, repo, *pats, **opts):
140 140 """add the specified files on the next commit
141 141
142 142 Schedule files to be version controlled and added to the
143 143 repository.
144 144
145 145 The files will be added to the repository at the next commit. To
146 146 undo an add before that, see :hg:`forget`.
147 147
148 148 If no names are given, add all files to the repository (except
149 149 files matching ``.hgignore``).
150 150
151 151 .. container:: verbose
152 152
153 153 Examples:
154 154
155 155 - New (unknown) files are added
156 156 automatically by :hg:`add`::
157 157
158 158 $ ls
159 159 foo.c
160 160 $ hg status
161 161 ? foo.c
162 162 $ hg add
163 163 adding foo.c
164 164 $ hg status
165 165 A foo.c
166 166
167 167 - Specific files to be added can be specified::
168 168
169 169 $ ls
170 170 bar.c foo.c
171 171 $ hg status
172 172 ? bar.c
173 173 ? foo.c
174 174 $ hg add bar.c
175 175 $ hg status
176 176 A bar.c
177 177 ? foo.c
178 178
179 179 Returns 0 if all files are successfully added.
180 180 """
181 181
182 182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 183 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
184 184 return rejected and 1 or 0
185 185
186 186 @command('addremove',
187 187 similarityopts + subrepoopts + walkopts + dryrunopts,
188 188 _('[OPTION]... [FILE]...'),
189 189 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 190 inferrepo=True)
191 191 def addremove(ui, repo, *pats, **opts):
192 192 """add all new files, delete all missing files
193 193
194 194 Add all new files and remove all missing files from the
195 195 repository.
196 196
197 197 Unless names are given, new files are ignored if they match any of
198 198 the patterns in ``.hgignore``. As with add, these changes take
199 199 effect at the next commit.
200 200
201 201 Use the -s/--similarity option to detect renamed files. This
202 202 option takes a percentage between 0 (disabled) and 100 (files must
203 203 be identical) as its parameter. With a parameter greater than 0,
204 204 this compares every removed file with every added file and records
205 205 those similar enough as renames. Detecting renamed files this way
206 206 can be expensive. After using this option, :hg:`status -C` can be
207 207 used to check which files were identified as moved or renamed. If
208 208 not specified, -s/--similarity defaults to 100 and only renames of
209 209 identical files are detected.
210 210
211 211 .. container:: verbose
212 212
213 213 Examples:
214 214
215 215 - A number of files (bar.c and foo.c) are new,
216 216 while foobar.c has been removed (without using :hg:`remove`)
217 217 from the repository::
218 218
219 219 $ ls
220 220 bar.c foo.c
221 221 $ hg status
222 222 ! foobar.c
223 223 ? bar.c
224 224 ? foo.c
225 225 $ hg addremove
226 226 adding bar.c
227 227 adding foo.c
228 228 removing foobar.c
229 229 $ hg status
230 230 A bar.c
231 231 A foo.c
232 232 R foobar.c
233 233
234 234 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 235 Afterwards, it was edited slightly::
236 236
237 237 $ ls
238 238 foo.c
239 239 $ hg status
240 240 ! foobar.c
241 241 ? foo.c
242 242 $ hg addremove --similarity 90
243 243 removing foobar.c
244 244 adding foo.c
245 245 recording removal of foobar.c as rename to foo.c (94% similar)
246 246 $ hg status -C
247 247 A foo.c
248 248 foobar.c
249 249 R foobar.c
250 250
251 251 Returns 0 if all files are successfully added.
252 252 """
253 253 opts = pycompat.byteskwargs(opts)
254 254 if not opts.get('similarity'):
255 255 opts['similarity'] = '100'
256 256 matcher = scmutil.match(repo[None], pats, opts)
257 257 return scmutil.addremove(repo, matcher, "", opts)
258 258
259 259 @command('annotate|blame',
260 260 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
261 261 ('', 'follow', None,
262 262 _('follow copies/renames and list the filename (DEPRECATED)')),
263 263 ('', 'no-follow', None, _("don't follow copies and renames")),
264 264 ('a', 'text', None, _('treat all files as text')),
265 265 ('u', 'user', None, _('list the author (long with -v)')),
266 266 ('f', 'file', None, _('list the filename')),
267 267 ('d', 'date', None, _('list the date (short with -q)')),
268 268 ('n', 'number', None, _('list the revision number (default)')),
269 269 ('c', 'changeset', None, _('list the changeset')),
270 270 ('l', 'line-number', None, _('show line number at the first appearance')),
271 271 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
272 272 ] + diffwsopts + walkopts + formatteropts,
273 273 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
274 274 helpcategory=command.CATEGORY_FILE_CONTENTS,
275 275 helpbasic=True, inferrepo=True)
276 276 def annotate(ui, repo, *pats, **opts):
277 277 """show changeset information by line for each file
278 278
279 279 List changes in files, showing the revision id responsible for
280 280 each line.
281 281
282 282 This command is useful for discovering when a change was made and
283 283 by whom.
284 284
285 285 If you include --file, --user, or --date, the revision number is
286 286 suppressed unless you also include --number.
287 287
288 288 Without the -a/--text option, annotate will avoid processing files
289 289 it detects as binary. With -a, annotate will annotate the file
290 290 anyway, although the results will probably be neither useful
291 291 nor desirable.
292 292
293 293 .. container:: verbose
294 294
295 295 Template:
296 296
297 297 The following keywords are supported in addition to the common template
298 298 keywords and functions. See also :hg:`help templates`.
299 299
300 300 :lines: List of lines with annotation data.
301 301 :path: String. Repository-absolute path of the specified file.
302 302
303 303 And each entry of ``{lines}`` provides the following sub-keywords in
304 304 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
305 305
306 306 :line: String. Line content.
307 307 :lineno: Integer. Line number at that revision.
308 308 :path: String. Repository-absolute path of the file at that revision.
309 309
310 310 See :hg:`help templates.operators` for the list expansion syntax.
311 311
312 312 Returns 0 on success.
313 313 """
314 314 opts = pycompat.byteskwargs(opts)
315 315 if not pats:
316 316 raise error.Abort(_('at least one filename or pattern is required'))
317 317
318 318 if opts.get('follow'):
319 319 # --follow is deprecated and now just an alias for -f/--file
320 320 # to mimic the behavior of Mercurial before version 1.5
321 321 opts['file'] = True
322 322
323 323 rev = opts.get('rev')
324 324 if rev:
325 325 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
326 326 ctx = scmutil.revsingle(repo, rev)
327 327
328 328 rootfm = ui.formatter('annotate', opts)
329 329 if ui.debugflag:
330 330 shorthex = pycompat.identity
331 331 else:
332 332 def shorthex(h):
333 333 return h[:12]
334 334 if ui.quiet:
335 335 datefunc = dateutil.shortdate
336 336 else:
337 337 datefunc = dateutil.datestr
338 338 if ctx.rev() is None:
339 339 if opts.get('changeset'):
340 340 # omit "+" suffix which is appended to node hex
341 341 def formatrev(rev):
342 342 if rev == wdirrev:
343 343 return '%d' % ctx.p1().rev()
344 344 else:
345 345 return '%d' % rev
346 346 else:
347 347 def formatrev(rev):
348 348 if rev == wdirrev:
349 349 return '%d+' % ctx.p1().rev()
350 350 else:
351 351 return '%d ' % rev
352 352 def formathex(h):
353 353 if h == wdirhex:
354 354 return '%s+' % shorthex(hex(ctx.p1().node()))
355 355 else:
356 356 return '%s ' % shorthex(h)
357 357 else:
358 358 formatrev = b'%d'.__mod__
359 359 formathex = shorthex
360 360
361 361 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
362 362 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
363 363 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
364 364 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
365 365 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
366 366 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
367 367 ]
368 368 opnamemap = {'rev': 'number', 'node': 'changeset', 'path': 'file',
369 369 'lineno': 'line_number'}
370 370
371 371 if (not opts.get('user') and not opts.get('changeset')
372 372 and not opts.get('date') and not opts.get('file')):
373 373 opts['number'] = True
374 374
375 375 linenumber = opts.get('line_number') is not None
376 376 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
377 377 raise error.Abort(_('at least one of -n/-c is required for -l'))
378 378
379 379 ui.pager('annotate')
380 380
381 381 if rootfm.isplain():
382 382 def makefunc(get, fmt):
383 383 return lambda x: fmt(get(x))
384 384 else:
385 385 def makefunc(get, fmt):
386 386 return get
387 387 datahint = rootfm.datahint()
388 388 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
389 389 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
390 390 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
391 391 fields = ' '.join(fn for fn, sep, get, fmt in opmap
392 392 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
393 393
394 394 def bad(x, y):
395 395 raise error.Abort("%s: %s" % (x, y))
396 396
397 397 m = scmutil.match(ctx, pats, opts, badfn=bad)
398 398
399 399 follow = not opts.get('no_follow')
400 400 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
401 401 whitespace=True)
402 402 skiprevs = opts.get('skip')
403 403 if skiprevs:
404 404 skiprevs = scmutil.revrange(repo, skiprevs)
405 405
406 406 for abs in ctx.walk(m):
407 407 fctx = ctx[abs]
408 408 rootfm.startitem()
409 409 rootfm.data(path=abs)
410 410 if not opts.get('text') and fctx.isbinary():
411 411 rootfm.plain(_("%s: binary file\n")
412 412 % ((pats and m.rel(abs)) or abs))
413 413 continue
414 414
415 415 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
416 416 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
417 417 diffopts=diffopts)
418 418 if not lines:
419 419 fm.end()
420 420 continue
421 421 formats = []
422 422 pieces = []
423 423
424 424 for f, sep in funcmap:
425 425 l = [f(n) for n in lines]
426 426 if fm.isplain():
427 427 sizes = [encoding.colwidth(x) for x in l]
428 428 ml = max(sizes)
429 429 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
430 430 else:
431 431 formats.append(['%s' for x in l])
432 432 pieces.append(l)
433 433
434 434 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
435 435 fm.startitem()
436 436 fm.context(fctx=n.fctx)
437 437 fm.write(fields, "".join(f), *p)
438 438 if n.skip:
439 439 fmt = "* %s"
440 440 else:
441 441 fmt = ": %s"
442 442 fm.write('line', fmt, n.text)
443 443
444 444 if not lines[-1].text.endswith('\n'):
445 445 fm.plain('\n')
446 446 fm.end()
447 447
448 448 rootfm.end()
449 449
450 450 @command('archive',
451 451 [('', 'no-decode', None, _('do not pass files through decoders')),
452 452 ('p', 'prefix', '', _('directory prefix for files in archive'),
453 453 _('PREFIX')),
454 454 ('r', 'rev', '', _('revision to distribute'), _('REV')),
455 455 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
456 456 ] + subrepoopts + walkopts,
457 457 _('[OPTION]... DEST'),
458 458 helpcategory=command.CATEGORY_IMPORT_EXPORT)
459 459 def archive(ui, repo, dest, **opts):
460 460 '''create an unversioned archive of a repository revision
461 461
462 462 By default, the revision used is the parent of the working
463 463 directory; use -r/--rev to specify a different revision.
464 464
465 465 The archive type is automatically detected based on file
466 466 extension (to override, use -t/--type).
467 467
468 468 .. container:: verbose
469 469
470 470 Examples:
471 471
472 472 - create a zip file containing the 1.0 release::
473 473
474 474 hg archive -r 1.0 project-1.0.zip
475 475
476 476 - create a tarball excluding .hg files::
477 477
478 478 hg archive project.tar.gz -X ".hg*"
479 479
480 480 Valid types are:
481 481
482 482 :``files``: a directory full of files (default)
483 483 :``tar``: tar archive, uncompressed
484 484 :``tbz2``: tar archive, compressed using bzip2
485 485 :``tgz``: tar archive, compressed using gzip
486 486 :``uzip``: zip archive, uncompressed
487 487 :``zip``: zip archive, compressed using deflate
488 488
489 489 The exact name of the destination archive or directory is given
490 490 using a format string; see :hg:`help export` for details.
491 491
492 492 Each member added to an archive file has a directory prefix
493 493 prepended. Use -p/--prefix to specify a format string for the
494 494 prefix. The default is the basename of the archive, with suffixes
495 495 removed.
496 496
497 497 Returns 0 on success.
498 498 '''
499 499
500 500 opts = pycompat.byteskwargs(opts)
501 501 rev = opts.get('rev')
502 502 if rev:
503 503 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
504 504 ctx = scmutil.revsingle(repo, rev)
505 505 if not ctx:
506 506 raise error.Abort(_('no working directory: please specify a revision'))
507 507 node = ctx.node()
508 508 dest = cmdutil.makefilename(ctx, dest)
509 509 if os.path.realpath(dest) == repo.root:
510 510 raise error.Abort(_('repository root cannot be destination'))
511 511
512 512 kind = opts.get('type') or archival.guesskind(dest) or 'files'
513 513 prefix = opts.get('prefix')
514 514
515 515 if dest == '-':
516 516 if kind == 'files':
517 517 raise error.Abort(_('cannot archive plain files to stdout'))
518 518 dest = cmdutil.makefileobj(ctx, dest)
519 519 if not prefix:
520 520 prefix = os.path.basename(repo.root) + '-%h'
521 521
522 522 prefix = cmdutil.makefilename(ctx, prefix)
523 523 match = scmutil.match(ctx, [], opts)
524 524 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
525 525 match, prefix, subrepos=opts.get('subrepos'))
526 526
527 527 @command('backout',
528 528 [('', 'merge', None, _('merge with old dirstate parent after backout')),
529 529 ('', 'commit', None,
530 530 _('commit if no conflicts were encountered (DEPRECATED)')),
531 531 ('', 'no-commit', None, _('do not commit')),
532 532 ('', 'parent', '',
533 533 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
534 534 ('r', 'rev', '', _('revision to backout'), _('REV')),
535 535 ('e', 'edit', False, _('invoke editor on commit messages')),
536 536 ] + mergetoolopts + walkopts + commitopts + commitopts2,
537 537 _('[OPTION]... [-r] REV'),
538 538 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
539 539 def backout(ui, repo, node=None, rev=None, **opts):
540 540 '''reverse effect of earlier changeset
541 541
542 542 Prepare a new changeset with the effect of REV undone in the
543 543 current working directory. If no conflicts were encountered,
544 544 it will be committed immediately.
545 545
546 546 If REV is the parent of the working directory, then this new changeset
547 547 is committed automatically (unless --no-commit is specified).
548 548
549 549 .. note::
550 550
551 551 :hg:`backout` cannot be used to fix either an unwanted or
552 552 incorrect merge.
553 553
554 554 .. container:: verbose
555 555
556 556 Examples:
557 557
558 558 - Reverse the effect of the parent of the working directory.
559 559 This backout will be committed immediately::
560 560
561 561 hg backout -r .
562 562
563 563 - Reverse the effect of previous bad revision 23::
564 564
565 565 hg backout -r 23
566 566
567 567 - Reverse the effect of previous bad revision 23 and
568 568 leave changes uncommitted::
569 569
570 570 hg backout -r 23 --no-commit
571 571 hg commit -m "Backout revision 23"
572 572
573 573 By default, the pending changeset will have one parent,
574 574 maintaining a linear history. With --merge, the pending
575 575 changeset will instead have two parents: the old parent of the
576 576 working directory and a new child of REV that simply undoes REV.
577 577
578 578 Before version 1.7, the behavior without --merge was equivalent
579 579 to specifying --merge followed by :hg:`update --clean .` to
580 580 cancel the merge and leave the child of REV as a head to be
581 581 merged separately.
582 582
583 583 See :hg:`help dates` for a list of formats valid for -d/--date.
584 584
585 585 See :hg:`help revert` for a way to restore files to the state
586 586 of another revision.
587 587
588 588 Returns 0 on success, 1 if nothing to backout or there are unresolved
589 589 files.
590 590 '''
591 591 with repo.wlock(), repo.lock():
592 592 return _dobackout(ui, repo, node, rev, **opts)
593 593
594 594 def _dobackout(ui, repo, node=None, rev=None, **opts):
595 595 opts = pycompat.byteskwargs(opts)
596 596 if opts.get('commit') and opts.get('no_commit'):
597 597 raise error.Abort(_("cannot use --commit with --no-commit"))
598 598 if opts.get('merge') and opts.get('no_commit'):
599 599 raise error.Abort(_("cannot use --merge with --no-commit"))
600 600
601 601 if rev and node:
602 602 raise error.Abort(_("please specify just one revision"))
603 603
604 604 if not rev:
605 605 rev = node
606 606
607 607 if not rev:
608 608 raise error.Abort(_("please specify a revision to backout"))
609 609
610 610 date = opts.get('date')
611 611 if date:
612 612 opts['date'] = dateutil.parsedate(date)
613 613
614 614 cmdutil.checkunfinished(repo)
615 615 cmdutil.bailifchanged(repo)
616 616 node = scmutil.revsingle(repo, rev).node()
617 617
618 618 op1, op2 = repo.dirstate.parents()
619 619 if not repo.changelog.isancestor(node, op1):
620 620 raise error.Abort(_('cannot backout change that is not an ancestor'))
621 621
622 622 p1, p2 = repo.changelog.parents(node)
623 623 if p1 == nullid:
624 624 raise error.Abort(_('cannot backout a change with no parents'))
625 625 if p2 != nullid:
626 626 if not opts.get('parent'):
627 627 raise error.Abort(_('cannot backout a merge changeset'))
628 628 p = repo.lookup(opts['parent'])
629 629 if p not in (p1, p2):
630 630 raise error.Abort(_('%s is not a parent of %s') %
631 631 (short(p), short(node)))
632 632 parent = p
633 633 else:
634 634 if opts.get('parent'):
635 635 raise error.Abort(_('cannot use --parent on non-merge changeset'))
636 636 parent = p1
637 637
638 638 # the backout should appear on the same branch
639 639 branch = repo.dirstate.branch()
640 640 bheads = repo.branchheads(branch)
641 641 rctx = scmutil.revsingle(repo, hex(parent))
642 642 if not opts.get('merge') and op1 != node:
643 643 with dirstateguard.dirstateguard(repo, 'backout'):
644 644 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
645 645 with ui.configoverride(overrides, 'backout'):
646 646 stats = mergemod.update(repo, parent, branchmerge=True,
647 647 force=True, ancestor=node,
648 648 mergeancestor=False)
649 649 repo.setparents(op1, op2)
650 650 hg._showstats(repo, stats)
651 651 if stats.unresolvedcount:
652 652 repo.ui.status(_("use 'hg resolve' to retry unresolved "
653 653 "file merges\n"))
654 654 return 1
655 655 else:
656 656 hg.clean(repo, node, show_stats=False)
657 657 repo.dirstate.setbranch(branch)
658 658 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
659 659
660 660 if opts.get('no_commit'):
661 661 msg = _("changeset %s backed out, "
662 662 "don't forget to commit.\n")
663 663 ui.status(msg % short(node))
664 664 return 0
665 665
666 666 def commitfunc(ui, repo, message, match, opts):
667 667 editform = 'backout'
668 668 e = cmdutil.getcommiteditor(editform=editform,
669 669 **pycompat.strkwargs(opts))
670 670 if not message:
671 671 # we don't translate commit messages
672 672 message = "Backed out changeset %s" % short(node)
673 673 e = cmdutil.getcommiteditor(edit=True, editform=editform)
674 674 return repo.commit(message, opts.get('user'), opts.get('date'),
675 675 match, editor=e)
676 676 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
677 677 if not newnode:
678 678 ui.status(_("nothing changed\n"))
679 679 return 1
680 680 cmdutil.commitstatus(repo, newnode, branch, bheads)
681 681
682 682 def nice(node):
683 683 return '%d:%s' % (repo.changelog.rev(node), short(node))
684 684 ui.status(_('changeset %s backs out changeset %s\n') %
685 685 (nice(repo.changelog.tip()), nice(node)))
686 686 if opts.get('merge') and op1 != node:
687 687 hg.clean(repo, op1, show_stats=False)
688 688 ui.status(_('merging with changeset %s\n')
689 689 % nice(repo.changelog.tip()))
690 690 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
691 691 with ui.configoverride(overrides, 'backout'):
692 692 return hg.merge(repo, hex(repo.changelog.tip()))
693 693 return 0
694 694
695 695 @command('bisect',
696 696 [('r', 'reset', False, _('reset bisect state')),
697 697 ('g', 'good', False, _('mark changeset good')),
698 698 ('b', 'bad', False, _('mark changeset bad')),
699 699 ('s', 'skip', False, _('skip testing changeset')),
700 700 ('e', 'extend', False, _('extend the bisect range')),
701 701 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
702 702 ('U', 'noupdate', False, _('do not update to target'))],
703 703 _("[-gbsr] [-U] [-c CMD] [REV]"),
704 704 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
705 705 def bisect(ui, repo, rev=None, extra=None, command=None,
706 706 reset=None, good=None, bad=None, skip=None, extend=None,
707 707 noupdate=None):
708 708 """subdivision search of changesets
709 709
710 710 This command helps to find changesets which introduce problems. To
711 711 use, mark the earliest changeset you know exhibits the problem as
712 712 bad, then mark the latest changeset which is free from the problem
713 713 as good. Bisect will update your working directory to a revision
714 714 for testing (unless the -U/--noupdate option is specified). Once
715 715 you have performed tests, mark the working directory as good or
716 716 bad, and bisect will either update to another candidate changeset
717 717 or announce that it has found the bad revision.
718 718
719 719 As a shortcut, you can also use the revision argument to mark a
720 720 revision as good or bad without checking it out first.
721 721
722 722 If you supply a command, it will be used for automatic bisection.
723 723 The environment variable HG_NODE will contain the ID of the
724 724 changeset being tested. The exit status of the command will be
725 725 used to mark revisions as good or bad: status 0 means good, 125
726 726 means to skip the revision, 127 (command not found) will abort the
727 727 bisection, and any other non-zero exit status means the revision
728 728 is bad.
729 729
730 730 .. container:: verbose
731 731
732 732 Some examples:
733 733
734 734 - start a bisection with known bad revision 34, and good revision 12::
735 735
736 736 hg bisect --bad 34
737 737 hg bisect --good 12
738 738
739 739 - advance the current bisection by marking current revision as good or
740 740 bad::
741 741
742 742 hg bisect --good
743 743 hg bisect --bad
744 744
745 745 - mark the current revision, or a known revision, to be skipped (e.g. if
746 746 that revision is not usable because of another issue)::
747 747
748 748 hg bisect --skip
749 749 hg bisect --skip 23
750 750
751 751 - skip all revisions that do not touch directories ``foo`` or ``bar``::
752 752
753 753 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
754 754
755 755 - forget the current bisection::
756 756
757 757 hg bisect --reset
758 758
759 759 - use 'make && make tests' to automatically find the first broken
760 760 revision::
761 761
762 762 hg bisect --reset
763 763 hg bisect --bad 34
764 764 hg bisect --good 12
765 765 hg bisect --command "make && make tests"
766 766
767 767 - see all changesets whose states are already known in the current
768 768 bisection::
769 769
770 770 hg log -r "bisect(pruned)"
771 771
772 772 - see the changeset currently being bisected (especially useful
773 773 if running with -U/--noupdate)::
774 774
775 775 hg log -r "bisect(current)"
776 776
777 777 - see all changesets that took part in the current bisection::
778 778
779 779 hg log -r "bisect(range)"
780 780
781 781 - you can even get a nice graph::
782 782
783 783 hg log --graph -r "bisect(range)"
784 784
785 785 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
786 786
787 787 Returns 0 on success.
788 788 """
789 789 # backward compatibility
790 790 if rev in "good bad reset init".split():
791 791 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
792 792 cmd, rev, extra = rev, extra, None
793 793 if cmd == "good":
794 794 good = True
795 795 elif cmd == "bad":
796 796 bad = True
797 797 else:
798 798 reset = True
799 799 elif extra:
800 800 raise error.Abort(_('incompatible arguments'))
801 801
802 802 incompatibles = {
803 803 '--bad': bad,
804 804 '--command': bool(command),
805 805 '--extend': extend,
806 806 '--good': good,
807 807 '--reset': reset,
808 808 '--skip': skip,
809 809 }
810 810
811 811 enabled = [x for x in incompatibles if incompatibles[x]]
812 812
813 813 if len(enabled) > 1:
814 814 raise error.Abort(_('%s and %s are incompatible') %
815 815 tuple(sorted(enabled)[0:2]))
816 816
817 817 if reset:
818 818 hbisect.resetstate(repo)
819 819 return
820 820
821 821 state = hbisect.load_state(repo)
822 822
823 823 # update state
824 824 if good or bad or skip:
825 825 if rev:
826 826 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
827 827 else:
828 828 nodes = [repo.lookup('.')]
829 829 if good:
830 830 state['good'] += nodes
831 831 elif bad:
832 832 state['bad'] += nodes
833 833 elif skip:
834 834 state['skip'] += nodes
835 835 hbisect.save_state(repo, state)
836 836 if not (state['good'] and state['bad']):
837 837 return
838 838
839 839 def mayupdate(repo, node, show_stats=True):
840 840 """common used update sequence"""
841 841 if noupdate:
842 842 return
843 843 cmdutil.checkunfinished(repo)
844 844 cmdutil.bailifchanged(repo)
845 845 return hg.clean(repo, node, show_stats=show_stats)
846 846
847 847 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
848 848
849 849 if command:
850 850 changesets = 1
851 851 if noupdate:
852 852 try:
853 853 node = state['current'][0]
854 854 except LookupError:
855 855 raise error.Abort(_('current bisect revision is unknown - '
856 856 'start a new bisect to fix'))
857 857 else:
858 858 node, p2 = repo.dirstate.parents()
859 859 if p2 != nullid:
860 860 raise error.Abort(_('current bisect revision is a merge'))
861 861 if rev:
862 862 node = repo[scmutil.revsingle(repo, rev, node)].node()
863 863 try:
864 864 while changesets:
865 865 # update state
866 866 state['current'] = [node]
867 867 hbisect.save_state(repo, state)
868 868 status = ui.system(command, environ={'HG_NODE': hex(node)},
869 869 blockedtag='bisect_check')
870 870 if status == 125:
871 871 transition = "skip"
872 872 elif status == 0:
873 873 transition = "good"
874 874 # status < 0 means process was killed
875 875 elif status == 127:
876 876 raise error.Abort(_("failed to execute %s") % command)
877 877 elif status < 0:
878 878 raise error.Abort(_("%s killed") % command)
879 879 else:
880 880 transition = "bad"
881 881 state[transition].append(node)
882 882 ctx = repo[node]
883 883 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
884 884 transition))
885 885 hbisect.checkstate(state)
886 886 # bisect
887 887 nodes, changesets, bgood = hbisect.bisect(repo, state)
888 888 # update to next check
889 889 node = nodes[0]
890 890 mayupdate(repo, node, show_stats=False)
891 891 finally:
892 892 state['current'] = [node]
893 893 hbisect.save_state(repo, state)
894 894 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
895 895 return
896 896
897 897 hbisect.checkstate(state)
898 898
899 899 # actually bisect
900 900 nodes, changesets, good = hbisect.bisect(repo, state)
901 901 if extend:
902 902 if not changesets:
903 903 extendnode = hbisect.extendrange(repo, state, nodes, good)
904 904 if extendnode is not None:
905 905 ui.write(_("Extending search to changeset %d:%s\n")
906 906 % (extendnode.rev(), extendnode))
907 907 state['current'] = [extendnode.node()]
908 908 hbisect.save_state(repo, state)
909 909 return mayupdate(repo, extendnode.node())
910 910 raise error.Abort(_("nothing to extend"))
911 911
912 912 if changesets == 0:
913 913 hbisect.printresult(ui, repo, state, displayer, nodes, good)
914 914 else:
915 915 assert len(nodes) == 1 # only a single node can be tested next
916 916 node = nodes[0]
917 917 # compute the approximate number of remaining tests
918 918 tests, size = 0, 2
919 919 while size <= changesets:
920 920 tests, size = tests + 1, size * 2
921 921 rev = repo.changelog.rev(node)
922 922 ui.write(_("Testing changeset %d:%s "
923 923 "(%d changesets remaining, ~%d tests)\n")
924 924 % (rev, short(node), changesets, tests))
925 925 state['current'] = [node]
926 926 hbisect.save_state(repo, state)
927 927 return mayupdate(repo, node)
928 928
929 929 @command('bookmarks|bookmark',
930 930 [('f', 'force', False, _('force')),
931 931 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
932 932 ('d', 'delete', False, _('delete a given bookmark')),
933 933 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
934 934 ('i', 'inactive', False, _('mark a bookmark inactive')),
935 935 ('l', 'list', False, _('list existing bookmarks')),
936 936 ] + formatteropts,
937 937 _('hg bookmarks [OPTIONS]... [NAME]...'),
938 938 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
939 939 def bookmark(ui, repo, *names, **opts):
940 940 '''create a new bookmark or list existing bookmarks
941 941
942 942 Bookmarks are labels on changesets to help track lines of development.
943 943 Bookmarks are unversioned and can be moved, renamed and deleted.
944 944 Deleting or moving a bookmark has no effect on the associated changesets.
945 945
946 946 Creating or updating to a bookmark causes it to be marked as 'active'.
947 947 The active bookmark is indicated with a '*'.
948 948 When a commit is made, the active bookmark will advance to the new commit.
949 949 A plain :hg:`update` will also advance an active bookmark, if possible.
950 950 Updating away from a bookmark will cause it to be deactivated.
951 951
952 952 Bookmarks can be pushed and pulled between repositories (see
953 953 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
954 954 diverged, a new 'divergent bookmark' of the form 'name@path' will
955 955 be created. Using :hg:`merge` will resolve the divergence.
956 956
957 957 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
958 958 the active bookmark's name.
959 959
960 960 A bookmark named '@' has the special property that :hg:`clone` will
961 961 check it out by default if it exists.
962 962
963 963 .. container:: verbose
964 964
965 965 Template:
966 966
967 967 The following keywords are supported in addition to the common template
968 968 keywords and functions such as ``{bookmark}``. See also
969 969 :hg:`help templates`.
970 970
971 971 :active: Boolean. True if the bookmark is active.
972 972
973 973 Examples:
974 974
975 975 - create an active bookmark for a new line of development::
976 976
977 977 hg book new-feature
978 978
979 979 - create an inactive bookmark as a place marker::
980 980
981 981 hg book -i reviewed
982 982
983 983 - create an inactive bookmark on another changeset::
984 984
985 985 hg book -r .^ tested
986 986
987 987 - rename bookmark turkey to dinner::
988 988
989 989 hg book -m turkey dinner
990 990
991 991 - move the '@' bookmark from another branch::
992 992
993 993 hg book -f @
994 994
995 995 - print only the active bookmark name::
996 996
997 997 hg book -ql .
998 998 '''
999 999 opts = pycompat.byteskwargs(opts)
1000 1000 force = opts.get('force')
1001 1001 rev = opts.get('rev')
1002 1002 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1003 1003
1004 1004 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1005 1005 if len(selactions) > 1:
1006 1006 raise error.Abort(_('--%s and --%s are incompatible')
1007 1007 % tuple(selactions[:2]))
1008 1008 if selactions:
1009 1009 action = selactions[0]
1010 1010 elif names or rev:
1011 1011 action = 'add'
1012 1012 elif inactive:
1013 1013 action = 'inactive' # meaning deactivate
1014 1014 else:
1015 1015 action = 'list'
1016 1016
1017 1017 if rev and action in {'delete', 'rename', 'list'}:
1018 1018 raise error.Abort(_("--rev is incompatible with --%s") % action)
1019 1019 if inactive and action in {'delete', 'list'}:
1020 1020 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1021 1021 if not names and action in {'add', 'delete'}:
1022 1022 raise error.Abort(_("bookmark name required"))
1023 1023
1024 1024 if action in {'add', 'delete', 'rename', 'inactive'}:
1025 1025 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1026 1026 if action == 'delete':
1027 1027 names = pycompat.maplist(repo._bookmarks.expandname, names)
1028 1028 bookmarks.delete(repo, tr, names)
1029 1029 elif action == 'rename':
1030 1030 if not names:
1031 1031 raise error.Abort(_("new bookmark name required"))
1032 1032 elif len(names) > 1:
1033 1033 raise error.Abort(_("only one new bookmark name allowed"))
1034 1034 oldname = repo._bookmarks.expandname(opts['rename'])
1035 1035 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1036 1036 elif action == 'add':
1037 1037 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1038 1038 elif action == 'inactive':
1039 1039 if len(repo._bookmarks) == 0:
1040 1040 ui.status(_("no bookmarks set\n"))
1041 1041 elif not repo._activebookmark:
1042 1042 ui.status(_("no active bookmark\n"))
1043 1043 else:
1044 1044 bookmarks.deactivate(repo)
1045 1045 elif action == 'list':
1046 1046 names = pycompat.maplist(repo._bookmarks.expandname, names)
1047 1047 with ui.formatter('bookmarks', opts) as fm:
1048 1048 bookmarks.printbookmarks(ui, repo, fm, names)
1049 1049 else:
1050 1050 raise error.ProgrammingError('invalid action: %s' % action)
1051 1051
1052 1052 @command('branch',
1053 1053 [('f', 'force', None,
1054 1054 _('set branch name even if it shadows an existing branch')),
1055 1055 ('C', 'clean', None, _('reset branch name to parent branch name')),
1056 1056 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1057 1057 ],
1058 1058 _('[-fC] [NAME]'),
1059 1059 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1060 1060 def branch(ui, repo, label=None, **opts):
1061 1061 """set or show the current branch name
1062 1062
1063 1063 .. note::
1064 1064
1065 1065 Branch names are permanent and global. Use :hg:`bookmark` to create a
1066 1066 light-weight bookmark instead. See :hg:`help glossary` for more
1067 1067 information about named branches and bookmarks.
1068 1068
1069 1069 With no argument, show the current branch name. With one argument,
1070 1070 set the working directory branch name (the branch will not exist
1071 1071 in the repository until the next commit). Standard practice
1072 1072 recommends that primary development take place on the 'default'
1073 1073 branch.
1074 1074
1075 1075 Unless -f/--force is specified, branch will not let you set a
1076 1076 branch name that already exists.
1077 1077
1078 1078 Use -C/--clean to reset the working directory branch to that of
1079 1079 the parent of the working directory, negating a previous branch
1080 1080 change.
1081 1081
1082 1082 Use the command :hg:`update` to switch to an existing branch. Use
1083 1083 :hg:`commit --close-branch` to mark this branch head as closed.
1084 1084 When all heads of a branch are closed, the branch will be
1085 1085 considered closed.
1086 1086
1087 1087 Returns 0 on success.
1088 1088 """
1089 1089 opts = pycompat.byteskwargs(opts)
1090 1090 revs = opts.get('rev')
1091 1091 if label:
1092 1092 label = label.strip()
1093 1093
1094 1094 if not opts.get('clean') and not label:
1095 1095 if revs:
1096 1096 raise error.Abort(_("no branch name specified for the revisions"))
1097 1097 ui.write("%s\n" % repo.dirstate.branch())
1098 1098 return
1099 1099
1100 1100 with repo.wlock():
1101 1101 if opts.get('clean'):
1102 1102 label = repo[None].p1().branch()
1103 1103 repo.dirstate.setbranch(label)
1104 1104 ui.status(_('reset working directory to branch %s\n') % label)
1105 1105 elif label:
1106 1106
1107 1107 scmutil.checknewlabel(repo, label, 'branch')
1108 1108 if revs:
1109 1109 return cmdutil.changebranch(ui, repo, revs, label)
1110 1110
1111 1111 if not opts.get('force') and label in repo.branchmap():
1112 1112 if label not in [p.branch() for p in repo[None].parents()]:
1113 1113 raise error.Abort(_('a branch of the same name already'
1114 1114 ' exists'),
1115 1115 # i18n: "it" refers to an existing branch
1116 1116 hint=_("use 'hg update' to switch to it"))
1117 1117
1118 1118 repo.dirstate.setbranch(label)
1119 1119 ui.status(_('marked working directory as branch %s\n') % label)
1120 1120
1121 1121 # find any open named branches aside from default
1122 1122 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1123 1123 if n != "default" and not c]
1124 1124 if not others:
1125 1125 ui.status(_('(branches are permanent and global, '
1126 1126 'did you want a bookmark?)\n'))
1127 1127
1128 1128 @command('branches',
1129 1129 [('a', 'active', False,
1130 1130 _('show only branches that have unmerged heads (DEPRECATED)')),
1131 1131 ('c', 'closed', False, _('show normal and closed branches')),
1132 1132 ] + formatteropts,
1133 1133 _('[-c]'),
1134 1134 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1135 1135 intents={INTENT_READONLY})
1136 1136 def branches(ui, repo, active=False, closed=False, **opts):
1137 1137 """list repository named branches
1138 1138
1139 1139 List the repository's named branches, indicating which ones are
1140 1140 inactive. If -c/--closed is specified, also list branches which have
1141 1141 been marked closed (see :hg:`commit --close-branch`).
1142 1142
1143 1143 Use the command :hg:`update` to switch to an existing branch.
1144 1144
1145 1145 .. container:: verbose
1146 1146
1147 1147 Template:
1148 1148
1149 1149 The following keywords are supported in addition to the common template
1150 1150 keywords and functions such as ``{branch}``. See also
1151 1151 :hg:`help templates`.
1152 1152
1153 1153 :active: Boolean. True if the branch is active.
1154 1154 :closed: Boolean. True if the branch is closed.
1155 1155 :current: Boolean. True if it is the current branch.
1156 1156
1157 1157 Returns 0.
1158 1158 """
1159 1159
1160 1160 opts = pycompat.byteskwargs(opts)
1161 1161 ui.pager('branches')
1162 1162 fm = ui.formatter('branches', opts)
1163 1163 hexfunc = fm.hexfunc
1164 1164
1165 1165 allheads = set(repo.heads())
1166 1166 branches = []
1167 1167 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1168 1168 isactive = False
1169 1169 if not isclosed:
1170 1170 openheads = set(repo.branchmap().iteropen(heads))
1171 1171 isactive = bool(openheads & allheads)
1172 1172 branches.append((tag, repo[tip], isactive, not isclosed))
1173 1173 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1174 1174 reverse=True)
1175 1175
1176 1176 for tag, ctx, isactive, isopen in branches:
1177 1177 if active and not isactive:
1178 1178 continue
1179 1179 if isactive:
1180 1180 label = 'branches.active'
1181 1181 notice = ''
1182 1182 elif not isopen:
1183 1183 if not closed:
1184 1184 continue
1185 1185 label = 'branches.closed'
1186 1186 notice = _(' (closed)')
1187 1187 else:
1188 1188 label = 'branches.inactive'
1189 1189 notice = _(' (inactive)')
1190 1190 current = (tag == repo.dirstate.branch())
1191 1191 if current:
1192 1192 label = 'branches.current'
1193 1193
1194 1194 fm.startitem()
1195 1195 fm.write('branch', '%s', tag, label=label)
1196 1196 rev = ctx.rev()
1197 1197 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1198 1198 fmt = ' ' * padsize + ' %d:%s'
1199 1199 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1200 1200 label='log.changeset changeset.%s' % ctx.phasestr())
1201 1201 fm.context(ctx=ctx)
1202 1202 fm.data(active=isactive, closed=not isopen, current=current)
1203 1203 if not ui.quiet:
1204 1204 fm.plain(notice)
1205 1205 fm.plain('\n')
1206 1206 fm.end()
1207 1207
1208 1208 @command('bundle',
1209 1209 [('f', 'force', None, _('run even when the destination is unrelated')),
1210 1210 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1211 1211 _('REV')),
1212 1212 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1213 1213 _('BRANCH')),
1214 1214 ('', 'base', [],
1215 1215 _('a base changeset assumed to be available at the destination'),
1216 1216 _('REV')),
1217 1217 ('a', 'all', None, _('bundle all changesets in the repository')),
1218 1218 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1219 1219 ] + remoteopts,
1220 1220 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1221 1221 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1222 1222 def bundle(ui, repo, fname, dest=None, **opts):
1223 1223 """create a bundle file
1224 1224
1225 1225 Generate a bundle file containing data to be transferred to another
1226 1226 repository.
1227 1227
1228 1228 To create a bundle containing all changesets, use -a/--all
1229 1229 (or --base null). Otherwise, hg assumes the destination will have
1230 1230 all the nodes you specify with --base parameters. Otherwise, hg
1231 1231 will assume the repository has all the nodes in destination, or
1232 1232 default-push/default if no destination is specified, where destination
1233 1233 is the repository you provide through DEST option.
1234 1234
1235 1235 You can change bundle format with the -t/--type option. See
1236 1236 :hg:`help bundlespec` for documentation on this format. By default,
1237 1237 the most appropriate format is used and compression defaults to
1238 1238 bzip2.
1239 1239
1240 1240 The bundle file can then be transferred using conventional means
1241 1241 and applied to another repository with the unbundle or pull
1242 1242 command. This is useful when direct push and pull are not
1243 1243 available or when exporting an entire repository is undesirable.
1244 1244
1245 1245 Applying bundles preserves all changeset contents including
1246 1246 permissions, copy/rename information, and revision history.
1247 1247
1248 1248 Returns 0 on success, 1 if no changes found.
1249 1249 """
1250 1250 opts = pycompat.byteskwargs(opts)
1251 1251 revs = None
1252 1252 if 'rev' in opts:
1253 1253 revstrings = opts['rev']
1254 1254 revs = scmutil.revrange(repo, revstrings)
1255 1255 if revstrings and not revs:
1256 1256 raise error.Abort(_('no commits to bundle'))
1257 1257
1258 1258 bundletype = opts.get('type', 'bzip2').lower()
1259 1259 try:
1260 1260 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1261 1261 except error.UnsupportedBundleSpecification as e:
1262 1262 raise error.Abort(pycompat.bytestr(e),
1263 1263 hint=_("see 'hg help bundlespec' for supported "
1264 1264 "values for --type"))
1265 1265 cgversion = bundlespec.contentopts["cg.version"]
1266 1266
1267 1267 # Packed bundles are a pseudo bundle format for now.
1268 1268 if cgversion == 's1':
1269 1269 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1270 1270 hint=_("use 'hg debugcreatestreamclonebundle'"))
1271 1271
1272 1272 if opts.get('all'):
1273 1273 if dest:
1274 1274 raise error.Abort(_("--all is incompatible with specifying "
1275 1275 "a destination"))
1276 1276 if opts.get('base'):
1277 1277 ui.warn(_("ignoring --base because --all was specified\n"))
1278 1278 base = [nullrev]
1279 1279 else:
1280 1280 base = scmutil.revrange(repo, opts.get('base'))
1281 1281 if cgversion not in changegroup.supportedoutgoingversions(repo):
1282 1282 raise error.Abort(_("repository does not support bundle version %s") %
1283 1283 cgversion)
1284 1284
1285 1285 if base:
1286 1286 if dest:
1287 1287 raise error.Abort(_("--base is incompatible with specifying "
1288 1288 "a destination"))
1289 1289 common = [repo[rev].node() for rev in base]
1290 1290 heads = [repo[r].node() for r in revs] if revs else None
1291 1291 outgoing = discovery.outgoing(repo, common, heads)
1292 1292 else:
1293 1293 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1294 1294 dest, branches = hg.parseurl(dest, opts.get('branch'))
1295 1295 other = hg.peer(repo, opts, dest)
1296 1296 revs = [repo[r].hex() for r in revs]
1297 1297 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1298 1298 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1299 1299 outgoing = discovery.findcommonoutgoing(repo, other,
1300 1300 onlyheads=heads,
1301 1301 force=opts.get('force'),
1302 1302 portable=True)
1303 1303
1304 1304 if not outgoing.missing:
1305 1305 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1306 1306 return 1
1307 1307
1308 1308 if cgversion == '01': #bundle1
1309 1309 bversion = 'HG10' + bundlespec.wirecompression
1310 1310 bcompression = None
1311 1311 elif cgversion in ('02', '03'):
1312 1312 bversion = 'HG20'
1313 1313 bcompression = bundlespec.wirecompression
1314 1314 else:
1315 1315 raise error.ProgrammingError(
1316 1316 'bundle: unexpected changegroup version %s' % cgversion)
1317 1317
1318 1318 # TODO compression options should be derived from bundlespec parsing.
1319 1319 # This is a temporary hack to allow adjusting bundle compression
1320 1320 # level without a) formalizing the bundlespec changes to declare it
1321 1321 # b) introducing a command flag.
1322 1322 compopts = {}
1323 1323 complevel = ui.configint('experimental',
1324 1324 'bundlecomplevel.' + bundlespec.compression)
1325 1325 if complevel is None:
1326 1326 complevel = ui.configint('experimental', 'bundlecomplevel')
1327 1327 if complevel is not None:
1328 1328 compopts['level'] = complevel
1329 1329
1330 1330 # Allow overriding the bundling of obsmarker in phases through
1331 1331 # configuration while we don't have a bundle version that include them
1332 1332 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1333 1333 bundlespec.contentopts['obsolescence'] = True
1334 1334 if repo.ui.configbool('experimental', 'bundle-phases'):
1335 1335 bundlespec.contentopts['phases'] = True
1336 1336
1337 1337 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1338 1338 bundlespec.contentopts, compression=bcompression,
1339 1339 compopts=compopts)
1340 1340
1341 1341 @command('cat',
1342 1342 [('o', 'output', '',
1343 1343 _('print output to file with formatted name'), _('FORMAT')),
1344 1344 ('r', 'rev', '', _('print the given revision'), _('REV')),
1345 1345 ('', 'decode', None, _('apply any matching decode filter')),
1346 1346 ] + walkopts + formatteropts,
1347 1347 _('[OPTION]... FILE...'),
1348 1348 helpcategory=command.CATEGORY_FILE_CONTENTS,
1349 1349 inferrepo=True,
1350 1350 intents={INTENT_READONLY})
1351 1351 def cat(ui, repo, file1, *pats, **opts):
1352 1352 """output the current or given revision of files
1353 1353
1354 1354 Print the specified files as they were at the given revision. If
1355 1355 no revision is given, the parent of the working directory is used.
1356 1356
1357 1357 Output may be to a file, in which case the name of the file is
1358 1358 given using a template string. See :hg:`help templates`. In addition
1359 1359 to the common template keywords, the following formatting rules are
1360 1360 supported:
1361 1361
1362 1362 :``%%``: literal "%" character
1363 1363 :``%s``: basename of file being printed
1364 1364 :``%d``: dirname of file being printed, or '.' if in repository root
1365 1365 :``%p``: root-relative path name of file being printed
1366 1366 :``%H``: changeset hash (40 hexadecimal digits)
1367 1367 :``%R``: changeset revision number
1368 1368 :``%h``: short-form changeset hash (12 hexadecimal digits)
1369 1369 :``%r``: zero-padded changeset revision number
1370 1370 :``%b``: basename of the exporting repository
1371 1371 :``\\``: literal "\\" character
1372 1372
1373 1373 .. container:: verbose
1374 1374
1375 1375 Template:
1376 1376
1377 1377 The following keywords are supported in addition to the common template
1378 1378 keywords and functions. See also :hg:`help templates`.
1379 1379
1380 1380 :data: String. File content.
1381 1381 :path: String. Repository-absolute path of the file.
1382 1382
1383 1383 Returns 0 on success.
1384 1384 """
1385 1385 opts = pycompat.byteskwargs(opts)
1386 1386 rev = opts.get('rev')
1387 1387 if rev:
1388 1388 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1389 1389 ctx = scmutil.revsingle(repo, rev)
1390 1390 m = scmutil.match(ctx, (file1,) + pats, opts)
1391 1391 fntemplate = opts.pop('output', '')
1392 1392 if cmdutil.isstdiofilename(fntemplate):
1393 1393 fntemplate = ''
1394 1394
1395 1395 if fntemplate:
1396 1396 fm = formatter.nullformatter(ui, 'cat', opts)
1397 1397 else:
1398 1398 ui.pager('cat')
1399 1399 fm = ui.formatter('cat', opts)
1400 1400 with fm:
1401 1401 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1402 1402 **pycompat.strkwargs(opts))
1403 1403
1404 1404 @command('clone',
1405 1405 [('U', 'noupdate', None, _('the clone will include an empty working '
1406 1406 'directory (only a repository)')),
1407 1407 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1408 1408 _('REV')),
1409 1409 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1410 1410 ' and its ancestors'), _('REV')),
1411 1411 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1412 1412 ' changesets and their ancestors'), _('BRANCH')),
1413 1413 ('', 'pull', None, _('use pull protocol to copy metadata')),
1414 1414 ('', 'uncompressed', None,
1415 1415 _('an alias to --stream (DEPRECATED)')),
1416 1416 ('', 'stream', None,
1417 1417 _('clone with minimal data processing')),
1418 1418 ] + remoteopts,
1419 1419 _('[OPTION]... SOURCE [DEST]'),
1420 1420 helpcategory=command.CATEGORY_REPO_CREATION,
1421 1421 helpbasic=True, norepo=True)
1422 1422 def clone(ui, source, dest=None, **opts):
1423 1423 """make a copy of an existing repository
1424 1424
1425 1425 Create a copy of an existing repository in a new directory.
1426 1426
1427 1427 If no destination directory name is specified, it defaults to the
1428 1428 basename of the source.
1429 1429
1430 1430 The location of the source is added to the new repository's
1431 1431 ``.hg/hgrc`` file, as the default to be used for future pulls.
1432 1432
1433 1433 Only local paths and ``ssh://`` URLs are supported as
1434 1434 destinations. For ``ssh://`` destinations, no working directory or
1435 1435 ``.hg/hgrc`` will be created on the remote side.
1436 1436
1437 1437 If the source repository has a bookmark called '@' set, that
1438 1438 revision will be checked out in the new repository by default.
1439 1439
1440 1440 To check out a particular version, use -u/--update, or
1441 1441 -U/--noupdate to create a clone with no working directory.
1442 1442
1443 1443 To pull only a subset of changesets, specify one or more revisions
1444 1444 identifiers with -r/--rev or branches with -b/--branch. The
1445 1445 resulting clone will contain only the specified changesets and
1446 1446 their ancestors. These options (or 'clone src#rev dest') imply
1447 1447 --pull, even for local source repositories.
1448 1448
1449 1449 In normal clone mode, the remote normalizes repository data into a common
1450 1450 exchange format and the receiving end translates this data into its local
1451 1451 storage format. --stream activates a different clone mode that essentially
1452 1452 copies repository files from the remote with minimal data processing. This
1453 1453 significantly reduces the CPU cost of a clone both remotely and locally.
1454 1454 However, it often increases the transferred data size by 30-40%. This can
1455 1455 result in substantially faster clones where I/O throughput is plentiful,
1456 1456 especially for larger repositories. A side-effect of --stream clones is
1457 1457 that storage settings and requirements on the remote are applied locally:
1458 1458 a modern client may inherit legacy or inefficient storage used by the
1459 1459 remote or a legacy Mercurial client may not be able to clone from a
1460 1460 modern Mercurial remote.
1461 1461
1462 1462 .. note::
1463 1463
1464 1464 Specifying a tag will include the tagged changeset but not the
1465 1465 changeset containing the tag.
1466 1466
1467 1467 .. container:: verbose
1468 1468
1469 1469 For efficiency, hardlinks are used for cloning whenever the
1470 1470 source and destination are on the same filesystem (note this
1471 1471 applies only to the repository data, not to the working
1472 1472 directory). Some filesystems, such as AFS, implement hardlinking
1473 1473 incorrectly, but do not report errors. In these cases, use the
1474 1474 --pull option to avoid hardlinking.
1475 1475
1476 1476 Mercurial will update the working directory to the first applicable
1477 1477 revision from this list:
1478 1478
1479 1479 a) null if -U or the source repository has no changesets
1480 1480 b) if -u . and the source repository is local, the first parent of
1481 1481 the source repository's working directory
1482 1482 c) the changeset specified with -u (if a branch name, this means the
1483 1483 latest head of that branch)
1484 1484 d) the changeset specified with -r
1485 1485 e) the tipmost head specified with -b
1486 1486 f) the tipmost head specified with the url#branch source syntax
1487 1487 g) the revision marked with the '@' bookmark, if present
1488 1488 h) the tipmost head of the default branch
1489 1489 i) tip
1490 1490
1491 1491 When cloning from servers that support it, Mercurial may fetch
1492 1492 pre-generated data from a server-advertised URL or inline from the
1493 1493 same stream. When this is done, hooks operating on incoming changesets
1494 1494 and changegroups may fire more than once, once for each pre-generated
1495 1495 bundle and as well as for any additional remaining data. In addition,
1496 1496 if an error occurs, the repository may be rolled back to a partial
1497 1497 clone. This behavior may change in future releases.
1498 1498 See :hg:`help -e clonebundles` for more.
1499 1499
1500 1500 Examples:
1501 1501
1502 1502 - clone a remote repository to a new directory named hg/::
1503 1503
1504 1504 hg clone https://www.mercurial-scm.org/repo/hg/
1505 1505
1506 1506 - create a lightweight local clone::
1507 1507
1508 1508 hg clone project/ project-feature/
1509 1509
1510 1510 - clone from an absolute path on an ssh server (note double-slash)::
1511 1511
1512 1512 hg clone ssh://user@server//home/projects/alpha/
1513 1513
1514 1514 - do a streaming clone while checking out a specified version::
1515 1515
1516 1516 hg clone --stream http://server/repo -u 1.5
1517 1517
1518 1518 - create a repository without changesets after a particular revision::
1519 1519
1520 1520 hg clone -r 04e544 experimental/ good/
1521 1521
1522 1522 - clone (and track) a particular named branch::
1523 1523
1524 1524 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1525 1525
1526 1526 See :hg:`help urls` for details on specifying URLs.
1527 1527
1528 1528 Returns 0 on success.
1529 1529 """
1530 1530 opts = pycompat.byteskwargs(opts)
1531 1531 if opts.get('noupdate') and opts.get('updaterev'):
1532 1532 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1533 1533
1534 1534 # --include/--exclude can come from narrow or sparse.
1535 1535 includepats, excludepats = None, None
1536 1536
1537 1537 # hg.clone() differentiates between None and an empty set. So make sure
1538 1538 # patterns are sets if narrow is requested without patterns.
1539 1539 if opts.get('narrow'):
1540 1540 includepats = set()
1541 1541 excludepats = set()
1542 1542
1543 1543 if opts.get('include'):
1544 1544 includepats = narrowspec.parsepatterns(opts.get('include'))
1545 1545 if opts.get('exclude'):
1546 1546 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1547 1547
1548 1548 r = hg.clone(ui, opts, source, dest,
1549 1549 pull=opts.get('pull'),
1550 1550 stream=opts.get('stream') or opts.get('uncompressed'),
1551 1551 revs=opts.get('rev'),
1552 1552 update=opts.get('updaterev') or not opts.get('noupdate'),
1553 1553 branch=opts.get('branch'),
1554 1554 shareopts=opts.get('shareopts'),
1555 1555 storeincludepats=includepats,
1556 1556 storeexcludepats=excludepats,
1557 1557 depth=opts.get('depth') or None)
1558 1558
1559 1559 return r is None
1560 1560
1561 1561 @command('commit|ci',
1562 1562 [('A', 'addremove', None,
1563 1563 _('mark new/missing files as added/removed before committing')),
1564 1564 ('', 'close-branch', None,
1565 1565 _('mark a branch head as closed')),
1566 1566 ('', 'amend', None, _('amend the parent of the working directory')),
1567 1567 ('s', 'secret', None, _('use the secret phase for committing')),
1568 1568 ('e', 'edit', None, _('invoke editor on commit messages')),
1569 1569 ('i', 'interactive', None, _('use interactive mode')),
1570 1570 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1571 1571 _('[OPTION]... [FILE]...'),
1572 1572 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1573 1573 inferrepo=True)
1574 1574 def commit(ui, repo, *pats, **opts):
1575 1575 """commit the specified files or all outstanding changes
1576 1576
1577 1577 Commit changes to the given files into the repository. Unlike a
1578 1578 centralized SCM, this operation is a local operation. See
1579 1579 :hg:`push` for a way to actively distribute your changes.
1580 1580
1581 1581 If a list of files is omitted, all changes reported by :hg:`status`
1582 1582 will be committed.
1583 1583
1584 1584 If you are committing the result of a merge, do not provide any
1585 1585 filenames or -I/-X filters.
1586 1586
1587 1587 If no commit message is specified, Mercurial starts your
1588 1588 configured editor where you can enter a message. In case your
1589 1589 commit fails, you will find a backup of your message in
1590 1590 ``.hg/last-message.txt``.
1591 1591
1592 1592 The --close-branch flag can be used to mark the current branch
1593 1593 head closed. When all heads of a branch are closed, the branch
1594 1594 will be considered closed and no longer listed.
1595 1595
1596 1596 The --amend flag can be used to amend the parent of the
1597 1597 working directory with a new commit that contains the changes
1598 1598 in the parent in addition to those currently reported by :hg:`status`,
1599 1599 if there are any. The old commit is stored in a backup bundle in
1600 1600 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1601 1601 on how to restore it).
1602 1602
1603 1603 Message, user and date are taken from the amended commit unless
1604 1604 specified. When a message isn't specified on the command line,
1605 1605 the editor will open with the message of the amended commit.
1606 1606
1607 1607 It is not possible to amend public changesets (see :hg:`help phases`)
1608 1608 or changesets that have children.
1609 1609
1610 1610 See :hg:`help dates` for a list of formats valid for -d/--date.
1611 1611
1612 1612 Returns 0 on success, 1 if nothing changed.
1613 1613
1614 1614 .. container:: verbose
1615 1615
1616 1616 Examples:
1617 1617
1618 1618 - commit all files ending in .py::
1619 1619
1620 1620 hg commit --include "set:**.py"
1621 1621
1622 1622 - commit all non-binary files::
1623 1623
1624 1624 hg commit --exclude "set:binary()"
1625 1625
1626 1626 - amend the current commit and set the date to now::
1627 1627
1628 1628 hg commit --amend --date now
1629 1629 """
1630 1630 with repo.wlock(), repo.lock():
1631 1631 return _docommit(ui, repo, *pats, **opts)
1632 1632
1633 1633 def _docommit(ui, repo, *pats, **opts):
1634 1634 if opts.get(r'interactive'):
1635 1635 opts.pop(r'interactive')
1636 1636 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1637 1637 cmdutil.recordfilter, *pats,
1638 1638 **opts)
1639 1639 # ret can be 0 (no changes to record) or the value returned by
1640 1640 # commit(), 1 if nothing changed or None on success.
1641 1641 return 1 if ret == 0 else ret
1642 1642
1643 1643 opts = pycompat.byteskwargs(opts)
1644 1644 if opts.get('subrepos'):
1645 1645 if opts.get('amend'):
1646 1646 raise error.Abort(_('cannot amend with --subrepos'))
1647 1647 # Let --subrepos on the command line override config setting.
1648 1648 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1649 1649
1650 1650 cmdutil.checkunfinished(repo, commit=True)
1651 1651
1652 1652 branch = repo[None].branch()
1653 1653 bheads = repo.branchheads(branch)
1654 1654
1655 1655 extra = {}
1656 1656 if opts.get('close_branch'):
1657 1657 extra['close'] = '1'
1658 1658
1659 1659 if not bheads:
1660 1660 raise error.Abort(_('can only close branch heads'))
1661 1661 elif opts.get('amend'):
1662 1662 if repo[None].parents()[0].p1().branch() != branch and \
1663 1663 repo[None].parents()[0].p2().branch() != branch:
1664 1664 raise error.Abort(_('can only close branch heads'))
1665 1665
1666 1666 if opts.get('amend'):
1667 1667 if ui.configbool('ui', 'commitsubrepos'):
1668 1668 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1669 1669
1670 1670 old = repo['.']
1671 1671 rewriteutil.precheck(repo, [old.rev()], 'amend')
1672 1672
1673 1673 # Currently histedit gets confused if an amend happens while histedit
1674 1674 # is in progress. Since we have a checkunfinished command, we are
1675 1675 # temporarily honoring it.
1676 1676 #
1677 1677 # Note: eventually this guard will be removed. Please do not expect
1678 1678 # this behavior to remain.
1679 1679 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1680 1680 cmdutil.checkunfinished(repo)
1681 1681
1682 1682 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1683 1683 if node == old.node():
1684 1684 ui.status(_("nothing changed\n"))
1685 1685 return 1
1686 1686 else:
1687 1687 def commitfunc(ui, repo, message, match, opts):
1688 1688 overrides = {}
1689 1689 if opts.get('secret'):
1690 1690 overrides[('phases', 'new-commit')] = 'secret'
1691 1691
1692 1692 baseui = repo.baseui
1693 1693 with baseui.configoverride(overrides, 'commit'):
1694 1694 with ui.configoverride(overrides, 'commit'):
1695 1695 editform = cmdutil.mergeeditform(repo[None],
1696 1696 'commit.normal')
1697 1697 editor = cmdutil.getcommiteditor(
1698 1698 editform=editform, **pycompat.strkwargs(opts))
1699 1699 return repo.commit(message,
1700 1700 opts.get('user'),
1701 1701 opts.get('date'),
1702 1702 match,
1703 1703 editor=editor,
1704 1704 extra=extra)
1705 1705
1706 1706 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1707 1707
1708 1708 if not node:
1709 1709 stat = cmdutil.postcommitstatus(repo, pats, opts)
1710 1710 if stat[3]:
1711 1711 ui.status(_("nothing changed (%d missing files, see "
1712 1712 "'hg status')\n") % len(stat[3]))
1713 1713 else:
1714 1714 ui.status(_("nothing changed\n"))
1715 1715 return 1
1716 1716
1717 1717 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1718 1718
1719 1719 @command('config|showconfig|debugconfig',
1720 1720 [('u', 'untrusted', None, _('show untrusted configuration options')),
1721 1721 ('e', 'edit', None, _('edit user config')),
1722 1722 ('l', 'local', None, _('edit repository config')),
1723 1723 ('g', 'global', None, _('edit global config'))] + formatteropts,
1724 1724 _('[-u] [NAME]...'),
1725 1725 helpcategory=command.CATEGORY_HELP,
1726 1726 optionalrepo=True,
1727 1727 intents={INTENT_READONLY})
1728 1728 def config(ui, repo, *values, **opts):
1729 1729 """show combined config settings from all hgrc files
1730 1730
1731 1731 With no arguments, print names and values of all config items.
1732 1732
1733 1733 With one argument of the form section.name, print just the value
1734 1734 of that config item.
1735 1735
1736 1736 With multiple arguments, print names and values of all config
1737 1737 items with matching section names or section.names.
1738 1738
1739 1739 With --edit, start an editor on the user-level config file. With
1740 1740 --global, edit the system-wide config file. With --local, edit the
1741 1741 repository-level config file.
1742 1742
1743 1743 With --debug, the source (filename and line number) is printed
1744 1744 for each config item.
1745 1745
1746 1746 See :hg:`help config` for more information about config files.
1747 1747
1748 1748 .. container:: verbose
1749 1749
1750 1750 Template:
1751 1751
1752 1752 The following keywords are supported. See also :hg:`help templates`.
1753 1753
1754 1754 :name: String. Config name.
1755 1755 :source: String. Filename and line number where the item is defined.
1756 1756 :value: String. Config value.
1757 1757
1758 1758 Returns 0 on success, 1 if NAME does not exist.
1759 1759
1760 1760 """
1761 1761
1762 1762 opts = pycompat.byteskwargs(opts)
1763 1763 if opts.get('edit') or opts.get('local') or opts.get('global'):
1764 1764 if opts.get('local') and opts.get('global'):
1765 1765 raise error.Abort(_("can't use --local and --global together"))
1766 1766
1767 1767 if opts.get('local'):
1768 1768 if not repo:
1769 1769 raise error.Abort(_("can't use --local outside a repository"))
1770 1770 paths = [repo.vfs.join('hgrc')]
1771 1771 elif opts.get('global'):
1772 1772 paths = rcutil.systemrcpath()
1773 1773 else:
1774 1774 paths = rcutil.userrcpath()
1775 1775
1776 1776 for f in paths:
1777 1777 if os.path.exists(f):
1778 1778 break
1779 1779 else:
1780 1780 if opts.get('global'):
1781 1781 samplehgrc = uimod.samplehgrcs['global']
1782 1782 elif opts.get('local'):
1783 1783 samplehgrc = uimod.samplehgrcs['local']
1784 1784 else:
1785 1785 samplehgrc = uimod.samplehgrcs['user']
1786 1786
1787 1787 f = paths[0]
1788 1788 fp = open(f, "wb")
1789 1789 fp.write(util.tonativeeol(samplehgrc))
1790 1790 fp.close()
1791 1791
1792 1792 editor = ui.geteditor()
1793 1793 ui.system("%s \"%s\"" % (editor, f),
1794 1794 onerr=error.Abort, errprefix=_("edit failed"),
1795 1795 blockedtag='config_edit')
1796 1796 return
1797 1797 ui.pager('config')
1798 1798 fm = ui.formatter('config', opts)
1799 1799 for t, f in rcutil.rccomponents():
1800 1800 if t == 'path':
1801 1801 ui.debug('read config from: %s\n' % f)
1802 1802 elif t == 'items':
1803 1803 for section, name, value, source in f:
1804 1804 ui.debug('set config by: %s\n' % source)
1805 1805 else:
1806 1806 raise error.ProgrammingError('unknown rctype: %s' % t)
1807 1807 untrusted = bool(opts.get('untrusted'))
1808 1808
1809 1809 selsections = selentries = []
1810 1810 if values:
1811 1811 selsections = [v for v in values if '.' not in v]
1812 1812 selentries = [v for v in values if '.' in v]
1813 1813 uniquesel = (len(selentries) == 1 and not selsections)
1814 1814 selsections = set(selsections)
1815 1815 selentries = set(selentries)
1816 1816
1817 1817 matched = False
1818 1818 for section, name, value in ui.walkconfig(untrusted=untrusted):
1819 1819 source = ui.configsource(section, name, untrusted)
1820 1820 value = pycompat.bytestr(value)
1821 1821 if fm.isplain():
1822 1822 source = source or 'none'
1823 1823 value = value.replace('\n', '\\n')
1824 1824 entryname = section + '.' + name
1825 1825 if values and not (section in selsections or entryname in selentries):
1826 1826 continue
1827 1827 fm.startitem()
1828 1828 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1829 1829 if uniquesel:
1830 1830 fm.data(name=entryname)
1831 1831 fm.write('value', '%s\n', value)
1832 1832 else:
1833 1833 fm.write('name value', '%s=%s\n', entryname, value)
1834 1834 matched = True
1835 1835 fm.end()
1836 1836 if matched:
1837 1837 return 0
1838 1838 return 1
1839 1839
1840 1840 @command('copy|cp',
1841 1841 [('A', 'after', None, _('record a copy that has already occurred')),
1842 1842 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1843 1843 ] + walkopts + dryrunopts,
1844 1844 _('[OPTION]... [SOURCE]... DEST'),
1845 1845 helpcategory=command.CATEGORY_FILE_CONTENTS)
1846 1846 def copy(ui, repo, *pats, **opts):
1847 1847 """mark files as copied for the next commit
1848 1848
1849 1849 Mark dest as having copies of source files. If dest is a
1850 1850 directory, copies are put in that directory. If dest is a file,
1851 1851 the source must be a single file.
1852 1852
1853 1853 By default, this command copies the contents of files as they
1854 1854 exist in the working directory. If invoked with -A/--after, the
1855 1855 operation is recorded, but no copying is performed.
1856 1856
1857 1857 This command takes effect with the next commit. To undo a copy
1858 1858 before that, see :hg:`revert`.
1859 1859
1860 1860 Returns 0 on success, 1 if errors are encountered.
1861 1861 """
1862 1862 opts = pycompat.byteskwargs(opts)
1863 1863 with repo.wlock(False):
1864 1864 return cmdutil.copy(ui, repo, pats, opts)
1865 1865
1866 1866 @command(
1867 1867 'debugcommands', [], _('[COMMAND]'),
1868 1868 helpcategory=command.CATEGORY_HELP,
1869 1869 norepo=True)
1870 1870 def debugcommands(ui, cmd='', *args):
1871 1871 """list all available commands and options"""
1872 1872 for cmd, vals in sorted(table.iteritems()):
1873 1873 cmd = cmd.split('|')[0]
1874 1874 opts = ', '.join([i[1] for i in vals[1]])
1875 1875 ui.write('%s: %s\n' % (cmd, opts))
1876 1876
1877 1877 @command('debugcomplete',
1878 1878 [('o', 'options', None, _('show the command options'))],
1879 1879 _('[-o] CMD'),
1880 1880 helpcategory=command.CATEGORY_HELP,
1881 1881 norepo=True)
1882 1882 def debugcomplete(ui, cmd='', **opts):
1883 1883 """returns the completion list associated with the given command"""
1884 1884
1885 1885 if opts.get(r'options'):
1886 1886 options = []
1887 1887 otables = [globalopts]
1888 1888 if cmd:
1889 1889 aliases, entry = cmdutil.findcmd(cmd, table, False)
1890 1890 otables.append(entry[1])
1891 1891 for t in otables:
1892 1892 for o in t:
1893 1893 if "(DEPRECATED)" in o[3]:
1894 1894 continue
1895 1895 if o[0]:
1896 1896 options.append('-%s' % o[0])
1897 1897 options.append('--%s' % o[1])
1898 1898 ui.write("%s\n" % "\n".join(options))
1899 1899 return
1900 1900
1901 1901 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1902 1902 if ui.verbose:
1903 1903 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1904 1904 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1905 1905
1906 1906 @command('diff',
1907 1907 [('r', 'rev', [], _('revision'), _('REV')),
1908 1908 ('c', 'change', '', _('change made by revision'), _('REV'))
1909 1909 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1910 1910 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1911 1911 helpcategory=command.CATEGORY_FILE_CONTENTS,
1912 1912 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1913 1913 def diff(ui, repo, *pats, **opts):
1914 1914 """diff repository (or selected files)
1915 1915
1916 1916 Show differences between revisions for the specified files.
1917 1917
1918 1918 Differences between files are shown using the unified diff format.
1919 1919
1920 1920 .. note::
1921 1921
1922 1922 :hg:`diff` may generate unexpected results for merges, as it will
1923 1923 default to comparing against the working directory's first
1924 1924 parent changeset if no revisions are specified.
1925 1925
1926 1926 When two revision arguments are given, then changes are shown
1927 1927 between those revisions. If only one revision is specified then
1928 1928 that revision is compared to the working directory, and, when no
1929 1929 revisions are specified, the working directory files are compared
1930 1930 to its first parent.
1931 1931
1932 1932 Alternatively you can specify -c/--change with a revision to see
1933 1933 the changes in that changeset relative to its first parent.
1934 1934
1935 1935 Without the -a/--text option, diff will avoid generating diffs of
1936 1936 files it detects as binary. With -a, diff will generate a diff
1937 1937 anyway, probably with undesirable results.
1938 1938
1939 1939 Use the -g/--git option to generate diffs in the git extended diff
1940 1940 format. For more information, read :hg:`help diffs`.
1941 1941
1942 1942 .. container:: verbose
1943 1943
1944 1944 Examples:
1945 1945
1946 1946 - compare a file in the current working directory to its parent::
1947 1947
1948 1948 hg diff foo.c
1949 1949
1950 1950 - compare two historical versions of a directory, with rename info::
1951 1951
1952 1952 hg diff --git -r 1.0:1.2 lib/
1953 1953
1954 1954 - get change stats relative to the last change on some date::
1955 1955
1956 1956 hg diff --stat -r "date('may 2')"
1957 1957
1958 1958 - diff all newly-added files that contain a keyword::
1959 1959
1960 1960 hg diff "set:added() and grep(GNU)"
1961 1961
1962 1962 - compare a revision and its parents::
1963 1963
1964 1964 hg diff -c 9353 # compare against first parent
1965 1965 hg diff -r 9353^:9353 # same using revset syntax
1966 1966 hg diff -r 9353^2:9353 # compare against the second parent
1967 1967
1968 1968 Returns 0 on success.
1969 1969 """
1970 1970
1971 1971 opts = pycompat.byteskwargs(opts)
1972 1972 revs = opts.get('rev')
1973 1973 change = opts.get('change')
1974 1974 stat = opts.get('stat')
1975 1975 reverse = opts.get('reverse')
1976 1976
1977 1977 if revs and change:
1978 1978 msg = _('cannot specify --rev and --change at the same time')
1979 1979 raise error.Abort(msg)
1980 1980 elif change:
1981 1981 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1982 1982 ctx2 = scmutil.revsingle(repo, change, None)
1983 1983 ctx1 = ctx2.p1()
1984 1984 else:
1985 1985 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1986 1986 ctx1, ctx2 = scmutil.revpair(repo, revs)
1987 1987 node1, node2 = ctx1.node(), ctx2.node()
1988 1988
1989 1989 if reverse:
1990 1990 node1, node2 = node2, node1
1991 1991
1992 1992 diffopts = patch.diffallopts(ui, opts)
1993 1993 m = scmutil.match(ctx2, pats, opts)
1994 1994 m = repo.narrowmatch(m)
1995 1995 ui.pager('diff')
1996 1996 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1997 1997 listsubrepos=opts.get('subrepos'),
1998 1998 root=opts.get('root'))
1999 1999
2000 2000 @command('export',
2001 2001 [('B', 'bookmark', '',
2002 2002 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2003 2003 ('o', 'output', '',
2004 2004 _('print output to file with formatted name'), _('FORMAT')),
2005 2005 ('', 'switch-parent', None, _('diff against the second parent')),
2006 2006 ('r', 'rev', [], _('revisions to export'), _('REV')),
2007 2007 ] + diffopts + formatteropts,
2008 2008 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2009 2009 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2010 2010 helpbasic=True, intents={INTENT_READONLY})
2011 2011 def export(ui, repo, *changesets, **opts):
2012 2012 """dump the header and diffs for one or more changesets
2013 2013
2014 2014 Print the changeset header and diffs for one or more revisions.
2015 2015 If no revision is given, the parent of the working directory is used.
2016 2016
2017 2017 The information shown in the changeset header is: author, date,
2018 2018 branch name (if non-default), changeset hash, parent(s) and commit
2019 2019 comment.
2020 2020
2021 2021 .. note::
2022 2022
2023 2023 :hg:`export` may generate unexpected diff output for merge
2024 2024 changesets, as it will compare the merge changeset against its
2025 2025 first parent only.
2026 2026
2027 2027 Output may be to a file, in which case the name of the file is
2028 2028 given using a template string. See :hg:`help templates`. In addition
2029 2029 to the common template keywords, the following formatting rules are
2030 2030 supported:
2031 2031
2032 2032 :``%%``: literal "%" character
2033 2033 :``%H``: changeset hash (40 hexadecimal digits)
2034 2034 :``%N``: number of patches being generated
2035 2035 :``%R``: changeset revision number
2036 2036 :``%b``: basename of the exporting repository
2037 2037 :``%h``: short-form changeset hash (12 hexadecimal digits)
2038 2038 :``%m``: first line of the commit message (only alphanumeric characters)
2039 2039 :``%n``: zero-padded sequence number, starting at 1
2040 2040 :``%r``: zero-padded changeset revision number
2041 2041 :``\\``: literal "\\" character
2042 2042
2043 2043 Without the -a/--text option, export will avoid generating diffs
2044 2044 of files it detects as binary. With -a, export will generate a
2045 2045 diff anyway, probably with undesirable results.
2046 2046
2047 2047 With -B/--bookmark changesets reachable by the given bookmark are
2048 2048 selected.
2049 2049
2050 2050 Use the -g/--git option to generate diffs in the git extended diff
2051 2051 format. See :hg:`help diffs` for more information.
2052 2052
2053 2053 With the --switch-parent option, the diff will be against the
2054 2054 second parent. It can be useful to review a merge.
2055 2055
2056 2056 .. container:: verbose
2057 2057
2058 2058 Template:
2059 2059
2060 2060 The following keywords are supported in addition to the common template
2061 2061 keywords and functions. See also :hg:`help templates`.
2062 2062
2063 2063 :diff: String. Diff content.
2064 2064 :parents: List of strings. Parent nodes of the changeset.
2065 2065
2066 2066 Examples:
2067 2067
2068 2068 - use export and import to transplant a bugfix to the current
2069 2069 branch::
2070 2070
2071 2071 hg export -r 9353 | hg import -
2072 2072
2073 2073 - export all the changesets between two revisions to a file with
2074 2074 rename information::
2075 2075
2076 2076 hg export --git -r 123:150 > changes.txt
2077 2077
2078 2078 - split outgoing changes into a series of patches with
2079 2079 descriptive names::
2080 2080
2081 2081 hg export -r "outgoing()" -o "%n-%m.patch"
2082 2082
2083 2083 Returns 0 on success.
2084 2084 """
2085 2085 opts = pycompat.byteskwargs(opts)
2086 2086 bookmark = opts.get('bookmark')
2087 2087 changesets += tuple(opts.get('rev', []))
2088 2088
2089 2089 if bookmark and changesets:
2090 2090 raise error.Abort(_("-r and -B are mutually exclusive"))
2091 2091
2092 2092 if bookmark:
2093 2093 if bookmark not in repo._bookmarks:
2094 2094 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2095 2095
2096 2096 revs = scmutil.bookmarkrevs(repo, bookmark)
2097 2097 else:
2098 2098 if not changesets:
2099 2099 changesets = ['.']
2100 2100
2101 2101 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2102 2102 revs = scmutil.revrange(repo, changesets)
2103 2103
2104 2104 if not revs:
2105 2105 raise error.Abort(_("export requires at least one changeset"))
2106 2106 if len(revs) > 1:
2107 2107 ui.note(_('exporting patches:\n'))
2108 2108 else:
2109 2109 ui.note(_('exporting patch:\n'))
2110 2110
2111 2111 fntemplate = opts.get('output')
2112 2112 if cmdutil.isstdiofilename(fntemplate):
2113 2113 fntemplate = ''
2114 2114
2115 2115 if fntemplate:
2116 2116 fm = formatter.nullformatter(ui, 'export', opts)
2117 2117 else:
2118 2118 ui.pager('export')
2119 2119 fm = ui.formatter('export', opts)
2120 2120 with fm:
2121 2121 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2122 2122 switch_parent=opts.get('switch_parent'),
2123 2123 opts=patch.diffallopts(ui, opts))
2124 2124
2125 2125 @command('files',
2126 2126 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2127 2127 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2128 2128 ] + walkopts + formatteropts + subrepoopts,
2129 2129 _('[OPTION]... [FILE]...'),
2130 2130 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2131 2131 intents={INTENT_READONLY})
2132 2132 def files(ui, repo, *pats, **opts):
2133 2133 """list tracked files
2134 2134
2135 2135 Print files under Mercurial control in the working directory or
2136 2136 specified revision for given files (excluding removed files).
2137 2137 Files can be specified as filenames or filesets.
2138 2138
2139 2139 If no files are given to match, this command prints the names
2140 2140 of all files under Mercurial control.
2141 2141
2142 2142 .. container:: verbose
2143 2143
2144 2144 Template:
2145 2145
2146 2146 The following keywords are supported in addition to the common template
2147 2147 keywords and functions. See also :hg:`help templates`.
2148 2148
2149 2149 :flags: String. Character denoting file's symlink and executable bits.
2150 2150 :path: String. Repository-absolute path of the file.
2151 2151 :size: Integer. Size of the file in bytes.
2152 2152
2153 2153 Examples:
2154 2154
2155 2155 - list all files under the current directory::
2156 2156
2157 2157 hg files .
2158 2158
2159 2159 - shows sizes and flags for current revision::
2160 2160
2161 2161 hg files -vr .
2162 2162
2163 2163 - list all files named README::
2164 2164
2165 2165 hg files -I "**/README"
2166 2166
2167 2167 - list all binary files::
2168 2168
2169 2169 hg files "set:binary()"
2170 2170
2171 2171 - find files containing a regular expression::
2172 2172
2173 2173 hg files "set:grep('bob')"
2174 2174
2175 2175 - search tracked file contents with xargs and grep::
2176 2176
2177 2177 hg files -0 | xargs -0 grep foo
2178 2178
2179 2179 See :hg:`help patterns` and :hg:`help filesets` for more information
2180 2180 on specifying file patterns.
2181 2181
2182 2182 Returns 0 if a match is found, 1 otherwise.
2183 2183
2184 2184 """
2185 2185
2186 2186 opts = pycompat.byteskwargs(opts)
2187 2187 rev = opts.get('rev')
2188 2188 if rev:
2189 2189 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2190 2190 ctx = scmutil.revsingle(repo, rev, None)
2191 2191
2192 2192 end = '\n'
2193 2193 if opts.get('print0'):
2194 2194 end = '\0'
2195 2195 fmt = '%s' + end
2196 2196
2197 2197 m = scmutil.match(ctx, pats, opts)
2198 2198 ui.pager('files')
2199 2199 with ui.formatter('files', opts) as fm:
2200 2200 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2201 2201
2202 2202 @command(
2203 2203 'forget',
2204 2204 [('i', 'interactive', None, _('use interactive mode')),
2205 2205 ] + walkopts + dryrunopts,
2206 2206 _('[OPTION]... FILE...'),
2207 2207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2208 2208 helpbasic=True, inferrepo=True)
2209 2209 def forget(ui, repo, *pats, **opts):
2210 2210 """forget the specified files on the next commit
2211 2211
2212 2212 Mark the specified files so they will no longer be tracked
2213 2213 after the next commit.
2214 2214
2215 2215 This only removes files from the current branch, not from the
2216 2216 entire project history, and it does not delete them from the
2217 2217 working directory.
2218 2218
2219 2219 To delete the file from the working directory, see :hg:`remove`.
2220 2220
2221 2221 To undo a forget before the next commit, see :hg:`add`.
2222 2222
2223 2223 .. container:: verbose
2224 2224
2225 2225 Examples:
2226 2226
2227 2227 - forget newly-added binary files::
2228 2228
2229 2229 hg forget "set:added() and binary()"
2230 2230
2231 2231 - forget files that would be excluded by .hgignore::
2232 2232
2233 2233 hg forget "set:hgignore()"
2234 2234
2235 2235 Returns 0 on success.
2236 2236 """
2237 2237
2238 2238 opts = pycompat.byteskwargs(opts)
2239 2239 if not pats:
2240 2240 raise error.Abort(_('no files specified'))
2241 2241
2242 2242 m = scmutil.match(repo[None], pats, opts)
2243 2243 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2244 2244 rejected = cmdutil.forget(ui, repo, m, prefix="",
2245 2245 explicitonly=False, dryrun=dryrun,
2246 2246 interactive=interactive)[0]
2247 2247 return rejected and 1 or 0
2248 2248
2249 2249 @command(
2250 2250 'graft',
2251 2251 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2252 2252 ('', 'base', '',
2253 2253 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2254 2254 ('c', 'continue', False, _('resume interrupted graft')),
2255 2255 ('', 'stop', False, _('stop interrupted graft')),
2256 2256 ('', 'abort', False, _('abort interrupted graft')),
2257 2257 ('e', 'edit', False, _('invoke editor on commit messages')),
2258 2258 ('', 'log', None, _('append graft info to log message')),
2259 2259 ('', 'no-commit', None,
2260 2260 _("don't commit, just apply the changes in working directory")),
2261 2261 ('f', 'force', False, _('force graft')),
2262 2262 ('D', 'currentdate', False,
2263 2263 _('record the current date as commit date')),
2264 2264 ('U', 'currentuser', False,
2265 2265 _('record the current user as committer'))]
2266 2266 + commitopts2 + mergetoolopts + dryrunopts,
2267 2267 _('[OPTION]... [-r REV]... REV...'),
2268 2268 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2269 2269 def graft(ui, repo, *revs, **opts):
2270 2270 '''copy changes from other branches onto the current branch
2271 2271
2272 2272 This command uses Mercurial's merge logic to copy individual
2273 2273 changes from other branches without merging branches in the
2274 2274 history graph. This is sometimes known as 'backporting' or
2275 2275 'cherry-picking'. By default, graft will copy user, date, and
2276 2276 description from the source changesets.
2277 2277
2278 2278 Changesets that are ancestors of the current revision, that have
2279 2279 already been grafted, or that are merges will be skipped.
2280 2280
2281 2281 If --log is specified, log messages will have a comment appended
2282 2282 of the form::
2283 2283
2284 2284 (grafted from CHANGESETHASH)
2285 2285
2286 2286 If --force is specified, revisions will be grafted even if they
2287 2287 are already ancestors of, or have been grafted to, the destination.
2288 2288 This is useful when the revisions have since been backed out.
2289 2289
2290 2290 If a graft merge results in conflicts, the graft process is
2291 2291 interrupted so that the current merge can be manually resolved.
2292 2292 Once all conflicts are addressed, the graft process can be
2293 2293 continued with the -c/--continue option.
2294 2294
2295 2295 The -c/--continue option reapplies all the earlier options.
2296 2296
2297 2297 .. container:: verbose
2298 2298
2299 2299 The --base option exposes more of how graft internally uses merge with a
2300 2300 custom base revision. --base can be used to specify another ancestor than
2301 2301 the first and only parent.
2302 2302
2303 2303 The command::
2304 2304
2305 2305 hg graft -r 345 --base 234
2306 2306
2307 2307 is thus pretty much the same as::
2308 2308
2309 2309 hg diff -r 234 -r 345 | hg import
2310 2310
2311 2311 but using merge to resolve conflicts and track moved files.
2312 2312
2313 2313 The result of a merge can thus be backported as a single commit by
2314 2314 specifying one of the merge parents as base, and thus effectively
2315 2315 grafting the changes from the other side.
2316 2316
2317 2317 It is also possible to collapse multiple changesets and clean up history
2318 2318 by specifying another ancestor as base, much like rebase --collapse
2319 2319 --keep.
2320 2320
2321 2321 The commit message can be tweaked after the fact using commit --amend .
2322 2322
2323 2323 For using non-ancestors as the base to backout changes, see the backout
2324 2324 command and the hidden --parent option.
2325 2325
2326 2326 .. container:: verbose
2327 2327
2328 2328 Examples:
2329 2329
2330 2330 - copy a single change to the stable branch and edit its description::
2331 2331
2332 2332 hg update stable
2333 2333 hg graft --edit 9393
2334 2334
2335 2335 - graft a range of changesets with one exception, updating dates::
2336 2336
2337 2337 hg graft -D "2085::2093 and not 2091"
2338 2338
2339 2339 - continue a graft after resolving conflicts::
2340 2340
2341 2341 hg graft -c
2342 2342
2343 2343 - show the source of a grafted changeset::
2344 2344
2345 2345 hg log --debug -r .
2346 2346
2347 2347 - show revisions sorted by date::
2348 2348
2349 2349 hg log -r "sort(all(), date)"
2350 2350
2351 2351 - backport the result of a merge as a single commit::
2352 2352
2353 2353 hg graft -r 123 --base 123^
2354 2354
2355 2355 - land a feature branch as one changeset::
2356 2356
2357 2357 hg up -cr default
2358 2358 hg graft -r featureX --base "ancestor('featureX', 'default')"
2359 2359
2360 2360 See :hg:`help revisions` for more about specifying revisions.
2361 2361
2362 2362 Returns 0 on successful completion.
2363 2363 '''
2364 2364 with repo.wlock():
2365 2365 return _dograft(ui, repo, *revs, **opts)
2366 2366
2367 2367 def _dograft(ui, repo, *revs, **opts):
2368 2368 opts = pycompat.byteskwargs(opts)
2369 2369 if revs and opts.get('rev'):
2370 2370 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2371 2371 'revision ordering!\n'))
2372 2372
2373 2373 revs = list(revs)
2374 2374 revs.extend(opts.get('rev'))
2375 2375 basectx = None
2376 2376 if opts.get('base'):
2377 2377 basectx = scmutil.revsingle(repo, opts['base'], None)
2378 2378 # a dict of data to be stored in state file
2379 2379 statedata = {}
2380 2380 # list of new nodes created by ongoing graft
2381 2381 statedata['newnodes'] = []
2382 2382
2383 2383 if not opts.get('user') and opts.get('currentuser'):
2384 2384 opts['user'] = ui.username()
2385 2385 if not opts.get('date') and opts.get('currentdate'):
2386 2386 opts['date'] = "%d %d" % dateutil.makedate()
2387 2387
2388 2388 editor = cmdutil.getcommiteditor(editform='graft',
2389 2389 **pycompat.strkwargs(opts))
2390 2390
2391 2391 cont = False
2392 2392 if opts.get('no_commit'):
2393 2393 if opts.get('edit'):
2394 2394 raise error.Abort(_("cannot specify --no-commit and "
2395 2395 "--edit together"))
2396 2396 if opts.get('currentuser'):
2397 2397 raise error.Abort(_("cannot specify --no-commit and "
2398 2398 "--currentuser together"))
2399 2399 if opts.get('currentdate'):
2400 2400 raise error.Abort(_("cannot specify --no-commit and "
2401 2401 "--currentdate together"))
2402 2402 if opts.get('log'):
2403 2403 raise error.Abort(_("cannot specify --no-commit and "
2404 2404 "--log together"))
2405 2405
2406 2406 graftstate = statemod.cmdstate(repo, 'graftstate')
2407 2407
2408 2408 if opts.get('stop'):
2409 2409 if opts.get('continue'):
2410 2410 raise error.Abort(_("cannot use '--continue' and "
2411 2411 "'--stop' together"))
2412 2412 if opts.get('abort'):
2413 2413 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2414 2414
2415 2415 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2416 2416 opts.get('date'), opts.get('currentdate'),
2417 2417 opts.get('currentuser'), opts.get('rev'))):
2418 2418 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2419 2419 return _stopgraft(ui, repo, graftstate)
2420 2420 elif opts.get('abort'):
2421 2421 if opts.get('continue'):
2422 2422 raise error.Abort(_("cannot use '--continue' and "
2423 2423 "'--abort' together"))
2424 2424 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2425 2425 opts.get('date'), opts.get('currentdate'),
2426 2426 opts.get('currentuser'), opts.get('rev'))):
2427 2427 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2428 2428
2429 2429 return _abortgraft(ui, repo, graftstate)
2430 2430 elif opts.get('continue'):
2431 2431 cont = True
2432 2432 if revs:
2433 2433 raise error.Abort(_("can't specify --continue and revisions"))
2434 2434 # read in unfinished revisions
2435 2435 if graftstate.exists():
2436 2436 statedata = _readgraftstate(repo, graftstate)
2437 2437 if statedata.get('date'):
2438 2438 opts['date'] = statedata['date']
2439 2439 if statedata.get('user'):
2440 2440 opts['user'] = statedata['user']
2441 2441 if statedata.get('log'):
2442 2442 opts['log'] = True
2443 2443 if statedata.get('no_commit'):
2444 2444 opts['no_commit'] = statedata.get('no_commit')
2445 2445 nodes = statedata['nodes']
2446 2446 revs = [repo[node].rev() for node in nodes]
2447 2447 else:
2448 2448 cmdutil.wrongtooltocontinue(repo, _('graft'))
2449 2449 else:
2450 2450 if not revs:
2451 2451 raise error.Abort(_('no revisions specified'))
2452 2452 cmdutil.checkunfinished(repo)
2453 2453 cmdutil.bailifchanged(repo)
2454 2454 revs = scmutil.revrange(repo, revs)
2455 2455
2456 2456 skipped = set()
2457 2457 if basectx is None:
2458 2458 # check for merges
2459 2459 for rev in repo.revs('%ld and merge()', revs):
2460 2460 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2461 2461 skipped.add(rev)
2462 2462 revs = [r for r in revs if r not in skipped]
2463 2463 if not revs:
2464 2464 return -1
2465 2465 if basectx is not None and len(revs) != 1:
2466 2466 raise error.Abort(_('only one revision allowed with --base '))
2467 2467
2468 2468 # Don't check in the --continue case, in effect retaining --force across
2469 2469 # --continues. That's because without --force, any revisions we decided to
2470 2470 # skip would have been filtered out here, so they wouldn't have made their
2471 2471 # way to the graftstate. With --force, any revisions we would have otherwise
2472 2472 # skipped would not have been filtered out, and if they hadn't been applied
2473 2473 # already, they'd have been in the graftstate.
2474 2474 if not (cont or opts.get('force')) and basectx is None:
2475 2475 # check for ancestors of dest branch
2476 2476 crev = repo['.'].rev()
2477 2477 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2478 2478 # XXX make this lazy in the future
2479 2479 # don't mutate while iterating, create a copy
2480 2480 for rev in list(revs):
2481 2481 if rev in ancestors:
2482 2482 ui.warn(_('skipping ancestor revision %d:%s\n') %
2483 2483 (rev, repo[rev]))
2484 2484 # XXX remove on list is slow
2485 2485 revs.remove(rev)
2486 2486 if not revs:
2487 2487 return -1
2488 2488
2489 2489 # analyze revs for earlier grafts
2490 2490 ids = {}
2491 2491 for ctx in repo.set("%ld", revs):
2492 2492 ids[ctx.hex()] = ctx.rev()
2493 2493 n = ctx.extra().get('source')
2494 2494 if n:
2495 2495 ids[n] = ctx.rev()
2496 2496
2497 2497 # check ancestors for earlier grafts
2498 2498 ui.debug('scanning for duplicate grafts\n')
2499 2499
2500 2500 # The only changesets we can be sure doesn't contain grafts of any
2501 2501 # revs, are the ones that are common ancestors of *all* revs:
2502 2502 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2503 2503 ctx = repo[rev]
2504 2504 n = ctx.extra().get('source')
2505 2505 if n in ids:
2506 2506 try:
2507 2507 r = repo[n].rev()
2508 2508 except error.RepoLookupError:
2509 2509 r = None
2510 2510 if r in revs:
2511 2511 ui.warn(_('skipping revision %d:%s '
2512 2512 '(already grafted to %d:%s)\n')
2513 2513 % (r, repo[r], rev, ctx))
2514 2514 revs.remove(r)
2515 2515 elif ids[n] in revs:
2516 2516 if r is None:
2517 2517 ui.warn(_('skipping already grafted revision %d:%s '
2518 2518 '(%d:%s also has unknown origin %s)\n')
2519 2519 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2520 2520 else:
2521 2521 ui.warn(_('skipping already grafted revision %d:%s '
2522 2522 '(%d:%s also has origin %d:%s)\n')
2523 2523 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2524 2524 revs.remove(ids[n])
2525 2525 elif ctx.hex() in ids:
2526 2526 r = ids[ctx.hex()]
2527 2527 if r in revs:
2528 2528 ui.warn(_('skipping already grafted revision %d:%s '
2529 2529 '(was grafted from %d:%s)\n') %
2530 2530 (r, repo[r], rev, ctx))
2531 2531 revs.remove(r)
2532 2532 if not revs:
2533 2533 return -1
2534 2534
2535 2535 if opts.get('no_commit'):
2536 2536 statedata['no_commit'] = True
2537 2537 for pos, ctx in enumerate(repo.set("%ld", revs)):
2538 2538 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2539 2539 ctx.description().split('\n', 1)[0])
2540 2540 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2541 2541 if names:
2542 2542 desc += ' (%s)' % ' '.join(names)
2543 2543 ui.status(_('grafting %s\n') % desc)
2544 2544 if opts.get('dry_run'):
2545 2545 continue
2546 2546
2547 2547 source = ctx.extra().get('source')
2548 2548 extra = {}
2549 2549 if source:
2550 2550 extra['source'] = source
2551 2551 extra['intermediate-source'] = ctx.hex()
2552 2552 else:
2553 2553 extra['source'] = ctx.hex()
2554 2554 user = ctx.user()
2555 2555 if opts.get('user'):
2556 2556 user = opts['user']
2557 2557 statedata['user'] = user
2558 2558 date = ctx.date()
2559 2559 if opts.get('date'):
2560 2560 date = opts['date']
2561 2561 statedata['date'] = date
2562 2562 message = ctx.description()
2563 2563 if opts.get('log'):
2564 2564 message += '\n(grafted from %s)' % ctx.hex()
2565 2565 statedata['log'] = True
2566 2566
2567 2567 # we don't merge the first commit when continuing
2568 2568 if not cont:
2569 2569 # perform the graft merge with p1(rev) as 'ancestor'
2570 2570 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2571 2571 base = ctx.p1() if basectx is None else basectx
2572 2572 with ui.configoverride(overrides, 'graft'):
2573 2573 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2574 2574 # report any conflicts
2575 2575 if stats.unresolvedcount > 0:
2576 2576 # write out state for --continue
2577 2577 nodes = [repo[rev].hex() for rev in revs[pos:]]
2578 2578 statedata['nodes'] = nodes
2579 2579 stateversion = 1
2580 2580 graftstate.save(stateversion, statedata)
2581 2581 hint = _("use 'hg resolve' and 'hg graft --continue'")
2582 2582 raise error.Abort(
2583 2583 _("unresolved conflicts, can't continue"),
2584 2584 hint=hint)
2585 2585 else:
2586 2586 cont = False
2587 2587
2588 2588 # commit if --no-commit is false
2589 2589 if not opts.get('no_commit'):
2590 2590 node = repo.commit(text=message, user=user, date=date, extra=extra,
2591 2591 editor=editor)
2592 2592 if node is None:
2593 2593 ui.warn(
2594 2594 _('note: graft of %d:%s created no changes to commit\n') %
2595 2595 (ctx.rev(), ctx))
2596 2596 # checking that newnodes exist because old state files won't have it
2597 2597 elif statedata.get('newnodes') is not None:
2598 2598 statedata['newnodes'].append(node)
2599 2599
2600 2600 # remove state when we complete successfully
2601 2601 if not opts.get('dry_run'):
2602 2602 graftstate.delete()
2603 2603
2604 2604 return 0
2605 2605
2606 2606 def _abortgraft(ui, repo, graftstate):
2607 2607 """abort the interrupted graft and rollbacks to the state before interrupted
2608 2608 graft"""
2609 2609 if not graftstate.exists():
2610 2610 raise error.Abort(_("no interrupted graft to abort"))
2611 2611 statedata = _readgraftstate(repo, graftstate)
2612 2612 newnodes = statedata.get('newnodes')
2613 2613 if newnodes is None:
2614 2614 # and old graft state which does not have all the data required to abort
2615 2615 # the graft
2616 2616 raise error.Abort(_("cannot abort using an old graftstate"))
2617 2617
2618 2618 # changeset from which graft operation was started
2619 2619 startctx = None
2620 2620 if len(newnodes) > 0:
2621 2621 startctx = repo[newnodes[0]].p1()
2622 2622 else:
2623 2623 startctx = repo['.']
2624 2624 # whether to strip or not
2625 2625 cleanup = False
2626 2626 if newnodes:
2627 2627 newnodes = [repo[r].rev() for r in newnodes]
2628 2628 cleanup = True
2629 2629 # checking that none of the newnodes turned public or is public
2630 2630 immutable = [c for c in newnodes if not repo[c].mutable()]
2631 2631 if immutable:
2632 2632 repo.ui.warn(_("cannot clean up public changesets %s\n")
2633 2633 % ', '.join(bytes(repo[r]) for r in immutable),
2634 2634 hint=_("see 'hg help phases' for details"))
2635 2635 cleanup = False
2636 2636
2637 2637 # checking that no new nodes are created on top of grafted revs
2638 2638 desc = set(repo.changelog.descendants(newnodes))
2639 2639 if desc - set(newnodes):
2640 2640 repo.ui.warn(_("new changesets detected on destination "
2641 2641 "branch, can't strip\n"))
2642 2642 cleanup = False
2643 2643
2644 2644 if cleanup:
2645 2645 with repo.wlock(), repo.lock():
2646 2646 hg.updaterepo(repo, startctx.node(), overwrite=True)
2647 2647 # stripping the new nodes created
2648 2648 strippoints = [c.node() for c in repo.set("roots(%ld)",
2649 2649 newnodes)]
2650 2650 repair.strip(repo.ui, repo, strippoints, backup=False)
2651 2651
2652 2652 if not cleanup:
2653 2653 # we don't update to the startnode if we can't strip
2654 2654 startctx = repo['.']
2655 2655 hg.updaterepo(repo, startctx.node(), overwrite=True)
2656 2656
2657 2657 ui.status(_("graft aborted\n"))
2658 2658 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2659 2659 graftstate.delete()
2660 2660 return 0
2661 2661
2662 2662 def _readgraftstate(repo, graftstate):
2663 2663 """read the graft state file and return a dict of the data stored in it"""
2664 2664 try:
2665 2665 return graftstate.read()
2666 2666 except error.CorruptedState:
2667 2667 nodes = repo.vfs.read('graftstate').splitlines()
2668 2668 return {'nodes': nodes}
2669 2669
2670 2670 def _stopgraft(ui, repo, graftstate):
2671 2671 """stop the interrupted graft"""
2672 2672 if not graftstate.exists():
2673 2673 raise error.Abort(_("no interrupted graft found"))
2674 2674 pctx = repo['.']
2675 2675 hg.updaterepo(repo, pctx.node(), overwrite=True)
2676 2676 graftstate.delete()
2677 2677 ui.status(_("stopped the interrupted graft\n"))
2678 2678 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2679 2679 return 0
2680 2680
2681 2681 @command('grep',
2682 2682 [('0', 'print0', None, _('end fields with NUL')),
2683 2683 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2684 2684 ('', 'diff', None, _('print all revisions when the term was introduced '
2685 2685 'or removed')),
2686 2686 ('a', 'text', None, _('treat all files as text')),
2687 2687 ('f', 'follow', None,
2688 2688 _('follow changeset history,'
2689 2689 ' or file history across copies and renames')),
2690 2690 ('i', 'ignore-case', None, _('ignore case when matching')),
2691 2691 ('l', 'files-with-matches', None,
2692 2692 _('print only filenames and revisions that match')),
2693 2693 ('n', 'line-number', None, _('print matching line numbers')),
2694 2694 ('r', 'rev', [],
2695 2695 _('only search files changed within revision range'), _('REV')),
2696 2696 ('', 'all-files', None,
2697 2697 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2698 2698 ('u', 'user', None, _('list the author (long with -v)')),
2699 2699 ('d', 'date', None, _('list the date (short with -q)')),
2700 2700 ] + formatteropts + walkopts,
2701 2701 _('[OPTION]... PATTERN [FILE]...'),
2702 2702 helpcategory=command.CATEGORY_FILE_CONTENTS,
2703 2703 inferrepo=True,
2704 2704 intents={INTENT_READONLY})
2705 2705 def grep(ui, repo, pattern, *pats, **opts):
2706 2706 """search revision history for a pattern in specified files
2707 2707
2708 2708 Search revision history for a regular expression in the specified
2709 2709 files or the entire project.
2710 2710
2711 2711 By default, grep prints the most recent revision number for each
2712 2712 file in which it finds a match. To get it to print every revision
2713 2713 that contains a change in match status ("-" for a match that becomes
2714 2714 a non-match, or "+" for a non-match that becomes a match), use the
2715 2715 --diff flag.
2716 2716
2717 2717 PATTERN can be any Python (roughly Perl-compatible) regular
2718 2718 expression.
2719 2719
2720 2720 If no FILEs are specified (and -f/--follow isn't set), all files in
2721 2721 the repository are searched, including those that don't exist in the
2722 2722 current branch or have been deleted in a prior changeset.
2723 2723
2724 2724 .. container:: verbose
2725 2725
2726 2726 Template:
2727 2727
2728 2728 The following keywords are supported in addition to the common template
2729 2729 keywords and functions. See also :hg:`help templates`.
2730 2730
2731 2731 :change: String. Character denoting insertion ``+`` or removal ``-``.
2732 2732 Available if ``--diff`` is specified.
2733 2733 :lineno: Integer. Line number of the match.
2734 2734 :path: String. Repository-absolute path of the file.
2735 2735 :texts: List of text chunks.
2736 2736
2737 2737 And each entry of ``{texts}`` provides the following sub-keywords.
2738 2738
2739 2739 :matched: Boolean. True if the chunk matches the specified pattern.
2740 2740 :text: String. Chunk content.
2741 2741
2742 2742 See :hg:`help templates.operators` for the list expansion syntax.
2743 2743
2744 2744 Returns 0 if a match is found, 1 otherwise.
2745 2745 """
2746 2746 opts = pycompat.byteskwargs(opts)
2747 2747 diff = opts.get('all') or opts.get('diff')
2748 2748 all_files = opts.get('all_files')
2749 2749 if diff and opts.get('all_files'):
2750 2750 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2751 2751 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2752 2752 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2753 2753 # experimental config: commands.grep.all-files
2754 2754 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2755 2755 plaingrep = opts.get('all_files') and not opts.get('rev')
2756 2756 if plaingrep:
2757 2757 opts['rev'] = ['wdir()']
2758 2758
2759 2759 reflags = re.M
2760 2760 if opts.get('ignore_case'):
2761 2761 reflags |= re.I
2762 2762 try:
2763 2763 regexp = util.re.compile(pattern, reflags)
2764 2764 except re.error as inst:
2765 2765 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2766 2766 return 1
2767 2767 sep, eol = ':', '\n'
2768 2768 if opts.get('print0'):
2769 2769 sep = eol = '\0'
2770 2770
2771 2771 getfile = util.lrucachefunc(repo.file)
2772 2772
2773 2773 def matchlines(body):
2774 2774 begin = 0
2775 2775 linenum = 0
2776 2776 while begin < len(body):
2777 2777 match = regexp.search(body, begin)
2778 2778 if not match:
2779 2779 break
2780 2780 mstart, mend = match.span()
2781 2781 linenum += body.count('\n', begin, mstart) + 1
2782 2782 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2783 2783 begin = body.find('\n', mend) + 1 or len(body) + 1
2784 2784 lend = begin - 1
2785 2785 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2786 2786
2787 2787 class linestate(object):
2788 2788 def __init__(self, line, linenum, colstart, colend):
2789 2789 self.line = line
2790 2790 self.linenum = linenum
2791 2791 self.colstart = colstart
2792 2792 self.colend = colend
2793 2793
2794 2794 def __hash__(self):
2795 2795 return hash((self.linenum, self.line))
2796 2796
2797 2797 def __eq__(self, other):
2798 2798 return self.line == other.line
2799 2799
2800 2800 def findpos(self):
2801 2801 """Iterate all (start, end) indices of matches"""
2802 2802 yield self.colstart, self.colend
2803 2803 p = self.colend
2804 2804 while p < len(self.line):
2805 2805 m = regexp.search(self.line, p)
2806 2806 if not m:
2807 2807 break
2808 2808 yield m.span()
2809 2809 p = m.end()
2810 2810
2811 2811 matches = {}
2812 2812 copies = {}
2813 2813 def grepbody(fn, rev, body):
2814 2814 matches[rev].setdefault(fn, [])
2815 2815 m = matches[rev][fn]
2816 2816 for lnum, cstart, cend, line in matchlines(body):
2817 2817 s = linestate(line, lnum, cstart, cend)
2818 2818 m.append(s)
2819 2819
2820 2820 def difflinestates(a, b):
2821 2821 sm = difflib.SequenceMatcher(None, a, b)
2822 2822 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2823 2823 if tag == r'insert':
2824 2824 for i in pycompat.xrange(blo, bhi):
2825 2825 yield ('+', b[i])
2826 2826 elif tag == r'delete':
2827 2827 for i in pycompat.xrange(alo, ahi):
2828 2828 yield ('-', a[i])
2829 2829 elif tag == r'replace':
2830 2830 for i in pycompat.xrange(alo, ahi):
2831 2831 yield ('-', a[i])
2832 2832 for i in pycompat.xrange(blo, bhi):
2833 2833 yield ('+', b[i])
2834 2834
2835 2835 def display(fm, fn, ctx, pstates, states):
2836 2836 rev = scmutil.intrev(ctx)
2837 2837 if fm.isplain():
2838 2838 formatuser = ui.shortuser
2839 2839 else:
2840 2840 formatuser = pycompat.bytestr
2841 2841 if ui.quiet:
2842 2842 datefmt = '%Y-%m-%d'
2843 2843 else:
2844 2844 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2845 2845 found = False
2846 2846 @util.cachefunc
2847 2847 def binary():
2848 2848 flog = getfile(fn)
2849 2849 try:
2850 2850 return stringutil.binary(flog.read(ctx.filenode(fn)))
2851 2851 except error.WdirUnsupported:
2852 2852 return ctx[fn].isbinary()
2853 2853
2854 2854 fieldnamemap = {'filename': 'path', 'linenumber': 'lineno'}
2855 2855 if diff:
2856 2856 iter = difflinestates(pstates, states)
2857 2857 else:
2858 2858 iter = [('', l) for l in states]
2859 2859 for change, l in iter:
2860 2860 fm.startitem()
2861 2861 fm.context(ctx=ctx)
2862 2862 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
2863 2863
2864 2864 cols = [
2865 2865 ('filename', '%s', fn, True),
2866 2866 ('rev', '%d', rev, not plaingrep),
2867 2867 ('linenumber', '%d', l.linenum, opts.get('line_number')),
2868 2868 ]
2869 2869 if diff:
2870 2870 cols.append(('change', '%s', change, True))
2871 2871 cols.extend([
2872 2872 ('user', '%s', formatuser(ctx.user()), opts.get('user')),
2873 2873 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2874 2874 opts.get('date')),
2875 2875 ])
2876 2876 lastcol = next(
2877 2877 name for name, fmt, data, cond in reversed(cols) if cond)
2878 2878 for name, fmt, data, cond in cols:
2879 2879 field = fieldnamemap.get(name, name)
2880 2880 fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
2881 2881 if cond and name != lastcol:
2882 2882 fm.plain(sep, label='grep.sep')
2883 2883 if not opts.get('files_with_matches'):
2884 2884 fm.plain(sep, label='grep.sep')
2885 2885 if not opts.get('text') and binary():
2886 2886 fm.plain(_(" Binary file matches"))
2887 2887 else:
2888 2888 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2889 2889 fm.plain(eol)
2890 2890 found = True
2891 2891 if opts.get('files_with_matches'):
2892 2892 break
2893 2893 return found
2894 2894
2895 2895 def displaymatches(fm, l):
2896 2896 p = 0
2897 2897 for s, e in l.findpos():
2898 2898 if p < s:
2899 2899 fm.startitem()
2900 2900 fm.write('text', '%s', l.line[p:s])
2901 2901 fm.data(matched=False)
2902 2902 fm.startitem()
2903 2903 fm.write('text', '%s', l.line[s:e], label='grep.match')
2904 2904 fm.data(matched=True)
2905 2905 p = e
2906 2906 if p < len(l.line):
2907 2907 fm.startitem()
2908 2908 fm.write('text', '%s', l.line[p:])
2909 2909 fm.data(matched=False)
2910 2910 fm.end()
2911 2911
2912 2912 skip = {}
2913 2913 revfiles = {}
2914 2914 match = scmutil.match(repo[None], pats, opts)
2915 2915 found = False
2916 2916 follow = opts.get('follow')
2917 2917
2918 2918 def prep(ctx, fns):
2919 2919 rev = ctx.rev()
2920 2920 pctx = ctx.p1()
2921 2921 parent = pctx.rev()
2922 2922 matches.setdefault(rev, {})
2923 2923 matches.setdefault(parent, {})
2924 2924 files = revfiles.setdefault(rev, [])
2925 2925 for fn in fns:
2926 2926 flog = getfile(fn)
2927 2927 try:
2928 2928 fnode = ctx.filenode(fn)
2929 2929 except error.LookupError:
2930 2930 continue
2931 2931 try:
2932 2932 copied = flog.renamed(fnode)
2933 2933 except error.WdirUnsupported:
2934 2934 copied = ctx[fn].renamed()
2935 2935 copy = follow and copied and copied[0]
2936 2936 if copy:
2937 2937 copies.setdefault(rev, {})[fn] = copy
2938 2938 if fn in skip:
2939 2939 if copy:
2940 2940 skip[copy] = True
2941 2941 continue
2942 2942 files.append(fn)
2943 2943
2944 2944 if fn not in matches[rev]:
2945 2945 try:
2946 2946 content = flog.read(fnode)
2947 2947 except error.WdirUnsupported:
2948 2948 content = ctx[fn].data()
2949 2949 grepbody(fn, rev, content)
2950 2950
2951 2951 pfn = copy or fn
2952 2952 if pfn not in matches[parent]:
2953 2953 try:
2954 2954 fnode = pctx.filenode(pfn)
2955 2955 grepbody(pfn, parent, flog.read(fnode))
2956 2956 except error.LookupError:
2957 2957 pass
2958 2958
2959 2959 ui.pager('grep')
2960 2960 fm = ui.formatter('grep', opts)
2961 2961 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2962 2962 rev = ctx.rev()
2963 2963 parent = ctx.p1().rev()
2964 2964 for fn in sorted(revfiles.get(rev, [])):
2965 2965 states = matches[rev][fn]
2966 2966 copy = copies.get(rev, {}).get(fn)
2967 2967 if fn in skip:
2968 2968 if copy:
2969 2969 skip[copy] = True
2970 2970 continue
2971 2971 pstates = matches.get(parent, {}).get(copy or fn, [])
2972 2972 if pstates or states:
2973 2973 r = display(fm, fn, ctx, pstates, states)
2974 2974 found = found or r
2975 2975 if r and not diff and not all_files:
2976 2976 skip[fn] = True
2977 2977 if copy:
2978 2978 skip[copy] = True
2979 2979 del revfiles[rev]
2980 2980 # We will keep the matches dict for the duration of the window
2981 2981 # clear the matches dict once the window is over
2982 2982 if not revfiles:
2983 2983 matches.clear()
2984 2984 fm.end()
2985 2985
2986 2986 return not found
2987 2987
2988 2988 @command('heads',
2989 2989 [('r', 'rev', '',
2990 2990 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2991 2991 ('t', 'topo', False, _('show topological heads only')),
2992 2992 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2993 2993 ('c', 'closed', False, _('show normal and closed branch heads')),
2994 2994 ] + templateopts,
2995 2995 _('[-ct] [-r STARTREV] [REV]...'),
2996 2996 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
2997 2997 intents={INTENT_READONLY})
2998 2998 def heads(ui, repo, *branchrevs, **opts):
2999 2999 """show branch heads
3000 3000
3001 3001 With no arguments, show all open branch heads in the repository.
3002 3002 Branch heads are changesets that have no descendants on the
3003 3003 same branch. They are where development generally takes place and
3004 3004 are the usual targets for update and merge operations.
3005 3005
3006 3006 If one or more REVs are given, only open branch heads on the
3007 3007 branches associated with the specified changesets are shown. This
3008 3008 means that you can use :hg:`heads .` to see the heads on the
3009 3009 currently checked-out branch.
3010 3010
3011 3011 If -c/--closed is specified, also show branch heads marked closed
3012 3012 (see :hg:`commit --close-branch`).
3013 3013
3014 3014 If STARTREV is specified, only those heads that are descendants of
3015 3015 STARTREV will be displayed.
3016 3016
3017 3017 If -t/--topo is specified, named branch mechanics will be ignored and only
3018 3018 topological heads (changesets with no children) will be shown.
3019 3019
3020 3020 Returns 0 if matching heads are found, 1 if not.
3021 3021 """
3022 3022
3023 3023 opts = pycompat.byteskwargs(opts)
3024 3024 start = None
3025 3025 rev = opts.get('rev')
3026 3026 if rev:
3027 3027 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3028 3028 start = scmutil.revsingle(repo, rev, None).node()
3029 3029
3030 3030 if opts.get('topo'):
3031 3031 heads = [repo[h] for h in repo.heads(start)]
3032 3032 else:
3033 3033 heads = []
3034 3034 for branch in repo.branchmap():
3035 3035 heads += repo.branchheads(branch, start, opts.get('closed'))
3036 3036 heads = [repo[h] for h in heads]
3037 3037
3038 3038 if branchrevs:
3039 3039 branches = set(repo[r].branch()
3040 3040 for r in scmutil.revrange(repo, branchrevs))
3041 3041 heads = [h for h in heads if h.branch() in branches]
3042 3042
3043 3043 if opts.get('active') and branchrevs:
3044 3044 dagheads = repo.heads(start)
3045 3045 heads = [h for h in heads if h.node() in dagheads]
3046 3046
3047 3047 if branchrevs:
3048 3048 haveheads = set(h.branch() for h in heads)
3049 3049 if branches - haveheads:
3050 3050 headless = ', '.join(b for b in branches - haveheads)
3051 3051 msg = _('no open branch heads found on branches %s')
3052 3052 if opts.get('rev'):
3053 3053 msg += _(' (started at %s)') % opts['rev']
3054 3054 ui.warn((msg + '\n') % headless)
3055 3055
3056 3056 if not heads:
3057 3057 return 1
3058 3058
3059 3059 ui.pager('heads')
3060 3060 heads = sorted(heads, key=lambda x: -x.rev())
3061 3061 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3062 3062 for ctx in heads:
3063 3063 displayer.show(ctx)
3064 3064 displayer.close()
3065 3065
3066 3066 @command('help',
3067 3067 [('e', 'extension', None, _('show only help for extensions')),
3068 3068 ('c', 'command', None, _('show only help for commands')),
3069 3069 ('k', 'keyword', None, _('show topics matching keyword')),
3070 3070 ('s', 'system', [],
3071 3071 _('show help for specific platform(s)'), _('PLATFORM')),
3072 3072 ],
3073 3073 _('[-eck] [-s PLATFORM] [TOPIC]'),
3074 3074 helpcategory=command.CATEGORY_HELP,
3075 3075 norepo=True,
3076 3076 intents={INTENT_READONLY})
3077 3077 def help_(ui, name=None, **opts):
3078 3078 """show help for a given topic or a help overview
3079 3079
3080 3080 With no arguments, print a list of commands with short help messages.
3081 3081
3082 3082 Given a topic, extension, or command name, print help for that
3083 3083 topic.
3084 3084
3085 3085 Returns 0 if successful.
3086 3086 """
3087 3087
3088 3088 keep = opts.get(r'system') or []
3089 3089 if len(keep) == 0:
3090 3090 if pycompat.sysplatform.startswith('win'):
3091 3091 keep.append('windows')
3092 3092 elif pycompat.sysplatform == 'OpenVMS':
3093 3093 keep.append('vms')
3094 3094 elif pycompat.sysplatform == 'plan9':
3095 3095 keep.append('plan9')
3096 3096 else:
3097 3097 keep.append('unix')
3098 3098 keep.append(pycompat.sysplatform.lower())
3099 3099 if ui.verbose:
3100 3100 keep.append('verbose')
3101 3101
3102 3102 commands = sys.modules[__name__]
3103 3103 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3104 3104 ui.pager('help')
3105 3105 ui.write(formatted)
3106 3106
3107 3107
3108 3108 @command('identify|id',
3109 3109 [('r', 'rev', '',
3110 3110 _('identify the specified revision'), _('REV')),
3111 3111 ('n', 'num', None, _('show local revision number')),
3112 3112 ('i', 'id', None, _('show global revision id')),
3113 3113 ('b', 'branch', None, _('show branch')),
3114 3114 ('t', 'tags', None, _('show tags')),
3115 3115 ('B', 'bookmarks', None, _('show bookmarks')),
3116 3116 ] + remoteopts + formatteropts,
3117 3117 _('[-nibtB] [-r REV] [SOURCE]'),
3118 3118 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3119 3119 optionalrepo=True,
3120 3120 intents={INTENT_READONLY})
3121 3121 def identify(ui, repo, source=None, rev=None,
3122 3122 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3123 3123 """identify the working directory or specified revision
3124 3124
3125 3125 Print a summary identifying the repository state at REV using one or
3126 3126 two parent hash identifiers, followed by a "+" if the working
3127 3127 directory has uncommitted changes, the branch name (if not default),
3128 3128 a list of tags, and a list of bookmarks.
3129 3129
3130 3130 When REV is not given, print a summary of the current state of the
3131 3131 repository including the working directory. Specify -r. to get information
3132 3132 of the working directory parent without scanning uncommitted changes.
3133 3133
3134 3134 Specifying a path to a repository root or Mercurial bundle will
3135 3135 cause lookup to operate on that repository/bundle.
3136 3136
3137 3137 .. container:: verbose
3138 3138
3139 3139 Template:
3140 3140
3141 3141 The following keywords are supported in addition to the common template
3142 3142 keywords and functions. See also :hg:`help templates`.
3143 3143
3144 3144 :dirty: String. Character ``+`` denoting if the working directory has
3145 3145 uncommitted changes.
3146 3146 :id: String. One or two nodes, optionally followed by ``+``.
3147 3147 :parents: List of strings. Parent nodes of the changeset.
3148 3148
3149 3149 Examples:
3150 3150
3151 3151 - generate a build identifier for the working directory::
3152 3152
3153 3153 hg id --id > build-id.dat
3154 3154
3155 3155 - find the revision corresponding to a tag::
3156 3156
3157 3157 hg id -n -r 1.3
3158 3158
3159 3159 - check the most recent revision of a remote repository::
3160 3160
3161 3161 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3162 3162
3163 3163 See :hg:`log` for generating more information about specific revisions,
3164 3164 including full hash identifiers.
3165 3165
3166 3166 Returns 0 if successful.
3167 3167 """
3168 3168
3169 3169 opts = pycompat.byteskwargs(opts)
3170 3170 if not repo and not source:
3171 3171 raise error.Abort(_("there is no Mercurial repository here "
3172 3172 "(.hg not found)"))
3173 3173
3174 3174 default = not (num or id or branch or tags or bookmarks)
3175 3175 output = []
3176 3176 revs = []
3177 3177
3178 3178 if source:
3179 3179 source, branches = hg.parseurl(ui.expandpath(source))
3180 3180 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3181 3181 repo = peer.local()
3182 3182 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3183 3183
3184 3184 fm = ui.formatter('identify', opts)
3185 3185 fm.startitem()
3186 3186
3187 3187 if not repo:
3188 3188 if num or branch or tags:
3189 3189 raise error.Abort(
3190 3190 _("can't query remote revision number, branch, or tags"))
3191 3191 if not rev and revs:
3192 3192 rev = revs[0]
3193 3193 if not rev:
3194 3194 rev = "tip"
3195 3195
3196 3196 remoterev = peer.lookup(rev)
3197 3197 hexrev = fm.hexfunc(remoterev)
3198 3198 if default or id:
3199 3199 output = [hexrev]
3200 3200 fm.data(id=hexrev)
3201 3201
3202 3202 @util.cachefunc
3203 3203 def getbms():
3204 3204 bms = []
3205 3205
3206 3206 if 'bookmarks' in peer.listkeys('namespaces'):
3207 3207 hexremoterev = hex(remoterev)
3208 3208 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3209 3209 if bmr == hexremoterev]
3210 3210
3211 3211 return sorted(bms)
3212 3212
3213 3213 if fm.isplain():
3214 3214 if bookmarks:
3215 3215 output.extend(getbms())
3216 3216 elif default and not ui.quiet:
3217 3217 # multiple bookmarks for a single parent separated by '/'
3218 3218 bm = '/'.join(getbms())
3219 3219 if bm:
3220 3220 output.append(bm)
3221 3221 else:
3222 3222 fm.data(node=hex(remoterev))
3223 3223 if bookmarks or 'bookmarks' in fm.datahint():
3224 3224 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3225 3225 else:
3226 3226 if rev:
3227 3227 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3228 3228 ctx = scmutil.revsingle(repo, rev, None)
3229 3229
3230 3230 if ctx.rev() is None:
3231 3231 ctx = repo[None]
3232 3232 parents = ctx.parents()
3233 3233 taglist = []
3234 3234 for p in parents:
3235 3235 taglist.extend(p.tags())
3236 3236
3237 3237 dirty = ""
3238 3238 if ctx.dirty(missing=True, merge=False, branch=False):
3239 3239 dirty = '+'
3240 3240 fm.data(dirty=dirty)
3241 3241
3242 3242 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3243 3243 if default or id:
3244 3244 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3245 3245 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3246 3246
3247 3247 if num:
3248 3248 numoutput = ["%d" % p.rev() for p in parents]
3249 3249 output.append("%s%s" % ('+'.join(numoutput), dirty))
3250 3250
3251 3251 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3252 3252 for p in parents], name='node'))
3253 3253 else:
3254 3254 hexoutput = fm.hexfunc(ctx.node())
3255 3255 if default or id:
3256 3256 output = [hexoutput]
3257 3257 fm.data(id=hexoutput)
3258 3258
3259 3259 if num:
3260 3260 output.append(pycompat.bytestr(ctx.rev()))
3261 3261 taglist = ctx.tags()
3262 3262
3263 3263 if default and not ui.quiet:
3264 3264 b = ctx.branch()
3265 3265 if b != 'default':
3266 3266 output.append("(%s)" % b)
3267 3267
3268 3268 # multiple tags for a single parent separated by '/'
3269 3269 t = '/'.join(taglist)
3270 3270 if t:
3271 3271 output.append(t)
3272 3272
3273 3273 # multiple bookmarks for a single parent separated by '/'
3274 3274 bm = '/'.join(ctx.bookmarks())
3275 3275 if bm:
3276 3276 output.append(bm)
3277 3277 else:
3278 3278 if branch:
3279 3279 output.append(ctx.branch())
3280 3280
3281 3281 if tags:
3282 3282 output.extend(taglist)
3283 3283
3284 3284 if bookmarks:
3285 3285 output.extend(ctx.bookmarks())
3286 3286
3287 3287 fm.data(node=ctx.hex())
3288 3288 fm.data(branch=ctx.branch())
3289 3289 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3290 3290 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3291 3291 fm.context(ctx=ctx)
3292 3292
3293 3293 fm.plain("%s\n" % ' '.join(output))
3294 3294 fm.end()
3295 3295
3296 3296 @command('import|patch',
3297 3297 [('p', 'strip', 1,
3298 3298 _('directory strip option for patch. This has the same '
3299 3299 'meaning as the corresponding patch option'), _('NUM')),
3300 3300 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3301 3301 ('e', 'edit', False, _('invoke editor on commit messages')),
3302 3302 ('f', 'force', None,
3303 3303 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3304 3304 ('', 'no-commit', None,
3305 3305 _("don't commit, just update the working directory")),
3306 3306 ('', 'bypass', None,
3307 3307 _("apply patch without touching the working directory")),
3308 3308 ('', 'partial', None,
3309 3309 _('commit even if some hunks fail')),
3310 3310 ('', 'exact', None,
3311 3311 _('abort if patch would apply lossily')),
3312 3312 ('', 'prefix', '',
3313 3313 _('apply patch to subdirectory'), _('DIR')),
3314 3314 ('', 'import-branch', None,
3315 3315 _('use any branch information in patch (implied by --exact)'))] +
3316 3316 commitopts + commitopts2 + similarityopts,
3317 3317 _('[OPTION]... PATCH...'),
3318 3318 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3319 3319 def import_(ui, repo, patch1=None, *patches, **opts):
3320 3320 """import an ordered set of patches
3321 3321
3322 3322 Import a list of patches and commit them individually (unless
3323 3323 --no-commit is specified).
3324 3324
3325 3325 To read a patch from standard input (stdin), use "-" as the patch
3326 3326 name. If a URL is specified, the patch will be downloaded from
3327 3327 there.
3328 3328
3329 3329 Import first applies changes to the working directory (unless
3330 3330 --bypass is specified), import will abort if there are outstanding
3331 3331 changes.
3332 3332
3333 3333 Use --bypass to apply and commit patches directly to the
3334 3334 repository, without affecting the working directory. Without
3335 3335 --exact, patches will be applied on top of the working directory
3336 3336 parent revision.
3337 3337
3338 3338 You can import a patch straight from a mail message. Even patches
3339 3339 as attachments work (to use the body part, it must have type
3340 3340 text/plain or text/x-patch). From and Subject headers of email
3341 3341 message are used as default committer and commit message. All
3342 3342 text/plain body parts before first diff are added to the commit
3343 3343 message.
3344 3344
3345 3345 If the imported patch was generated by :hg:`export`, user and
3346 3346 description from patch override values from message headers and
3347 3347 body. Values given on command line with -m/--message and -u/--user
3348 3348 override these.
3349 3349
3350 3350 If --exact is specified, import will set the working directory to
3351 3351 the parent of each patch before applying it, and will abort if the
3352 3352 resulting changeset has a different ID than the one recorded in
3353 3353 the patch. This will guard against various ways that portable
3354 3354 patch formats and mail systems might fail to transfer Mercurial
3355 3355 data or metadata. See :hg:`bundle` for lossless transmission.
3356 3356
3357 3357 Use --partial to ensure a changeset will be created from the patch
3358 3358 even if some hunks fail to apply. Hunks that fail to apply will be
3359 3359 written to a <target-file>.rej file. Conflicts can then be resolved
3360 3360 by hand before :hg:`commit --amend` is run to update the created
3361 3361 changeset. This flag exists to let people import patches that
3362 3362 partially apply without losing the associated metadata (author,
3363 3363 date, description, ...).
3364 3364
3365 3365 .. note::
3366 3366
3367 3367 When no hunks apply cleanly, :hg:`import --partial` will create
3368 3368 an empty changeset, importing only the patch metadata.
3369 3369
3370 3370 With -s/--similarity, hg will attempt to discover renames and
3371 3371 copies in the patch in the same way as :hg:`addremove`.
3372 3372
3373 3373 It is possible to use external patch programs to perform the patch
3374 3374 by setting the ``ui.patch`` configuration option. For the default
3375 3375 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3376 3376 See :hg:`help config` for more information about configuration
3377 3377 files and how to use these options.
3378 3378
3379 3379 See :hg:`help dates` for a list of formats valid for -d/--date.
3380 3380
3381 3381 .. container:: verbose
3382 3382
3383 3383 Examples:
3384 3384
3385 3385 - import a traditional patch from a website and detect renames::
3386 3386
3387 3387 hg import -s 80 http://example.com/bugfix.patch
3388 3388
3389 3389 - import a changeset from an hgweb server::
3390 3390
3391 3391 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3392 3392
3393 3393 - import all the patches in an Unix-style mbox::
3394 3394
3395 3395 hg import incoming-patches.mbox
3396 3396
3397 3397 - import patches from stdin::
3398 3398
3399 3399 hg import -
3400 3400
3401 3401 - attempt to exactly restore an exported changeset (not always
3402 3402 possible)::
3403 3403
3404 3404 hg import --exact proposed-fix.patch
3405 3405
3406 3406 - use an external tool to apply a patch which is too fuzzy for
3407 3407 the default internal tool.
3408 3408
3409 3409 hg import --config ui.patch="patch --merge" fuzzy.patch
3410 3410
3411 3411 - change the default fuzzing from 2 to a less strict 7
3412 3412
3413 3413 hg import --config ui.fuzz=7 fuzz.patch
3414 3414
3415 3415 Returns 0 on success, 1 on partial success (see --partial).
3416 3416 """
3417 3417
3418 3418 opts = pycompat.byteskwargs(opts)
3419 3419 if not patch1:
3420 3420 raise error.Abort(_('need at least one patch to import'))
3421 3421
3422 3422 patches = (patch1,) + patches
3423 3423
3424 3424 date = opts.get('date')
3425 3425 if date:
3426 3426 opts['date'] = dateutil.parsedate(date)
3427 3427
3428 3428 exact = opts.get('exact')
3429 3429 update = not opts.get('bypass')
3430 3430 if not update and opts.get('no_commit'):
3431 3431 raise error.Abort(_('cannot use --no-commit with --bypass'))
3432 3432 try:
3433 3433 sim = float(opts.get('similarity') or 0)
3434 3434 except ValueError:
3435 3435 raise error.Abort(_('similarity must be a number'))
3436 3436 if sim < 0 or sim > 100:
3437 3437 raise error.Abort(_('similarity must be between 0 and 100'))
3438 3438 if sim and not update:
3439 3439 raise error.Abort(_('cannot use --similarity with --bypass'))
3440 3440 if exact:
3441 3441 if opts.get('edit'):
3442 3442 raise error.Abort(_('cannot use --exact with --edit'))
3443 3443 if opts.get('prefix'):
3444 3444 raise error.Abort(_('cannot use --exact with --prefix'))
3445 3445
3446 3446 base = opts["base"]
3447 3447 msgs = []
3448 3448 ret = 0
3449 3449
3450 3450 with repo.wlock():
3451 3451 if update:
3452 3452 cmdutil.checkunfinished(repo)
3453 3453 if (exact or not opts.get('force')):
3454 3454 cmdutil.bailifchanged(repo)
3455 3455
3456 3456 if not opts.get('no_commit'):
3457 3457 lock = repo.lock
3458 3458 tr = lambda: repo.transaction('import')
3459 3459 dsguard = util.nullcontextmanager
3460 3460 else:
3461 3461 lock = util.nullcontextmanager
3462 3462 tr = util.nullcontextmanager
3463 3463 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3464 3464 with lock(), tr(), dsguard():
3465 3465 parents = repo[None].parents()
3466 3466 for patchurl in patches:
3467 3467 if patchurl == '-':
3468 3468 ui.status(_('applying patch from stdin\n'))
3469 3469 patchfile = ui.fin
3470 3470 patchurl = 'stdin' # for error message
3471 3471 else:
3472 3472 patchurl = os.path.join(base, patchurl)
3473 3473 ui.status(_('applying %s\n') % patchurl)
3474 3474 patchfile = hg.openpath(ui, patchurl)
3475 3475
3476 3476 haspatch = False
3477 3477 for hunk in patch.split(patchfile):
3478 3478 with patch.extract(ui, hunk) as patchdata:
3479 3479 msg, node, rej = cmdutil.tryimportone(ui, repo,
3480 3480 patchdata,
3481 3481 parents, opts,
3482 3482 msgs, hg.clean)
3483 3483 if msg:
3484 3484 haspatch = True
3485 3485 ui.note(msg + '\n')
3486 3486 if update or exact:
3487 3487 parents = repo[None].parents()
3488 3488 else:
3489 3489 parents = [repo[node]]
3490 3490 if rej:
3491 3491 ui.write_err(_("patch applied partially\n"))
3492 3492 ui.write_err(_("(fix the .rej files and run "
3493 3493 "`hg commit --amend`)\n"))
3494 3494 ret = 1
3495 3495 break
3496 3496
3497 3497 if not haspatch:
3498 3498 raise error.Abort(_('%s: no diffs found') % patchurl)
3499 3499
3500 3500 if msgs:
3501 3501 repo.savecommitmessage('\n* * *\n'.join(msgs))
3502 3502 return ret
3503 3503
3504 3504 @command('incoming|in',
3505 3505 [('f', 'force', None,
3506 3506 _('run even if remote repository is unrelated')),
3507 3507 ('n', 'newest-first', None, _('show newest record first')),
3508 3508 ('', 'bundle', '',
3509 3509 _('file to store the bundles into'), _('FILE')),
3510 3510 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3511 3511 ('B', 'bookmarks', False, _("compare bookmarks")),
3512 3512 ('b', 'branch', [],
3513 3513 _('a specific branch you would like to pull'), _('BRANCH')),
3514 3514 ] + logopts + remoteopts + subrepoopts,
3515 3515 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3516 3516 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3517 3517 def incoming(ui, repo, source="default", **opts):
3518 3518 """show new changesets found in source
3519 3519
3520 3520 Show new changesets found in the specified path/URL or the default
3521 3521 pull location. These are the changesets that would have been pulled
3522 3522 by :hg:`pull` at the time you issued this command.
3523 3523
3524 3524 See pull for valid source format details.
3525 3525
3526 3526 .. container:: verbose
3527 3527
3528 3528 With -B/--bookmarks, the result of bookmark comparison between
3529 3529 local and remote repositories is displayed. With -v/--verbose,
3530 3530 status is also displayed for each bookmark like below::
3531 3531
3532 3532 BM1 01234567890a added
3533 3533 BM2 1234567890ab advanced
3534 3534 BM3 234567890abc diverged
3535 3535 BM4 34567890abcd changed
3536 3536
3537 3537 The action taken locally when pulling depends on the
3538 3538 status of each bookmark:
3539 3539
3540 3540 :``added``: pull will create it
3541 3541 :``advanced``: pull will update it
3542 3542 :``diverged``: pull will create a divergent bookmark
3543 3543 :``changed``: result depends on remote changesets
3544 3544
3545 3545 From the point of view of pulling behavior, bookmark
3546 3546 existing only in the remote repository are treated as ``added``,
3547 3547 even if it is in fact locally deleted.
3548 3548
3549 3549 .. container:: verbose
3550 3550
3551 3551 For remote repository, using --bundle avoids downloading the
3552 3552 changesets twice if the incoming is followed by a pull.
3553 3553
3554 3554 Examples:
3555 3555
3556 3556 - show incoming changes with patches and full description::
3557 3557
3558 3558 hg incoming -vp
3559 3559
3560 3560 - show incoming changes excluding merges, store a bundle::
3561 3561
3562 3562 hg in -vpM --bundle incoming.hg
3563 3563 hg pull incoming.hg
3564 3564
3565 3565 - briefly list changes inside a bundle::
3566 3566
3567 3567 hg in changes.hg -T "{desc|firstline}\\n"
3568 3568
3569 3569 Returns 0 if there are incoming changes, 1 otherwise.
3570 3570 """
3571 3571 opts = pycompat.byteskwargs(opts)
3572 3572 if opts.get('graph'):
3573 3573 logcmdutil.checkunsupportedgraphflags([], opts)
3574 3574 def display(other, chlist, displayer):
3575 3575 revdag = logcmdutil.graphrevs(other, chlist, opts)
3576 3576 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3577 3577 graphmod.asciiedges)
3578 3578
3579 3579 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3580 3580 return 0
3581 3581
3582 3582 if opts.get('bundle') and opts.get('subrepos'):
3583 3583 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3584 3584
3585 3585 if opts.get('bookmarks'):
3586 3586 source, branches = hg.parseurl(ui.expandpath(source),
3587 3587 opts.get('branch'))
3588 3588 other = hg.peer(repo, opts, source)
3589 3589 if 'bookmarks' not in other.listkeys('namespaces'):
3590 3590 ui.warn(_("remote doesn't support bookmarks\n"))
3591 3591 return 0
3592 3592 ui.pager('incoming')
3593 3593 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3594 3594 return bookmarks.incoming(ui, repo, other)
3595 3595
3596 3596 repo._subtoppath = ui.expandpath(source)
3597 3597 try:
3598 3598 return hg.incoming(ui, repo, source, opts)
3599 3599 finally:
3600 3600 del repo._subtoppath
3601 3601
3602 3602
3603 3603 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3604 3604 helpcategory=command.CATEGORY_REPO_CREATION,
3605 3605 helpbasic=True, norepo=True)
3606 3606 def init(ui, dest=".", **opts):
3607 3607 """create a new repository in the given directory
3608 3608
3609 3609 Initialize a new repository in the given directory. If the given
3610 3610 directory does not exist, it will be created.
3611 3611
3612 3612 If no directory is given, the current directory is used.
3613 3613
3614 3614 It is possible to specify an ``ssh://`` URL as the destination.
3615 3615 See :hg:`help urls` for more information.
3616 3616
3617 3617 Returns 0 on success.
3618 3618 """
3619 3619 opts = pycompat.byteskwargs(opts)
3620 3620 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3621 3621
3622 3622 @command('locate',
3623 3623 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3624 3624 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3625 3625 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3626 3626 ] + walkopts,
3627 3627 _('[OPTION]... [PATTERN]...'),
3628 3628 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3629 3629 def locate(ui, repo, *pats, **opts):
3630 3630 """locate files matching specific patterns (DEPRECATED)
3631 3631
3632 3632 Print files under Mercurial control in the working directory whose
3633 3633 names match the given patterns.
3634 3634
3635 3635 By default, this command searches all directories in the working
3636 3636 directory. To search just the current directory and its
3637 3637 subdirectories, use "--include .".
3638 3638
3639 3639 If no patterns are given to match, this command prints the names
3640 3640 of all files under Mercurial control in the working directory.
3641 3641
3642 3642 If you want to feed the output of this command into the "xargs"
3643 3643 command, use the -0 option to both this command and "xargs". This
3644 3644 will avoid the problem of "xargs" treating single filenames that
3645 3645 contain whitespace as multiple filenames.
3646 3646
3647 3647 See :hg:`help files` for a more versatile command.
3648 3648
3649 3649 Returns 0 if a match is found, 1 otherwise.
3650 3650 """
3651 3651 opts = pycompat.byteskwargs(opts)
3652 3652 if opts.get('print0'):
3653 3653 end = '\0'
3654 3654 else:
3655 3655 end = '\n'
3656 3656 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3657 3657
3658 3658 ret = 1
3659 3659 m = scmutil.match(ctx, pats, opts, default='relglob',
3660 3660 badfn=lambda x, y: False)
3661 3661
3662 3662 ui.pager('locate')
3663 3663 if ctx.rev() is None:
3664 3664 # When run on the working copy, "locate" includes removed files, so
3665 3665 # we get the list of files from the dirstate.
3666 3666 filesgen = sorted(repo.dirstate.matches(m))
3667 3667 else:
3668 3668 filesgen = ctx.matches(m)
3669 3669 for abs in filesgen:
3670 3670 if opts.get('fullpath'):
3671 3671 ui.write(repo.wjoin(abs), end)
3672 3672 else:
3673 3673 ui.write(((pats and m.rel(abs)) or abs), end)
3674 3674 ret = 0
3675 3675
3676 3676 return ret
3677 3677
3678 3678 @command('log|history',
3679 3679 [('f', 'follow', None,
3680 3680 _('follow changeset history, or file history across copies and renames')),
3681 3681 ('', 'follow-first', None,
3682 3682 _('only follow the first parent of merge changesets (DEPRECATED)')),
3683 3683 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3684 3684 ('C', 'copies', None, _('show copied files')),
3685 3685 ('k', 'keyword', [],
3686 3686 _('do case-insensitive search for a given text'), _('TEXT')),
3687 3687 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3688 3688 ('L', 'line-range', [],
3689 3689 _('follow line range of specified file (EXPERIMENTAL)'),
3690 3690 _('FILE,RANGE')),
3691 3691 ('', 'removed', None, _('include revisions where files were removed')),
3692 3692 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3693 3693 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3694 3694 ('', 'only-branch', [],
3695 3695 _('show only changesets within the given named branch (DEPRECATED)'),
3696 3696 _('BRANCH')),
3697 3697 ('b', 'branch', [],
3698 3698 _('show changesets within the given named branch'), _('BRANCH')),
3699 3699 ('P', 'prune', [],
3700 3700 _('do not display revision or any of its ancestors'), _('REV')),
3701 3701 ] + logopts + walkopts,
3702 3702 _('[OPTION]... [FILE]'),
3703 3703 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3704 3704 helpbasic=True, inferrepo=True,
3705 3705 intents={INTENT_READONLY})
3706 3706 def log(ui, repo, *pats, **opts):
3707 3707 """show revision history of entire repository or files
3708 3708
3709 3709 Print the revision history of the specified files or the entire
3710 3710 project.
3711 3711
3712 3712 If no revision range is specified, the default is ``tip:0`` unless
3713 3713 --follow is set, in which case the working directory parent is
3714 3714 used as the starting revision.
3715 3715
3716 3716 File history is shown without following rename or copy history of
3717 3717 files. Use -f/--follow with a filename to follow history across
3718 3718 renames and copies. --follow without a filename will only show
3719 3719 ancestors of the starting revision.
3720 3720
3721 3721 By default this command prints revision number and changeset id,
3722 3722 tags, non-trivial parents, user, date and time, and a summary for
3723 3723 each commit. When the -v/--verbose switch is used, the list of
3724 3724 changed files and full commit message are shown.
3725 3725
3726 3726 With --graph the revisions are shown as an ASCII art DAG with the most
3727 3727 recent changeset at the top.
3728 3728 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3729 3729 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3730 3730 changeset from the lines below is a parent of the 'o' merge on the same
3731 3731 line.
3732 3732 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3733 3733 of a '|' indicates one or more revisions in a path are omitted.
3734 3734
3735 3735 .. container:: verbose
3736 3736
3737 3737 Use -L/--line-range FILE,M:N options to follow the history of lines
3738 3738 from M to N in FILE. With -p/--patch only diff hunks affecting
3739 3739 specified line range will be shown. This option requires --follow;
3740 3740 it can be specified multiple times. Currently, this option is not
3741 3741 compatible with --graph. This option is experimental.
3742 3742
3743 3743 .. note::
3744 3744
3745 3745 :hg:`log --patch` may generate unexpected diff output for merge
3746 3746 changesets, as it will only compare the merge changeset against
3747 3747 its first parent. Also, only files different from BOTH parents
3748 3748 will appear in files:.
3749 3749
3750 3750 .. note::
3751 3751
3752 3752 For performance reasons, :hg:`log FILE` may omit duplicate changes
3753 3753 made on branches and will not show removals or mode changes. To
3754 3754 see all such changes, use the --removed switch.
3755 3755
3756 3756 .. container:: verbose
3757 3757
3758 3758 .. note::
3759 3759
3760 3760 The history resulting from -L/--line-range options depends on diff
3761 3761 options; for instance if white-spaces are ignored, respective changes
3762 3762 with only white-spaces in specified line range will not be listed.
3763 3763
3764 3764 .. container:: verbose
3765 3765
3766 3766 Some examples:
3767 3767
3768 3768 - changesets with full descriptions and file lists::
3769 3769
3770 3770 hg log -v
3771 3771
3772 3772 - changesets ancestral to the working directory::
3773 3773
3774 3774 hg log -f
3775 3775
3776 3776 - last 10 commits on the current branch::
3777 3777
3778 3778 hg log -l 10 -b .
3779 3779
3780 3780 - changesets showing all modifications of a file, including removals::
3781 3781
3782 3782 hg log --removed file.c
3783 3783
3784 3784 - all changesets that touch a directory, with diffs, excluding merges::
3785 3785
3786 3786 hg log -Mp lib/
3787 3787
3788 3788 - all revision numbers that match a keyword::
3789 3789
3790 3790 hg log -k bug --template "{rev}\\n"
3791 3791
3792 3792 - the full hash identifier of the working directory parent::
3793 3793
3794 3794 hg log -r . --template "{node}\\n"
3795 3795
3796 3796 - list available log templates::
3797 3797
3798 3798 hg log -T list
3799 3799
3800 3800 - check if a given changeset is included in a tagged release::
3801 3801
3802 3802 hg log -r "a21ccf and ancestor(1.9)"
3803 3803
3804 3804 - find all changesets by some user in a date range::
3805 3805
3806 3806 hg log -k alice -d "may 2008 to jul 2008"
3807 3807
3808 3808 - summary of all changesets after the last tag::
3809 3809
3810 3810 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3811 3811
3812 3812 - changesets touching lines 13 to 23 for file.c::
3813 3813
3814 3814 hg log -L file.c,13:23
3815 3815
3816 3816 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3817 3817 main.c with patch::
3818 3818
3819 3819 hg log -L file.c,13:23 -L main.c,2:6 -p
3820 3820
3821 3821 See :hg:`help dates` for a list of formats valid for -d/--date.
3822 3822
3823 3823 See :hg:`help revisions` for more about specifying and ordering
3824 3824 revisions.
3825 3825
3826 3826 See :hg:`help templates` for more about pre-packaged styles and
3827 3827 specifying custom templates. The default template used by the log
3828 3828 command can be customized via the ``ui.logtemplate`` configuration
3829 3829 setting.
3830 3830
3831 3831 Returns 0 on success.
3832 3832
3833 3833 """
3834 3834 opts = pycompat.byteskwargs(opts)
3835 3835 linerange = opts.get('line_range')
3836 3836
3837 3837 if linerange and not opts.get('follow'):
3838 3838 raise error.Abort(_('--line-range requires --follow'))
3839 3839
3840 3840 if linerange and pats:
3841 3841 # TODO: take pats as patterns with no line-range filter
3842 3842 raise error.Abort(
3843 3843 _('FILE arguments are not compatible with --line-range option')
3844 3844 )
3845 3845
3846 3846 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3847 3847 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3848 3848 if linerange:
3849 3849 # TODO: should follow file history from logcmdutil._initialrevs(),
3850 3850 # then filter the result by logcmdutil._makerevset() and --limit
3851 3851 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3852 3852
3853 3853 getrenamed = None
3854 3854 if opts.get('copies'):
3855 3855 endrev = None
3856 3856 if revs:
3857 3857 endrev = revs.max() + 1
3858 3858 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3859 3859
3860 3860 ui.pager('log')
3861 3861 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3862 3862 buffered=True)
3863 3863 if opts.get('graph'):
3864 3864 displayfn = logcmdutil.displaygraphrevs
3865 3865 else:
3866 3866 displayfn = logcmdutil.displayrevs
3867 3867 displayfn(ui, repo, revs, displayer, getrenamed)
3868 3868
3869 3869 @command('manifest',
3870 3870 [('r', 'rev', '', _('revision to display'), _('REV')),
3871 3871 ('', 'all', False, _("list files from all revisions"))]
3872 3872 + formatteropts,
3873 3873 _('[-r REV]'),
3874 3874 helpcategory=command.CATEGORY_MAINTENANCE,
3875 3875 intents={INTENT_READONLY})
3876 3876 def manifest(ui, repo, node=None, rev=None, **opts):
3877 3877 """output the current or given revision of the project manifest
3878 3878
3879 3879 Print a list of version controlled files for the given revision.
3880 3880 If no revision is given, the first parent of the working directory
3881 3881 is used, or the null revision if no revision is checked out.
3882 3882
3883 3883 With -v, print file permissions, symlink and executable bits.
3884 3884 With --debug, print file revision hashes.
3885 3885
3886 3886 If option --all is specified, the list of all files from all revisions
3887 3887 is printed. This includes deleted and renamed files.
3888 3888
3889 3889 Returns 0 on success.
3890 3890 """
3891 3891 opts = pycompat.byteskwargs(opts)
3892 3892 fm = ui.formatter('manifest', opts)
3893 3893
3894 3894 if opts.get('all'):
3895 3895 if rev or node:
3896 3896 raise error.Abort(_("can't specify a revision with --all"))
3897 3897
3898 3898 res = set()
3899 3899 for rev in repo:
3900 3900 ctx = repo[rev]
3901 3901 res |= set(ctx.files())
3902 3902
3903 3903 ui.pager('manifest')
3904 3904 for f in sorted(res):
3905 3905 fm.startitem()
3906 3906 fm.write("path", '%s\n', f)
3907 3907 fm.end()
3908 3908 return
3909 3909
3910 3910 if rev and node:
3911 3911 raise error.Abort(_("please specify just one revision"))
3912 3912
3913 3913 if not node:
3914 3914 node = rev
3915 3915
3916 3916 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3917 3917 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3918 3918 if node:
3919 3919 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3920 3920 ctx = scmutil.revsingle(repo, node)
3921 3921 mf = ctx.manifest()
3922 3922 ui.pager('manifest')
3923 3923 for f in ctx:
3924 3924 fm.startitem()
3925 3925 fm.context(ctx=ctx)
3926 3926 fl = ctx[f].flags()
3927 3927 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3928 3928 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3929 3929 fm.write('path', '%s\n', f)
3930 3930 fm.end()
3931 3931
3932 3932 @command('merge',
3933 3933 [('f', 'force', None,
3934 3934 _('force a merge including outstanding changes (DEPRECATED)')),
3935 3935 ('r', 'rev', '', _('revision to merge'), _('REV')),
3936 3936 ('P', 'preview', None,
3937 3937 _('review revisions to merge (no merge is performed)')),
3938 3938 ('', 'abort', None, _('abort the ongoing merge')),
3939 3939 ] + mergetoolopts,
3940 3940 _('[-P] [[-r] REV]'),
3941 3941 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3942 3942 def merge(ui, repo, node=None, **opts):
3943 3943 """merge another revision into working directory
3944 3944
3945 3945 The current working directory is updated with all changes made in
3946 3946 the requested revision since the last common predecessor revision.
3947 3947
3948 3948 Files that changed between either parent are marked as changed for
3949 3949 the next commit and a commit must be performed before any further
3950 3950 updates to the repository are allowed. The next commit will have
3951 3951 two parents.
3952 3952
3953 3953 ``--tool`` can be used to specify the merge tool used for file
3954 3954 merges. It overrides the HGMERGE environment variable and your
3955 3955 configuration files. See :hg:`help merge-tools` for options.
3956 3956
3957 3957 If no revision is specified, the working directory's parent is a
3958 3958 head revision, and the current branch contains exactly one other
3959 3959 head, the other head is merged with by default. Otherwise, an
3960 3960 explicit revision with which to merge with must be provided.
3961 3961
3962 3962 See :hg:`help resolve` for information on handling file conflicts.
3963 3963
3964 3964 To undo an uncommitted merge, use :hg:`merge --abort` which
3965 3965 will check out a clean copy of the original merge parent, losing
3966 3966 all changes.
3967 3967
3968 3968 Returns 0 on success, 1 if there are unresolved files.
3969 3969 """
3970 3970
3971 3971 opts = pycompat.byteskwargs(opts)
3972 3972 abort = opts.get('abort')
3973 3973 if abort and repo.dirstate.p2() == nullid:
3974 3974 cmdutil.wrongtooltocontinue(repo, _('merge'))
3975 3975 if abort:
3976 3976 if node:
3977 3977 raise error.Abort(_("cannot specify a node with --abort"))
3978 3978 if opts.get('rev'):
3979 3979 raise error.Abort(_("cannot specify both --rev and --abort"))
3980 3980 if opts.get('preview'):
3981 3981 raise error.Abort(_("cannot specify --preview with --abort"))
3982 3982 if opts.get('rev') and node:
3983 3983 raise error.Abort(_("please specify just one revision"))
3984 3984 if not node:
3985 3985 node = opts.get('rev')
3986 3986
3987 3987 if node:
3988 3988 node = scmutil.revsingle(repo, node).node()
3989 3989
3990 3990 if not node and not abort:
3991 3991 node = repo[destutil.destmerge(repo)].node()
3992 3992
3993 3993 if opts.get('preview'):
3994 3994 # find nodes that are ancestors of p2 but not of p1
3995 3995 p1 = repo.lookup('.')
3996 3996 p2 = node
3997 3997 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3998 3998
3999 3999 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4000 4000 for node in nodes:
4001 4001 displayer.show(repo[node])
4002 4002 displayer.close()
4003 4003 return 0
4004 4004
4005 4005 # ui.forcemerge is an internal variable, do not document
4006 4006 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4007 4007 with ui.configoverride(overrides, 'merge'):
4008 4008 force = opts.get('force')
4009 4009 labels = ['working copy', 'merge rev']
4010 4010 return hg.merge(repo, node, force=force, mergeforce=force,
4011 4011 labels=labels, abort=abort)
4012 4012
4013 4013 @command('outgoing|out',
4014 4014 [('f', 'force', None, _('run even when the destination is unrelated')),
4015 4015 ('r', 'rev', [],
4016 4016 _('a changeset intended to be included in the destination'), _('REV')),
4017 4017 ('n', 'newest-first', None, _('show newest record first')),
4018 4018 ('B', 'bookmarks', False, _('compare bookmarks')),
4019 4019 ('b', 'branch', [], _('a specific branch you would like to push'),
4020 4020 _('BRANCH')),
4021 4021 ] + logopts + remoteopts + subrepoopts,
4022 4022 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4023 4023 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4024 4024 def outgoing(ui, repo, dest=None, **opts):
4025 4025 """show changesets not found in the destination
4026 4026
4027 4027 Show changesets not found in the specified destination repository
4028 4028 or the default push location. These are the changesets that would
4029 4029 be pushed if a push was requested.
4030 4030
4031 4031 See pull for details of valid destination formats.
4032 4032
4033 4033 .. container:: verbose
4034 4034
4035 4035 With -B/--bookmarks, the result of bookmark comparison between
4036 4036 local and remote repositories is displayed. With -v/--verbose,
4037 4037 status is also displayed for each bookmark like below::
4038 4038
4039 4039 BM1 01234567890a added
4040 4040 BM2 deleted
4041 4041 BM3 234567890abc advanced
4042 4042 BM4 34567890abcd diverged
4043 4043 BM5 4567890abcde changed
4044 4044
4045 4045 The action taken when pushing depends on the
4046 4046 status of each bookmark:
4047 4047
4048 4048 :``added``: push with ``-B`` will create it
4049 4049 :``deleted``: push with ``-B`` will delete it
4050 4050 :``advanced``: push will update it
4051 4051 :``diverged``: push with ``-B`` will update it
4052 4052 :``changed``: push with ``-B`` will update it
4053 4053
4054 4054 From the point of view of pushing behavior, bookmarks
4055 4055 existing only in the remote repository are treated as
4056 4056 ``deleted``, even if it is in fact added remotely.
4057 4057
4058 4058 Returns 0 if there are outgoing changes, 1 otherwise.
4059 4059 """
4060 4060 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4061 4061 # style URLs, so don't overwrite dest.
4062 4062 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4063 4063 if not path:
4064 4064 raise error.Abort(_('default repository not configured!'),
4065 4065 hint=_("see 'hg help config.paths'"))
4066 4066
4067 4067 opts = pycompat.byteskwargs(opts)
4068 4068 if opts.get('graph'):
4069 4069 logcmdutil.checkunsupportedgraphflags([], opts)
4070 4070 o, other = hg._outgoing(ui, repo, dest, opts)
4071 4071 if not o:
4072 4072 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4073 4073 return
4074 4074
4075 4075 revdag = logcmdutil.graphrevs(repo, o, opts)
4076 4076 ui.pager('outgoing')
4077 4077 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4078 4078 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4079 4079 graphmod.asciiedges)
4080 4080 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4081 4081 return 0
4082 4082
4083 4083 if opts.get('bookmarks'):
4084 4084 dest = path.pushloc or path.loc
4085 4085 other = hg.peer(repo, opts, dest)
4086 4086 if 'bookmarks' not in other.listkeys('namespaces'):
4087 4087 ui.warn(_("remote doesn't support bookmarks\n"))
4088 4088 return 0
4089 4089 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4090 4090 ui.pager('outgoing')
4091 4091 return bookmarks.outgoing(ui, repo, other)
4092 4092
4093 4093 repo._subtoppath = path.pushloc or path.loc
4094 4094 try:
4095 4095 return hg.outgoing(ui, repo, dest, opts)
4096 4096 finally:
4097 4097 del repo._subtoppath
4098 4098
4099 4099 @command('parents',
4100 4100 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4101 4101 ] + templateopts,
4102 4102 _('[-r REV] [FILE]'),
4103 4103 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4104 4104 inferrepo=True)
4105 4105 def parents(ui, repo, file_=None, **opts):
4106 4106 """show the parents of the working directory or revision (DEPRECATED)
4107 4107
4108 4108 Print the working directory's parent revisions. If a revision is
4109 4109 given via -r/--rev, the parent of that revision will be printed.
4110 4110 If a file argument is given, the revision in which the file was
4111 4111 last changed (before the working directory revision or the
4112 4112 argument to --rev if given) is printed.
4113 4113
4114 4114 This command is equivalent to::
4115 4115
4116 4116 hg log -r "p1()+p2()" or
4117 4117 hg log -r "p1(REV)+p2(REV)" or
4118 4118 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4119 4119 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4120 4120
4121 4121 See :hg:`summary` and :hg:`help revsets` for related information.
4122 4122
4123 4123 Returns 0 on success.
4124 4124 """
4125 4125
4126 4126 opts = pycompat.byteskwargs(opts)
4127 4127 rev = opts.get('rev')
4128 4128 if rev:
4129 4129 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4130 4130 ctx = scmutil.revsingle(repo, rev, None)
4131 4131
4132 4132 if file_:
4133 4133 m = scmutil.match(ctx, (file_,), opts)
4134 4134 if m.anypats() or len(m.files()) != 1:
4135 4135 raise error.Abort(_('can only specify an explicit filename'))
4136 4136 file_ = m.files()[0]
4137 4137 filenodes = []
4138 4138 for cp in ctx.parents():
4139 4139 if not cp:
4140 4140 continue
4141 4141 try:
4142 4142 filenodes.append(cp.filenode(file_))
4143 4143 except error.LookupError:
4144 4144 pass
4145 4145 if not filenodes:
4146 4146 raise error.Abort(_("'%s' not found in manifest!") % file_)
4147 4147 p = []
4148 4148 for fn in filenodes:
4149 4149 fctx = repo.filectx(file_, fileid=fn)
4150 4150 p.append(fctx.node())
4151 4151 else:
4152 4152 p = [cp.node() for cp in ctx.parents()]
4153 4153
4154 4154 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4155 4155 for n in p:
4156 4156 if n != nullid:
4157 4157 displayer.show(repo[n])
4158 4158 displayer.close()
4159 4159
4160 4160 @command('paths', formatteropts, _('[NAME]'),
4161 4161 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4162 4162 optionalrepo=True, intents={INTENT_READONLY})
4163 4163 def paths(ui, repo, search=None, **opts):
4164 4164 """show aliases for remote repositories
4165 4165
4166 4166 Show definition of symbolic path name NAME. If no name is given,
4167 4167 show definition of all available names.
4168 4168
4169 4169 Option -q/--quiet suppresses all output when searching for NAME
4170 4170 and shows only the path names when listing all definitions.
4171 4171
4172 4172 Path names are defined in the [paths] section of your
4173 4173 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4174 4174 repository, ``.hg/hgrc`` is used, too.
4175 4175
4176 4176 The path names ``default`` and ``default-push`` have a special
4177 4177 meaning. When performing a push or pull operation, they are used
4178 4178 as fallbacks if no location is specified on the command-line.
4179 4179 When ``default-push`` is set, it will be used for push and
4180 4180 ``default`` will be used for pull; otherwise ``default`` is used
4181 4181 as the fallback for both. When cloning a repository, the clone
4182 4182 source is written as ``default`` in ``.hg/hgrc``.
4183 4183
4184 4184 .. note::
4185 4185
4186 4186 ``default`` and ``default-push`` apply to all inbound (e.g.
4187 4187 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4188 4188 and :hg:`bundle`) operations.
4189 4189
4190 4190 See :hg:`help urls` for more information.
4191 4191
4192 4192 .. container:: verbose
4193 4193
4194 4194 Template:
4195 4195
4196 4196 The following keywords are supported. See also :hg:`help templates`.
4197 4197
4198 4198 :name: String. Symbolic name of the path alias.
4199 4199 :pushurl: String. URL for push operations.
4200 4200 :url: String. URL or directory path for the other operations.
4201 4201
4202 4202 Returns 0 on success.
4203 4203 """
4204 4204
4205 4205 opts = pycompat.byteskwargs(opts)
4206 4206 ui.pager('paths')
4207 4207 if search:
4208 4208 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4209 4209 if name == search]
4210 4210 else:
4211 4211 pathitems = sorted(ui.paths.iteritems())
4212 4212
4213 4213 fm = ui.formatter('paths', opts)
4214 4214 if fm.isplain():
4215 4215 hidepassword = util.hidepassword
4216 4216 else:
4217 4217 hidepassword = bytes
4218 4218 if ui.quiet:
4219 4219 namefmt = '%s\n'
4220 4220 else:
4221 4221 namefmt = '%s = '
4222 4222 showsubopts = not search and not ui.quiet
4223 4223
4224 4224 for name, path in pathitems:
4225 4225 fm.startitem()
4226 4226 fm.condwrite(not search, 'name', namefmt, name)
4227 4227 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4228 4228 for subopt, value in sorted(path.suboptions.items()):
4229 4229 assert subopt not in ('name', 'url')
4230 4230 if showsubopts:
4231 4231 fm.plain('%s:%s = ' % (name, subopt))
4232 4232 fm.condwrite(showsubopts, subopt, '%s\n', value)
4233 4233
4234 4234 fm.end()
4235 4235
4236 4236 if search and not pathitems:
4237 4237 if not ui.quiet:
4238 4238 ui.warn(_("not found!\n"))
4239 4239 return 1
4240 4240 else:
4241 4241 return 0
4242 4242
4243 4243 @command('phase',
4244 4244 [('p', 'public', False, _('set changeset phase to public')),
4245 4245 ('d', 'draft', False, _('set changeset phase to draft')),
4246 4246 ('s', 'secret', False, _('set changeset phase to secret')),
4247 4247 ('f', 'force', False, _('allow to move boundary backward')),
4248 4248 ('r', 'rev', [], _('target revision'), _('REV')),
4249 4249 ],
4250 4250 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4251 4251 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4252 4252 def phase(ui, repo, *revs, **opts):
4253 4253 """set or show the current phase name
4254 4254
4255 4255 With no argument, show the phase name of the current revision(s).
4256 4256
4257 4257 With one of -p/--public, -d/--draft or -s/--secret, change the
4258 4258 phase value of the specified revisions.
4259 4259
4260 4260 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4261 4261 lower phase to a higher phase. Phases are ordered as follows::
4262 4262
4263 4263 public < draft < secret
4264 4264
4265 4265 Returns 0 on success, 1 if some phases could not be changed.
4266 4266
4267 4267 (For more information about the phases concept, see :hg:`help phases`.)
4268 4268 """
4269 4269 opts = pycompat.byteskwargs(opts)
4270 4270 # search for a unique phase argument
4271 4271 targetphase = None
4272 4272 for idx, name in enumerate(phases.cmdphasenames):
4273 4273 if opts[name]:
4274 4274 if targetphase is not None:
4275 4275 raise error.Abort(_('only one phase can be specified'))
4276 4276 targetphase = idx
4277 4277
4278 4278 # look for specified revision
4279 4279 revs = list(revs)
4280 4280 revs.extend(opts['rev'])
4281 4281 if not revs:
4282 4282 # display both parents as the second parent phase can influence
4283 4283 # the phase of a merge commit
4284 4284 revs = [c.rev() for c in repo[None].parents()]
4285 4285
4286 4286 revs = scmutil.revrange(repo, revs)
4287 4287
4288 4288 ret = 0
4289 4289 if targetphase is None:
4290 4290 # display
4291 4291 for r in revs:
4292 4292 ctx = repo[r]
4293 4293 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4294 4294 else:
4295 4295 with repo.lock(), repo.transaction("phase") as tr:
4296 4296 # set phase
4297 4297 if not revs:
4298 4298 raise error.Abort(_('empty revision set'))
4299 4299 nodes = [repo[r].node() for r in revs]
4300 4300 # moving revision from public to draft may hide them
4301 4301 # We have to check result on an unfiltered repository
4302 4302 unfi = repo.unfiltered()
4303 4303 getphase = unfi._phasecache.phase
4304 4304 olddata = [getphase(unfi, r) for r in unfi]
4305 4305 phases.advanceboundary(repo, tr, targetphase, nodes)
4306 4306 if opts['force']:
4307 4307 phases.retractboundary(repo, tr, targetphase, nodes)
4308 4308 getphase = unfi._phasecache.phase
4309 4309 newdata = [getphase(unfi, r) for r in unfi]
4310 4310 changes = sum(newdata[r] != olddata[r] for r in unfi)
4311 4311 cl = unfi.changelog
4312 4312 rejected = [n for n in nodes
4313 4313 if newdata[cl.rev(n)] < targetphase]
4314 4314 if rejected:
4315 4315 ui.warn(_('cannot move %i changesets to a higher '
4316 4316 'phase, use --force\n') % len(rejected))
4317 4317 ret = 1
4318 4318 if changes:
4319 4319 msg = _('phase changed for %i changesets\n') % changes
4320 4320 if ret:
4321 4321 ui.status(msg)
4322 4322 else:
4323 4323 ui.note(msg)
4324 4324 else:
4325 4325 ui.warn(_('no phases changed\n'))
4326 4326 return ret
4327 4327
4328 4328 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4329 4329 """Run after a changegroup has been added via pull/unbundle
4330 4330
4331 4331 This takes arguments below:
4332 4332
4333 4333 :modheads: change of heads by pull/unbundle
4334 4334 :optupdate: updating working directory is needed or not
4335 4335 :checkout: update destination revision (or None to default destination)
4336 4336 :brev: a name, which might be a bookmark to be activated after updating
4337 4337 """
4338 4338 if modheads == 0:
4339 4339 return
4340 4340 if optupdate:
4341 4341 try:
4342 4342 return hg.updatetotally(ui, repo, checkout, brev)
4343 4343 except error.UpdateAbort as inst:
4344 4344 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4345 4345 hint = inst.hint
4346 4346 raise error.UpdateAbort(msg, hint=hint)
4347 4347 if modheads > 1:
4348 4348 currentbranchheads = len(repo.branchheads())
4349 4349 if currentbranchheads == modheads:
4350 4350 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4351 4351 elif currentbranchheads > 1:
4352 4352 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4353 4353 "merge)\n"))
4354 4354 else:
4355 4355 ui.status(_("(run 'hg heads' to see heads)\n"))
4356 4356 elif not ui.configbool('commands', 'update.requiredest'):
4357 4357 ui.status(_("(run 'hg update' to get a working copy)\n"))
4358 4358
4359 4359 @command('pull',
4360 4360 [('u', 'update', None,
4361 4361 _('update to new branch head if new descendants were pulled')),
4362 4362 ('f', 'force', None, _('run even when remote repository is unrelated')),
4363 4363 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4364 4364 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4365 4365 ('b', 'branch', [], _('a specific branch you would like to pull'),
4366 4366 _('BRANCH')),
4367 4367 ] + remoteopts,
4368 4368 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4369 4369 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4370 4370 helpbasic=True)
4371 4371 def pull(ui, repo, source="default", **opts):
4372 4372 """pull changes from the specified source
4373 4373
4374 4374 Pull changes from a remote repository to a local one.
4375 4375
4376 4376 This finds all changes from the repository at the specified path
4377 4377 or URL and adds them to a local repository (the current one unless
4378 4378 -R is specified). By default, this does not update the copy of the
4379 4379 project in the working directory.
4380 4380
4381 4381 When cloning from servers that support it, Mercurial may fetch
4382 4382 pre-generated data. When this is done, hooks operating on incoming
4383 4383 changesets and changegroups may fire more than once, once for each
4384 4384 pre-generated bundle and as well as for any additional remaining
4385 4385 data. See :hg:`help -e clonebundles` for more.
4386 4386
4387 4387 Use :hg:`incoming` if you want to see what would have been added
4388 4388 by a pull at the time you issued this command. If you then decide
4389 4389 to add those changes to the repository, you should use :hg:`pull
4390 4390 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4391 4391
4392 4392 If SOURCE is omitted, the 'default' path will be used.
4393 4393 See :hg:`help urls` for more information.
4394 4394
4395 4395 Specifying bookmark as ``.`` is equivalent to specifying the active
4396 4396 bookmark's name.
4397 4397
4398 4398 Returns 0 on success, 1 if an update had unresolved files.
4399 4399 """
4400 4400
4401 4401 opts = pycompat.byteskwargs(opts)
4402 4402 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4403 4403 msg = _('update destination required by configuration')
4404 4404 hint = _('use hg pull followed by hg update DEST')
4405 4405 raise error.Abort(msg, hint=hint)
4406 4406
4407 4407 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4408 4408 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4409 4409 other = hg.peer(repo, opts, source)
4410 4410 try:
4411 4411 revs, checkout = hg.addbranchrevs(repo, other, branches,
4412 4412 opts.get('rev'))
4413 4413
4414 4414
4415 4415 pullopargs = {}
4416 4416 if opts.get('bookmark'):
4417 4417 if not revs:
4418 4418 revs = []
4419 4419 # The list of bookmark used here is not the one used to actually
4420 4420 # update the bookmark name. This can result in the revision pulled
4421 4421 # not ending up with the name of the bookmark because of a race
4422 4422 # condition on the server. (See issue 4689 for details)
4423 4423 remotebookmarks = other.listkeys('bookmarks')
4424 4424 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4425 4425 pullopargs['remotebookmarks'] = remotebookmarks
4426 4426 for b in opts['bookmark']:
4427 4427 b = repo._bookmarks.expandname(b)
4428 4428 if b not in remotebookmarks:
4429 4429 raise error.Abort(_('remote bookmark %s not found!') % b)
4430 4430 revs.append(hex(remotebookmarks[b]))
4431 4431
4432 4432 if revs:
4433 4433 try:
4434 4434 # When 'rev' is a bookmark name, we cannot guarantee that it
4435 4435 # will be updated with that name because of a race condition
4436 4436 # server side. (See issue 4689 for details)
4437 4437 oldrevs = revs
4438 4438 revs = [] # actually, nodes
4439 4439 for r in oldrevs:
4440 4440 with other.commandexecutor() as e:
4441 4441 node = e.callcommand('lookup', {'key': r}).result()
4442 4442
4443 4443 revs.append(node)
4444 4444 if r == checkout:
4445 4445 checkout = node
4446 4446 except error.CapabilityError:
4447 4447 err = _("other repository doesn't support revision lookup, "
4448 4448 "so a rev cannot be specified.")
4449 4449 raise error.Abort(err)
4450 4450
4451 4451 wlock = util.nullcontextmanager()
4452 4452 if opts.get('update'):
4453 4453 wlock = repo.wlock()
4454 4454 with wlock:
4455 4455 pullopargs.update(opts.get('opargs', {}))
4456 4456 modheads = exchange.pull(repo, other, heads=revs,
4457 4457 force=opts.get('force'),
4458 4458 bookmarks=opts.get('bookmark', ()),
4459 4459 opargs=pullopargs).cgresult
4460 4460
4461 4461 # brev is a name, which might be a bookmark to be activated at
4462 4462 # the end of the update. In other words, it is an explicit
4463 4463 # destination of the update
4464 4464 brev = None
4465 4465
4466 4466 if checkout:
4467 4467 checkout = repo.changelog.rev(checkout)
4468 4468
4469 4469 # order below depends on implementation of
4470 4470 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4471 4471 # because 'checkout' is determined without it.
4472 4472 if opts.get('rev'):
4473 4473 brev = opts['rev'][0]
4474 4474 elif opts.get('branch'):
4475 4475 brev = opts['branch'][0]
4476 4476 else:
4477 4477 brev = branches[0]
4478 4478 repo._subtoppath = source
4479 4479 try:
4480 4480 ret = postincoming(ui, repo, modheads, opts.get('update'),
4481 4481 checkout, brev)
4482 4482
4483 4483 finally:
4484 4484 del repo._subtoppath
4485 4485
4486 4486 finally:
4487 4487 other.close()
4488 4488 return ret
4489 4489
4490 4490 @command('push',
4491 4491 [('f', 'force', None, _('force push')),
4492 4492 ('r', 'rev', [],
4493 4493 _('a changeset intended to be included in the destination'),
4494 4494 _('REV')),
4495 4495 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4496 4496 ('b', 'branch', [],
4497 4497 _('a specific branch you would like to push'), _('BRANCH')),
4498 4498 ('', 'new-branch', False, _('allow pushing a new branch')),
4499 4499 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4500 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4500 4501 ] + remoteopts,
4501 4502 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4502 4503 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4503 4504 helpbasic=True)
4504 4505 def push(ui, repo, dest=None, **opts):
4505 4506 """push changes to the specified destination
4506 4507
4507 4508 Push changesets from the local repository to the specified
4508 4509 destination.
4509 4510
4510 4511 This operation is symmetrical to pull: it is identical to a pull
4511 4512 in the destination repository from the current one.
4512 4513
4513 4514 By default, push will not allow creation of new heads at the
4514 4515 destination, since multiple heads would make it unclear which head
4515 4516 to use. In this situation, it is recommended to pull and merge
4516 4517 before pushing.
4517 4518
4518 4519 Use --new-branch if you want to allow push to create a new named
4519 4520 branch that is not present at the destination. This allows you to
4520 4521 only create a new branch without forcing other changes.
4521 4522
4522 4523 .. note::
4523 4524
4524 4525 Extra care should be taken with the -f/--force option,
4525 4526 which will push all new heads on all branches, an action which will
4526 4527 almost always cause confusion for collaborators.
4527 4528
4528 4529 If -r/--rev is used, the specified revision and all its ancestors
4529 4530 will be pushed to the remote repository.
4530 4531
4531 4532 If -B/--bookmark is used, the specified bookmarked revision, its
4532 4533 ancestors, and the bookmark will be pushed to the remote
4533 4534 repository. Specifying ``.`` is equivalent to specifying the active
4534 4535 bookmark's name.
4535 4536
4536 4537 Please see :hg:`help urls` for important details about ``ssh://``
4537 4538 URLs. If DESTINATION is omitted, a default path will be used.
4538 4539
4539 4540 .. container:: verbose
4540 4541
4541 4542 The --pushvars option sends strings to the server that become
4542 4543 environment variables prepended with ``HG_USERVAR_``. For example,
4543 4544 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4544 4545 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4545 4546
4546 4547 pushvars can provide for user-overridable hooks as well as set debug
4547 4548 levels. One example is having a hook that blocks commits containing
4548 4549 conflict markers, but enables the user to override the hook if the file
4549 4550 is using conflict markers for testing purposes or the file format has
4550 4551 strings that look like conflict markers.
4551 4552
4552 4553 By default, servers will ignore `--pushvars`. To enable it add the
4553 4554 following to your configuration file::
4554 4555
4555 4556 [push]
4556 4557 pushvars.server = true
4557 4558
4558 4559 Returns 0 if push was successful, 1 if nothing to push.
4559 4560 """
4560 4561
4561 4562 opts = pycompat.byteskwargs(opts)
4562 4563 if opts.get('bookmark'):
4563 4564 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4564 4565 for b in opts['bookmark']:
4565 4566 # translate -B options to -r so changesets get pushed
4566 4567 b = repo._bookmarks.expandname(b)
4567 4568 if b in repo._bookmarks:
4568 4569 opts.setdefault('rev', []).append(b)
4569 4570 else:
4570 4571 # if we try to push a deleted bookmark, translate it to null
4571 4572 # this lets simultaneous -r, -b options continue working
4572 4573 opts.setdefault('rev', []).append("null")
4573 4574
4574 4575 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4575 4576 if not path:
4576 4577 raise error.Abort(_('default repository not configured!'),
4577 4578 hint=_("see 'hg help config.paths'"))
4578 4579 dest = path.pushloc or path.loc
4579 4580 branches = (path.branch, opts.get('branch') or [])
4580 4581 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4581 4582 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4582 4583 other = hg.peer(repo, opts, dest)
4583 4584
4584 4585 if revs:
4585 4586 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4586 4587 if not revs:
4587 4588 raise error.Abort(_("specified revisions evaluate to an empty set"),
4588 4589 hint=_("use different revision arguments"))
4589 4590 elif path.pushrev:
4590 4591 # It doesn't make any sense to specify ancestor revisions. So limit
4591 4592 # to DAG heads to make discovery simpler.
4592 4593 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4593 4594 revs = scmutil.revrange(repo, [expr])
4594 4595 revs = [repo[rev].node() for rev in revs]
4595 4596 if not revs:
4596 4597 raise error.Abort(_('default push revset for path evaluates to an '
4597 4598 'empty set'))
4598 4599
4599 4600 repo._subtoppath = dest
4600 4601 try:
4601 4602 # push subrepos depth-first for coherent ordering
4602 4603 c = repo['.']
4603 4604 subs = c.substate # only repos that are committed
4604 4605 for s in sorted(subs):
4605 4606 result = c.sub(s).push(opts)
4606 4607 if result == 0:
4607 4608 return not result
4608 4609 finally:
4609 4610 del repo._subtoppath
4610 4611
4611 4612 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4612 4613 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4613 4614
4614 4615 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4615 4616 newbranch=opts.get('new_branch'),
4616 4617 bookmarks=opts.get('bookmark', ()),
4618 publish=opts.get('publish'),
4617 4619 opargs=opargs)
4618 4620
4619 4621 result = not pushop.cgresult
4620 4622
4621 4623 if pushop.bkresult is not None:
4622 4624 if pushop.bkresult == 2:
4623 4625 result = 2
4624 4626 elif not result and pushop.bkresult:
4625 4627 result = 2
4626 4628
4627 4629 return result
4628 4630
4629 4631 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4630 4632 def recover(ui, repo):
4631 4633 """roll back an interrupted transaction
4632 4634
4633 4635 Recover from an interrupted commit or pull.
4634 4636
4635 4637 This command tries to fix the repository status after an
4636 4638 interrupted operation. It should only be necessary when Mercurial
4637 4639 suggests it.
4638 4640
4639 4641 Returns 0 if successful, 1 if nothing to recover or verify fails.
4640 4642 """
4641 4643 if repo.recover():
4642 4644 return hg.verify(repo)
4643 4645 return 1
4644 4646
4645 4647 @command('remove|rm',
4646 4648 [('A', 'after', None, _('record delete for missing files')),
4647 4649 ('f', 'force', None,
4648 4650 _('forget added files, delete modified files')),
4649 4651 ] + subrepoopts + walkopts + dryrunopts,
4650 4652 _('[OPTION]... FILE...'),
4651 4653 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4652 4654 helpbasic=True, inferrepo=True)
4653 4655 def remove(ui, repo, *pats, **opts):
4654 4656 """remove the specified files on the next commit
4655 4657
4656 4658 Schedule the indicated files for removal from the current branch.
4657 4659
4658 4660 This command schedules the files to be removed at the next commit.
4659 4661 To undo a remove before that, see :hg:`revert`. To undo added
4660 4662 files, see :hg:`forget`.
4661 4663
4662 4664 .. container:: verbose
4663 4665
4664 4666 -A/--after can be used to remove only files that have already
4665 4667 been deleted, -f/--force can be used to force deletion, and -Af
4666 4668 can be used to remove files from the next revision without
4667 4669 deleting them from the working directory.
4668 4670
4669 4671 The following table details the behavior of remove for different
4670 4672 file states (columns) and option combinations (rows). The file
4671 4673 states are Added [A], Clean [C], Modified [M] and Missing [!]
4672 4674 (as reported by :hg:`status`). The actions are Warn, Remove
4673 4675 (from branch) and Delete (from disk):
4674 4676
4675 4677 ========= == == == ==
4676 4678 opt/state A C M !
4677 4679 ========= == == == ==
4678 4680 none W RD W R
4679 4681 -f R RD RD R
4680 4682 -A W W W R
4681 4683 -Af R R R R
4682 4684 ========= == == == ==
4683 4685
4684 4686 .. note::
4685 4687
4686 4688 :hg:`remove` never deletes files in Added [A] state from the
4687 4689 working directory, not even if ``--force`` is specified.
4688 4690
4689 4691 Returns 0 on success, 1 if any warnings encountered.
4690 4692 """
4691 4693
4692 4694 opts = pycompat.byteskwargs(opts)
4693 4695 after, force = opts.get('after'), opts.get('force')
4694 4696 dryrun = opts.get('dry_run')
4695 4697 if not pats and not after:
4696 4698 raise error.Abort(_('no files specified'))
4697 4699
4698 4700 m = scmutil.match(repo[None], pats, opts)
4699 4701 subrepos = opts.get('subrepos')
4700 4702 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4701 4703 dryrun=dryrun)
4702 4704
4703 4705 @command('rename|move|mv',
4704 4706 [('A', 'after', None, _('record a rename that has already occurred')),
4705 4707 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4706 4708 ] + walkopts + dryrunopts,
4707 4709 _('[OPTION]... SOURCE... DEST'),
4708 4710 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4709 4711 def rename(ui, repo, *pats, **opts):
4710 4712 """rename files; equivalent of copy + remove
4711 4713
4712 4714 Mark dest as copies of sources; mark sources for deletion. If dest
4713 4715 is a directory, copies are put in that directory. If dest is a
4714 4716 file, there can only be one source.
4715 4717
4716 4718 By default, this command copies the contents of files as they
4717 4719 exist in the working directory. If invoked with -A/--after, the
4718 4720 operation is recorded, but no copying is performed.
4719 4721
4720 4722 This command takes effect at the next commit. To undo a rename
4721 4723 before that, see :hg:`revert`.
4722 4724
4723 4725 Returns 0 on success, 1 if errors are encountered.
4724 4726 """
4725 4727 opts = pycompat.byteskwargs(opts)
4726 4728 with repo.wlock(False):
4727 4729 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4728 4730
4729 4731 @command('resolve',
4730 4732 [('a', 'all', None, _('select all unresolved files')),
4731 4733 ('l', 'list', None, _('list state of files needing merge')),
4732 4734 ('m', 'mark', None, _('mark files as resolved')),
4733 4735 ('u', 'unmark', None, _('mark files as unresolved')),
4734 4736 ('n', 'no-status', None, _('hide status prefix')),
4735 4737 ('', 're-merge', None, _('re-merge files'))]
4736 4738 + mergetoolopts + walkopts + formatteropts,
4737 4739 _('[OPTION]... [FILE]...'),
4738 4740 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4739 4741 inferrepo=True)
4740 4742 def resolve(ui, repo, *pats, **opts):
4741 4743 """redo merges or set/view the merge status of files
4742 4744
4743 4745 Merges with unresolved conflicts are often the result of
4744 4746 non-interactive merging using the ``internal:merge`` configuration
4745 4747 setting, or a command-line merge tool like ``diff3``. The resolve
4746 4748 command is used to manage the files involved in a merge, after
4747 4749 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4748 4750 working directory must have two parents). See :hg:`help
4749 4751 merge-tools` for information on configuring merge tools.
4750 4752
4751 4753 The resolve command can be used in the following ways:
4752 4754
4753 4755 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4754 4756 the specified files, discarding any previous merge attempts. Re-merging
4755 4757 is not performed for files already marked as resolved. Use ``--all/-a``
4756 4758 to select all unresolved files. ``--tool`` can be used to specify
4757 4759 the merge tool used for the given files. It overrides the HGMERGE
4758 4760 environment variable and your configuration files. Previous file
4759 4761 contents are saved with a ``.orig`` suffix.
4760 4762
4761 4763 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4762 4764 (e.g. after having manually fixed-up the files). The default is
4763 4765 to mark all unresolved files.
4764 4766
4765 4767 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4766 4768 default is to mark all resolved files.
4767 4769
4768 4770 - :hg:`resolve -l`: list files which had or still have conflicts.
4769 4771 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4770 4772 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4771 4773 the list. See :hg:`help filesets` for details.
4772 4774
4773 4775 .. note::
4774 4776
4775 4777 Mercurial will not let you commit files with unresolved merge
4776 4778 conflicts. You must use :hg:`resolve -m ...` before you can
4777 4779 commit after a conflicting merge.
4778 4780
4779 4781 .. container:: verbose
4780 4782
4781 4783 Template:
4782 4784
4783 4785 The following keywords are supported in addition to the common template
4784 4786 keywords and functions. See also :hg:`help templates`.
4785 4787
4786 4788 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4787 4789 :path: String. Repository-absolute path of the file.
4788 4790
4789 4791 Returns 0 on success, 1 if any files fail a resolve attempt.
4790 4792 """
4791 4793
4792 4794 opts = pycompat.byteskwargs(opts)
4793 4795 confirm = ui.configbool('commands', 'resolve.confirm')
4794 4796 flaglist = 'all mark unmark list no_status re_merge'.split()
4795 4797 all, mark, unmark, show, nostatus, remerge = \
4796 4798 [opts.get(o) for o in flaglist]
4797 4799
4798 4800 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4799 4801 if actioncount > 1:
4800 4802 raise error.Abort(_("too many actions specified"))
4801 4803 elif (actioncount == 0
4802 4804 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4803 4805 hint = _('use --mark, --unmark, --list or --re-merge')
4804 4806 raise error.Abort(_('no action specified'), hint=hint)
4805 4807 if pats and all:
4806 4808 raise error.Abort(_("can't specify --all and patterns"))
4807 4809 if not (all or pats or show or mark or unmark):
4808 4810 raise error.Abort(_('no files or directories specified'),
4809 4811 hint=('use --all to re-merge all unresolved files'))
4810 4812
4811 4813 if confirm:
4812 4814 if all:
4813 4815 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4814 4816 b'$$ &Yes $$ &No')):
4815 4817 raise error.Abort(_('user quit'))
4816 4818 if mark and not pats:
4817 4819 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4818 4820 b'$$ &Yes $$ &No')):
4819 4821 raise error.Abort(_('user quit'))
4820 4822 if unmark and not pats:
4821 4823 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4822 4824 b'$$ &Yes $$ &No')):
4823 4825 raise error.Abort(_('user quit'))
4824 4826
4825 4827 if show:
4826 4828 ui.pager('resolve')
4827 4829 fm = ui.formatter('resolve', opts)
4828 4830 ms = mergemod.mergestate.read(repo)
4829 4831 wctx = repo[None]
4830 4832 m = scmutil.match(wctx, pats, opts)
4831 4833
4832 4834 # Labels and keys based on merge state. Unresolved path conflicts show
4833 4835 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4834 4836 # resolved conflicts.
4835 4837 mergestateinfo = {
4836 4838 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4837 4839 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4838 4840 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4839 4841 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4840 4842 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4841 4843 'D'),
4842 4844 }
4843 4845
4844 4846 for f in ms:
4845 4847 if not m(f):
4846 4848 continue
4847 4849
4848 4850 label, key = mergestateinfo[ms[f]]
4849 4851 fm.startitem()
4850 4852 fm.context(ctx=wctx)
4851 4853 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4852 4854 fm.write('path', '%s\n', f, label=label)
4853 4855 fm.end()
4854 4856 return 0
4855 4857
4856 4858 with repo.wlock():
4857 4859 ms = mergemod.mergestate.read(repo)
4858 4860
4859 4861 if not (ms.active() or repo.dirstate.p2() != nullid):
4860 4862 raise error.Abort(
4861 4863 _('resolve command not applicable when not merging'))
4862 4864
4863 4865 wctx = repo[None]
4864 4866
4865 4867 if (ms.mergedriver
4866 4868 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4867 4869 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4868 4870 ms.commit()
4869 4871 # allow mark and unmark to go through
4870 4872 if not mark and not unmark and not proceed:
4871 4873 return 1
4872 4874
4873 4875 m = scmutil.match(wctx, pats, opts)
4874 4876 ret = 0
4875 4877 didwork = False
4876 4878 runconclude = False
4877 4879
4878 4880 tocomplete = []
4879 4881 hasconflictmarkers = []
4880 4882 if mark:
4881 4883 markcheck = ui.config('commands', 'resolve.mark-check')
4882 4884 if markcheck not in ['warn', 'abort']:
4883 4885 # Treat all invalid / unrecognized values as 'none'.
4884 4886 markcheck = False
4885 4887 for f in ms:
4886 4888 if not m(f):
4887 4889 continue
4888 4890
4889 4891 didwork = True
4890 4892
4891 4893 # don't let driver-resolved files be marked, and run the conclude
4892 4894 # step if asked to resolve
4893 4895 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4894 4896 exact = m.exact(f)
4895 4897 if mark:
4896 4898 if exact:
4897 4899 ui.warn(_('not marking %s as it is driver-resolved\n')
4898 4900 % f)
4899 4901 elif unmark:
4900 4902 if exact:
4901 4903 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4902 4904 % f)
4903 4905 else:
4904 4906 runconclude = True
4905 4907 continue
4906 4908
4907 4909 # path conflicts must be resolved manually
4908 4910 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4909 4911 mergemod.MERGE_RECORD_RESOLVED_PATH):
4910 4912 if mark:
4911 4913 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4912 4914 elif unmark:
4913 4915 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4914 4916 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4915 4917 ui.warn(_('%s: path conflict must be resolved manually\n')
4916 4918 % f)
4917 4919 continue
4918 4920
4919 4921 if mark:
4920 4922 if markcheck:
4921 4923 with repo.wvfs(f) as fobj:
4922 4924 fdata = fobj.read()
4923 4925 if filemerge.hasconflictmarkers(fdata) and \
4924 4926 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4925 4927 hasconflictmarkers.append(f)
4926 4928 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4927 4929 elif unmark:
4928 4930 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4929 4931 else:
4930 4932 # backup pre-resolve (merge uses .orig for its own purposes)
4931 4933 a = repo.wjoin(f)
4932 4934 try:
4933 4935 util.copyfile(a, a + ".resolve")
4934 4936 except (IOError, OSError) as inst:
4935 4937 if inst.errno != errno.ENOENT:
4936 4938 raise
4937 4939
4938 4940 try:
4939 4941 # preresolve file
4940 4942 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4941 4943 with ui.configoverride(overrides, 'resolve'):
4942 4944 complete, r = ms.preresolve(f, wctx)
4943 4945 if not complete:
4944 4946 tocomplete.append(f)
4945 4947 elif r:
4946 4948 ret = 1
4947 4949 finally:
4948 4950 ms.commit()
4949 4951
4950 4952 # replace filemerge's .orig file with our resolve file, but only
4951 4953 # for merges that are complete
4952 4954 if complete:
4953 4955 try:
4954 4956 util.rename(a + ".resolve",
4955 4957 scmutil.origpath(ui, repo, a))
4956 4958 except OSError as inst:
4957 4959 if inst.errno != errno.ENOENT:
4958 4960 raise
4959 4961
4960 4962 if hasconflictmarkers:
4961 4963 ui.warn(_('warning: the following files still have conflict '
4962 4964 'markers:\n ') + '\n '.join(hasconflictmarkers) + '\n')
4963 4965 if markcheck == 'abort' and not all and not pats:
4964 4966 raise error.Abort(_('conflict markers detected'),
4965 4967 hint=_('use --all to mark anyway'))
4966 4968
4967 4969 for f in tocomplete:
4968 4970 try:
4969 4971 # resolve file
4970 4972 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4971 4973 with ui.configoverride(overrides, 'resolve'):
4972 4974 r = ms.resolve(f, wctx)
4973 4975 if r:
4974 4976 ret = 1
4975 4977 finally:
4976 4978 ms.commit()
4977 4979
4978 4980 # replace filemerge's .orig file with our resolve file
4979 4981 a = repo.wjoin(f)
4980 4982 try:
4981 4983 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4982 4984 except OSError as inst:
4983 4985 if inst.errno != errno.ENOENT:
4984 4986 raise
4985 4987
4986 4988 ms.commit()
4987 4989 ms.recordactions()
4988 4990
4989 4991 if not didwork and pats:
4990 4992 hint = None
4991 4993 if not any([p for p in pats if p.find(':') >= 0]):
4992 4994 pats = ['path:%s' % p for p in pats]
4993 4995 m = scmutil.match(wctx, pats, opts)
4994 4996 for f in ms:
4995 4997 if not m(f):
4996 4998 continue
4997 4999 def flag(o):
4998 5000 if o == 're_merge':
4999 5001 return '--re-merge '
5000 5002 return '-%s ' % o[0:1]
5001 5003 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5002 5004 hint = _("(try: hg resolve %s%s)\n") % (
5003 5005 flags,
5004 5006 ' '.join(pats))
5005 5007 break
5006 5008 ui.warn(_("arguments do not match paths that need resolving\n"))
5007 5009 if hint:
5008 5010 ui.warn(hint)
5009 5011 elif ms.mergedriver and ms.mdstate() != 's':
5010 5012 # run conclude step when either a driver-resolved file is requested
5011 5013 # or there are no driver-resolved files
5012 5014 # we can't use 'ret' to determine whether any files are unresolved
5013 5015 # because we might not have tried to resolve some
5014 5016 if ((runconclude or not list(ms.driverresolved()))
5015 5017 and not list(ms.unresolved())):
5016 5018 proceed = mergemod.driverconclude(repo, ms, wctx)
5017 5019 ms.commit()
5018 5020 if not proceed:
5019 5021 return 1
5020 5022
5021 5023 # Nudge users into finishing an unfinished operation
5022 5024 unresolvedf = list(ms.unresolved())
5023 5025 driverresolvedf = list(ms.driverresolved())
5024 5026 if not unresolvedf and not driverresolvedf:
5025 5027 ui.status(_('(no more unresolved files)\n'))
5026 5028 cmdutil.checkafterresolved(repo)
5027 5029 elif not unresolvedf:
5028 5030 ui.status(_('(no more unresolved files -- '
5029 5031 'run "hg resolve --all" to conclude)\n'))
5030 5032
5031 5033 return ret
5032 5034
5033 5035 @command('revert',
5034 5036 [('a', 'all', None, _('revert all changes when no arguments given')),
5035 5037 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5036 5038 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5037 5039 ('C', 'no-backup', None, _('do not save backup copies of files')),
5038 5040 ('i', 'interactive', None, _('interactively select the changes')),
5039 5041 ] + walkopts + dryrunopts,
5040 5042 _('[OPTION]... [-r REV] [NAME]...'),
5041 5043 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5042 5044 def revert(ui, repo, *pats, **opts):
5043 5045 """restore files to their checkout state
5044 5046
5045 5047 .. note::
5046 5048
5047 5049 To check out earlier revisions, you should use :hg:`update REV`.
5048 5050 To cancel an uncommitted merge (and lose your changes),
5049 5051 use :hg:`merge --abort`.
5050 5052
5051 5053 With no revision specified, revert the specified files or directories
5052 5054 to the contents they had in the parent of the working directory.
5053 5055 This restores the contents of files to an unmodified
5054 5056 state and unschedules adds, removes, copies, and renames. If the
5055 5057 working directory has two parents, you must explicitly specify a
5056 5058 revision.
5057 5059
5058 5060 Using the -r/--rev or -d/--date options, revert the given files or
5059 5061 directories to their states as of a specific revision. Because
5060 5062 revert does not change the working directory parents, this will
5061 5063 cause these files to appear modified. This can be helpful to "back
5062 5064 out" some or all of an earlier change. See :hg:`backout` for a
5063 5065 related method.
5064 5066
5065 5067 Modified files are saved with a .orig suffix before reverting.
5066 5068 To disable these backups, use --no-backup. It is possible to store
5067 5069 the backup files in a custom directory relative to the root of the
5068 5070 repository by setting the ``ui.origbackuppath`` configuration
5069 5071 option.
5070 5072
5071 5073 See :hg:`help dates` for a list of formats valid for -d/--date.
5072 5074
5073 5075 See :hg:`help backout` for a way to reverse the effect of an
5074 5076 earlier changeset.
5075 5077
5076 5078 Returns 0 on success.
5077 5079 """
5078 5080
5079 5081 opts = pycompat.byteskwargs(opts)
5080 5082 if opts.get("date"):
5081 5083 if opts.get("rev"):
5082 5084 raise error.Abort(_("you can't specify a revision and a date"))
5083 5085 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5084 5086
5085 5087 parent, p2 = repo.dirstate.parents()
5086 5088 if not opts.get('rev') and p2 != nullid:
5087 5089 # revert after merge is a trap for new users (issue2915)
5088 5090 raise error.Abort(_('uncommitted merge with no revision specified'),
5089 5091 hint=_("use 'hg update' or see 'hg help revert'"))
5090 5092
5091 5093 rev = opts.get('rev')
5092 5094 if rev:
5093 5095 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5094 5096 ctx = scmutil.revsingle(repo, rev)
5095 5097
5096 5098 if (not (pats or opts.get('include') or opts.get('exclude') or
5097 5099 opts.get('all') or opts.get('interactive'))):
5098 5100 msg = _("no files or directories specified")
5099 5101 if p2 != nullid:
5100 5102 hint = _("uncommitted merge, use --all to discard all changes,"
5101 5103 " or 'hg update -C .' to abort the merge")
5102 5104 raise error.Abort(msg, hint=hint)
5103 5105 dirty = any(repo.status())
5104 5106 node = ctx.node()
5105 5107 if node != parent:
5106 5108 if dirty:
5107 5109 hint = _("uncommitted changes, use --all to discard all"
5108 5110 " changes, or 'hg update %d' to update") % ctx.rev()
5109 5111 else:
5110 5112 hint = _("use --all to revert all files,"
5111 5113 " or 'hg update %d' to update") % ctx.rev()
5112 5114 elif dirty:
5113 5115 hint = _("uncommitted changes, use --all to discard all changes")
5114 5116 else:
5115 5117 hint = _("use --all to revert all files")
5116 5118 raise error.Abort(msg, hint=hint)
5117 5119
5118 5120 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5119 5121 **pycompat.strkwargs(opts))
5120 5122
5121 5123 @command(
5122 5124 'rollback',
5123 5125 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5124 5126 helpcategory=command.CATEGORY_MAINTENANCE)
5125 5127 def rollback(ui, repo, **opts):
5126 5128 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5127 5129
5128 5130 Please use :hg:`commit --amend` instead of rollback to correct
5129 5131 mistakes in the last commit.
5130 5132
5131 5133 This command should be used with care. There is only one level of
5132 5134 rollback, and there is no way to undo a rollback. It will also
5133 5135 restore the dirstate at the time of the last transaction, losing
5134 5136 any dirstate changes since that time. This command does not alter
5135 5137 the working directory.
5136 5138
5137 5139 Transactions are used to encapsulate the effects of all commands
5138 5140 that create new changesets or propagate existing changesets into a
5139 5141 repository.
5140 5142
5141 5143 .. container:: verbose
5142 5144
5143 5145 For example, the following commands are transactional, and their
5144 5146 effects can be rolled back:
5145 5147
5146 5148 - commit
5147 5149 - import
5148 5150 - pull
5149 5151 - push (with this repository as the destination)
5150 5152 - unbundle
5151 5153
5152 5154 To avoid permanent data loss, rollback will refuse to rollback a
5153 5155 commit transaction if it isn't checked out. Use --force to
5154 5156 override this protection.
5155 5157
5156 5158 The rollback command can be entirely disabled by setting the
5157 5159 ``ui.rollback`` configuration setting to false. If you're here
5158 5160 because you want to use rollback and it's disabled, you can
5159 5161 re-enable the command by setting ``ui.rollback`` to true.
5160 5162
5161 5163 This command is not intended for use on public repositories. Once
5162 5164 changes are visible for pull by other users, rolling a transaction
5163 5165 back locally is ineffective (someone else may already have pulled
5164 5166 the changes). Furthermore, a race is possible with readers of the
5165 5167 repository; for example an in-progress pull from the repository
5166 5168 may fail if a rollback is performed.
5167 5169
5168 5170 Returns 0 on success, 1 if no rollback data is available.
5169 5171 """
5170 5172 if not ui.configbool('ui', 'rollback'):
5171 5173 raise error.Abort(_('rollback is disabled because it is unsafe'),
5172 5174 hint=('see `hg help -v rollback` for information'))
5173 5175 return repo.rollback(dryrun=opts.get(r'dry_run'),
5174 5176 force=opts.get(r'force'))
5175 5177
5176 5178 @command(
5177 5179 'root', [], intents={INTENT_READONLY},
5178 5180 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5179 5181 def root(ui, repo):
5180 5182 """print the root (top) of the current working directory
5181 5183
5182 5184 Print the root directory of the current repository.
5183 5185
5184 5186 Returns 0 on success.
5185 5187 """
5186 5188 ui.write(repo.root + "\n")
5187 5189
5188 5190 @command('serve',
5189 5191 [('A', 'accesslog', '', _('name of access log file to write to'),
5190 5192 _('FILE')),
5191 5193 ('d', 'daemon', None, _('run server in background')),
5192 5194 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5193 5195 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5194 5196 # use string type, then we can check if something was passed
5195 5197 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5196 5198 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5197 5199 _('ADDR')),
5198 5200 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5199 5201 _('PREFIX')),
5200 5202 ('n', 'name', '',
5201 5203 _('name to show in web pages (default: working directory)'), _('NAME')),
5202 5204 ('', 'web-conf', '',
5203 5205 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5204 5206 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5205 5207 _('FILE')),
5206 5208 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5207 5209 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5208 5210 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5209 5211 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5210 5212 ('', 'style', '', _('template style to use'), _('STYLE')),
5211 5213 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5212 5214 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5213 5215 ('', 'print-url', None, _('start and print only the URL'))]
5214 5216 + subrepoopts,
5215 5217 _('[OPTION]...'),
5216 5218 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5217 5219 helpbasic=True, optionalrepo=True)
5218 5220 def serve(ui, repo, **opts):
5219 5221 """start stand-alone webserver
5220 5222
5221 5223 Start a local HTTP repository browser and pull server. You can use
5222 5224 this for ad-hoc sharing and browsing of repositories. It is
5223 5225 recommended to use a real web server to serve a repository for
5224 5226 longer periods of time.
5225 5227
5226 5228 Please note that the server does not implement access control.
5227 5229 This means that, by default, anybody can read from the server and
5228 5230 nobody can write to it by default. Set the ``web.allow-push``
5229 5231 option to ``*`` to allow everybody to push to the server. You
5230 5232 should use a real web server if you need to authenticate users.
5231 5233
5232 5234 By default, the server logs accesses to stdout and errors to
5233 5235 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5234 5236 files.
5235 5237
5236 5238 To have the server choose a free port number to listen on, specify
5237 5239 a port number of 0; in this case, the server will print the port
5238 5240 number it uses.
5239 5241
5240 5242 Returns 0 on success.
5241 5243 """
5242 5244
5243 5245 opts = pycompat.byteskwargs(opts)
5244 5246 if opts["stdio"] and opts["cmdserver"]:
5245 5247 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5246 5248 if opts["print_url"] and ui.verbose:
5247 5249 raise error.Abort(_("cannot use --print-url with --verbose"))
5248 5250
5249 5251 if opts["stdio"]:
5250 5252 if repo is None:
5251 5253 raise error.RepoError(_("there is no Mercurial repository here"
5252 5254 " (.hg not found)"))
5253 5255 s = wireprotoserver.sshserver(ui, repo)
5254 5256 s.serve_forever()
5255 5257
5256 5258 service = server.createservice(ui, repo, opts)
5257 5259 return server.runservice(opts, initfn=service.init, runfn=service.run)
5258 5260
5259 5261 _NOTTERSE = 'nothing'
5260 5262
5261 5263 @command('status|st',
5262 5264 [('A', 'all', None, _('show status of all files')),
5263 5265 ('m', 'modified', None, _('show only modified files')),
5264 5266 ('a', 'added', None, _('show only added files')),
5265 5267 ('r', 'removed', None, _('show only removed files')),
5266 5268 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5267 5269 ('c', 'clean', None, _('show only files without changes')),
5268 5270 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5269 5271 ('i', 'ignored', None, _('show only ignored files')),
5270 5272 ('n', 'no-status', None, _('hide status prefix')),
5271 5273 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5272 5274 ('C', 'copies', None, _('show source of copied files')),
5273 5275 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5274 5276 ('', 'rev', [], _('show difference from revision'), _('REV')),
5275 5277 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5276 5278 ] + walkopts + subrepoopts + formatteropts,
5277 5279 _('[OPTION]... [FILE]...'),
5278 5280 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5279 5281 helpbasic=True, inferrepo=True,
5280 5282 intents={INTENT_READONLY})
5281 5283 def status(ui, repo, *pats, **opts):
5282 5284 """show changed files in the working directory
5283 5285
5284 5286 Show status of files in the repository. If names are given, only
5285 5287 files that match are shown. Files that are clean or ignored or
5286 5288 the source of a copy/move operation, are not listed unless
5287 5289 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5288 5290 Unless options described with "show only ..." are given, the
5289 5291 options -mardu are used.
5290 5292
5291 5293 Option -q/--quiet hides untracked (unknown and ignored) files
5292 5294 unless explicitly requested with -u/--unknown or -i/--ignored.
5293 5295
5294 5296 .. note::
5295 5297
5296 5298 :hg:`status` may appear to disagree with diff if permissions have
5297 5299 changed or a merge has occurred. The standard diff format does
5298 5300 not report permission changes and diff only reports changes
5299 5301 relative to one merge parent.
5300 5302
5301 5303 If one revision is given, it is used as the base revision.
5302 5304 If two revisions are given, the differences between them are
5303 5305 shown. The --change option can also be used as a shortcut to list
5304 5306 the changed files of a revision from its first parent.
5305 5307
5306 5308 The codes used to show the status of files are::
5307 5309
5308 5310 M = modified
5309 5311 A = added
5310 5312 R = removed
5311 5313 C = clean
5312 5314 ! = missing (deleted by non-hg command, but still tracked)
5313 5315 ? = not tracked
5314 5316 I = ignored
5315 5317 = origin of the previous file (with --copies)
5316 5318
5317 5319 .. container:: verbose
5318 5320
5319 5321 The -t/--terse option abbreviates the output by showing only the directory
5320 5322 name if all the files in it share the same status. The option takes an
5321 5323 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5322 5324 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5323 5325 for 'ignored' and 'c' for clean.
5324 5326
5325 5327 It abbreviates only those statuses which are passed. Note that clean and
5326 5328 ignored files are not displayed with '--terse ic' unless the -c/--clean
5327 5329 and -i/--ignored options are also used.
5328 5330
5329 5331 The -v/--verbose option shows information when the repository is in an
5330 5332 unfinished merge, shelve, rebase state etc. You can have this behavior
5331 5333 turned on by default by enabling the ``commands.status.verbose`` option.
5332 5334
5333 5335 You can skip displaying some of these states by setting
5334 5336 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5335 5337 'histedit', 'merge', 'rebase', or 'unshelve'.
5336 5338
5337 5339 Template:
5338 5340
5339 5341 The following keywords are supported in addition to the common template
5340 5342 keywords and functions. See also :hg:`help templates`.
5341 5343
5342 5344 :path: String. Repository-absolute path of the file.
5343 5345 :source: String. Repository-absolute path of the file originated from.
5344 5346 Available if ``--copies`` is specified.
5345 5347 :status: String. Character denoting file's status.
5346 5348
5347 5349 Examples:
5348 5350
5349 5351 - show changes in the working directory relative to a
5350 5352 changeset::
5351 5353
5352 5354 hg status --rev 9353
5353 5355
5354 5356 - show changes in the working directory relative to the
5355 5357 current directory (see :hg:`help patterns` for more information)::
5356 5358
5357 5359 hg status re:
5358 5360
5359 5361 - show all changes including copies in an existing changeset::
5360 5362
5361 5363 hg status --copies --change 9353
5362 5364
5363 5365 - get a NUL separated list of added files, suitable for xargs::
5364 5366
5365 5367 hg status -an0
5366 5368
5367 5369 - show more information about the repository status, abbreviating
5368 5370 added, removed, modified, deleted, and untracked paths::
5369 5371
5370 5372 hg status -v -t mardu
5371 5373
5372 5374 Returns 0 on success.
5373 5375
5374 5376 """
5375 5377
5376 5378 opts = pycompat.byteskwargs(opts)
5377 5379 revs = opts.get('rev')
5378 5380 change = opts.get('change')
5379 5381 terse = opts.get('terse')
5380 5382 if terse is _NOTTERSE:
5381 5383 if revs:
5382 5384 terse = ''
5383 5385 else:
5384 5386 terse = ui.config('commands', 'status.terse')
5385 5387
5386 5388 if revs and change:
5387 5389 msg = _('cannot specify --rev and --change at the same time')
5388 5390 raise error.Abort(msg)
5389 5391 elif revs and terse:
5390 5392 msg = _('cannot use --terse with --rev')
5391 5393 raise error.Abort(msg)
5392 5394 elif change:
5393 5395 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5394 5396 ctx2 = scmutil.revsingle(repo, change, None)
5395 5397 ctx1 = ctx2.p1()
5396 5398 else:
5397 5399 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5398 5400 ctx1, ctx2 = scmutil.revpair(repo, revs)
5399 5401
5400 5402 if pats or ui.configbool('commands', 'status.relative'):
5401 5403 cwd = repo.getcwd()
5402 5404 else:
5403 5405 cwd = ''
5404 5406
5405 5407 if opts.get('print0'):
5406 5408 end = '\0'
5407 5409 else:
5408 5410 end = '\n'
5409 5411 copy = {}
5410 5412 states = 'modified added removed deleted unknown ignored clean'.split()
5411 5413 show = [k for k in states if opts.get(k)]
5412 5414 if opts.get('all'):
5413 5415 show += ui.quiet and (states[:4] + ['clean']) or states
5414 5416
5415 5417 if not show:
5416 5418 if ui.quiet:
5417 5419 show = states[:4]
5418 5420 else:
5419 5421 show = states[:5]
5420 5422
5421 5423 m = scmutil.match(ctx2, pats, opts)
5422 5424 if terse:
5423 5425 # we need to compute clean and unknown to terse
5424 5426 stat = repo.status(ctx1.node(), ctx2.node(), m,
5425 5427 'ignored' in show or 'i' in terse,
5426 5428 clean=True, unknown=True,
5427 5429 listsubrepos=opts.get('subrepos'))
5428 5430
5429 5431 stat = cmdutil.tersedir(stat, terse)
5430 5432 else:
5431 5433 stat = repo.status(ctx1.node(), ctx2.node(), m,
5432 5434 'ignored' in show, 'clean' in show,
5433 5435 'unknown' in show, opts.get('subrepos'))
5434 5436
5435 5437 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5436 5438
5437 5439 if (opts.get('all') or opts.get('copies')
5438 5440 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5439 5441 copy = copies.pathcopies(ctx1, ctx2, m)
5440 5442
5441 5443 ui.pager('status')
5442 5444 fm = ui.formatter('status', opts)
5443 5445 fmt = '%s' + end
5444 5446 showchar = not opts.get('no_status')
5445 5447
5446 5448 for state, char, files in changestates:
5447 5449 if state in show:
5448 5450 label = 'status.' + state
5449 5451 for f in files:
5450 5452 fm.startitem()
5451 5453 fm.context(ctx=ctx2)
5452 5454 fm.data(path=f)
5453 5455 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5454 5456 fm.plain(fmt % repo.pathto(f, cwd), label=label)
5455 5457 if f in copy:
5456 5458 fm.data(source=copy[f])
5457 5459 fm.plain((' %s' + end) % repo.pathto(copy[f], cwd),
5458 5460 label='status.copied')
5459 5461
5460 5462 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5461 5463 and not ui.plain()):
5462 5464 cmdutil.morestatus(repo, fm)
5463 5465 fm.end()
5464 5466
5465 5467 @command('summary|sum',
5466 5468 [('', 'remote', None, _('check for push and pull'))],
5467 5469 '[--remote]',
5468 5470 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5469 5471 helpbasic=True,
5470 5472 intents={INTENT_READONLY})
5471 5473 def summary(ui, repo, **opts):
5472 5474 """summarize working directory state
5473 5475
5474 5476 This generates a brief summary of the working directory state,
5475 5477 including parents, branch, commit status, phase and available updates.
5476 5478
5477 5479 With the --remote option, this will check the default paths for
5478 5480 incoming and outgoing changes. This can be time-consuming.
5479 5481
5480 5482 Returns 0 on success.
5481 5483 """
5482 5484
5483 5485 opts = pycompat.byteskwargs(opts)
5484 5486 ui.pager('summary')
5485 5487 ctx = repo[None]
5486 5488 parents = ctx.parents()
5487 5489 pnode = parents[0].node()
5488 5490 marks = []
5489 5491
5490 5492 ms = None
5491 5493 try:
5492 5494 ms = mergemod.mergestate.read(repo)
5493 5495 except error.UnsupportedMergeRecords as e:
5494 5496 s = ' '.join(e.recordtypes)
5495 5497 ui.warn(
5496 5498 _('warning: merge state has unsupported record types: %s\n') % s)
5497 5499 unresolved = []
5498 5500 else:
5499 5501 unresolved = list(ms.unresolved())
5500 5502
5501 5503 for p in parents:
5502 5504 # label with log.changeset (instead of log.parent) since this
5503 5505 # shows a working directory parent *changeset*:
5504 5506 # i18n: column positioning for "hg summary"
5505 5507 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5506 5508 label=logcmdutil.changesetlabels(p))
5507 5509 ui.write(' '.join(p.tags()), label='log.tag')
5508 5510 if p.bookmarks():
5509 5511 marks.extend(p.bookmarks())
5510 5512 if p.rev() == -1:
5511 5513 if not len(repo):
5512 5514 ui.write(_(' (empty repository)'))
5513 5515 else:
5514 5516 ui.write(_(' (no revision checked out)'))
5515 5517 if p.obsolete():
5516 5518 ui.write(_(' (obsolete)'))
5517 5519 if p.isunstable():
5518 5520 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5519 5521 for instability in p.instabilities())
5520 5522 ui.write(' ('
5521 5523 + ', '.join(instabilities)
5522 5524 + ')')
5523 5525 ui.write('\n')
5524 5526 if p.description():
5525 5527 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5526 5528 label='log.summary')
5527 5529
5528 5530 branch = ctx.branch()
5529 5531 bheads = repo.branchheads(branch)
5530 5532 # i18n: column positioning for "hg summary"
5531 5533 m = _('branch: %s\n') % branch
5532 5534 if branch != 'default':
5533 5535 ui.write(m, label='log.branch')
5534 5536 else:
5535 5537 ui.status(m, label='log.branch')
5536 5538
5537 5539 if marks:
5538 5540 active = repo._activebookmark
5539 5541 # i18n: column positioning for "hg summary"
5540 5542 ui.write(_('bookmarks:'), label='log.bookmark')
5541 5543 if active is not None:
5542 5544 if active in marks:
5543 5545 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5544 5546 marks.remove(active)
5545 5547 else:
5546 5548 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5547 5549 for m in marks:
5548 5550 ui.write(' ' + m, label='log.bookmark')
5549 5551 ui.write('\n', label='log.bookmark')
5550 5552
5551 5553 status = repo.status(unknown=True)
5552 5554
5553 5555 c = repo.dirstate.copies()
5554 5556 copied, renamed = [], []
5555 5557 for d, s in c.iteritems():
5556 5558 if s in status.removed:
5557 5559 status.removed.remove(s)
5558 5560 renamed.append(d)
5559 5561 else:
5560 5562 copied.append(d)
5561 5563 if d in status.added:
5562 5564 status.added.remove(d)
5563 5565
5564 5566 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5565 5567
5566 5568 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5567 5569 (ui.label(_('%d added'), 'status.added'), status.added),
5568 5570 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5569 5571 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5570 5572 (ui.label(_('%d copied'), 'status.copied'), copied),
5571 5573 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5572 5574 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5573 5575 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5574 5576 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5575 5577 t = []
5576 5578 for l, s in labels:
5577 5579 if s:
5578 5580 t.append(l % len(s))
5579 5581
5580 5582 t = ', '.join(t)
5581 5583 cleanworkdir = False
5582 5584
5583 5585 if repo.vfs.exists('graftstate'):
5584 5586 t += _(' (graft in progress)')
5585 5587 if repo.vfs.exists('updatestate'):
5586 5588 t += _(' (interrupted update)')
5587 5589 elif len(parents) > 1:
5588 5590 t += _(' (merge)')
5589 5591 elif branch != parents[0].branch():
5590 5592 t += _(' (new branch)')
5591 5593 elif (parents[0].closesbranch() and
5592 5594 pnode in repo.branchheads(branch, closed=True)):
5593 5595 t += _(' (head closed)')
5594 5596 elif not (status.modified or status.added or status.removed or renamed or
5595 5597 copied or subs):
5596 5598 t += _(' (clean)')
5597 5599 cleanworkdir = True
5598 5600 elif pnode not in bheads:
5599 5601 t += _(' (new branch head)')
5600 5602
5601 5603 if parents:
5602 5604 pendingphase = max(p.phase() for p in parents)
5603 5605 else:
5604 5606 pendingphase = phases.public
5605 5607
5606 5608 if pendingphase > phases.newcommitphase(ui):
5607 5609 t += ' (%s)' % phases.phasenames[pendingphase]
5608 5610
5609 5611 if cleanworkdir:
5610 5612 # i18n: column positioning for "hg summary"
5611 5613 ui.status(_('commit: %s\n') % t.strip())
5612 5614 else:
5613 5615 # i18n: column positioning for "hg summary"
5614 5616 ui.write(_('commit: %s\n') % t.strip())
5615 5617
5616 5618 # all ancestors of branch heads - all ancestors of parent = new csets
5617 5619 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5618 5620 bheads))
5619 5621
5620 5622 if new == 0:
5621 5623 # i18n: column positioning for "hg summary"
5622 5624 ui.status(_('update: (current)\n'))
5623 5625 elif pnode not in bheads:
5624 5626 # i18n: column positioning for "hg summary"
5625 5627 ui.write(_('update: %d new changesets (update)\n') % new)
5626 5628 else:
5627 5629 # i18n: column positioning for "hg summary"
5628 5630 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5629 5631 (new, len(bheads)))
5630 5632
5631 5633 t = []
5632 5634 draft = len(repo.revs('draft()'))
5633 5635 if draft:
5634 5636 t.append(_('%d draft') % draft)
5635 5637 secret = len(repo.revs('secret()'))
5636 5638 if secret:
5637 5639 t.append(_('%d secret') % secret)
5638 5640
5639 5641 if draft or secret:
5640 5642 ui.status(_('phases: %s\n') % ', '.join(t))
5641 5643
5642 5644 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5643 5645 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5644 5646 numtrouble = len(repo.revs(trouble + "()"))
5645 5647 # We write all the possibilities to ease translation
5646 5648 troublemsg = {
5647 5649 "orphan": _("orphan: %d changesets"),
5648 5650 "contentdivergent": _("content-divergent: %d changesets"),
5649 5651 "phasedivergent": _("phase-divergent: %d changesets"),
5650 5652 }
5651 5653 if numtrouble > 0:
5652 5654 ui.status(troublemsg[trouble] % numtrouble + "\n")
5653 5655
5654 5656 cmdutil.summaryhooks(ui, repo)
5655 5657
5656 5658 if opts.get('remote'):
5657 5659 needsincoming, needsoutgoing = True, True
5658 5660 else:
5659 5661 needsincoming, needsoutgoing = False, False
5660 5662 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5661 5663 if i:
5662 5664 needsincoming = True
5663 5665 if o:
5664 5666 needsoutgoing = True
5665 5667 if not needsincoming and not needsoutgoing:
5666 5668 return
5667 5669
5668 5670 def getincoming():
5669 5671 source, branches = hg.parseurl(ui.expandpath('default'))
5670 5672 sbranch = branches[0]
5671 5673 try:
5672 5674 other = hg.peer(repo, {}, source)
5673 5675 except error.RepoError:
5674 5676 if opts.get('remote'):
5675 5677 raise
5676 5678 return source, sbranch, None, None, None
5677 5679 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5678 5680 if revs:
5679 5681 revs = [other.lookup(rev) for rev in revs]
5680 5682 ui.debug('comparing with %s\n' % util.hidepassword(source))
5681 5683 repo.ui.pushbuffer()
5682 5684 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5683 5685 repo.ui.popbuffer()
5684 5686 return source, sbranch, other, commoninc, commoninc[1]
5685 5687
5686 5688 if needsincoming:
5687 5689 source, sbranch, sother, commoninc, incoming = getincoming()
5688 5690 else:
5689 5691 source = sbranch = sother = commoninc = incoming = None
5690 5692
5691 5693 def getoutgoing():
5692 5694 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5693 5695 dbranch = branches[0]
5694 5696 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5695 5697 if source != dest:
5696 5698 try:
5697 5699 dother = hg.peer(repo, {}, dest)
5698 5700 except error.RepoError:
5699 5701 if opts.get('remote'):
5700 5702 raise
5701 5703 return dest, dbranch, None, None
5702 5704 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5703 5705 elif sother is None:
5704 5706 # there is no explicit destination peer, but source one is invalid
5705 5707 return dest, dbranch, None, None
5706 5708 else:
5707 5709 dother = sother
5708 5710 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5709 5711 common = None
5710 5712 else:
5711 5713 common = commoninc
5712 5714 if revs:
5713 5715 revs = [repo.lookup(rev) for rev in revs]
5714 5716 repo.ui.pushbuffer()
5715 5717 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5716 5718 commoninc=common)
5717 5719 repo.ui.popbuffer()
5718 5720 return dest, dbranch, dother, outgoing
5719 5721
5720 5722 if needsoutgoing:
5721 5723 dest, dbranch, dother, outgoing = getoutgoing()
5722 5724 else:
5723 5725 dest = dbranch = dother = outgoing = None
5724 5726
5725 5727 if opts.get('remote'):
5726 5728 t = []
5727 5729 if incoming:
5728 5730 t.append(_('1 or more incoming'))
5729 5731 o = outgoing.missing
5730 5732 if o:
5731 5733 t.append(_('%d outgoing') % len(o))
5732 5734 other = dother or sother
5733 5735 if 'bookmarks' in other.listkeys('namespaces'):
5734 5736 counts = bookmarks.summary(repo, other)
5735 5737 if counts[0] > 0:
5736 5738 t.append(_('%d incoming bookmarks') % counts[0])
5737 5739 if counts[1] > 0:
5738 5740 t.append(_('%d outgoing bookmarks') % counts[1])
5739 5741
5740 5742 if t:
5741 5743 # i18n: column positioning for "hg summary"
5742 5744 ui.write(_('remote: %s\n') % (', '.join(t)))
5743 5745 else:
5744 5746 # i18n: column positioning for "hg summary"
5745 5747 ui.status(_('remote: (synced)\n'))
5746 5748
5747 5749 cmdutil.summaryremotehooks(ui, repo, opts,
5748 5750 ((source, sbranch, sother, commoninc),
5749 5751 (dest, dbranch, dother, outgoing)))
5750 5752
5751 5753 @command('tag',
5752 5754 [('f', 'force', None, _('force tag')),
5753 5755 ('l', 'local', None, _('make the tag local')),
5754 5756 ('r', 'rev', '', _('revision to tag'), _('REV')),
5755 5757 ('', 'remove', None, _('remove a tag')),
5756 5758 # -l/--local is already there, commitopts cannot be used
5757 5759 ('e', 'edit', None, _('invoke editor on commit messages')),
5758 5760 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5759 5761 ] + commitopts2,
5760 5762 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5761 5763 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5762 5764 def tag(ui, repo, name1, *names, **opts):
5763 5765 """add one or more tags for the current or given revision
5764 5766
5765 5767 Name a particular revision using <name>.
5766 5768
5767 5769 Tags are used to name particular revisions of the repository and are
5768 5770 very useful to compare different revisions, to go back to significant
5769 5771 earlier versions or to mark branch points as releases, etc. Changing
5770 5772 an existing tag is normally disallowed; use -f/--force to override.
5771 5773
5772 5774 If no revision is given, the parent of the working directory is
5773 5775 used.
5774 5776
5775 5777 To facilitate version control, distribution, and merging of tags,
5776 5778 they are stored as a file named ".hgtags" which is managed similarly
5777 5779 to other project files and can be hand-edited if necessary. This
5778 5780 also means that tagging creates a new commit. The file
5779 5781 ".hg/localtags" is used for local tags (not shared among
5780 5782 repositories).
5781 5783
5782 5784 Tag commits are usually made at the head of a branch. If the parent
5783 5785 of the working directory is not a branch head, :hg:`tag` aborts; use
5784 5786 -f/--force to force the tag commit to be based on a non-head
5785 5787 changeset.
5786 5788
5787 5789 See :hg:`help dates` for a list of formats valid for -d/--date.
5788 5790
5789 5791 Since tag names have priority over branch names during revision
5790 5792 lookup, using an existing branch name as a tag name is discouraged.
5791 5793
5792 5794 Returns 0 on success.
5793 5795 """
5794 5796 opts = pycompat.byteskwargs(opts)
5795 5797 with repo.wlock(), repo.lock():
5796 5798 rev_ = "."
5797 5799 names = [t.strip() for t in (name1,) + names]
5798 5800 if len(names) != len(set(names)):
5799 5801 raise error.Abort(_('tag names must be unique'))
5800 5802 for n in names:
5801 5803 scmutil.checknewlabel(repo, n, 'tag')
5802 5804 if not n:
5803 5805 raise error.Abort(_('tag names cannot consist entirely of '
5804 5806 'whitespace'))
5805 5807 if opts.get('rev') and opts.get('remove'):
5806 5808 raise error.Abort(_("--rev and --remove are incompatible"))
5807 5809 if opts.get('rev'):
5808 5810 rev_ = opts['rev']
5809 5811 message = opts.get('message')
5810 5812 if opts.get('remove'):
5811 5813 if opts.get('local'):
5812 5814 expectedtype = 'local'
5813 5815 else:
5814 5816 expectedtype = 'global'
5815 5817
5816 5818 for n in names:
5817 5819 if not repo.tagtype(n):
5818 5820 raise error.Abort(_("tag '%s' does not exist") % n)
5819 5821 if repo.tagtype(n) != expectedtype:
5820 5822 if expectedtype == 'global':
5821 5823 raise error.Abort(_("tag '%s' is not a global tag") % n)
5822 5824 else:
5823 5825 raise error.Abort(_("tag '%s' is not a local tag") % n)
5824 5826 rev_ = 'null'
5825 5827 if not message:
5826 5828 # we don't translate commit messages
5827 5829 message = 'Removed tag %s' % ', '.join(names)
5828 5830 elif not opts.get('force'):
5829 5831 for n in names:
5830 5832 if n in repo.tags():
5831 5833 raise error.Abort(_("tag '%s' already exists "
5832 5834 "(use -f to force)") % n)
5833 5835 if not opts.get('local'):
5834 5836 p1, p2 = repo.dirstate.parents()
5835 5837 if p2 != nullid:
5836 5838 raise error.Abort(_('uncommitted merge'))
5837 5839 bheads = repo.branchheads()
5838 5840 if not opts.get('force') and bheads and p1 not in bheads:
5839 5841 raise error.Abort(_('working directory is not at a branch head '
5840 5842 '(use -f to force)'))
5841 5843 node = scmutil.revsingle(repo, rev_).node()
5842 5844
5843 5845 if not message:
5844 5846 # we don't translate commit messages
5845 5847 message = ('Added tag %s for changeset %s' %
5846 5848 (', '.join(names), short(node)))
5847 5849
5848 5850 date = opts.get('date')
5849 5851 if date:
5850 5852 date = dateutil.parsedate(date)
5851 5853
5852 5854 if opts.get('remove'):
5853 5855 editform = 'tag.remove'
5854 5856 else:
5855 5857 editform = 'tag.add'
5856 5858 editor = cmdutil.getcommiteditor(editform=editform,
5857 5859 **pycompat.strkwargs(opts))
5858 5860
5859 5861 # don't allow tagging the null rev
5860 5862 if (not opts.get('remove') and
5861 5863 scmutil.revsingle(repo, rev_).rev() == nullrev):
5862 5864 raise error.Abort(_("cannot tag null revision"))
5863 5865
5864 5866 tagsmod.tag(repo, names, node, message, opts.get('local'),
5865 5867 opts.get('user'), date, editor=editor)
5866 5868
5867 5869 @command(
5868 5870 'tags', formatteropts, '',
5869 5871 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5870 5872 intents={INTENT_READONLY})
5871 5873 def tags(ui, repo, **opts):
5872 5874 """list repository tags
5873 5875
5874 5876 This lists both regular and local tags. When the -v/--verbose
5875 5877 switch is used, a third column "local" is printed for local tags.
5876 5878 When the -q/--quiet switch is used, only the tag name is printed.
5877 5879
5878 5880 .. container:: verbose
5879 5881
5880 5882 Template:
5881 5883
5882 5884 The following keywords are supported in addition to the common template
5883 5885 keywords and functions such as ``{tag}``. See also
5884 5886 :hg:`help templates`.
5885 5887
5886 5888 :type: String. ``local`` for local tags.
5887 5889
5888 5890 Returns 0 on success.
5889 5891 """
5890 5892
5891 5893 opts = pycompat.byteskwargs(opts)
5892 5894 ui.pager('tags')
5893 5895 fm = ui.formatter('tags', opts)
5894 5896 hexfunc = fm.hexfunc
5895 5897 tagtype = ""
5896 5898
5897 5899 for t, n in reversed(repo.tagslist()):
5898 5900 hn = hexfunc(n)
5899 5901 label = 'tags.normal'
5900 5902 tagtype = ''
5901 5903 if repo.tagtype(t) == 'local':
5902 5904 label = 'tags.local'
5903 5905 tagtype = 'local'
5904 5906
5905 5907 fm.startitem()
5906 5908 fm.context(repo=repo)
5907 5909 fm.write('tag', '%s', t, label=label)
5908 5910 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5909 5911 fm.condwrite(not ui.quiet, 'rev node', fmt,
5910 5912 repo.changelog.rev(n), hn, label=label)
5911 5913 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5912 5914 tagtype, label=label)
5913 5915 fm.plain('\n')
5914 5916 fm.end()
5915 5917
5916 5918 @command('tip',
5917 5919 [('p', 'patch', None, _('show patch')),
5918 5920 ('g', 'git', None, _('use git extended diff format')),
5919 5921 ] + templateopts,
5920 5922 _('[-p] [-g]'),
5921 5923 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5922 5924 def tip(ui, repo, **opts):
5923 5925 """show the tip revision (DEPRECATED)
5924 5926
5925 5927 The tip revision (usually just called the tip) is the changeset
5926 5928 most recently added to the repository (and therefore the most
5927 5929 recently changed head).
5928 5930
5929 5931 If you have just made a commit, that commit will be the tip. If
5930 5932 you have just pulled changes from another repository, the tip of
5931 5933 that repository becomes the current tip. The "tip" tag is special
5932 5934 and cannot be renamed or assigned to a different changeset.
5933 5935
5934 5936 This command is deprecated, please use :hg:`heads` instead.
5935 5937
5936 5938 Returns 0 on success.
5937 5939 """
5938 5940 opts = pycompat.byteskwargs(opts)
5939 5941 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5940 5942 displayer.show(repo['tip'])
5941 5943 displayer.close()
5942 5944
5943 5945 @command('unbundle',
5944 5946 [('u', 'update', None,
5945 5947 _('update to new branch head if changesets were unbundled'))],
5946 5948 _('[-u] FILE...'),
5947 5949 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5948 5950 def unbundle(ui, repo, fname1, *fnames, **opts):
5949 5951 """apply one or more bundle files
5950 5952
5951 5953 Apply one or more bundle files generated by :hg:`bundle`.
5952 5954
5953 5955 Returns 0 on success, 1 if an update has unresolved files.
5954 5956 """
5955 5957 fnames = (fname1,) + fnames
5956 5958
5957 5959 with repo.lock():
5958 5960 for fname in fnames:
5959 5961 f = hg.openpath(ui, fname)
5960 5962 gen = exchange.readbundle(ui, f, fname)
5961 5963 if isinstance(gen, streamclone.streamcloneapplier):
5962 5964 raise error.Abort(
5963 5965 _('packed bundles cannot be applied with '
5964 5966 '"hg unbundle"'),
5965 5967 hint=_('use "hg debugapplystreamclonebundle"'))
5966 5968 url = 'bundle:' + fname
5967 5969 try:
5968 5970 txnname = 'unbundle'
5969 5971 if not isinstance(gen, bundle2.unbundle20):
5970 5972 txnname = 'unbundle\n%s' % util.hidepassword(url)
5971 5973 with repo.transaction(txnname) as tr:
5972 5974 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5973 5975 url=url)
5974 5976 except error.BundleUnknownFeatureError as exc:
5975 5977 raise error.Abort(
5976 5978 _('%s: unknown bundle feature, %s') % (fname, exc),
5977 5979 hint=_("see https://mercurial-scm.org/"
5978 5980 "wiki/BundleFeature for more "
5979 5981 "information"))
5980 5982 modheads = bundle2.combinechangegroupresults(op)
5981 5983
5982 5984 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5983 5985
5984 5986 @command('update|up|checkout|co',
5985 5987 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5986 5988 ('c', 'check', None, _('require clean working directory')),
5987 5989 ('m', 'merge', None, _('merge uncommitted changes')),
5988 5990 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5989 5991 ('r', 'rev', '', _('revision'), _('REV'))
5990 5992 ] + mergetoolopts,
5991 5993 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
5992 5994 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5993 5995 helpbasic=True)
5994 5996 def update(ui, repo, node=None, **opts):
5995 5997 """update working directory (or switch revisions)
5996 5998
5997 5999 Update the repository's working directory to the specified
5998 6000 changeset. If no changeset is specified, update to the tip of the
5999 6001 current named branch and move the active bookmark (see :hg:`help
6000 6002 bookmarks`).
6001 6003
6002 6004 Update sets the working directory's parent revision to the specified
6003 6005 changeset (see :hg:`help parents`).
6004 6006
6005 6007 If the changeset is not a descendant or ancestor of the working
6006 6008 directory's parent and there are uncommitted changes, the update is
6007 6009 aborted. With the -c/--check option, the working directory is checked
6008 6010 for uncommitted changes; if none are found, the working directory is
6009 6011 updated to the specified changeset.
6010 6012
6011 6013 .. container:: verbose
6012 6014
6013 6015 The -C/--clean, -c/--check, and -m/--merge options control what
6014 6016 happens if the working directory contains uncommitted changes.
6015 6017 At most of one of them can be specified.
6016 6018
6017 6019 1. If no option is specified, and if
6018 6020 the requested changeset is an ancestor or descendant of
6019 6021 the working directory's parent, the uncommitted changes
6020 6022 are merged into the requested changeset and the merged
6021 6023 result is left uncommitted. If the requested changeset is
6022 6024 not an ancestor or descendant (that is, it is on another
6023 6025 branch), the update is aborted and the uncommitted changes
6024 6026 are preserved.
6025 6027
6026 6028 2. With the -m/--merge option, the update is allowed even if the
6027 6029 requested changeset is not an ancestor or descendant of
6028 6030 the working directory's parent.
6029 6031
6030 6032 3. With the -c/--check option, the update is aborted and the
6031 6033 uncommitted changes are preserved.
6032 6034
6033 6035 4. With the -C/--clean option, uncommitted changes are discarded and
6034 6036 the working directory is updated to the requested changeset.
6035 6037
6036 6038 To cancel an uncommitted merge (and lose your changes), use
6037 6039 :hg:`merge --abort`.
6038 6040
6039 6041 Use null as the changeset to remove the working directory (like
6040 6042 :hg:`clone -U`).
6041 6043
6042 6044 If you want to revert just one file to an older revision, use
6043 6045 :hg:`revert [-r REV] NAME`.
6044 6046
6045 6047 See :hg:`help dates` for a list of formats valid for -d/--date.
6046 6048
6047 6049 Returns 0 on success, 1 if there are unresolved files.
6048 6050 """
6049 6051 rev = opts.get(r'rev')
6050 6052 date = opts.get(r'date')
6051 6053 clean = opts.get(r'clean')
6052 6054 check = opts.get(r'check')
6053 6055 merge = opts.get(r'merge')
6054 6056 if rev and node:
6055 6057 raise error.Abort(_("please specify just one revision"))
6056 6058
6057 6059 if ui.configbool('commands', 'update.requiredest'):
6058 6060 if not node and not rev and not date:
6059 6061 raise error.Abort(_('you must specify a destination'),
6060 6062 hint=_('for example: hg update ".::"'))
6061 6063
6062 6064 if rev is None or rev == '':
6063 6065 rev = node
6064 6066
6065 6067 if date and rev is not None:
6066 6068 raise error.Abort(_("you can't specify a revision and a date"))
6067 6069
6068 6070 if len([x for x in (clean, check, merge) if x]) > 1:
6069 6071 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6070 6072 "or -m/--merge"))
6071 6073
6072 6074 updatecheck = None
6073 6075 if check:
6074 6076 updatecheck = 'abort'
6075 6077 elif merge:
6076 6078 updatecheck = 'none'
6077 6079
6078 6080 with repo.wlock():
6079 6081 cmdutil.clearunfinished(repo)
6080 6082
6081 6083 if date:
6082 6084 rev = cmdutil.finddate(ui, repo, date)
6083 6085
6084 6086 # if we defined a bookmark, we have to remember the original name
6085 6087 brev = rev
6086 6088 if rev:
6087 6089 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6088 6090 ctx = scmutil.revsingle(repo, rev, rev)
6089 6091 rev = ctx.rev()
6090 6092 hidden = ctx.hidden()
6091 6093 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6092 6094 with ui.configoverride(overrides, 'update'):
6093 6095 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6094 6096 updatecheck=updatecheck)
6095 6097 if hidden:
6096 6098 ctxstr = ctx.hex()[:12]
6097 6099 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6098 6100
6099 6101 if ctx.obsolete():
6100 6102 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6101 6103 ui.warn("(%s)\n" % obsfatemsg)
6102 6104 return ret
6103 6105
6104 6106 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6105 6107 def verify(ui, repo):
6106 6108 """verify the integrity of the repository
6107 6109
6108 6110 Verify the integrity of the current repository.
6109 6111
6110 6112 This will perform an extensive check of the repository's
6111 6113 integrity, validating the hashes and checksums of each entry in
6112 6114 the changelog, manifest, and tracked files, as well as the
6113 6115 integrity of their crosslinks and indices.
6114 6116
6115 6117 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6116 6118 for more information about recovery from corruption of the
6117 6119 repository.
6118 6120
6119 6121 Returns 0 on success, 1 if errors are encountered.
6120 6122 """
6121 6123 return hg.verify(repo)
6122 6124
6123 6125 @command(
6124 6126 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6125 6127 norepo=True, intents={INTENT_READONLY})
6126 6128 def version_(ui, **opts):
6127 6129 """output version and copyright information
6128 6130
6129 6131 .. container:: verbose
6130 6132
6131 6133 Template:
6132 6134
6133 6135 The following keywords are supported. See also :hg:`help templates`.
6134 6136
6135 6137 :extensions: List of extensions.
6136 6138 :ver: String. Version number.
6137 6139
6138 6140 And each entry of ``{extensions}`` provides the following sub-keywords
6139 6141 in addition to ``{ver}``.
6140 6142
6141 6143 :bundled: Boolean. True if included in the release.
6142 6144 :name: String. Extension name.
6143 6145 """
6144 6146 opts = pycompat.byteskwargs(opts)
6145 6147 if ui.verbose:
6146 6148 ui.pager('version')
6147 6149 fm = ui.formatter("version", opts)
6148 6150 fm.startitem()
6149 6151 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6150 6152 util.version())
6151 6153 license = _(
6152 6154 "(see https://mercurial-scm.org for more information)\n"
6153 6155 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
6154 6156 "This is free software; see the source for copying conditions. "
6155 6157 "There is NO\nwarranty; "
6156 6158 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6157 6159 )
6158 6160 if not ui.quiet:
6159 6161 fm.plain(license)
6160 6162
6161 6163 if ui.verbose:
6162 6164 fm.plain(_("\nEnabled extensions:\n\n"))
6163 6165 # format names and versions into columns
6164 6166 names = []
6165 6167 vers = []
6166 6168 isinternals = []
6167 6169 for name, module in extensions.extensions():
6168 6170 names.append(name)
6169 6171 vers.append(extensions.moduleversion(module) or None)
6170 6172 isinternals.append(extensions.ismoduleinternal(module))
6171 6173 fn = fm.nested("extensions", tmpl='{name}\n')
6172 6174 if names:
6173 6175 namefmt = " %%-%ds " % max(len(n) for n in names)
6174 6176 places = [_("external"), _("internal")]
6175 6177 for n, v, p in zip(names, vers, isinternals):
6176 6178 fn.startitem()
6177 6179 fn.condwrite(ui.verbose, "name", namefmt, n)
6178 6180 if ui.verbose:
6179 6181 fn.plain("%s " % places[p])
6180 6182 fn.data(bundled=p)
6181 6183 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6182 6184 if ui.verbose:
6183 6185 fn.plain("\n")
6184 6186 fn.end()
6185 6187 fm.end()
6186 6188
6187 6189 def loadcmdtable(ui, name, cmdtable):
6188 6190 """Load command functions from specified cmdtable
6189 6191 """
6190 6192 cmdtable = cmdtable.copy()
6191 6193 for cmd in list(cmdtable):
6192 6194 if not cmd.startswith('^'):
6193 6195 continue
6194 6196 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6195 6197 % (cmd, name), '4.8')
6196 6198 entry = cmdtable.pop(cmd)
6197 6199 entry[0].helpbasic = True
6198 6200 cmdtable[cmd[1:]] = entry
6199 6201
6200 6202 overrides = [cmd for cmd in cmdtable if cmd in table]
6201 6203 if overrides:
6202 6204 ui.warn(_("extension '%s' overrides commands: %s\n")
6203 6205 % (name, " ".join(overrides)))
6204 6206 table.update(cmdtable)
@@ -1,2665 +1,2670 b''
1 1 # exchange.py - utility to exchange data between repos.
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 collections
11 11 import hashlib
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 bin,
16 16 hex,
17 17 nullid,
18 18 nullrev,
19 19 )
20 20 from .thirdparty import (
21 21 attr,
22 22 )
23 23 from . import (
24 24 bookmarks as bookmod,
25 25 bundle2,
26 26 changegroup,
27 27 discovery,
28 28 error,
29 29 exchangev2,
30 30 lock as lockmod,
31 31 logexchange,
32 32 narrowspec,
33 33 obsolete,
34 34 phases,
35 35 pushkey,
36 36 pycompat,
37 37 repository,
38 38 scmutil,
39 39 sslutil,
40 40 streamclone,
41 41 url as urlmod,
42 42 util,
43 43 wireprototypes,
44 44 )
45 45 from .utils import (
46 46 stringutil,
47 47 )
48 48
49 49 urlerr = util.urlerr
50 50 urlreq = util.urlreq
51 51
52 52 _NARROWACL_SECTION = 'narrowhgacl'
53 53
54 54 # Maps bundle version human names to changegroup versions.
55 55 _bundlespeccgversions = {'v1': '01',
56 56 'v2': '02',
57 57 'packed1': 's1',
58 58 'bundle2': '02', #legacy
59 59 }
60 60
61 61 # Maps bundle version with content opts to choose which part to bundle
62 62 _bundlespeccontentopts = {
63 63 'v1': {
64 64 'changegroup': True,
65 65 'cg.version': '01',
66 66 'obsolescence': False,
67 67 'phases': False,
68 68 'tagsfnodescache': False,
69 69 'revbranchcache': False
70 70 },
71 71 'v2': {
72 72 'changegroup': True,
73 73 'cg.version': '02',
74 74 'obsolescence': False,
75 75 'phases': False,
76 76 'tagsfnodescache': True,
77 77 'revbranchcache': True
78 78 },
79 79 'packed1' : {
80 80 'cg.version': 's1'
81 81 }
82 82 }
83 83 _bundlespeccontentopts['bundle2'] = _bundlespeccontentopts['v2']
84 84
85 85 _bundlespecvariants = {"streamv2": {"changegroup": False, "streamv2": True,
86 86 "tagsfnodescache": False,
87 87 "revbranchcache": False}}
88 88
89 89 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
90 90 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
91 91
92 92 @attr.s
93 93 class bundlespec(object):
94 94 compression = attr.ib()
95 95 wirecompression = attr.ib()
96 96 version = attr.ib()
97 97 wireversion = attr.ib()
98 98 params = attr.ib()
99 99 contentopts = attr.ib()
100 100
101 101 def parsebundlespec(repo, spec, strict=True):
102 102 """Parse a bundle string specification into parts.
103 103
104 104 Bundle specifications denote a well-defined bundle/exchange format.
105 105 The content of a given specification should not change over time in
106 106 order to ensure that bundles produced by a newer version of Mercurial are
107 107 readable from an older version.
108 108
109 109 The string currently has the form:
110 110
111 111 <compression>-<type>[;<parameter0>[;<parameter1>]]
112 112
113 113 Where <compression> is one of the supported compression formats
114 114 and <type> is (currently) a version string. A ";" can follow the type and
115 115 all text afterwards is interpreted as URI encoded, ";" delimited key=value
116 116 pairs.
117 117
118 118 If ``strict`` is True (the default) <compression> is required. Otherwise,
119 119 it is optional.
120 120
121 121 Returns a bundlespec object of (compression, version, parameters).
122 122 Compression will be ``None`` if not in strict mode and a compression isn't
123 123 defined.
124 124
125 125 An ``InvalidBundleSpecification`` is raised when the specification is
126 126 not syntactically well formed.
127 127
128 128 An ``UnsupportedBundleSpecification`` is raised when the compression or
129 129 bundle type/version is not recognized.
130 130
131 131 Note: this function will likely eventually return a more complex data
132 132 structure, including bundle2 part information.
133 133 """
134 134 def parseparams(s):
135 135 if ';' not in s:
136 136 return s, {}
137 137
138 138 params = {}
139 139 version, paramstr = s.split(';', 1)
140 140
141 141 for p in paramstr.split(';'):
142 142 if '=' not in p:
143 143 raise error.InvalidBundleSpecification(
144 144 _('invalid bundle specification: '
145 145 'missing "=" in parameter: %s') % p)
146 146
147 147 key, value = p.split('=', 1)
148 148 key = urlreq.unquote(key)
149 149 value = urlreq.unquote(value)
150 150 params[key] = value
151 151
152 152 return version, params
153 153
154 154
155 155 if strict and '-' not in spec:
156 156 raise error.InvalidBundleSpecification(
157 157 _('invalid bundle specification; '
158 158 'must be prefixed with compression: %s') % spec)
159 159
160 160 if '-' in spec:
161 161 compression, version = spec.split('-', 1)
162 162
163 163 if compression not in util.compengines.supportedbundlenames:
164 164 raise error.UnsupportedBundleSpecification(
165 165 _('%s compression is not supported') % compression)
166 166
167 167 version, params = parseparams(version)
168 168
169 169 if version not in _bundlespeccgversions:
170 170 raise error.UnsupportedBundleSpecification(
171 171 _('%s is not a recognized bundle version') % version)
172 172 else:
173 173 # Value could be just the compression or just the version, in which
174 174 # case some defaults are assumed (but only when not in strict mode).
175 175 assert not strict
176 176
177 177 spec, params = parseparams(spec)
178 178
179 179 if spec in util.compengines.supportedbundlenames:
180 180 compression = spec
181 181 version = 'v1'
182 182 # Generaldelta repos require v2.
183 183 if 'generaldelta' in repo.requirements:
184 184 version = 'v2'
185 185 # Modern compression engines require v2.
186 186 if compression not in _bundlespecv1compengines:
187 187 version = 'v2'
188 188 elif spec in _bundlespeccgversions:
189 189 if spec == 'packed1':
190 190 compression = 'none'
191 191 else:
192 192 compression = 'bzip2'
193 193 version = spec
194 194 else:
195 195 raise error.UnsupportedBundleSpecification(
196 196 _('%s is not a recognized bundle specification') % spec)
197 197
198 198 # Bundle version 1 only supports a known set of compression engines.
199 199 if version == 'v1' and compression not in _bundlespecv1compengines:
200 200 raise error.UnsupportedBundleSpecification(
201 201 _('compression engine %s is not supported on v1 bundles') %
202 202 compression)
203 203
204 204 # The specification for packed1 can optionally declare the data formats
205 205 # required to apply it. If we see this metadata, compare against what the
206 206 # repo supports and error if the bundle isn't compatible.
207 207 if version == 'packed1' and 'requirements' in params:
208 208 requirements = set(params['requirements'].split(','))
209 209 missingreqs = requirements - repo.supportedformats
210 210 if missingreqs:
211 211 raise error.UnsupportedBundleSpecification(
212 212 _('missing support for repository features: %s') %
213 213 ', '.join(sorted(missingreqs)))
214 214
215 215 # Compute contentopts based on the version
216 216 contentopts = _bundlespeccontentopts.get(version, {}).copy()
217 217
218 218 # Process the variants
219 219 if "stream" in params and params["stream"] == "v2":
220 220 variant = _bundlespecvariants["streamv2"]
221 221 contentopts.update(variant)
222 222
223 223 engine = util.compengines.forbundlename(compression)
224 224 compression, wirecompression = engine.bundletype()
225 225 wireversion = _bundlespeccgversions[version]
226 226
227 227 return bundlespec(compression, wirecompression, version, wireversion,
228 228 params, contentopts)
229 229
230 230 def readbundle(ui, fh, fname, vfs=None):
231 231 header = changegroup.readexactly(fh, 4)
232 232
233 233 alg = None
234 234 if not fname:
235 235 fname = "stream"
236 236 if not header.startswith('HG') and header.startswith('\0'):
237 237 fh = changegroup.headerlessfixup(fh, header)
238 238 header = "HG10"
239 239 alg = 'UN'
240 240 elif vfs:
241 241 fname = vfs.join(fname)
242 242
243 243 magic, version = header[0:2], header[2:4]
244 244
245 245 if magic != 'HG':
246 246 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
247 247 if version == '10':
248 248 if alg is None:
249 249 alg = changegroup.readexactly(fh, 2)
250 250 return changegroup.cg1unpacker(fh, alg)
251 251 elif version.startswith('2'):
252 252 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
253 253 elif version == 'S1':
254 254 return streamclone.streamcloneapplier(fh)
255 255 else:
256 256 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
257 257
258 258 def getbundlespec(ui, fh):
259 259 """Infer the bundlespec from a bundle file handle.
260 260
261 261 The input file handle is seeked and the original seek position is not
262 262 restored.
263 263 """
264 264 def speccompression(alg):
265 265 try:
266 266 return util.compengines.forbundletype(alg).bundletype()[0]
267 267 except KeyError:
268 268 return None
269 269
270 270 b = readbundle(ui, fh, None)
271 271 if isinstance(b, changegroup.cg1unpacker):
272 272 alg = b._type
273 273 if alg == '_truncatedBZ':
274 274 alg = 'BZ'
275 275 comp = speccompression(alg)
276 276 if not comp:
277 277 raise error.Abort(_('unknown compression algorithm: %s') % alg)
278 278 return '%s-v1' % comp
279 279 elif isinstance(b, bundle2.unbundle20):
280 280 if 'Compression' in b.params:
281 281 comp = speccompression(b.params['Compression'])
282 282 if not comp:
283 283 raise error.Abort(_('unknown compression algorithm: %s') % comp)
284 284 else:
285 285 comp = 'none'
286 286
287 287 version = None
288 288 for part in b.iterparts():
289 289 if part.type == 'changegroup':
290 290 version = part.params['version']
291 291 if version in ('01', '02'):
292 292 version = 'v2'
293 293 else:
294 294 raise error.Abort(_('changegroup version %s does not have '
295 295 'a known bundlespec') % version,
296 296 hint=_('try upgrading your Mercurial '
297 297 'client'))
298 298 elif part.type == 'stream2' and version is None:
299 299 # A stream2 part requires to be part of a v2 bundle
300 300 version = "v2"
301 301 requirements = urlreq.unquote(part.params['requirements'])
302 302 splitted = requirements.split()
303 303 params = bundle2._formatrequirementsparams(splitted)
304 304 return 'none-v2;stream=v2;%s' % params
305 305
306 306 if not version:
307 307 raise error.Abort(_('could not identify changegroup version in '
308 308 'bundle'))
309 309
310 310 return '%s-%s' % (comp, version)
311 311 elif isinstance(b, streamclone.streamcloneapplier):
312 312 requirements = streamclone.readbundle1header(fh)[2]
313 313 formatted = bundle2._formatrequirementsparams(requirements)
314 314 return 'none-packed1;%s' % formatted
315 315 else:
316 316 raise error.Abort(_('unknown bundle type: %s') % b)
317 317
318 318 def _computeoutgoing(repo, heads, common):
319 319 """Computes which revs are outgoing given a set of common
320 320 and a set of heads.
321 321
322 322 This is a separate function so extensions can have access to
323 323 the logic.
324 324
325 325 Returns a discovery.outgoing object.
326 326 """
327 327 cl = repo.changelog
328 328 if common:
329 329 hasnode = cl.hasnode
330 330 common = [n for n in common if hasnode(n)]
331 331 else:
332 332 common = [nullid]
333 333 if not heads:
334 334 heads = cl.heads()
335 335 return discovery.outgoing(repo, common, heads)
336 336
337 337 def _forcebundle1(op):
338 338 """return true if a pull/push must use bundle1
339 339
340 340 This function is used to allow testing of the older bundle version"""
341 341 ui = op.repo.ui
342 342 # The goal is this config is to allow developer to choose the bundle
343 343 # version used during exchanged. This is especially handy during test.
344 344 # Value is a list of bundle version to be picked from, highest version
345 345 # should be used.
346 346 #
347 347 # developer config: devel.legacy.exchange
348 348 exchange = ui.configlist('devel', 'legacy.exchange')
349 349 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
350 350 return forcebundle1 or not op.remote.capable('bundle2')
351 351
352 352 class pushoperation(object):
353 353 """A object that represent a single push operation
354 354
355 355 Its purpose is to carry push related state and very common operations.
356 356
357 357 A new pushoperation should be created at the beginning of each push and
358 358 discarded afterward.
359 359 """
360 360
361 361 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
362 bookmarks=(), pushvars=None):
362 bookmarks=(), publish=False, pushvars=None):
363 363 # repo we push from
364 364 self.repo = repo
365 365 self.ui = repo.ui
366 366 # repo we push to
367 367 self.remote = remote
368 368 # force option provided
369 369 self.force = force
370 370 # revs to be pushed (None is "all")
371 371 self.revs = revs
372 372 # bookmark explicitly pushed
373 373 self.bookmarks = bookmarks
374 374 # allow push of new branch
375 375 self.newbranch = newbranch
376 376 # step already performed
377 377 # (used to check what steps have been already performed through bundle2)
378 378 self.stepsdone = set()
379 379 # Integer version of the changegroup push result
380 380 # - None means nothing to push
381 381 # - 0 means HTTP error
382 382 # - 1 means we pushed and remote head count is unchanged *or*
383 383 # we have outgoing changesets but refused to push
384 384 # - other values as described by addchangegroup()
385 385 self.cgresult = None
386 386 # Boolean value for the bookmark push
387 387 self.bkresult = None
388 388 # discover.outgoing object (contains common and outgoing data)
389 389 self.outgoing = None
390 390 # all remote topological heads before the push
391 391 self.remoteheads = None
392 392 # Details of the remote branch pre and post push
393 393 #
394 394 # mapping: {'branch': ([remoteheads],
395 395 # [newheads],
396 396 # [unsyncedheads],
397 397 # [discardedheads])}
398 398 # - branch: the branch name
399 399 # - remoteheads: the list of remote heads known locally
400 400 # None if the branch is new
401 401 # - newheads: the new remote heads (known locally) with outgoing pushed
402 402 # - unsyncedheads: the list of remote heads unknown locally.
403 403 # - discardedheads: the list of remote heads made obsolete by the push
404 404 self.pushbranchmap = None
405 405 # testable as a boolean indicating if any nodes are missing locally.
406 406 self.incoming = None
407 407 # summary of the remote phase situation
408 408 self.remotephases = None
409 409 # phases changes that must be pushed along side the changesets
410 410 self.outdatedphases = None
411 411 # phases changes that must be pushed if changeset push fails
412 412 self.fallbackoutdatedphases = None
413 413 # outgoing obsmarkers
414 414 self.outobsmarkers = set()
415 415 # outgoing bookmarks
416 416 self.outbookmarks = []
417 417 # transaction manager
418 418 self.trmanager = None
419 419 # map { pushkey partid -> callback handling failure}
420 420 # used to handle exception from mandatory pushkey part failure
421 421 self.pkfailcb = {}
422 422 # an iterable of pushvars or None
423 423 self.pushvars = pushvars
424 # publish pushed changesets
425 self.publish = publish
424 426
425 427 @util.propertycache
426 428 def futureheads(self):
427 429 """future remote heads if the changeset push succeeds"""
428 430 return self.outgoing.missingheads
429 431
430 432 @util.propertycache
431 433 def fallbackheads(self):
432 434 """future remote heads if the changeset push fails"""
433 435 if self.revs is None:
434 436 # not target to push, all common are relevant
435 437 return self.outgoing.commonheads
436 438 unfi = self.repo.unfiltered()
437 439 # I want cheads = heads(::missingheads and ::commonheads)
438 440 # (missingheads is revs with secret changeset filtered out)
439 441 #
440 442 # This can be expressed as:
441 443 # cheads = ( (missingheads and ::commonheads)
442 444 # + (commonheads and ::missingheads))"
443 445 # )
444 446 #
445 447 # while trying to push we already computed the following:
446 448 # common = (::commonheads)
447 449 # missing = ((commonheads::missingheads) - commonheads)
448 450 #
449 451 # We can pick:
450 452 # * missingheads part of common (::commonheads)
451 453 common = self.outgoing.common
452 454 nm = self.repo.changelog.nodemap
453 455 cheads = [node for node in self.revs if nm[node] in common]
454 456 # and
455 457 # * commonheads parents on missing
456 458 revset = unfi.set('%ln and parents(roots(%ln))',
457 459 self.outgoing.commonheads,
458 460 self.outgoing.missing)
459 461 cheads.extend(c.node() for c in revset)
460 462 return cheads
461 463
462 464 @property
463 465 def commonheads(self):
464 466 """set of all common heads after changeset bundle push"""
465 467 if self.cgresult:
466 468 return self.futureheads
467 469 else:
468 470 return self.fallbackheads
469 471
470 472 # mapping of message used when pushing bookmark
471 473 bookmsgmap = {'update': (_("updating bookmark %s\n"),
472 474 _('updating bookmark %s failed!\n')),
473 475 'export': (_("exporting bookmark %s\n"),
474 476 _('exporting bookmark %s failed!\n')),
475 477 'delete': (_("deleting remote bookmark %s\n"),
476 478 _('deleting remote bookmark %s failed!\n')),
477 479 }
478 480
479 481
480 482 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
481 opargs=None):
483 publish=False, opargs=None):
482 484 '''Push outgoing changesets (limited by revs) from a local
483 485 repository to remote. Return an integer:
484 486 - None means nothing to push
485 487 - 0 means HTTP error
486 488 - 1 means we pushed and remote head count is unchanged *or*
487 489 we have outgoing changesets but refused to push
488 490 - other values as described by addchangegroup()
489 491 '''
490 492 if opargs is None:
491 493 opargs = {}
492 494 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
493 **pycompat.strkwargs(opargs))
495 publish, **pycompat.strkwargs(opargs))
494 496 if pushop.remote.local():
495 497 missing = (set(pushop.repo.requirements)
496 498 - pushop.remote.local().supported)
497 499 if missing:
498 500 msg = _("required features are not"
499 501 " supported in the destination:"
500 502 " %s") % (', '.join(sorted(missing)))
501 503 raise error.Abort(msg)
502 504
503 505 if not pushop.remote.canpush():
504 506 raise error.Abort(_("destination does not support push"))
505 507
506 508 if not pushop.remote.capable('unbundle'):
507 509 raise error.Abort(_('cannot push: destination does not support the '
508 510 'unbundle wire protocol command'))
509 511
510 512 # get lock as we might write phase data
511 513 wlock = lock = None
512 514 try:
513 515 # bundle2 push may receive a reply bundle touching bookmarks or other
514 516 # things requiring the wlock. Take it now to ensure proper ordering.
515 517 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
516 518 if (not _forcebundle1(pushop)) and maypushback:
517 519 wlock = pushop.repo.wlock()
518 520 lock = pushop.repo.lock()
519 521 pushop.trmanager = transactionmanager(pushop.repo,
520 522 'push-response',
521 523 pushop.remote.url())
522 524 except error.LockUnavailable as err:
523 525 # source repo cannot be locked.
524 526 # We do not abort the push, but just disable the local phase
525 527 # synchronisation.
526 528 msg = ('cannot lock source repository: %s\n'
527 529 % stringutil.forcebytestr(err))
528 530 pushop.ui.debug(msg)
529 531
530 532 with wlock or util.nullcontextmanager(), \
531 533 lock or util.nullcontextmanager(), \
532 534 pushop.trmanager or util.nullcontextmanager():
533 535 pushop.repo.checkpush(pushop)
534 536 _pushdiscovery(pushop)
535 537 if not _forcebundle1(pushop):
536 538 _pushbundle2(pushop)
537 539 _pushchangeset(pushop)
538 540 _pushsyncphase(pushop)
539 541 _pushobsolete(pushop)
540 542 _pushbookmark(pushop)
541 543
542 544 if repo.ui.configbool('experimental', 'remotenames'):
543 545 logexchange.pullremotenames(repo, remote)
544 546
545 547 return pushop
546 548
547 549 # list of steps to perform discovery before push
548 550 pushdiscoveryorder = []
549 551
550 552 # Mapping between step name and function
551 553 #
552 554 # This exists to help extensions wrap steps if necessary
553 555 pushdiscoverymapping = {}
554 556
555 557 def pushdiscovery(stepname):
556 558 """decorator for function performing discovery before push
557 559
558 560 The function is added to the step -> function mapping and appended to the
559 561 list of steps. Beware that decorated function will be added in order (this
560 562 may matter).
561 563
562 564 You can only use this decorator for a new step, if you want to wrap a step
563 565 from an extension, change the pushdiscovery dictionary directly."""
564 566 def dec(func):
565 567 assert stepname not in pushdiscoverymapping
566 568 pushdiscoverymapping[stepname] = func
567 569 pushdiscoveryorder.append(stepname)
568 570 return func
569 571 return dec
570 572
571 573 def _pushdiscovery(pushop):
572 574 """Run all discovery steps"""
573 575 for stepname in pushdiscoveryorder:
574 576 step = pushdiscoverymapping[stepname]
575 577 step(pushop)
576 578
577 579 @pushdiscovery('changeset')
578 580 def _pushdiscoverychangeset(pushop):
579 581 """discover the changeset that need to be pushed"""
580 582 fci = discovery.findcommonincoming
581 583 if pushop.revs:
582 584 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,
583 585 ancestorsof=pushop.revs)
584 586 else:
585 587 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
586 588 common, inc, remoteheads = commoninc
587 589 fco = discovery.findcommonoutgoing
588 590 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
589 591 commoninc=commoninc, force=pushop.force)
590 592 pushop.outgoing = outgoing
591 593 pushop.remoteheads = remoteheads
592 594 pushop.incoming = inc
593 595
594 596 @pushdiscovery('phase')
595 597 def _pushdiscoveryphase(pushop):
596 598 """discover the phase that needs to be pushed
597 599
598 600 (computed for both success and failure case for changesets push)"""
599 601 outgoing = pushop.outgoing
600 602 unfi = pushop.repo.unfiltered()
601 603 remotephases = listkeys(pushop.remote, 'phases')
602 604
603 605 if (pushop.ui.configbool('ui', '_usedassubrepo')
604 606 and remotephases # server supports phases
605 607 and not pushop.outgoing.missing # no changesets to be pushed
606 608 and remotephases.get('publishing', False)):
607 609 # When:
608 610 # - this is a subrepo push
609 611 # - and remote support phase
610 612 # - and no changeset are to be pushed
611 613 # - and remote is publishing
612 614 # We may be in issue 3781 case!
613 615 # We drop the possible phase synchronisation done by
614 616 # courtesy to publish changesets possibly locally draft
615 617 # on the remote.
616 618 pushop.outdatedphases = []
617 619 pushop.fallbackoutdatedphases = []
618 620 return
619 621
620 622 pushop.remotephases = phases.remotephasessummary(pushop.repo,
621 623 pushop.fallbackheads,
622 624 remotephases)
623 625 droots = pushop.remotephases.draftroots
624 626
625 627 extracond = ''
626 628 if not pushop.remotephases.publishing:
627 629 extracond = ' and public()'
628 630 revset = 'heads((%%ln::%%ln) %s)' % extracond
629 631 # Get the list of all revs draft on remote by public here.
630 632 # XXX Beware that revset break if droots is not strictly
631 633 # XXX root we may want to ensure it is but it is costly
632 634 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
633 if not outgoing.missing:
635 if not pushop.remotephases.publishing and pushop.publish:
636 future = list(unfi.set('%ln and (not public() or %ln::)',
637 pushop.futureheads, droots))
638 elif not outgoing.missing:
634 639 future = fallback
635 640 else:
636 641 # adds changeset we are going to push as draft
637 642 #
638 643 # should not be necessary for publishing server, but because of an
639 644 # issue fixed in xxxxx we have to do it anyway.
640 645 fdroots = list(unfi.set('roots(%ln + %ln::)',
641 646 outgoing.missing, droots))
642 647 fdroots = [f.node() for f in fdroots]
643 648 future = list(unfi.set(revset, fdroots, pushop.futureheads))
644 649 pushop.outdatedphases = future
645 650 pushop.fallbackoutdatedphases = fallback
646 651
647 652 @pushdiscovery('obsmarker')
648 653 def _pushdiscoveryobsmarkers(pushop):
649 654 if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):
650 655 return
651 656
652 657 if not pushop.repo.obsstore:
653 658 return
654 659
655 660 if 'obsolete' not in listkeys(pushop.remote, 'namespaces'):
656 661 return
657 662
658 663 repo = pushop.repo
659 664 # very naive computation, that can be quite expensive on big repo.
660 665 # However: evolution is currently slow on them anyway.
661 666 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
662 667 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
663 668
664 669 @pushdiscovery('bookmarks')
665 670 def _pushdiscoverybookmarks(pushop):
666 671 ui = pushop.ui
667 672 repo = pushop.repo.unfiltered()
668 673 remote = pushop.remote
669 674 ui.debug("checking for updated bookmarks\n")
670 675 ancestors = ()
671 676 if pushop.revs:
672 677 revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)
673 678 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
674 679
675 680 remotebookmark = listkeys(remote, 'bookmarks')
676 681
677 682 explicit = set([repo._bookmarks.expandname(bookmark)
678 683 for bookmark in pushop.bookmarks])
679 684
680 685 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
681 686 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
682 687
683 688 def safehex(x):
684 689 if x is None:
685 690 return x
686 691 return hex(x)
687 692
688 693 def hexifycompbookmarks(bookmarks):
689 694 return [(b, safehex(scid), safehex(dcid))
690 695 for (b, scid, dcid) in bookmarks]
691 696
692 697 comp = [hexifycompbookmarks(marks) for marks in comp]
693 698 return _processcompared(pushop, ancestors, explicit, remotebookmark, comp)
694 699
695 700 def _processcompared(pushop, pushed, explicit, remotebms, comp):
696 701 """take decision on bookmark to pull from the remote bookmark
697 702
698 703 Exist to help extensions who want to alter this behavior.
699 704 """
700 705 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
701 706
702 707 repo = pushop.repo
703 708
704 709 for b, scid, dcid in advsrc:
705 710 if b in explicit:
706 711 explicit.remove(b)
707 712 if not pushed or repo[scid].rev() in pushed:
708 713 pushop.outbookmarks.append((b, dcid, scid))
709 714 # search added bookmark
710 715 for b, scid, dcid in addsrc:
711 716 if b in explicit:
712 717 explicit.remove(b)
713 718 pushop.outbookmarks.append((b, '', scid))
714 719 # search for overwritten bookmark
715 720 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
716 721 if b in explicit:
717 722 explicit.remove(b)
718 723 pushop.outbookmarks.append((b, dcid, scid))
719 724 # search for bookmark to delete
720 725 for b, scid, dcid in adddst:
721 726 if b in explicit:
722 727 explicit.remove(b)
723 728 # treat as "deleted locally"
724 729 pushop.outbookmarks.append((b, dcid, ''))
725 730 # identical bookmarks shouldn't get reported
726 731 for b, scid, dcid in same:
727 732 if b in explicit:
728 733 explicit.remove(b)
729 734
730 735 if explicit:
731 736 explicit = sorted(explicit)
732 737 # we should probably list all of them
733 738 pushop.ui.warn(_('bookmark %s does not exist on the local '
734 739 'or remote repository!\n') % explicit[0])
735 740 pushop.bkresult = 2
736 741
737 742 pushop.outbookmarks.sort()
738 743
739 744 def _pushcheckoutgoing(pushop):
740 745 outgoing = pushop.outgoing
741 746 unfi = pushop.repo.unfiltered()
742 747 if not outgoing.missing:
743 748 # nothing to push
744 749 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
745 750 return False
746 751 # something to push
747 752 if not pushop.force:
748 753 # if repo.obsstore == False --> no obsolete
749 754 # then, save the iteration
750 755 if unfi.obsstore:
751 756 # this message are here for 80 char limit reason
752 757 mso = _("push includes obsolete changeset: %s!")
753 758 mspd = _("push includes phase-divergent changeset: %s!")
754 759 mscd = _("push includes content-divergent changeset: %s!")
755 760 mst = {"orphan": _("push includes orphan changeset: %s!"),
756 761 "phase-divergent": mspd,
757 762 "content-divergent": mscd}
758 763 # If we are to push if there is at least one
759 764 # obsolete or unstable changeset in missing, at
760 765 # least one of the missinghead will be obsolete or
761 766 # unstable. So checking heads only is ok
762 767 for node in outgoing.missingheads:
763 768 ctx = unfi[node]
764 769 if ctx.obsolete():
765 770 raise error.Abort(mso % ctx)
766 771 elif ctx.isunstable():
767 772 # TODO print more than one instability in the abort
768 773 # message
769 774 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
770 775
771 776 discovery.checkheads(pushop)
772 777 return True
773 778
774 779 # List of names of steps to perform for an outgoing bundle2, order matters.
775 780 b2partsgenorder = []
776 781
777 782 # Mapping between step name and function
778 783 #
779 784 # This exists to help extensions wrap steps if necessary
780 785 b2partsgenmapping = {}
781 786
782 787 def b2partsgenerator(stepname, idx=None):
783 788 """decorator for function generating bundle2 part
784 789
785 790 The function is added to the step -> function mapping and appended to the
786 791 list of steps. Beware that decorated functions will be added in order
787 792 (this may matter).
788 793
789 794 You can only use this decorator for new steps, if you want to wrap a step
790 795 from an extension, attack the b2partsgenmapping dictionary directly."""
791 796 def dec(func):
792 797 assert stepname not in b2partsgenmapping
793 798 b2partsgenmapping[stepname] = func
794 799 if idx is None:
795 800 b2partsgenorder.append(stepname)
796 801 else:
797 802 b2partsgenorder.insert(idx, stepname)
798 803 return func
799 804 return dec
800 805
801 806 def _pushb2ctxcheckheads(pushop, bundler):
802 807 """Generate race condition checking parts
803 808
804 809 Exists as an independent function to aid extensions
805 810 """
806 811 # * 'force' do not check for push race,
807 812 # * if we don't push anything, there are nothing to check.
808 813 if not pushop.force and pushop.outgoing.missingheads:
809 814 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
810 815 emptyremote = pushop.pushbranchmap is None
811 816 if not allowunrelated or emptyremote:
812 817 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
813 818 else:
814 819 affected = set()
815 820 for branch, heads in pushop.pushbranchmap.iteritems():
816 821 remoteheads, newheads, unsyncedheads, discardedheads = heads
817 822 if remoteheads is not None:
818 823 remote = set(remoteheads)
819 824 affected |= set(discardedheads) & remote
820 825 affected |= remote - set(newheads)
821 826 if affected:
822 827 data = iter(sorted(affected))
823 828 bundler.newpart('check:updated-heads', data=data)
824 829
825 830 def _pushing(pushop):
826 831 """return True if we are pushing anything"""
827 832 return bool(pushop.outgoing.missing
828 833 or pushop.outdatedphases
829 834 or pushop.outobsmarkers
830 835 or pushop.outbookmarks)
831 836
832 837 @b2partsgenerator('check-bookmarks')
833 838 def _pushb2checkbookmarks(pushop, bundler):
834 839 """insert bookmark move checking"""
835 840 if not _pushing(pushop) or pushop.force:
836 841 return
837 842 b2caps = bundle2.bundle2caps(pushop.remote)
838 843 hasbookmarkcheck = 'bookmarks' in b2caps
839 844 if not (pushop.outbookmarks and hasbookmarkcheck):
840 845 return
841 846 data = []
842 847 for book, old, new in pushop.outbookmarks:
843 848 old = bin(old)
844 849 data.append((book, old))
845 850 checkdata = bookmod.binaryencode(data)
846 851 bundler.newpart('check:bookmarks', data=checkdata)
847 852
848 853 @b2partsgenerator('check-phases')
849 854 def _pushb2checkphases(pushop, bundler):
850 855 """insert phase move checking"""
851 856 if not _pushing(pushop) or pushop.force:
852 857 return
853 858 b2caps = bundle2.bundle2caps(pushop.remote)
854 859 hasphaseheads = 'heads' in b2caps.get('phases', ())
855 860 if pushop.remotephases is not None and hasphaseheads:
856 861 # check that the remote phase has not changed
857 862 checks = [[] for p in phases.allphases]
858 863 checks[phases.public].extend(pushop.remotephases.publicheads)
859 864 checks[phases.draft].extend(pushop.remotephases.draftroots)
860 865 if any(checks):
861 866 for nodes in checks:
862 867 nodes.sort()
863 868 checkdata = phases.binaryencode(checks)
864 869 bundler.newpart('check:phases', data=checkdata)
865 870
866 871 @b2partsgenerator('changeset')
867 872 def _pushb2ctx(pushop, bundler):
868 873 """handle changegroup push through bundle2
869 874
870 875 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
871 876 """
872 877 if 'changesets' in pushop.stepsdone:
873 878 return
874 879 pushop.stepsdone.add('changesets')
875 880 # Send known heads to the server for race detection.
876 881 if not _pushcheckoutgoing(pushop):
877 882 return
878 883 pushop.repo.prepushoutgoinghooks(pushop)
879 884
880 885 _pushb2ctxcheckheads(pushop, bundler)
881 886
882 887 b2caps = bundle2.bundle2caps(pushop.remote)
883 888 version = '01'
884 889 cgversions = b2caps.get('changegroup')
885 890 if cgversions: # 3.1 and 3.2 ship with an empty value
886 891 cgversions = [v for v in cgversions
887 892 if v in changegroup.supportedoutgoingversions(
888 893 pushop.repo)]
889 894 if not cgversions:
890 895 raise ValueError(_('no common changegroup version'))
891 896 version = max(cgversions)
892 897 cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
893 898 'push')
894 899 cgpart = bundler.newpart('changegroup', data=cgstream)
895 900 if cgversions:
896 901 cgpart.addparam('version', version)
897 902 if 'treemanifest' in pushop.repo.requirements:
898 903 cgpart.addparam('treemanifest', '1')
899 904 def handlereply(op):
900 905 """extract addchangegroup returns from server reply"""
901 906 cgreplies = op.records.getreplies(cgpart.id)
902 907 assert len(cgreplies['changegroup']) == 1
903 908 pushop.cgresult = cgreplies['changegroup'][0]['return']
904 909 return handlereply
905 910
906 911 @b2partsgenerator('phase')
907 912 def _pushb2phases(pushop, bundler):
908 913 """handle phase push through bundle2"""
909 914 if 'phases' in pushop.stepsdone:
910 915 return
911 916 b2caps = bundle2.bundle2caps(pushop.remote)
912 917 ui = pushop.repo.ui
913 918
914 919 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
915 920 haspushkey = 'pushkey' in b2caps
916 921 hasphaseheads = 'heads' in b2caps.get('phases', ())
917 922
918 923 if hasphaseheads and not legacyphase:
919 924 return _pushb2phaseheads(pushop, bundler)
920 925 elif haspushkey:
921 926 return _pushb2phasespushkey(pushop, bundler)
922 927
923 928 def _pushb2phaseheads(pushop, bundler):
924 929 """push phase information through a bundle2 - binary part"""
925 930 pushop.stepsdone.add('phases')
926 931 if pushop.outdatedphases:
927 932 updates = [[] for p in phases.allphases]
928 933 updates[0].extend(h.node() for h in pushop.outdatedphases)
929 934 phasedata = phases.binaryencode(updates)
930 935 bundler.newpart('phase-heads', data=phasedata)
931 936
932 937 def _pushb2phasespushkey(pushop, bundler):
933 938 """push phase information through a bundle2 - pushkey part"""
934 939 pushop.stepsdone.add('phases')
935 940 part2node = []
936 941
937 942 def handlefailure(pushop, exc):
938 943 targetid = int(exc.partid)
939 944 for partid, node in part2node:
940 945 if partid == targetid:
941 946 raise error.Abort(_('updating %s to public failed') % node)
942 947
943 948 enc = pushkey.encode
944 949 for newremotehead in pushop.outdatedphases:
945 950 part = bundler.newpart('pushkey')
946 951 part.addparam('namespace', enc('phases'))
947 952 part.addparam('key', enc(newremotehead.hex()))
948 953 part.addparam('old', enc('%d' % phases.draft))
949 954 part.addparam('new', enc('%d' % phases.public))
950 955 part2node.append((part.id, newremotehead))
951 956 pushop.pkfailcb[part.id] = handlefailure
952 957
953 958 def handlereply(op):
954 959 for partid, node in part2node:
955 960 partrep = op.records.getreplies(partid)
956 961 results = partrep['pushkey']
957 962 assert len(results) <= 1
958 963 msg = None
959 964 if not results:
960 965 msg = _('server ignored update of %s to public!\n') % node
961 966 elif not int(results[0]['return']):
962 967 msg = _('updating %s to public failed!\n') % node
963 968 if msg is not None:
964 969 pushop.ui.warn(msg)
965 970 return handlereply
966 971
967 972 @b2partsgenerator('obsmarkers')
968 973 def _pushb2obsmarkers(pushop, bundler):
969 974 if 'obsmarkers' in pushop.stepsdone:
970 975 return
971 976 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
972 977 if obsolete.commonversion(remoteversions) is None:
973 978 return
974 979 pushop.stepsdone.add('obsmarkers')
975 980 if pushop.outobsmarkers:
976 981 markers = sorted(pushop.outobsmarkers)
977 982 bundle2.buildobsmarkerspart(bundler, markers)
978 983
979 984 @b2partsgenerator('bookmarks')
980 985 def _pushb2bookmarks(pushop, bundler):
981 986 """handle bookmark push through bundle2"""
982 987 if 'bookmarks' in pushop.stepsdone:
983 988 return
984 989 b2caps = bundle2.bundle2caps(pushop.remote)
985 990
986 991 legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')
987 992 legacybooks = 'bookmarks' in legacy
988 993
989 994 if not legacybooks and 'bookmarks' in b2caps:
990 995 return _pushb2bookmarkspart(pushop, bundler)
991 996 elif 'pushkey' in b2caps:
992 997 return _pushb2bookmarkspushkey(pushop, bundler)
993 998
994 999 def _bmaction(old, new):
995 1000 """small utility for bookmark pushing"""
996 1001 if not old:
997 1002 return 'export'
998 1003 elif not new:
999 1004 return 'delete'
1000 1005 return 'update'
1001 1006
1002 1007 def _pushb2bookmarkspart(pushop, bundler):
1003 1008 pushop.stepsdone.add('bookmarks')
1004 1009 if not pushop.outbookmarks:
1005 1010 return
1006 1011
1007 1012 allactions = []
1008 1013 data = []
1009 1014 for book, old, new in pushop.outbookmarks:
1010 1015 new = bin(new)
1011 1016 data.append((book, new))
1012 1017 allactions.append((book, _bmaction(old, new)))
1013 1018 checkdata = bookmod.binaryencode(data)
1014 1019 bundler.newpart('bookmarks', data=checkdata)
1015 1020
1016 1021 def handlereply(op):
1017 1022 ui = pushop.ui
1018 1023 # if success
1019 1024 for book, action in allactions:
1020 1025 ui.status(bookmsgmap[action][0] % book)
1021 1026
1022 1027 return handlereply
1023 1028
1024 1029 def _pushb2bookmarkspushkey(pushop, bundler):
1025 1030 pushop.stepsdone.add('bookmarks')
1026 1031 part2book = []
1027 1032 enc = pushkey.encode
1028 1033
1029 1034 def handlefailure(pushop, exc):
1030 1035 targetid = int(exc.partid)
1031 1036 for partid, book, action in part2book:
1032 1037 if partid == targetid:
1033 1038 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
1034 1039 # we should not be called for part we did not generated
1035 1040 assert False
1036 1041
1037 1042 for book, old, new in pushop.outbookmarks:
1038 1043 part = bundler.newpart('pushkey')
1039 1044 part.addparam('namespace', enc('bookmarks'))
1040 1045 part.addparam('key', enc(book))
1041 1046 part.addparam('old', enc(old))
1042 1047 part.addparam('new', enc(new))
1043 1048 action = 'update'
1044 1049 if not old:
1045 1050 action = 'export'
1046 1051 elif not new:
1047 1052 action = 'delete'
1048 1053 part2book.append((part.id, book, action))
1049 1054 pushop.pkfailcb[part.id] = handlefailure
1050 1055
1051 1056 def handlereply(op):
1052 1057 ui = pushop.ui
1053 1058 for partid, book, action in part2book:
1054 1059 partrep = op.records.getreplies(partid)
1055 1060 results = partrep['pushkey']
1056 1061 assert len(results) <= 1
1057 1062 if not results:
1058 1063 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
1059 1064 else:
1060 1065 ret = int(results[0]['return'])
1061 1066 if ret:
1062 1067 ui.status(bookmsgmap[action][0] % book)
1063 1068 else:
1064 1069 ui.warn(bookmsgmap[action][1] % book)
1065 1070 if pushop.bkresult is not None:
1066 1071 pushop.bkresult = 1
1067 1072 return handlereply
1068 1073
1069 1074 @b2partsgenerator('pushvars', idx=0)
1070 1075 def _getbundlesendvars(pushop, bundler):
1071 1076 '''send shellvars via bundle2'''
1072 1077 pushvars = pushop.pushvars
1073 1078 if pushvars:
1074 1079 shellvars = {}
1075 1080 for raw in pushvars:
1076 1081 if '=' not in raw:
1077 1082 msg = ("unable to parse variable '%s', should follow "
1078 1083 "'KEY=VALUE' or 'KEY=' format")
1079 1084 raise error.Abort(msg % raw)
1080 1085 k, v = raw.split('=', 1)
1081 1086 shellvars[k] = v
1082 1087
1083 1088 part = bundler.newpart('pushvars')
1084 1089
1085 1090 for key, value in shellvars.iteritems():
1086 1091 part.addparam(key, value, mandatory=False)
1087 1092
1088 1093 def _pushbundle2(pushop):
1089 1094 """push data to the remote using bundle2
1090 1095
1091 1096 The only currently supported type of data is changegroup but this will
1092 1097 evolve in the future."""
1093 1098 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1094 1099 pushback = (pushop.trmanager
1095 1100 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
1096 1101
1097 1102 # create reply capability
1098 1103 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
1099 1104 allowpushback=pushback,
1100 1105 role='client'))
1101 1106 bundler.newpart('replycaps', data=capsblob)
1102 1107 replyhandlers = []
1103 1108 for partgenname in b2partsgenorder:
1104 1109 partgen = b2partsgenmapping[partgenname]
1105 1110 ret = partgen(pushop, bundler)
1106 1111 if callable(ret):
1107 1112 replyhandlers.append(ret)
1108 1113 # do not push if nothing to push
1109 1114 if bundler.nbparts <= 1:
1110 1115 return
1111 1116 stream = util.chunkbuffer(bundler.getchunks())
1112 1117 try:
1113 1118 try:
1114 1119 with pushop.remote.commandexecutor() as e:
1115 1120 reply = e.callcommand('unbundle', {
1116 1121 'bundle': stream,
1117 1122 'heads': ['force'],
1118 1123 'url': pushop.remote.url(),
1119 1124 }).result()
1120 1125 except error.BundleValueError as exc:
1121 1126 raise error.Abort(_('missing support for %s') % exc)
1122 1127 try:
1123 1128 trgetter = None
1124 1129 if pushback:
1125 1130 trgetter = pushop.trmanager.transaction
1126 1131 op = bundle2.processbundle(pushop.repo, reply, trgetter)
1127 1132 except error.BundleValueError as exc:
1128 1133 raise error.Abort(_('missing support for %s') % exc)
1129 1134 except bundle2.AbortFromPart as exc:
1130 1135 pushop.ui.status(_('remote: %s\n') % exc)
1131 1136 if exc.hint is not None:
1132 1137 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
1133 1138 raise error.Abort(_('push failed on remote'))
1134 1139 except error.PushkeyFailed as exc:
1135 1140 partid = int(exc.partid)
1136 1141 if partid not in pushop.pkfailcb:
1137 1142 raise
1138 1143 pushop.pkfailcb[partid](pushop, exc)
1139 1144 for rephand in replyhandlers:
1140 1145 rephand(op)
1141 1146
1142 1147 def _pushchangeset(pushop):
1143 1148 """Make the actual push of changeset bundle to remote repo"""
1144 1149 if 'changesets' in pushop.stepsdone:
1145 1150 return
1146 1151 pushop.stepsdone.add('changesets')
1147 1152 if not _pushcheckoutgoing(pushop):
1148 1153 return
1149 1154
1150 1155 # Should have verified this in push().
1151 1156 assert pushop.remote.capable('unbundle')
1152 1157
1153 1158 pushop.repo.prepushoutgoinghooks(pushop)
1154 1159 outgoing = pushop.outgoing
1155 1160 # TODO: get bundlecaps from remote
1156 1161 bundlecaps = None
1157 1162 # create a changegroup from local
1158 1163 if pushop.revs is None and not (outgoing.excluded
1159 1164 or pushop.repo.changelog.filteredrevs):
1160 1165 # push everything,
1161 1166 # use the fast path, no race possible on push
1162 1167 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
1163 1168 fastpath=True, bundlecaps=bundlecaps)
1164 1169 else:
1165 1170 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
1166 1171 'push', bundlecaps=bundlecaps)
1167 1172
1168 1173 # apply changegroup to remote
1169 1174 # local repo finds heads on server, finds out what
1170 1175 # revs it must push. once revs transferred, if server
1171 1176 # finds it has different heads (someone else won
1172 1177 # commit/push race), server aborts.
1173 1178 if pushop.force:
1174 1179 remoteheads = ['force']
1175 1180 else:
1176 1181 remoteheads = pushop.remoteheads
1177 1182 # ssh: return remote's addchangegroup()
1178 1183 # http: return remote's addchangegroup() or 0 for error
1179 1184 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
1180 1185 pushop.repo.url())
1181 1186
1182 1187 def _pushsyncphase(pushop):
1183 1188 """synchronise phase information locally and remotely"""
1184 1189 cheads = pushop.commonheads
1185 1190 # even when we don't push, exchanging phase data is useful
1186 1191 remotephases = listkeys(pushop.remote, 'phases')
1187 1192 if (pushop.ui.configbool('ui', '_usedassubrepo')
1188 1193 and remotephases # server supports phases
1189 1194 and pushop.cgresult is None # nothing was pushed
1190 1195 and remotephases.get('publishing', False)):
1191 1196 # When:
1192 1197 # - this is a subrepo push
1193 1198 # - and remote support phase
1194 1199 # - and no changeset was pushed
1195 1200 # - and remote is publishing
1196 1201 # We may be in issue 3871 case!
1197 1202 # We drop the possible phase synchronisation done by
1198 1203 # courtesy to publish changesets possibly locally draft
1199 1204 # on the remote.
1200 1205 remotephases = {'publishing': 'True'}
1201 1206 if not remotephases: # old server or public only reply from non-publishing
1202 1207 _localphasemove(pushop, cheads)
1203 1208 # don't push any phase data as there is nothing to push
1204 1209 else:
1205 1210 ana = phases.analyzeremotephases(pushop.repo, cheads,
1206 1211 remotephases)
1207 1212 pheads, droots = ana
1208 1213 ### Apply remote phase on local
1209 1214 if remotephases.get('publishing', False):
1210 1215 _localphasemove(pushop, cheads)
1211 1216 else: # publish = False
1212 1217 _localphasemove(pushop, pheads)
1213 1218 _localphasemove(pushop, cheads, phases.draft)
1214 1219 ### Apply local phase on remote
1215 1220
1216 1221 if pushop.cgresult:
1217 1222 if 'phases' in pushop.stepsdone:
1218 1223 # phases already pushed though bundle2
1219 1224 return
1220 1225 outdated = pushop.outdatedphases
1221 1226 else:
1222 1227 outdated = pushop.fallbackoutdatedphases
1223 1228
1224 1229 pushop.stepsdone.add('phases')
1225 1230
1226 1231 # filter heads already turned public by the push
1227 1232 outdated = [c for c in outdated if c.node() not in pheads]
1228 1233 # fallback to independent pushkey command
1229 1234 for newremotehead in outdated:
1230 1235 with pushop.remote.commandexecutor() as e:
1231 1236 r = e.callcommand('pushkey', {
1232 1237 'namespace': 'phases',
1233 1238 'key': newremotehead.hex(),
1234 1239 'old': '%d' % phases.draft,
1235 1240 'new': '%d' % phases.public
1236 1241 }).result()
1237 1242
1238 1243 if not r:
1239 1244 pushop.ui.warn(_('updating %s to public failed!\n')
1240 1245 % newremotehead)
1241 1246
1242 1247 def _localphasemove(pushop, nodes, phase=phases.public):
1243 1248 """move <nodes> to <phase> in the local source repo"""
1244 1249 if pushop.trmanager:
1245 1250 phases.advanceboundary(pushop.repo,
1246 1251 pushop.trmanager.transaction(),
1247 1252 phase,
1248 1253 nodes)
1249 1254 else:
1250 1255 # repo is not locked, do not change any phases!
1251 1256 # Informs the user that phases should have been moved when
1252 1257 # applicable.
1253 1258 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1254 1259 phasestr = phases.phasenames[phase]
1255 1260 if actualmoves:
1256 1261 pushop.ui.status(_('cannot lock source repo, skipping '
1257 1262 'local %s phase update\n') % phasestr)
1258 1263
1259 1264 def _pushobsolete(pushop):
1260 1265 """utility function to push obsolete markers to a remote"""
1261 1266 if 'obsmarkers' in pushop.stepsdone:
1262 1267 return
1263 1268 repo = pushop.repo
1264 1269 remote = pushop.remote
1265 1270 pushop.stepsdone.add('obsmarkers')
1266 1271 if pushop.outobsmarkers:
1267 1272 pushop.ui.debug('try to push obsolete markers to remote\n')
1268 1273 rslts = []
1269 1274 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1270 1275 for key in sorted(remotedata, reverse=True):
1271 1276 # reverse sort to ensure we end with dump0
1272 1277 data = remotedata[key]
1273 1278 rslts.append(remote.pushkey('obsolete', key, '', data))
1274 1279 if [r for r in rslts if not r]:
1275 1280 msg = _('failed to push some obsolete markers!\n')
1276 1281 repo.ui.warn(msg)
1277 1282
1278 1283 def _pushbookmark(pushop):
1279 1284 """Update bookmark position on remote"""
1280 1285 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1281 1286 return
1282 1287 pushop.stepsdone.add('bookmarks')
1283 1288 ui = pushop.ui
1284 1289 remote = pushop.remote
1285 1290
1286 1291 for b, old, new in pushop.outbookmarks:
1287 1292 action = 'update'
1288 1293 if not old:
1289 1294 action = 'export'
1290 1295 elif not new:
1291 1296 action = 'delete'
1292 1297
1293 1298 with remote.commandexecutor() as e:
1294 1299 r = e.callcommand('pushkey', {
1295 1300 'namespace': 'bookmarks',
1296 1301 'key': b,
1297 1302 'old': old,
1298 1303 'new': new,
1299 1304 }).result()
1300 1305
1301 1306 if r:
1302 1307 ui.status(bookmsgmap[action][0] % b)
1303 1308 else:
1304 1309 ui.warn(bookmsgmap[action][1] % b)
1305 1310 # discovery can have set the value form invalid entry
1306 1311 if pushop.bkresult is not None:
1307 1312 pushop.bkresult = 1
1308 1313
1309 1314 class pulloperation(object):
1310 1315 """A object that represent a single pull operation
1311 1316
1312 1317 It purpose is to carry pull related state and very common operation.
1313 1318
1314 1319 A new should be created at the beginning of each pull and discarded
1315 1320 afterward.
1316 1321 """
1317 1322
1318 1323 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1319 1324 remotebookmarks=None, streamclonerequested=None,
1320 1325 includepats=None, excludepats=None, depth=None):
1321 1326 # repo we pull into
1322 1327 self.repo = repo
1323 1328 # repo we pull from
1324 1329 self.remote = remote
1325 1330 # revision we try to pull (None is "all")
1326 1331 self.heads = heads
1327 1332 # bookmark pulled explicitly
1328 1333 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1329 1334 for bookmark in bookmarks]
1330 1335 # do we force pull?
1331 1336 self.force = force
1332 1337 # whether a streaming clone was requested
1333 1338 self.streamclonerequested = streamclonerequested
1334 1339 # transaction manager
1335 1340 self.trmanager = None
1336 1341 # set of common changeset between local and remote before pull
1337 1342 self.common = None
1338 1343 # set of pulled head
1339 1344 self.rheads = None
1340 1345 # list of missing changeset to fetch remotely
1341 1346 self.fetch = None
1342 1347 # remote bookmarks data
1343 1348 self.remotebookmarks = remotebookmarks
1344 1349 # result of changegroup pulling (used as return code by pull)
1345 1350 self.cgresult = None
1346 1351 # list of step already done
1347 1352 self.stepsdone = set()
1348 1353 # Whether we attempted a clone from pre-generated bundles.
1349 1354 self.clonebundleattempted = False
1350 1355 # Set of file patterns to include.
1351 1356 self.includepats = includepats
1352 1357 # Set of file patterns to exclude.
1353 1358 self.excludepats = excludepats
1354 1359 # Number of ancestor changesets to pull from each pulled head.
1355 1360 self.depth = depth
1356 1361
1357 1362 @util.propertycache
1358 1363 def pulledsubset(self):
1359 1364 """heads of the set of changeset target by the pull"""
1360 1365 # compute target subset
1361 1366 if self.heads is None:
1362 1367 # We pulled every thing possible
1363 1368 # sync on everything common
1364 1369 c = set(self.common)
1365 1370 ret = list(self.common)
1366 1371 for n in self.rheads:
1367 1372 if n not in c:
1368 1373 ret.append(n)
1369 1374 return ret
1370 1375 else:
1371 1376 # We pulled a specific subset
1372 1377 # sync on this subset
1373 1378 return self.heads
1374 1379
1375 1380 @util.propertycache
1376 1381 def canusebundle2(self):
1377 1382 return not _forcebundle1(self)
1378 1383
1379 1384 @util.propertycache
1380 1385 def remotebundle2caps(self):
1381 1386 return bundle2.bundle2caps(self.remote)
1382 1387
1383 1388 def gettransaction(self):
1384 1389 # deprecated; talk to trmanager directly
1385 1390 return self.trmanager.transaction()
1386 1391
1387 1392 class transactionmanager(util.transactional):
1388 1393 """An object to manage the life cycle of a transaction
1389 1394
1390 1395 It creates the transaction on demand and calls the appropriate hooks when
1391 1396 closing the transaction."""
1392 1397 def __init__(self, repo, source, url):
1393 1398 self.repo = repo
1394 1399 self.source = source
1395 1400 self.url = url
1396 1401 self._tr = None
1397 1402
1398 1403 def transaction(self):
1399 1404 """Return an open transaction object, constructing if necessary"""
1400 1405 if not self._tr:
1401 1406 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1402 1407 self._tr = self.repo.transaction(trname)
1403 1408 self._tr.hookargs['source'] = self.source
1404 1409 self._tr.hookargs['url'] = self.url
1405 1410 return self._tr
1406 1411
1407 1412 def close(self):
1408 1413 """close transaction if created"""
1409 1414 if self._tr is not None:
1410 1415 self._tr.close()
1411 1416
1412 1417 def release(self):
1413 1418 """release transaction if created"""
1414 1419 if self._tr is not None:
1415 1420 self._tr.release()
1416 1421
1417 1422 def listkeys(remote, namespace):
1418 1423 with remote.commandexecutor() as e:
1419 1424 return e.callcommand('listkeys', {'namespace': namespace}).result()
1420 1425
1421 1426 def _fullpullbundle2(repo, pullop):
1422 1427 # The server may send a partial reply, i.e. when inlining
1423 1428 # pre-computed bundles. In that case, update the common
1424 1429 # set based on the results and pull another bundle.
1425 1430 #
1426 1431 # There are two indicators that the process is finished:
1427 1432 # - no changeset has been added, or
1428 1433 # - all remote heads are known locally.
1429 1434 # The head check must use the unfiltered view as obsoletion
1430 1435 # markers can hide heads.
1431 1436 unfi = repo.unfiltered()
1432 1437 unficl = unfi.changelog
1433 1438 def headsofdiff(h1, h2):
1434 1439 """Returns heads(h1 % h2)"""
1435 1440 res = unfi.set('heads(%ln %% %ln)', h1, h2)
1436 1441 return set(ctx.node() for ctx in res)
1437 1442 def headsofunion(h1, h2):
1438 1443 """Returns heads((h1 + h2) - null)"""
1439 1444 res = unfi.set('heads((%ln + %ln - null))', h1, h2)
1440 1445 return set(ctx.node() for ctx in res)
1441 1446 while True:
1442 1447 old_heads = unficl.heads()
1443 1448 clstart = len(unficl)
1444 1449 _pullbundle2(pullop)
1445 1450 if repository.NARROW_REQUIREMENT in repo.requirements:
1446 1451 # XXX narrow clones filter the heads on the server side during
1447 1452 # XXX getbundle and result in partial replies as well.
1448 1453 # XXX Disable pull bundles in this case as band aid to avoid
1449 1454 # XXX extra round trips.
1450 1455 break
1451 1456 if clstart == len(unficl):
1452 1457 break
1453 1458 if all(unficl.hasnode(n) for n in pullop.rheads):
1454 1459 break
1455 1460 new_heads = headsofdiff(unficl.heads(), old_heads)
1456 1461 pullop.common = headsofunion(new_heads, pullop.common)
1457 1462 pullop.rheads = set(pullop.rheads) - pullop.common
1458 1463
1459 1464 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1460 1465 streamclonerequested=None, includepats=None, excludepats=None,
1461 1466 depth=None):
1462 1467 """Fetch repository data from a remote.
1463 1468
1464 1469 This is the main function used to retrieve data from a remote repository.
1465 1470
1466 1471 ``repo`` is the local repository to clone into.
1467 1472 ``remote`` is a peer instance.
1468 1473 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1469 1474 default) means to pull everything from the remote.
1470 1475 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1471 1476 default, all remote bookmarks are pulled.
1472 1477 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1473 1478 initialization.
1474 1479 ``streamclonerequested`` is a boolean indicating whether a "streaming
1475 1480 clone" is requested. A "streaming clone" is essentially a raw file copy
1476 1481 of revlogs from the server. This only works when the local repository is
1477 1482 empty. The default value of ``None`` means to respect the server
1478 1483 configuration for preferring stream clones.
1479 1484 ``includepats`` and ``excludepats`` define explicit file patterns to
1480 1485 include and exclude in storage, respectively. If not defined, narrow
1481 1486 patterns from the repo instance are used, if available.
1482 1487 ``depth`` is an integer indicating the DAG depth of history we're
1483 1488 interested in. If defined, for each revision specified in ``heads``, we
1484 1489 will fetch up to this many of its ancestors and data associated with them.
1485 1490
1486 1491 Returns the ``pulloperation`` created for this pull.
1487 1492 """
1488 1493 if opargs is None:
1489 1494 opargs = {}
1490 1495
1491 1496 # We allow the narrow patterns to be passed in explicitly to provide more
1492 1497 # flexibility for API consumers.
1493 1498 if includepats or excludepats:
1494 1499 includepats = includepats or set()
1495 1500 excludepats = excludepats or set()
1496 1501 else:
1497 1502 includepats, excludepats = repo.narrowpats
1498 1503
1499 1504 narrowspec.validatepatterns(includepats)
1500 1505 narrowspec.validatepatterns(excludepats)
1501 1506
1502 1507 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1503 1508 streamclonerequested=streamclonerequested,
1504 1509 includepats=includepats, excludepats=excludepats,
1505 1510 depth=depth,
1506 1511 **pycompat.strkwargs(opargs))
1507 1512
1508 1513 peerlocal = pullop.remote.local()
1509 1514 if peerlocal:
1510 1515 missing = set(peerlocal.requirements) - pullop.repo.supported
1511 1516 if missing:
1512 1517 msg = _("required features are not"
1513 1518 " supported in the destination:"
1514 1519 " %s") % (', '.join(sorted(missing)))
1515 1520 raise error.Abort(msg)
1516 1521
1517 1522 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1518 1523 with repo.wlock(), repo.lock(), pullop.trmanager:
1519 1524 # Use the modern wire protocol, if available.
1520 1525 if remote.capable('command-changesetdata'):
1521 1526 exchangev2.pull(pullop)
1522 1527 else:
1523 1528 # This should ideally be in _pullbundle2(). However, it needs to run
1524 1529 # before discovery to avoid extra work.
1525 1530 _maybeapplyclonebundle(pullop)
1526 1531 streamclone.maybeperformlegacystreamclone(pullop)
1527 1532 _pulldiscovery(pullop)
1528 1533 if pullop.canusebundle2:
1529 1534 _fullpullbundle2(repo, pullop)
1530 1535 _pullchangeset(pullop)
1531 1536 _pullphase(pullop)
1532 1537 _pullbookmarks(pullop)
1533 1538 _pullobsolete(pullop)
1534 1539
1535 1540 # storing remotenames
1536 1541 if repo.ui.configbool('experimental', 'remotenames'):
1537 1542 logexchange.pullremotenames(repo, remote)
1538 1543
1539 1544 return pullop
1540 1545
1541 1546 # list of steps to perform discovery before pull
1542 1547 pulldiscoveryorder = []
1543 1548
1544 1549 # Mapping between step name and function
1545 1550 #
1546 1551 # This exists to help extensions wrap steps if necessary
1547 1552 pulldiscoverymapping = {}
1548 1553
1549 1554 def pulldiscovery(stepname):
1550 1555 """decorator for function performing discovery before pull
1551 1556
1552 1557 The function is added to the step -> function mapping and appended to the
1553 1558 list of steps. Beware that decorated function will be added in order (this
1554 1559 may matter).
1555 1560
1556 1561 You can only use this decorator for a new step, if you want to wrap a step
1557 1562 from an extension, change the pulldiscovery dictionary directly."""
1558 1563 def dec(func):
1559 1564 assert stepname not in pulldiscoverymapping
1560 1565 pulldiscoverymapping[stepname] = func
1561 1566 pulldiscoveryorder.append(stepname)
1562 1567 return func
1563 1568 return dec
1564 1569
1565 1570 def _pulldiscovery(pullop):
1566 1571 """Run all discovery steps"""
1567 1572 for stepname in pulldiscoveryorder:
1568 1573 step = pulldiscoverymapping[stepname]
1569 1574 step(pullop)
1570 1575
1571 1576 @pulldiscovery('b1:bookmarks')
1572 1577 def _pullbookmarkbundle1(pullop):
1573 1578 """fetch bookmark data in bundle1 case
1574 1579
1575 1580 If not using bundle2, we have to fetch bookmarks before changeset
1576 1581 discovery to reduce the chance and impact of race conditions."""
1577 1582 if pullop.remotebookmarks is not None:
1578 1583 return
1579 1584 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1580 1585 # all known bundle2 servers now support listkeys, but lets be nice with
1581 1586 # new implementation.
1582 1587 return
1583 1588 books = listkeys(pullop.remote, 'bookmarks')
1584 1589 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1585 1590
1586 1591
1587 1592 @pulldiscovery('changegroup')
1588 1593 def _pulldiscoverychangegroup(pullop):
1589 1594 """discovery phase for the pull
1590 1595
1591 1596 Current handle changeset discovery only, will change handle all discovery
1592 1597 at some point."""
1593 1598 tmp = discovery.findcommonincoming(pullop.repo,
1594 1599 pullop.remote,
1595 1600 heads=pullop.heads,
1596 1601 force=pullop.force)
1597 1602 common, fetch, rheads = tmp
1598 1603 nm = pullop.repo.unfiltered().changelog.nodemap
1599 1604 if fetch and rheads:
1600 1605 # If a remote heads is filtered locally, put in back in common.
1601 1606 #
1602 1607 # This is a hackish solution to catch most of "common but locally
1603 1608 # hidden situation". We do not performs discovery on unfiltered
1604 1609 # repository because it end up doing a pathological amount of round
1605 1610 # trip for w huge amount of changeset we do not care about.
1606 1611 #
1607 1612 # If a set of such "common but filtered" changeset exist on the server
1608 1613 # but are not including a remote heads, we'll not be able to detect it,
1609 1614 scommon = set(common)
1610 1615 for n in rheads:
1611 1616 if n in nm:
1612 1617 if n not in scommon:
1613 1618 common.append(n)
1614 1619 if set(rheads).issubset(set(common)):
1615 1620 fetch = []
1616 1621 pullop.common = common
1617 1622 pullop.fetch = fetch
1618 1623 pullop.rheads = rheads
1619 1624
1620 1625 def _pullbundle2(pullop):
1621 1626 """pull data using bundle2
1622 1627
1623 1628 For now, the only supported data are changegroup."""
1624 1629 kwargs = {'bundlecaps': caps20to10(pullop.repo, role='client')}
1625 1630
1626 1631 # make ui easier to access
1627 1632 ui = pullop.repo.ui
1628 1633
1629 1634 # At the moment we don't do stream clones over bundle2. If that is
1630 1635 # implemented then here's where the check for that will go.
1631 1636 streaming = streamclone.canperformstreamclone(pullop, bundle2=True)[0]
1632 1637
1633 1638 # declare pull perimeters
1634 1639 kwargs['common'] = pullop.common
1635 1640 kwargs['heads'] = pullop.heads or pullop.rheads
1636 1641
1637 1642 # check server supports narrow and then adding includepats and excludepats
1638 1643 servernarrow = pullop.remote.capable(wireprototypes.NARROWCAP)
1639 1644 if servernarrow and pullop.includepats:
1640 1645 kwargs['includepats'] = pullop.includepats
1641 1646 if servernarrow and pullop.excludepats:
1642 1647 kwargs['excludepats'] = pullop.excludepats
1643 1648
1644 1649 if streaming:
1645 1650 kwargs['cg'] = False
1646 1651 kwargs['stream'] = True
1647 1652 pullop.stepsdone.add('changegroup')
1648 1653 pullop.stepsdone.add('phases')
1649 1654
1650 1655 else:
1651 1656 # pulling changegroup
1652 1657 pullop.stepsdone.add('changegroup')
1653 1658
1654 1659 kwargs['cg'] = pullop.fetch
1655 1660
1656 1661 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
1657 1662 hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
1658 1663 if (not legacyphase and hasbinaryphase):
1659 1664 kwargs['phases'] = True
1660 1665 pullop.stepsdone.add('phases')
1661 1666
1662 1667 if 'listkeys' in pullop.remotebundle2caps:
1663 1668 if 'phases' not in pullop.stepsdone:
1664 1669 kwargs['listkeys'] = ['phases']
1665 1670
1666 1671 bookmarksrequested = False
1667 1672 legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')
1668 1673 hasbinarybook = 'bookmarks' in pullop.remotebundle2caps
1669 1674
1670 1675 if pullop.remotebookmarks is not None:
1671 1676 pullop.stepsdone.add('request-bookmarks')
1672 1677
1673 1678 if ('request-bookmarks' not in pullop.stepsdone
1674 1679 and pullop.remotebookmarks is None
1675 1680 and not legacybookmark and hasbinarybook):
1676 1681 kwargs['bookmarks'] = True
1677 1682 bookmarksrequested = True
1678 1683
1679 1684 if 'listkeys' in pullop.remotebundle2caps:
1680 1685 if 'request-bookmarks' not in pullop.stepsdone:
1681 1686 # make sure to always includes bookmark data when migrating
1682 1687 # `hg incoming --bundle` to using this function.
1683 1688 pullop.stepsdone.add('request-bookmarks')
1684 1689 kwargs.setdefault('listkeys', []).append('bookmarks')
1685 1690
1686 1691 # If this is a full pull / clone and the server supports the clone bundles
1687 1692 # feature, tell the server whether we attempted a clone bundle. The
1688 1693 # presence of this flag indicates the client supports clone bundles. This
1689 1694 # will enable the server to treat clients that support clone bundles
1690 1695 # differently from those that don't.
1691 1696 if (pullop.remote.capable('clonebundles')
1692 1697 and pullop.heads is None and list(pullop.common) == [nullid]):
1693 1698 kwargs['cbattempted'] = pullop.clonebundleattempted
1694 1699
1695 1700 if streaming:
1696 1701 pullop.repo.ui.status(_('streaming all changes\n'))
1697 1702 elif not pullop.fetch:
1698 1703 pullop.repo.ui.status(_("no changes found\n"))
1699 1704 pullop.cgresult = 0
1700 1705 else:
1701 1706 if pullop.heads is None and list(pullop.common) == [nullid]:
1702 1707 pullop.repo.ui.status(_("requesting all changes\n"))
1703 1708 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1704 1709 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1705 1710 if obsolete.commonversion(remoteversions) is not None:
1706 1711 kwargs['obsmarkers'] = True
1707 1712 pullop.stepsdone.add('obsmarkers')
1708 1713 _pullbundle2extraprepare(pullop, kwargs)
1709 1714
1710 1715 with pullop.remote.commandexecutor() as e:
1711 1716 args = dict(kwargs)
1712 1717 args['source'] = 'pull'
1713 1718 bundle = e.callcommand('getbundle', args).result()
1714 1719
1715 1720 try:
1716 1721 op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction,
1717 1722 source='pull')
1718 1723 op.modes['bookmarks'] = 'records'
1719 1724 bundle2.processbundle(pullop.repo, bundle, op=op)
1720 1725 except bundle2.AbortFromPart as exc:
1721 1726 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1722 1727 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1723 1728 except error.BundleValueError as exc:
1724 1729 raise error.Abort(_('missing support for %s') % exc)
1725 1730
1726 1731 if pullop.fetch:
1727 1732 pullop.cgresult = bundle2.combinechangegroupresults(op)
1728 1733
1729 1734 # processing phases change
1730 1735 for namespace, value in op.records['listkeys']:
1731 1736 if namespace == 'phases':
1732 1737 _pullapplyphases(pullop, value)
1733 1738
1734 1739 # processing bookmark update
1735 1740 if bookmarksrequested:
1736 1741 books = {}
1737 1742 for record in op.records['bookmarks']:
1738 1743 books[record['bookmark']] = record["node"]
1739 1744 pullop.remotebookmarks = books
1740 1745 else:
1741 1746 for namespace, value in op.records['listkeys']:
1742 1747 if namespace == 'bookmarks':
1743 1748 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1744 1749
1745 1750 # bookmark data were either already there or pulled in the bundle
1746 1751 if pullop.remotebookmarks is not None:
1747 1752 _pullbookmarks(pullop)
1748 1753
1749 1754 def _pullbundle2extraprepare(pullop, kwargs):
1750 1755 """hook function so that extensions can extend the getbundle call"""
1751 1756
1752 1757 def _pullchangeset(pullop):
1753 1758 """pull changeset from unbundle into the local repo"""
1754 1759 # We delay the open of the transaction as late as possible so we
1755 1760 # don't open transaction for nothing or you break future useful
1756 1761 # rollback call
1757 1762 if 'changegroup' in pullop.stepsdone:
1758 1763 return
1759 1764 pullop.stepsdone.add('changegroup')
1760 1765 if not pullop.fetch:
1761 1766 pullop.repo.ui.status(_("no changes found\n"))
1762 1767 pullop.cgresult = 0
1763 1768 return
1764 1769 tr = pullop.gettransaction()
1765 1770 if pullop.heads is None and list(pullop.common) == [nullid]:
1766 1771 pullop.repo.ui.status(_("requesting all changes\n"))
1767 1772 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1768 1773 # issue1320, avoid a race if remote changed after discovery
1769 1774 pullop.heads = pullop.rheads
1770 1775
1771 1776 if pullop.remote.capable('getbundle'):
1772 1777 # TODO: get bundlecaps from remote
1773 1778 cg = pullop.remote.getbundle('pull', common=pullop.common,
1774 1779 heads=pullop.heads or pullop.rheads)
1775 1780 elif pullop.heads is None:
1776 1781 with pullop.remote.commandexecutor() as e:
1777 1782 cg = e.callcommand('changegroup', {
1778 1783 'nodes': pullop.fetch,
1779 1784 'source': 'pull',
1780 1785 }).result()
1781 1786
1782 1787 elif not pullop.remote.capable('changegroupsubset'):
1783 1788 raise error.Abort(_("partial pull cannot be done because "
1784 1789 "other repository doesn't support "
1785 1790 "changegroupsubset."))
1786 1791 else:
1787 1792 with pullop.remote.commandexecutor() as e:
1788 1793 cg = e.callcommand('changegroupsubset', {
1789 1794 'bases': pullop.fetch,
1790 1795 'heads': pullop.heads,
1791 1796 'source': 'pull',
1792 1797 }).result()
1793 1798
1794 1799 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1795 1800 pullop.remote.url())
1796 1801 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1797 1802
1798 1803 def _pullphase(pullop):
1799 1804 # Get remote phases data from remote
1800 1805 if 'phases' in pullop.stepsdone:
1801 1806 return
1802 1807 remotephases = listkeys(pullop.remote, 'phases')
1803 1808 _pullapplyphases(pullop, remotephases)
1804 1809
1805 1810 def _pullapplyphases(pullop, remotephases):
1806 1811 """apply phase movement from observed remote state"""
1807 1812 if 'phases' in pullop.stepsdone:
1808 1813 return
1809 1814 pullop.stepsdone.add('phases')
1810 1815 publishing = bool(remotephases.get('publishing', False))
1811 1816 if remotephases and not publishing:
1812 1817 # remote is new and non-publishing
1813 1818 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1814 1819 pullop.pulledsubset,
1815 1820 remotephases)
1816 1821 dheads = pullop.pulledsubset
1817 1822 else:
1818 1823 # Remote is old or publishing all common changesets
1819 1824 # should be seen as public
1820 1825 pheads = pullop.pulledsubset
1821 1826 dheads = []
1822 1827 unfi = pullop.repo.unfiltered()
1823 1828 phase = unfi._phasecache.phase
1824 1829 rev = unfi.changelog.nodemap.get
1825 1830 public = phases.public
1826 1831 draft = phases.draft
1827 1832
1828 1833 # exclude changesets already public locally and update the others
1829 1834 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1830 1835 if pheads:
1831 1836 tr = pullop.gettransaction()
1832 1837 phases.advanceboundary(pullop.repo, tr, public, pheads)
1833 1838
1834 1839 # exclude changesets already draft locally and update the others
1835 1840 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1836 1841 if dheads:
1837 1842 tr = pullop.gettransaction()
1838 1843 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1839 1844
1840 1845 def _pullbookmarks(pullop):
1841 1846 """process the remote bookmark information to update the local one"""
1842 1847 if 'bookmarks' in pullop.stepsdone:
1843 1848 return
1844 1849 pullop.stepsdone.add('bookmarks')
1845 1850 repo = pullop.repo
1846 1851 remotebookmarks = pullop.remotebookmarks
1847 1852 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1848 1853 pullop.remote.url(),
1849 1854 pullop.gettransaction,
1850 1855 explicit=pullop.explicitbookmarks)
1851 1856
1852 1857 def _pullobsolete(pullop):
1853 1858 """utility function to pull obsolete markers from a remote
1854 1859
1855 1860 The `gettransaction` is function that return the pull transaction, creating
1856 1861 one if necessary. We return the transaction to inform the calling code that
1857 1862 a new transaction have been created (when applicable).
1858 1863
1859 1864 Exists mostly to allow overriding for experimentation purpose"""
1860 1865 if 'obsmarkers' in pullop.stepsdone:
1861 1866 return
1862 1867 pullop.stepsdone.add('obsmarkers')
1863 1868 tr = None
1864 1869 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1865 1870 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1866 1871 remoteobs = listkeys(pullop.remote, 'obsolete')
1867 1872 if 'dump0' in remoteobs:
1868 1873 tr = pullop.gettransaction()
1869 1874 markers = []
1870 1875 for key in sorted(remoteobs, reverse=True):
1871 1876 if key.startswith('dump'):
1872 1877 data = util.b85decode(remoteobs[key])
1873 1878 version, newmarks = obsolete._readmarkers(data)
1874 1879 markers += newmarks
1875 1880 if markers:
1876 1881 pullop.repo.obsstore.add(tr, markers)
1877 1882 pullop.repo.invalidatevolatilesets()
1878 1883 return tr
1879 1884
1880 1885 def applynarrowacl(repo, kwargs):
1881 1886 """Apply narrow fetch access control.
1882 1887
1883 1888 This massages the named arguments for getbundle wire protocol commands
1884 1889 so requested data is filtered through access control rules.
1885 1890 """
1886 1891 ui = repo.ui
1887 1892 # TODO this assumes existence of HTTP and is a layering violation.
1888 1893 username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())
1889 1894 user_includes = ui.configlist(
1890 1895 _NARROWACL_SECTION, username + '.includes',
1891 1896 ui.configlist(_NARROWACL_SECTION, 'default.includes'))
1892 1897 user_excludes = ui.configlist(
1893 1898 _NARROWACL_SECTION, username + '.excludes',
1894 1899 ui.configlist(_NARROWACL_SECTION, 'default.excludes'))
1895 1900 if not user_includes:
1896 1901 raise error.Abort(_("{} configuration for user {} is empty")
1897 1902 .format(_NARROWACL_SECTION, username))
1898 1903
1899 1904 user_includes = [
1900 1905 'path:.' if p == '*' else 'path:' + p for p in user_includes]
1901 1906 user_excludes = [
1902 1907 'path:.' if p == '*' else 'path:' + p for p in user_excludes]
1903 1908
1904 1909 req_includes = set(kwargs.get(r'includepats', []))
1905 1910 req_excludes = set(kwargs.get(r'excludepats', []))
1906 1911
1907 1912 req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
1908 1913 req_includes, req_excludes, user_includes, user_excludes)
1909 1914
1910 1915 if invalid_includes:
1911 1916 raise error.Abort(
1912 1917 _("The following includes are not accessible for {}: {}")
1913 1918 .format(username, invalid_includes))
1914 1919
1915 1920 new_args = {}
1916 1921 new_args.update(kwargs)
1917 1922 new_args[r'narrow'] = True
1918 1923 new_args[r'narrow_acl'] = True
1919 1924 new_args[r'includepats'] = req_includes
1920 1925 if req_excludes:
1921 1926 new_args[r'excludepats'] = req_excludes
1922 1927
1923 1928 return new_args
1924 1929
1925 1930 def _computeellipsis(repo, common, heads, known, match, depth=None):
1926 1931 """Compute the shape of a narrowed DAG.
1927 1932
1928 1933 Args:
1929 1934 repo: The repository we're transferring.
1930 1935 common: The roots of the DAG range we're transferring.
1931 1936 May be just [nullid], which means all ancestors of heads.
1932 1937 heads: The heads of the DAG range we're transferring.
1933 1938 match: The narrowmatcher that allows us to identify relevant changes.
1934 1939 depth: If not None, only consider nodes to be full nodes if they are at
1935 1940 most depth changesets away from one of heads.
1936 1941
1937 1942 Returns:
1938 1943 A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
1939 1944
1940 1945 visitnodes: The list of nodes (either full or ellipsis) which
1941 1946 need to be sent to the client.
1942 1947 relevant_nodes: The set of changelog nodes which change a file inside
1943 1948 the narrowspec. The client needs these as non-ellipsis nodes.
1944 1949 ellipsisroots: A dict of {rev: parents} that is used in
1945 1950 narrowchangegroup to produce ellipsis nodes with the
1946 1951 correct parents.
1947 1952 """
1948 1953 cl = repo.changelog
1949 1954 mfl = repo.manifestlog
1950 1955
1951 1956 clrev = cl.rev
1952 1957
1953 1958 commonrevs = {clrev(n) for n in common} | {nullrev}
1954 1959 headsrevs = {clrev(n) for n in heads}
1955 1960
1956 1961 if depth:
1957 1962 revdepth = {h: 0 for h in headsrevs}
1958 1963
1959 1964 ellipsisheads = collections.defaultdict(set)
1960 1965 ellipsisroots = collections.defaultdict(set)
1961 1966
1962 1967 def addroot(head, curchange):
1963 1968 """Add a root to an ellipsis head, splitting heads with 3 roots."""
1964 1969 ellipsisroots[head].add(curchange)
1965 1970 # Recursively split ellipsis heads with 3 roots by finding the
1966 1971 # roots' youngest common descendant which is an elided merge commit.
1967 1972 # That descendant takes 2 of the 3 roots as its own, and becomes a
1968 1973 # root of the head.
1969 1974 while len(ellipsisroots[head]) > 2:
1970 1975 child, roots = splithead(head)
1971 1976 splitroots(head, child, roots)
1972 1977 head = child # Recurse in case we just added a 3rd root
1973 1978
1974 1979 def splitroots(head, child, roots):
1975 1980 ellipsisroots[head].difference_update(roots)
1976 1981 ellipsisroots[head].add(child)
1977 1982 ellipsisroots[child].update(roots)
1978 1983 ellipsisroots[child].discard(child)
1979 1984
1980 1985 def splithead(head):
1981 1986 r1, r2, r3 = sorted(ellipsisroots[head])
1982 1987 for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
1983 1988 mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
1984 1989 nr1, head, nr2, head)
1985 1990 for j in mid:
1986 1991 if j == nr2:
1987 1992 return nr2, (nr1, nr2)
1988 1993 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
1989 1994 return j, (nr1, nr2)
1990 1995 raise error.Abort(_('Failed to split up ellipsis node! head: %d, '
1991 1996 'roots: %d %d %d') % (head, r1, r2, r3))
1992 1997
1993 1998 missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
1994 1999 visit = reversed(missing)
1995 2000 relevant_nodes = set()
1996 2001 visitnodes = [cl.node(m) for m in missing]
1997 2002 required = set(headsrevs) | known
1998 2003 for rev in visit:
1999 2004 clrev = cl.changelogrevision(rev)
2000 2005 ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]
2001 2006 if depth is not None:
2002 2007 curdepth = revdepth[rev]
2003 2008 for p in ps:
2004 2009 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
2005 2010 needed = False
2006 2011 shallow_enough = depth is None or revdepth[rev] <= depth
2007 2012 if shallow_enough:
2008 2013 curmf = mfl[clrev.manifest].read()
2009 2014 if ps:
2010 2015 # We choose to not trust the changed files list in
2011 2016 # changesets because it's not always correct. TODO: could
2012 2017 # we trust it for the non-merge case?
2013 2018 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
2014 2019 needed = bool(curmf.diff(p1mf, match))
2015 2020 if not needed and len(ps) > 1:
2016 2021 # For merge changes, the list of changed files is not
2017 2022 # helpful, since we need to emit the merge if a file
2018 2023 # in the narrow spec has changed on either side of the
2019 2024 # merge. As a result, we do a manifest diff to check.
2020 2025 p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
2021 2026 needed = bool(curmf.diff(p2mf, match))
2022 2027 else:
2023 2028 # For a root node, we need to include the node if any
2024 2029 # files in the node match the narrowspec.
2025 2030 needed = any(curmf.walk(match))
2026 2031
2027 2032 if needed:
2028 2033 for head in ellipsisheads[rev]:
2029 2034 addroot(head, rev)
2030 2035 for p in ps:
2031 2036 required.add(p)
2032 2037 relevant_nodes.add(cl.node(rev))
2033 2038 else:
2034 2039 if not ps:
2035 2040 ps = [nullrev]
2036 2041 if rev in required:
2037 2042 for head in ellipsisheads[rev]:
2038 2043 addroot(head, rev)
2039 2044 for p in ps:
2040 2045 ellipsisheads[p].add(rev)
2041 2046 else:
2042 2047 for p in ps:
2043 2048 ellipsisheads[p] |= ellipsisheads[rev]
2044 2049
2045 2050 # add common changesets as roots of their reachable ellipsis heads
2046 2051 for c in commonrevs:
2047 2052 for head in ellipsisheads[c]:
2048 2053 addroot(head, c)
2049 2054 return visitnodes, relevant_nodes, ellipsisroots
2050 2055
2051 2056 def caps20to10(repo, role):
2052 2057 """return a set with appropriate options to use bundle20 during getbundle"""
2053 2058 caps = {'HG20'}
2054 2059 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=role))
2055 2060 caps.add('bundle2=' + urlreq.quote(capsblob))
2056 2061 return caps
2057 2062
2058 2063 # List of names of steps to perform for a bundle2 for getbundle, order matters.
2059 2064 getbundle2partsorder = []
2060 2065
2061 2066 # Mapping between step name and function
2062 2067 #
2063 2068 # This exists to help extensions wrap steps if necessary
2064 2069 getbundle2partsmapping = {}
2065 2070
2066 2071 def getbundle2partsgenerator(stepname, idx=None):
2067 2072 """decorator for function generating bundle2 part for getbundle
2068 2073
2069 2074 The function is added to the step -> function mapping and appended to the
2070 2075 list of steps. Beware that decorated functions will be added in order
2071 2076 (this may matter).
2072 2077
2073 2078 You can only use this decorator for new steps, if you want to wrap a step
2074 2079 from an extension, attack the getbundle2partsmapping dictionary directly."""
2075 2080 def dec(func):
2076 2081 assert stepname not in getbundle2partsmapping
2077 2082 getbundle2partsmapping[stepname] = func
2078 2083 if idx is None:
2079 2084 getbundle2partsorder.append(stepname)
2080 2085 else:
2081 2086 getbundle2partsorder.insert(idx, stepname)
2082 2087 return func
2083 2088 return dec
2084 2089
2085 2090 def bundle2requested(bundlecaps):
2086 2091 if bundlecaps is not None:
2087 2092 return any(cap.startswith('HG2') for cap in bundlecaps)
2088 2093 return False
2089 2094
2090 2095 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
2091 2096 **kwargs):
2092 2097 """Return chunks constituting a bundle's raw data.
2093 2098
2094 2099 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
2095 2100 passed.
2096 2101
2097 2102 Returns a 2-tuple of a dict with metadata about the generated bundle
2098 2103 and an iterator over raw chunks (of varying sizes).
2099 2104 """
2100 2105 kwargs = pycompat.byteskwargs(kwargs)
2101 2106 info = {}
2102 2107 usebundle2 = bundle2requested(bundlecaps)
2103 2108 # bundle10 case
2104 2109 if not usebundle2:
2105 2110 if bundlecaps and not kwargs.get('cg', True):
2106 2111 raise ValueError(_('request for bundle10 must include changegroup'))
2107 2112
2108 2113 if kwargs:
2109 2114 raise ValueError(_('unsupported getbundle arguments: %s')
2110 2115 % ', '.join(sorted(kwargs.keys())))
2111 2116 outgoing = _computeoutgoing(repo, heads, common)
2112 2117 info['bundleversion'] = 1
2113 2118 return info, changegroup.makestream(repo, outgoing, '01', source,
2114 2119 bundlecaps=bundlecaps)
2115 2120
2116 2121 # bundle20 case
2117 2122 info['bundleversion'] = 2
2118 2123 b2caps = {}
2119 2124 for bcaps in bundlecaps:
2120 2125 if bcaps.startswith('bundle2='):
2121 2126 blob = urlreq.unquote(bcaps[len('bundle2='):])
2122 2127 b2caps.update(bundle2.decodecaps(blob))
2123 2128 bundler = bundle2.bundle20(repo.ui, b2caps)
2124 2129
2125 2130 kwargs['heads'] = heads
2126 2131 kwargs['common'] = common
2127 2132
2128 2133 for name in getbundle2partsorder:
2129 2134 func = getbundle2partsmapping[name]
2130 2135 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
2131 2136 **pycompat.strkwargs(kwargs))
2132 2137
2133 2138 info['prefercompressed'] = bundler.prefercompressed
2134 2139
2135 2140 return info, bundler.getchunks()
2136 2141
2137 2142 @getbundle2partsgenerator('stream2')
2138 2143 def _getbundlestream2(bundler, repo, *args, **kwargs):
2139 2144 return bundle2.addpartbundlestream2(bundler, repo, **kwargs)
2140 2145
2141 2146 @getbundle2partsgenerator('changegroup')
2142 2147 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
2143 2148 b2caps=None, heads=None, common=None, **kwargs):
2144 2149 """add a changegroup part to the requested bundle"""
2145 2150 if not kwargs.get(r'cg', True):
2146 2151 return
2147 2152
2148 2153 version = '01'
2149 2154 cgversions = b2caps.get('changegroup')
2150 2155 if cgversions: # 3.1 and 3.2 ship with an empty value
2151 2156 cgversions = [v for v in cgversions
2152 2157 if v in changegroup.supportedoutgoingversions(repo)]
2153 2158 if not cgversions:
2154 2159 raise ValueError(_('no common changegroup version'))
2155 2160 version = max(cgversions)
2156 2161
2157 2162 outgoing = _computeoutgoing(repo, heads, common)
2158 2163 if not outgoing.missing:
2159 2164 return
2160 2165
2161 2166 if kwargs.get(r'narrow', False):
2162 2167 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
2163 2168 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
2164 2169 matcher = narrowspec.match(repo.root, include=include, exclude=exclude)
2165 2170 else:
2166 2171 matcher = None
2167 2172
2168 2173 cgstream = changegroup.makestream(repo, outgoing, version, source,
2169 2174 bundlecaps=bundlecaps, matcher=matcher)
2170 2175
2171 2176 part = bundler.newpart('changegroup', data=cgstream)
2172 2177 if cgversions:
2173 2178 part.addparam('version', version)
2174 2179
2175 2180 part.addparam('nbchanges', '%d' % len(outgoing.missing),
2176 2181 mandatory=False)
2177 2182
2178 2183 if 'treemanifest' in repo.requirements:
2179 2184 part.addparam('treemanifest', '1')
2180 2185
2181 2186 if (kwargs.get(r'narrow', False) and kwargs.get(r'narrow_acl', False)
2182 2187 and (include or exclude)):
2183 2188 narrowspecpart = bundler.newpart('narrow:spec')
2184 2189 if include:
2185 2190 narrowspecpart.addparam(
2186 2191 'include', '\n'.join(include), mandatory=True)
2187 2192 if exclude:
2188 2193 narrowspecpart.addparam(
2189 2194 'exclude', '\n'.join(exclude), mandatory=True)
2190 2195
2191 2196 @getbundle2partsgenerator('bookmarks')
2192 2197 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
2193 2198 b2caps=None, **kwargs):
2194 2199 """add a bookmark part to the requested bundle"""
2195 2200 if not kwargs.get(r'bookmarks', False):
2196 2201 return
2197 2202 if 'bookmarks' not in b2caps:
2198 2203 raise ValueError(_('no common bookmarks exchange method'))
2199 2204 books = bookmod.listbinbookmarks(repo)
2200 2205 data = bookmod.binaryencode(books)
2201 2206 if data:
2202 2207 bundler.newpart('bookmarks', data=data)
2203 2208
2204 2209 @getbundle2partsgenerator('listkeys')
2205 2210 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
2206 2211 b2caps=None, **kwargs):
2207 2212 """add parts containing listkeys namespaces to the requested bundle"""
2208 2213 listkeys = kwargs.get(r'listkeys', ())
2209 2214 for namespace in listkeys:
2210 2215 part = bundler.newpart('listkeys')
2211 2216 part.addparam('namespace', namespace)
2212 2217 keys = repo.listkeys(namespace).items()
2213 2218 part.data = pushkey.encodekeys(keys)
2214 2219
2215 2220 @getbundle2partsgenerator('obsmarkers')
2216 2221 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
2217 2222 b2caps=None, heads=None, **kwargs):
2218 2223 """add an obsolescence markers part to the requested bundle"""
2219 2224 if kwargs.get(r'obsmarkers', False):
2220 2225 if heads is None:
2221 2226 heads = repo.heads()
2222 2227 subset = [c.node() for c in repo.set('::%ln', heads)]
2223 2228 markers = repo.obsstore.relevantmarkers(subset)
2224 2229 markers = sorted(markers)
2225 2230 bundle2.buildobsmarkerspart(bundler, markers)
2226 2231
2227 2232 @getbundle2partsgenerator('phases')
2228 2233 def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
2229 2234 b2caps=None, heads=None, **kwargs):
2230 2235 """add phase heads part to the requested bundle"""
2231 2236 if kwargs.get(r'phases', False):
2232 2237 if not 'heads' in b2caps.get('phases'):
2233 2238 raise ValueError(_('no common phases exchange method'))
2234 2239 if heads is None:
2235 2240 heads = repo.heads()
2236 2241
2237 2242 headsbyphase = collections.defaultdict(set)
2238 2243 if repo.publishing():
2239 2244 headsbyphase[phases.public] = heads
2240 2245 else:
2241 2246 # find the appropriate heads to move
2242 2247
2243 2248 phase = repo._phasecache.phase
2244 2249 node = repo.changelog.node
2245 2250 rev = repo.changelog.rev
2246 2251 for h in heads:
2247 2252 headsbyphase[phase(repo, rev(h))].add(h)
2248 2253 seenphases = list(headsbyphase.keys())
2249 2254
2250 2255 # We do not handle anything but public and draft phase for now)
2251 2256 if seenphases:
2252 2257 assert max(seenphases) <= phases.draft
2253 2258
2254 2259 # if client is pulling non-public changesets, we need to find
2255 2260 # intermediate public heads.
2256 2261 draftheads = headsbyphase.get(phases.draft, set())
2257 2262 if draftheads:
2258 2263 publicheads = headsbyphase.get(phases.public, set())
2259 2264
2260 2265 revset = 'heads(only(%ln, %ln) and public())'
2261 2266 extraheads = repo.revs(revset, draftheads, publicheads)
2262 2267 for r in extraheads:
2263 2268 headsbyphase[phases.public].add(node(r))
2264 2269
2265 2270 # transform data in a format used by the encoding function
2266 2271 phasemapping = []
2267 2272 for phase in phases.allphases:
2268 2273 phasemapping.append(sorted(headsbyphase[phase]))
2269 2274
2270 2275 # generate the actual part
2271 2276 phasedata = phases.binaryencode(phasemapping)
2272 2277 bundler.newpart('phase-heads', data=phasedata)
2273 2278
2274 2279 @getbundle2partsgenerator('hgtagsfnodes')
2275 2280 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
2276 2281 b2caps=None, heads=None, common=None,
2277 2282 **kwargs):
2278 2283 """Transfer the .hgtags filenodes mapping.
2279 2284
2280 2285 Only values for heads in this bundle will be transferred.
2281 2286
2282 2287 The part data consists of pairs of 20 byte changeset node and .hgtags
2283 2288 filenodes raw values.
2284 2289 """
2285 2290 # Don't send unless:
2286 2291 # - changeset are being exchanged,
2287 2292 # - the client supports it.
2288 2293 if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):
2289 2294 return
2290 2295
2291 2296 outgoing = _computeoutgoing(repo, heads, common)
2292 2297 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
2293 2298
2294 2299 @getbundle2partsgenerator('cache:rev-branch-cache')
2295 2300 def _getbundlerevbranchcache(bundler, repo, source, bundlecaps=None,
2296 2301 b2caps=None, heads=None, common=None,
2297 2302 **kwargs):
2298 2303 """Transfer the rev-branch-cache mapping
2299 2304
2300 2305 The payload is a series of data related to each branch
2301 2306
2302 2307 1) branch name length
2303 2308 2) number of open heads
2304 2309 3) number of closed heads
2305 2310 4) open heads nodes
2306 2311 5) closed heads nodes
2307 2312 """
2308 2313 # Don't send unless:
2309 2314 # - changeset are being exchanged,
2310 2315 # - the client supports it.
2311 2316 # - narrow bundle isn't in play (not currently compatible).
2312 2317 if (not kwargs.get(r'cg', True)
2313 2318 or 'rev-branch-cache' not in b2caps
2314 2319 or kwargs.get(r'narrow', False)
2315 2320 or repo.ui.has_section(_NARROWACL_SECTION)):
2316 2321 return
2317 2322
2318 2323 outgoing = _computeoutgoing(repo, heads, common)
2319 2324 bundle2.addpartrevbranchcache(repo, bundler, outgoing)
2320 2325
2321 2326 def check_heads(repo, their_heads, context):
2322 2327 """check if the heads of a repo have been modified
2323 2328
2324 2329 Used by peer for unbundling.
2325 2330 """
2326 2331 heads = repo.heads()
2327 2332 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
2328 2333 if not (their_heads == ['force'] or their_heads == heads or
2329 2334 their_heads == ['hashed', heads_hash]):
2330 2335 # someone else committed/pushed/unbundled while we
2331 2336 # were transferring data
2332 2337 raise error.PushRaced('repository changed while %s - '
2333 2338 'please try again' % context)
2334 2339
2335 2340 def unbundle(repo, cg, heads, source, url):
2336 2341 """Apply a bundle to a repo.
2337 2342
2338 2343 this function makes sure the repo is locked during the application and have
2339 2344 mechanism to check that no push race occurred between the creation of the
2340 2345 bundle and its application.
2341 2346
2342 2347 If the push was raced as PushRaced exception is raised."""
2343 2348 r = 0
2344 2349 # need a transaction when processing a bundle2 stream
2345 2350 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
2346 2351 lockandtr = [None, None, None]
2347 2352 recordout = None
2348 2353 # quick fix for output mismatch with bundle2 in 3.4
2349 2354 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
2350 2355 if url.startswith('remote:http:') or url.startswith('remote:https:'):
2351 2356 captureoutput = True
2352 2357 try:
2353 2358 # note: outside bundle1, 'heads' is expected to be empty and this
2354 2359 # 'check_heads' call wil be a no-op
2355 2360 check_heads(repo, heads, 'uploading changes')
2356 2361 # push can proceed
2357 2362 if not isinstance(cg, bundle2.unbundle20):
2358 2363 # legacy case: bundle1 (changegroup 01)
2359 2364 txnname = "\n".join([source, util.hidepassword(url)])
2360 2365 with repo.lock(), repo.transaction(txnname) as tr:
2361 2366 op = bundle2.applybundle(repo, cg, tr, source, url)
2362 2367 r = bundle2.combinechangegroupresults(op)
2363 2368 else:
2364 2369 r = None
2365 2370 try:
2366 2371 def gettransaction():
2367 2372 if not lockandtr[2]:
2368 2373 lockandtr[0] = repo.wlock()
2369 2374 lockandtr[1] = repo.lock()
2370 2375 lockandtr[2] = repo.transaction(source)
2371 2376 lockandtr[2].hookargs['source'] = source
2372 2377 lockandtr[2].hookargs['url'] = url
2373 2378 lockandtr[2].hookargs['bundle2'] = '1'
2374 2379 return lockandtr[2]
2375 2380
2376 2381 # Do greedy locking by default until we're satisfied with lazy
2377 2382 # locking.
2378 2383 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
2379 2384 gettransaction()
2380 2385
2381 2386 op = bundle2.bundleoperation(repo, gettransaction,
2382 2387 captureoutput=captureoutput,
2383 2388 source='push')
2384 2389 try:
2385 2390 op = bundle2.processbundle(repo, cg, op=op)
2386 2391 finally:
2387 2392 r = op.reply
2388 2393 if captureoutput and r is not None:
2389 2394 repo.ui.pushbuffer(error=True, subproc=True)
2390 2395 def recordout(output):
2391 2396 r.newpart('output', data=output, mandatory=False)
2392 2397 if lockandtr[2] is not None:
2393 2398 lockandtr[2].close()
2394 2399 except BaseException as exc:
2395 2400 exc.duringunbundle2 = True
2396 2401 if captureoutput and r is not None:
2397 2402 parts = exc._bundle2salvagedoutput = r.salvageoutput()
2398 2403 def recordout(output):
2399 2404 part = bundle2.bundlepart('output', data=output,
2400 2405 mandatory=False)
2401 2406 parts.append(part)
2402 2407 raise
2403 2408 finally:
2404 2409 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
2405 2410 if recordout is not None:
2406 2411 recordout(repo.ui.popbuffer())
2407 2412 return r
2408 2413
2409 2414 def _maybeapplyclonebundle(pullop):
2410 2415 """Apply a clone bundle from a remote, if possible."""
2411 2416
2412 2417 repo = pullop.repo
2413 2418 remote = pullop.remote
2414 2419
2415 2420 if not repo.ui.configbool('ui', 'clonebundles'):
2416 2421 return
2417 2422
2418 2423 # Only run if local repo is empty.
2419 2424 if len(repo):
2420 2425 return
2421 2426
2422 2427 if pullop.heads:
2423 2428 return
2424 2429
2425 2430 if not remote.capable('clonebundles'):
2426 2431 return
2427 2432
2428 2433 with remote.commandexecutor() as e:
2429 2434 res = e.callcommand('clonebundles', {}).result()
2430 2435
2431 2436 # If we call the wire protocol command, that's good enough to record the
2432 2437 # attempt.
2433 2438 pullop.clonebundleattempted = True
2434 2439
2435 2440 entries = parseclonebundlesmanifest(repo, res)
2436 2441 if not entries:
2437 2442 repo.ui.note(_('no clone bundles available on remote; '
2438 2443 'falling back to regular clone\n'))
2439 2444 return
2440 2445
2441 2446 entries = filterclonebundleentries(
2442 2447 repo, entries, streamclonerequested=pullop.streamclonerequested)
2443 2448
2444 2449 if not entries:
2445 2450 # There is a thundering herd concern here. However, if a server
2446 2451 # operator doesn't advertise bundles appropriate for its clients,
2447 2452 # they deserve what's coming. Furthermore, from a client's
2448 2453 # perspective, no automatic fallback would mean not being able to
2449 2454 # clone!
2450 2455 repo.ui.warn(_('no compatible clone bundles available on server; '
2451 2456 'falling back to regular clone\n'))
2452 2457 repo.ui.warn(_('(you may want to report this to the server '
2453 2458 'operator)\n'))
2454 2459 return
2455 2460
2456 2461 entries = sortclonebundleentries(repo.ui, entries)
2457 2462
2458 2463 url = entries[0]['URL']
2459 2464 repo.ui.status(_('applying clone bundle from %s\n') % url)
2460 2465 if trypullbundlefromurl(repo.ui, repo, url):
2461 2466 repo.ui.status(_('finished applying clone bundle\n'))
2462 2467 # Bundle failed.
2463 2468 #
2464 2469 # We abort by default to avoid the thundering herd of
2465 2470 # clients flooding a server that was expecting expensive
2466 2471 # clone load to be offloaded.
2467 2472 elif repo.ui.configbool('ui', 'clonebundlefallback'):
2468 2473 repo.ui.warn(_('falling back to normal clone\n'))
2469 2474 else:
2470 2475 raise error.Abort(_('error applying bundle'),
2471 2476 hint=_('if this error persists, consider contacting '
2472 2477 'the server operator or disable clone '
2473 2478 'bundles via '
2474 2479 '"--config ui.clonebundles=false"'))
2475 2480
2476 2481 def parseclonebundlesmanifest(repo, s):
2477 2482 """Parses the raw text of a clone bundles manifest.
2478 2483
2479 2484 Returns a list of dicts. The dicts have a ``URL`` key corresponding
2480 2485 to the URL and other keys are the attributes for the entry.
2481 2486 """
2482 2487 m = []
2483 2488 for line in s.splitlines():
2484 2489 fields = line.split()
2485 2490 if not fields:
2486 2491 continue
2487 2492 attrs = {'URL': fields[0]}
2488 2493 for rawattr in fields[1:]:
2489 2494 key, value = rawattr.split('=', 1)
2490 2495 key = urlreq.unquote(key)
2491 2496 value = urlreq.unquote(value)
2492 2497 attrs[key] = value
2493 2498
2494 2499 # Parse BUNDLESPEC into components. This makes client-side
2495 2500 # preferences easier to specify since you can prefer a single
2496 2501 # component of the BUNDLESPEC.
2497 2502 if key == 'BUNDLESPEC':
2498 2503 try:
2499 2504 bundlespec = parsebundlespec(repo, value)
2500 2505 attrs['COMPRESSION'] = bundlespec.compression
2501 2506 attrs['VERSION'] = bundlespec.version
2502 2507 except error.InvalidBundleSpecification:
2503 2508 pass
2504 2509 except error.UnsupportedBundleSpecification:
2505 2510 pass
2506 2511
2507 2512 m.append(attrs)
2508 2513
2509 2514 return m
2510 2515
2511 2516 def isstreamclonespec(bundlespec):
2512 2517 # Stream clone v1
2513 2518 if (bundlespec.wirecompression == 'UN' and bundlespec.wireversion == 's1'):
2514 2519 return True
2515 2520
2516 2521 # Stream clone v2
2517 2522 if (bundlespec.wirecompression == 'UN' and \
2518 2523 bundlespec.wireversion == '02' and \
2519 2524 bundlespec.contentopts.get('streamv2')):
2520 2525 return True
2521 2526
2522 2527 return False
2523 2528
2524 2529 def filterclonebundleentries(repo, entries, streamclonerequested=False):
2525 2530 """Remove incompatible clone bundle manifest entries.
2526 2531
2527 2532 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
2528 2533 and returns a new list consisting of only the entries that this client
2529 2534 should be able to apply.
2530 2535
2531 2536 There is no guarantee we'll be able to apply all returned entries because
2532 2537 the metadata we use to filter on may be missing or wrong.
2533 2538 """
2534 2539 newentries = []
2535 2540 for entry in entries:
2536 2541 spec = entry.get('BUNDLESPEC')
2537 2542 if spec:
2538 2543 try:
2539 2544 bundlespec = parsebundlespec(repo, spec, strict=True)
2540 2545
2541 2546 # If a stream clone was requested, filter out non-streamclone
2542 2547 # entries.
2543 2548 if streamclonerequested and not isstreamclonespec(bundlespec):
2544 2549 repo.ui.debug('filtering %s because not a stream clone\n' %
2545 2550 entry['URL'])
2546 2551 continue
2547 2552
2548 2553 except error.InvalidBundleSpecification as e:
2549 2554 repo.ui.debug(stringutil.forcebytestr(e) + '\n')
2550 2555 continue
2551 2556 except error.UnsupportedBundleSpecification as e:
2552 2557 repo.ui.debug('filtering %s because unsupported bundle '
2553 2558 'spec: %s\n' % (
2554 2559 entry['URL'], stringutil.forcebytestr(e)))
2555 2560 continue
2556 2561 # If we don't have a spec and requested a stream clone, we don't know
2557 2562 # what the entry is so don't attempt to apply it.
2558 2563 elif streamclonerequested:
2559 2564 repo.ui.debug('filtering %s because cannot determine if a stream '
2560 2565 'clone bundle\n' % entry['URL'])
2561 2566 continue
2562 2567
2563 2568 if 'REQUIRESNI' in entry and not sslutil.hassni:
2564 2569 repo.ui.debug('filtering %s because SNI not supported\n' %
2565 2570 entry['URL'])
2566 2571 continue
2567 2572
2568 2573 newentries.append(entry)
2569 2574
2570 2575 return newentries
2571 2576
2572 2577 class clonebundleentry(object):
2573 2578 """Represents an item in a clone bundles manifest.
2574 2579
2575 2580 This rich class is needed to support sorting since sorted() in Python 3
2576 2581 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
2577 2582 won't work.
2578 2583 """
2579 2584
2580 2585 def __init__(self, value, prefers):
2581 2586 self.value = value
2582 2587 self.prefers = prefers
2583 2588
2584 2589 def _cmp(self, other):
2585 2590 for prefkey, prefvalue in self.prefers:
2586 2591 avalue = self.value.get(prefkey)
2587 2592 bvalue = other.value.get(prefkey)
2588 2593
2589 2594 # Special case for b missing attribute and a matches exactly.
2590 2595 if avalue is not None and bvalue is None and avalue == prefvalue:
2591 2596 return -1
2592 2597
2593 2598 # Special case for a missing attribute and b matches exactly.
2594 2599 if bvalue is not None and avalue is None and bvalue == prefvalue:
2595 2600 return 1
2596 2601
2597 2602 # We can't compare unless attribute present on both.
2598 2603 if avalue is None or bvalue is None:
2599 2604 continue
2600 2605
2601 2606 # Same values should fall back to next attribute.
2602 2607 if avalue == bvalue:
2603 2608 continue
2604 2609
2605 2610 # Exact matches come first.
2606 2611 if avalue == prefvalue:
2607 2612 return -1
2608 2613 if bvalue == prefvalue:
2609 2614 return 1
2610 2615
2611 2616 # Fall back to next attribute.
2612 2617 continue
2613 2618
2614 2619 # If we got here we couldn't sort by attributes and prefers. Fall
2615 2620 # back to index order.
2616 2621 return 0
2617 2622
2618 2623 def __lt__(self, other):
2619 2624 return self._cmp(other) < 0
2620 2625
2621 2626 def __gt__(self, other):
2622 2627 return self._cmp(other) > 0
2623 2628
2624 2629 def __eq__(self, other):
2625 2630 return self._cmp(other) == 0
2626 2631
2627 2632 def __le__(self, other):
2628 2633 return self._cmp(other) <= 0
2629 2634
2630 2635 def __ge__(self, other):
2631 2636 return self._cmp(other) >= 0
2632 2637
2633 2638 def __ne__(self, other):
2634 2639 return self._cmp(other) != 0
2635 2640
2636 2641 def sortclonebundleentries(ui, entries):
2637 2642 prefers = ui.configlist('ui', 'clonebundleprefers')
2638 2643 if not prefers:
2639 2644 return list(entries)
2640 2645
2641 2646 prefers = [p.split('=', 1) for p in prefers]
2642 2647
2643 2648 items = sorted(clonebundleentry(v, prefers) for v in entries)
2644 2649 return [i.value for i in items]
2645 2650
2646 2651 def trypullbundlefromurl(ui, repo, url):
2647 2652 """Attempt to apply a bundle from a URL."""
2648 2653 with repo.lock(), repo.transaction('bundleurl') as tr:
2649 2654 try:
2650 2655 fh = urlmod.open(ui, url)
2651 2656 cg = readbundle(ui, fh, 'stream')
2652 2657
2653 2658 if isinstance(cg, streamclone.streamcloneapplier):
2654 2659 cg.apply(repo)
2655 2660 else:
2656 2661 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2657 2662 return True
2658 2663 except urlerr.httperror as e:
2659 2664 ui.warn(_('HTTP error fetching bundle: %s\n') %
2660 2665 stringutil.forcebytestr(e))
2661 2666 except urlerr.urlerror as e:
2662 2667 ui.warn(_('error fetching bundle: %s\n') %
2663 2668 stringutil.forcebytestr(e.reason))
2664 2669
2665 2670 return False
@@ -1,408 +1,408 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 config
17 17 copy
18 18 diff
19 19 export
20 20 files
21 21 forget
22 22 graft
23 23 grep
24 24 heads
25 25 help
26 26 identify
27 27 import
28 28 incoming
29 29 init
30 30 locate
31 31 log
32 32 manifest
33 33 merge
34 34 outgoing
35 35 parents
36 36 paths
37 37 phase
38 38 pull
39 39 push
40 40 recover
41 41 remove
42 42 rename
43 43 resolve
44 44 revert
45 45 rollback
46 46 root
47 47 serve
48 48 status
49 49 summary
50 50 tag
51 51 tags
52 52 tip
53 53 unbundle
54 54 update
55 55 verify
56 56 version
57 57
58 58 Show all commands that start with "a"
59 59 $ hg debugcomplete a
60 60 add
61 61 addremove
62 62 annotate
63 63 archive
64 64
65 65 Do not show debug commands if there are other candidates
66 66 $ hg debugcomplete d
67 67 diff
68 68
69 69 Show debug commands if there are no other candidates
70 70 $ hg debugcomplete debug
71 71 debugancestor
72 72 debugapplystreamclonebundle
73 73 debugbuilddag
74 74 debugbundle
75 75 debugcapabilities
76 76 debugcheckstate
77 77 debugcolor
78 78 debugcommands
79 79 debugcomplete
80 80 debugconfig
81 81 debugcreatestreamclonebundle
82 82 debugdag
83 83 debugdata
84 84 debugdate
85 85 debugdeltachain
86 86 debugdirstate
87 87 debugdiscovery
88 88 debugdownload
89 89 debugextensions
90 90 debugfileset
91 91 debugformat
92 92 debugfsinfo
93 93 debuggetbundle
94 94 debugignore
95 95 debugindex
96 96 debugindexdot
97 97 debugindexstats
98 98 debuginstall
99 99 debugknown
100 100 debuglabelcomplete
101 101 debuglocks
102 102 debugmanifestfulltextcache
103 103 debugmergestate
104 104 debugnamecomplete
105 105 debugobsolete
106 106 debugpathcomplete
107 107 debugpeer
108 108 debugpickmergetool
109 109 debugpushkey
110 110 debugpvec
111 111 debugrebuilddirstate
112 112 debugrebuildfncache
113 113 debugrename
114 114 debugrevlog
115 115 debugrevlogindex
116 116 debugrevspec
117 117 debugserve
118 118 debugsetparents
119 119 debugssl
120 120 debugsub
121 121 debugsuccessorssets
122 122 debugtemplate
123 123 debuguigetpass
124 124 debuguiprompt
125 125 debugupdatecaches
126 126 debugupgraderepo
127 127 debugwalk
128 128 debugwhyunstable
129 129 debugwireargs
130 130 debugwireproto
131 131
132 132 Do not show the alias of a debug command if there are other candidates
133 133 (this should hide rawcommit)
134 134 $ hg debugcomplete r
135 135 recover
136 136 remove
137 137 rename
138 138 resolve
139 139 revert
140 140 rollback
141 141 root
142 142 Show the alias of a debug command if there are no other candidates
143 143 $ hg debugcomplete rawc
144 144
145 145
146 146 Show the global options
147 147 $ hg debugcomplete --options | sort
148 148 --color
149 149 --config
150 150 --cwd
151 151 --debug
152 152 --debugger
153 153 --encoding
154 154 --encodingmode
155 155 --help
156 156 --hidden
157 157 --noninteractive
158 158 --pager
159 159 --profile
160 160 --quiet
161 161 --repository
162 162 --time
163 163 --traceback
164 164 --verbose
165 165 --version
166 166 -R
167 167 -h
168 168 -q
169 169 -v
170 170 -y
171 171
172 172 Show the options for the "serve" command
173 173 $ hg debugcomplete --options serve | sort
174 174 --accesslog
175 175 --address
176 176 --certificate
177 177 --cmdserver
178 178 --color
179 179 --config
180 180 --cwd
181 181 --daemon
182 182 --daemon-postexec
183 183 --debug
184 184 --debugger
185 185 --encoding
186 186 --encodingmode
187 187 --errorlog
188 188 --help
189 189 --hidden
190 190 --ipv6
191 191 --name
192 192 --noninteractive
193 193 --pager
194 194 --pid-file
195 195 --port
196 196 --prefix
197 197 --print-url
198 198 --profile
199 199 --quiet
200 200 --repository
201 201 --stdio
202 202 --style
203 203 --subrepos
204 204 --templates
205 205 --time
206 206 --traceback
207 207 --verbose
208 208 --version
209 209 --web-conf
210 210 -6
211 211 -A
212 212 -E
213 213 -R
214 214 -S
215 215 -a
216 216 -d
217 217 -h
218 218 -n
219 219 -p
220 220 -q
221 221 -t
222 222 -v
223 223 -y
224 224
225 225 Show an error if we use --options with an ambiguous abbreviation
226 226 $ hg debugcomplete --options s
227 227 hg: command 's' is ambiguous:
228 228 serve showconfig status summary
229 229 [255]
230 230
231 231 Show all commands + options
232 232 $ hg debugcommands
233 233 add: include, exclude, subrepos, dry-run
234 234 addremove: similarity, subrepos, include, exclude, dry-run
235 235 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
236 236 archive: no-decode, prefix, rev, type, subrepos, include, exclude
237 237 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
238 238 bisect: reset, good, bad, skip, extend, command, noupdate
239 239 bookmarks: force, rev, delete, rename, inactive, list, template
240 240 branch: force, clean, rev
241 241 branches: active, closed, template
242 242 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
243 243 cat: output, rev, decode, include, exclude, template
244 244 clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
245 245 commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
246 246 config: untrusted, edit, local, global, template
247 247 copy: after, force, include, exclude, dry-run
248 248 debugancestor:
249 249 debugapplystreamclonebundle:
250 250 debugbuilddag: mergeable-file, overwritten-file, new-file
251 251 debugbundle: all, part-type, spec
252 252 debugcapabilities:
253 253 debugcheckstate:
254 254 debugcolor: style
255 255 debugcommands:
256 256 debugcomplete: options
257 257 debugcreatestreamclonebundle:
258 258 debugdag: tags, branches, dots, spaces
259 259 debugdata: changelog, manifest, dir
260 260 debugdate: extended
261 261 debugdeltachain: changelog, manifest, dir, template
262 262 debugdirstate: nodates, dates, datesort
263 263 debugdiscovery: old, nonheads, rev, ssh, remotecmd, insecure
264 264 debugdownload: output
265 265 debugextensions: template
266 266 debugfileset: rev, all-files, show-matcher, show-stage
267 267 debugformat: template
268 268 debugfsinfo:
269 269 debuggetbundle: head, common, type
270 270 debugignore:
271 271 debugindex: changelog, manifest, dir, template
272 272 debugindexdot: changelog, manifest, dir
273 273 debugindexstats:
274 274 debuginstall: template
275 275 debugknown:
276 276 debuglabelcomplete:
277 277 debuglocks: force-lock, force-wlock, set-lock, set-wlock
278 278 debugmanifestfulltextcache: clear, add
279 279 debugmergestate:
280 280 debugnamecomplete:
281 281 debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
282 282 debugpathcomplete: full, normal, added, removed
283 283 debugpeer:
284 284 debugpickmergetool: rev, changedelete, include, exclude, tool
285 285 debugpushkey:
286 286 debugpvec:
287 287 debugrebuilddirstate: rev, minimal
288 288 debugrebuildfncache:
289 289 debugrename: rev
290 290 debugrevlog: changelog, manifest, dir, dump
291 291 debugrevlogindex: changelog, manifest, dir, format
292 292 debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
293 293 debugserve: sshstdio, logiofd, logiofile
294 294 debugsetparents:
295 295 debugssl:
296 296 debugsub: rev
297 297 debugsuccessorssets: closest
298 298 debugtemplate: rev, define
299 299 debuguigetpass: prompt
300 300 debuguiprompt: prompt
301 301 debugupdatecaches:
302 302 debugupgraderepo: optimize, run
303 303 debugwalk: include, exclude
304 304 debugwhyunstable:
305 305 debugwireargs: three, four, five, ssh, remotecmd, insecure
306 306 debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
307 307 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
308 308 export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template
309 309 files: rev, print0, include, exclude, template, subrepos
310 310 forget: interactive, include, exclude, dry-run
311 311 graft: rev, base, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run
312 312 grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude
313 313 heads: rev, topo, active, closed, style, template
314 314 help: extension, command, keyword, system
315 315 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
316 316 import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
317 317 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
318 318 init: ssh, remotecmd, insecure
319 319 locate: rev, print0, fullpath, include, exclude
320 320 log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
321 321 manifest: rev, all, template
322 322 merge: force, rev, preview, abort, tool
323 323 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
324 324 parents: rev, style, template
325 325 paths: template
326 326 phase: public, draft, secret, force, rev
327 327 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
328 push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
328 push: force, rev, bookmark, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure
329 329 recover:
330 330 remove: after, force, subrepos, include, exclude, dry-run
331 331 rename: after, force, include, exclude, dry-run
332 332 resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template
333 333 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
334 334 rollback: dry-run, force
335 335 root:
336 336 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos
337 337 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
338 338 summary: remote
339 339 tag: force, local, rev, remove, edit, message, date, user
340 340 tags: template
341 341 tip: patch, git, style, template
342 342 unbundle: update
343 343 update: clean, check, merge, date, rev, tool
344 344 verify:
345 345 version: template
346 346
347 347 $ hg init a
348 348 $ cd a
349 349 $ echo fee > fee
350 350 $ hg ci -q -Amfee
351 351 $ hg tag fee
352 352 $ mkdir fie
353 353 $ echo dead > fie/dead
354 354 $ echo live > fie/live
355 355 $ hg bookmark fo
356 356 $ hg branch -q fie
357 357 $ hg ci -q -Amfie
358 358 $ echo fo > fo
359 359 $ hg branch -qf default
360 360 $ hg ci -q -Amfo
361 361 $ echo Fum > Fum
362 362 $ hg ci -q -AmFum
363 363 $ hg bookmark Fum
364 364
365 365 Test debugpathcomplete
366 366
367 367 $ hg debugpathcomplete f
368 368 fee
369 369 fie
370 370 fo
371 371 $ hg debugpathcomplete -f f
372 372 fee
373 373 fie/dead
374 374 fie/live
375 375 fo
376 376
377 377 $ hg rm Fum
378 378 $ hg debugpathcomplete -r F
379 379 Fum
380 380
381 381 Test debugnamecomplete
382 382
383 383 $ hg debugnamecomplete
384 384 Fum
385 385 default
386 386 fee
387 387 fie
388 388 fo
389 389 tip
390 390 $ hg debugnamecomplete f
391 391 fee
392 392 fie
393 393 fo
394 394
395 395 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
396 396 used for completions in some shells.
397 397
398 398 $ hg debuglabelcomplete
399 399 Fum
400 400 default
401 401 fee
402 402 fie
403 403 fo
404 404 tip
405 405 $ hg debuglabelcomplete f
406 406 fee
407 407 fie
408 408 fo
@@ -1,1561 +1,1752 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [extensions]
3 3 > drawdag=$TESTDIR/drawdag.py
4 4 > phasereport=$TESTDIR/testlib/ext-phase-report.py
5 5 > EOF
6 6
7 7 $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
8 8
9 9 $ mkcommit() {
10 10 > echo "$1" > "$1"
11 11 > hg add "$1"
12 12 > message="$1"
13 13 > shift
14 14 > hg ci -m "$message" $*
15 15 > }
16 16
17 17 $ hg init alpha
18 18 $ cd alpha
19 19 $ mkcommit a-A
20 20 test-debug-phase: new rev 0: x -> 1
21 21 $ mkcommit a-B
22 22 test-debug-phase: new rev 1: x -> 1
23 23 $ mkcommit a-C
24 24 test-debug-phase: new rev 2: x -> 1
25 25 $ mkcommit a-D
26 26 test-debug-phase: new rev 3: x -> 1
27 27 $ hgph
28 28 @ 3 draft a-D - b555f63b6063
29 29 |
30 30 o 2 draft a-C - 54acac6f23ab
31 31 |
32 32 o 1 draft a-B - 548a3d25dbf0
33 33 |
34 34 o 0 draft a-A - 054250a37db4
35 35
36 36
37 37 $ hg init ../beta
38 38 $ hg push -r 1 ../beta
39 39 pushing to ../beta
40 40 searching for changes
41 41 adding changesets
42 42 adding manifests
43 43 adding file changes
44 44 added 2 changesets with 2 changes to 2 files
45 45 test-debug-phase: new rev 0: x -> 0
46 46 test-debug-phase: new rev 1: x -> 0
47 47 test-debug-phase: move rev 0: 1 -> 0
48 48 test-debug-phase: move rev 1: 1 -> 0
49 49 $ hgph
50 50 @ 3 draft a-D - b555f63b6063
51 51 |
52 52 o 2 draft a-C - 54acac6f23ab
53 53 |
54 54 o 1 public a-B - 548a3d25dbf0
55 55 |
56 56 o 0 public a-A - 054250a37db4
57 57
58 58
59 59 $ cd ../beta
60 60 $ hgph
61 61 o 1 public a-B - 548a3d25dbf0
62 62 |
63 63 o 0 public a-A - 054250a37db4
64 64
65 65 $ hg up -q
66 66 $ mkcommit b-A
67 67 test-debug-phase: new rev 2: x -> 1
68 68 $ hgph
69 69 @ 2 draft b-A - f54f1bb90ff3
70 70 |
71 71 o 1 public a-B - 548a3d25dbf0
72 72 |
73 73 o 0 public a-A - 054250a37db4
74 74
75 75 $ hg pull ../alpha
76 76 pulling from ../alpha
77 77 searching for changes
78 78 adding changesets
79 79 adding manifests
80 80 adding file changes
81 81 added 2 changesets with 2 changes to 2 files (+1 heads)
82 82 new changesets 54acac6f23ab:b555f63b6063
83 83 test-debug-phase: new rev 3: x -> 0
84 84 test-debug-phase: new rev 4: x -> 0
85 85 (run 'hg heads' to see heads, 'hg merge' to merge)
86 86 $ hgph
87 87 o 4 public a-D - b555f63b6063
88 88 |
89 89 o 3 public a-C - 54acac6f23ab
90 90 |
91 91 | @ 2 draft b-A - f54f1bb90ff3
92 92 |/
93 93 o 1 public a-B - 548a3d25dbf0
94 94 |
95 95 o 0 public a-A - 054250a37db4
96 96
97 97
98 98 pull did not updated ../alpha state.
99 99 push from alpha to beta should update phase even if nothing is transferred
100 100
101 101 $ cd ../alpha
102 102 $ hgph # not updated by remote pull
103 103 @ 3 draft a-D - b555f63b6063
104 104 |
105 105 o 2 draft a-C - 54acac6f23ab
106 106 |
107 107 o 1 public a-B - 548a3d25dbf0
108 108 |
109 109 o 0 public a-A - 054250a37db4
110 110
111 111 $ hg push -r 2 ../beta
112 112 pushing to ../beta
113 113 searching for changes
114 114 no changes found
115 115 test-debug-phase: move rev 2: 1 -> 0
116 116 [1]
117 117 $ hgph
118 118 @ 3 draft a-D - b555f63b6063
119 119 |
120 120 o 2 public a-C - 54acac6f23ab
121 121 |
122 122 o 1 public a-B - 548a3d25dbf0
123 123 |
124 124 o 0 public a-A - 054250a37db4
125 125
126 126 $ hg push ../beta
127 127 pushing to ../beta
128 128 searching for changes
129 129 no changes found
130 130 test-debug-phase: move rev 3: 1 -> 0
131 131 [1]
132 132 $ hgph
133 133 @ 3 public a-D - b555f63b6063
134 134 |
135 135 o 2 public a-C - 54acac6f23ab
136 136 |
137 137 o 1 public a-B - 548a3d25dbf0
138 138 |
139 139 o 0 public a-A - 054250a37db4
140 140
141 141
142 142 update must update phase of common changeset too
143 143
144 144 $ hg pull ../beta # getting b-A
145 145 pulling from ../beta
146 146 searching for changes
147 147 adding changesets
148 148 adding manifests
149 149 adding file changes
150 150 added 1 changesets with 1 changes to 1 files (+1 heads)
151 151 new changesets f54f1bb90ff3
152 152 test-debug-phase: new rev 4: x -> 0
153 153 (run 'hg heads' to see heads, 'hg merge' to merge)
154 154
155 155 $ cd ../beta
156 156 $ hgph # not updated by remote pull
157 157 o 4 public a-D - b555f63b6063
158 158 |
159 159 o 3 public a-C - 54acac6f23ab
160 160 |
161 161 | @ 2 draft b-A - f54f1bb90ff3
162 162 |/
163 163 o 1 public a-B - 548a3d25dbf0
164 164 |
165 165 o 0 public a-A - 054250a37db4
166 166
167 167 $ hg pull ../alpha
168 168 pulling from ../alpha
169 169 searching for changes
170 170 no changes found
171 171 1 local changesets published
172 172 test-debug-phase: move rev 2: 1 -> 0
173 173 $ hgph
174 174 o 4 public a-D - b555f63b6063
175 175 |
176 176 o 3 public a-C - 54acac6f23ab
177 177 |
178 178 | @ 2 public b-A - f54f1bb90ff3
179 179 |/
180 180 o 1 public a-B - 548a3d25dbf0
181 181 |
182 182 o 0 public a-A - 054250a37db4
183 183
184 184
185 185 Publish configuration option
186 186 ----------------------------
187 187
188 188 Pull
189 189 ````
190 190
191 191 changegroup are added without phase movement
192 192
193 193 $ hg bundle -a ../base.bundle
194 194 5 changesets found
195 195 $ cd ..
196 196 $ hg init mu
197 197 $ cd mu
198 198 $ cat > .hg/hgrc << EOF
199 199 > [phases]
200 200 > publish=0
201 201 > EOF
202 202 $ hg unbundle ../base.bundle
203 203 adding changesets
204 204 adding manifests
205 205 adding file changes
206 206 added 5 changesets with 5 changes to 5 files (+1 heads)
207 207 new changesets 054250a37db4:b555f63b6063 (5 drafts)
208 208 test-debug-phase: new rev 0: x -> 1
209 209 test-debug-phase: new rev 1: x -> 1
210 210 test-debug-phase: new rev 2: x -> 1
211 211 test-debug-phase: new rev 3: x -> 1
212 212 test-debug-phase: new rev 4: x -> 1
213 213 (run 'hg heads' to see heads, 'hg merge' to merge)
214 214 $ hgph
215 215 o 4 draft a-D - b555f63b6063
216 216 |
217 217 o 3 draft a-C - 54acac6f23ab
218 218 |
219 219 | o 2 draft b-A - f54f1bb90ff3
220 220 |/
221 221 o 1 draft a-B - 548a3d25dbf0
222 222 |
223 223 o 0 draft a-A - 054250a37db4
224 224
225 225 $ cd ..
226 226
227 227 Pulling from publish=False to publish=False does not move boundary.
228 228
229 229 $ hg init nu
230 230 $ cd nu
231 231 $ cat > .hg/hgrc << EOF
232 232 > [phases]
233 233 > publish=0
234 234 > EOF
235 235 $ hg pull ../mu -r 54acac6f23ab
236 236 pulling from ../mu
237 237 adding changesets
238 238 adding manifests
239 239 adding file changes
240 240 added 3 changesets with 3 changes to 3 files
241 241 new changesets 054250a37db4:54acac6f23ab (3 drafts)
242 242 test-debug-phase: new rev 0: x -> 1
243 243 test-debug-phase: new rev 1: x -> 1
244 244 test-debug-phase: new rev 2: x -> 1
245 245 (run 'hg update' to get a working copy)
246 246 $ hgph
247 247 o 2 draft a-C - 54acac6f23ab
248 248 |
249 249 o 1 draft a-B - 548a3d25dbf0
250 250 |
251 251 o 0 draft a-A - 054250a37db4
252 252
253 253
254 254 Even for common
255 255
256 256 $ hg pull ../mu -r f54f1bb90ff3
257 257 pulling from ../mu
258 258 searching for changes
259 259 adding changesets
260 260 adding manifests
261 261 adding file changes
262 262 added 1 changesets with 1 changes to 1 files (+1 heads)
263 263 new changesets f54f1bb90ff3 (1 drafts)
264 264 test-debug-phase: new rev 3: x -> 1
265 265 (run 'hg heads' to see heads, 'hg merge' to merge)
266 266 $ hgph
267 267 o 3 draft b-A - f54f1bb90ff3
268 268 |
269 269 | o 2 draft a-C - 54acac6f23ab
270 270 |/
271 271 o 1 draft a-B - 548a3d25dbf0
272 272 |
273 273 o 0 draft a-A - 054250a37db4
274 274
275 275
276 276
277 277 Pulling from Publish=True to Publish=False move boundary in common set.
278 278 we are in nu
279 279
280 280 $ hg pull ../alpha -r b555f63b6063
281 281 pulling from ../alpha
282 282 searching for changes
283 283 adding changesets
284 284 adding manifests
285 285 adding file changes
286 286 added 1 changesets with 1 changes to 1 files
287 287 new changesets b555f63b6063
288 288 3 local changesets published
289 289 test-debug-phase: move rev 0: 1 -> 0
290 290 test-debug-phase: move rev 1: 1 -> 0
291 291 test-debug-phase: move rev 2: 1 -> 0
292 292 test-debug-phase: new rev 4: x -> 0
293 293 (run 'hg update' to get a working copy)
294 294 $ hgph # f54f1bb90ff3 stay draft, not ancestor of -r
295 295 o 4 public a-D - b555f63b6063
296 296 |
297 297 | o 3 draft b-A - f54f1bb90ff3
298 298 | |
299 299 o | 2 public a-C - 54acac6f23ab
300 300 |/
301 301 o 1 public a-B - 548a3d25dbf0
302 302 |
303 303 o 0 public a-A - 054250a37db4
304 304
305 305
306 306 pulling from Publish=False to publish=False with some public
307 307
308 308 $ hg up -q f54f1bb90ff3
309 309 $ mkcommit n-A
310 310 test-debug-phase: new rev 5: x -> 1
311 311 $ mkcommit n-B
312 312 test-debug-phase: new rev 6: x -> 1
313 313 $ hgph
314 314 @ 6 draft n-B - 145e75495359
315 315 |
316 316 o 5 draft n-A - d6bcb4f74035
317 317 |
318 318 | o 4 public a-D - b555f63b6063
319 319 | |
320 320 o | 3 draft b-A - f54f1bb90ff3
321 321 | |
322 322 | o 2 public a-C - 54acac6f23ab
323 323 |/
324 324 o 1 public a-B - 548a3d25dbf0
325 325 |
326 326 o 0 public a-A - 054250a37db4
327 327
328 328 $ cd ../mu
329 329 $ hg pull ../nu
330 330 pulling from ../nu
331 331 searching for changes
332 332 adding changesets
333 333 adding manifests
334 334 adding file changes
335 335 added 2 changesets with 2 changes to 2 files
336 336 new changesets d6bcb4f74035:145e75495359 (2 drafts)
337 337 4 local changesets published
338 338 test-debug-phase: move rev 0: 1 -> 0
339 339 test-debug-phase: move rev 1: 1 -> 0
340 340 test-debug-phase: move rev 3: 1 -> 0
341 341 test-debug-phase: move rev 4: 1 -> 0
342 342 test-debug-phase: new rev 5: x -> 1
343 343 test-debug-phase: new rev 6: x -> 1
344 344 (run 'hg update' to get a working copy)
345 345 $ hgph
346 346 o 6 draft n-B - 145e75495359
347 347 |
348 348 o 5 draft n-A - d6bcb4f74035
349 349 |
350 350 | o 4 public a-D - b555f63b6063
351 351 | |
352 352 | o 3 public a-C - 54acac6f23ab
353 353 | |
354 354 o | 2 draft b-A - f54f1bb90ff3
355 355 |/
356 356 o 1 public a-B - 548a3d25dbf0
357 357 |
358 358 o 0 public a-A - 054250a37db4
359 359
360 360 $ cd ..
361 361
362 362 pulling into publish=True
363 363
364 364 $ cd alpha
365 365 $ hgph
366 366 o 4 public b-A - f54f1bb90ff3
367 367 |
368 368 | @ 3 public a-D - b555f63b6063
369 369 | |
370 370 | o 2 public a-C - 54acac6f23ab
371 371 |/
372 372 o 1 public a-B - 548a3d25dbf0
373 373 |
374 374 o 0 public a-A - 054250a37db4
375 375
376 376 $ hg pull ../mu
377 377 pulling from ../mu
378 378 searching for changes
379 379 adding changesets
380 380 adding manifests
381 381 adding file changes
382 382 added 2 changesets with 2 changes to 2 files
383 383 new changesets d6bcb4f74035:145e75495359 (2 drafts)
384 384 test-debug-phase: new rev 5: x -> 1
385 385 test-debug-phase: new rev 6: x -> 1
386 386 (run 'hg update' to get a working copy)
387 387 $ hgph
388 388 o 6 draft n-B - 145e75495359
389 389 |
390 390 o 5 draft n-A - d6bcb4f74035
391 391 |
392 392 o 4 public b-A - f54f1bb90ff3
393 393 |
394 394 | @ 3 public a-D - b555f63b6063
395 395 | |
396 396 | o 2 public a-C - 54acac6f23ab
397 397 |/
398 398 o 1 public a-B - 548a3d25dbf0
399 399 |
400 400 o 0 public a-A - 054250a37db4
401 401
402 402 $ cd ..
403 403
404 404 pulling back into original repo
405 405
406 406 $ cd nu
407 407 $ hg pull ../alpha
408 408 pulling from ../alpha
409 409 searching for changes
410 410 no changes found
411 411 3 local changesets published
412 412 test-debug-phase: move rev 3: 1 -> 0
413 413 test-debug-phase: move rev 5: 1 -> 0
414 414 test-debug-phase: move rev 6: 1 -> 0
415 415 $ hgph
416 416 @ 6 public n-B - 145e75495359
417 417 |
418 418 o 5 public n-A - d6bcb4f74035
419 419 |
420 420 | o 4 public a-D - b555f63b6063
421 421 | |
422 422 o | 3 public b-A - f54f1bb90ff3
423 423 | |
424 424 | o 2 public a-C - 54acac6f23ab
425 425 |/
426 426 o 1 public a-B - 548a3d25dbf0
427 427 |
428 428 o 0 public a-A - 054250a37db4
429 429
430 430
431 431 Push
432 432 ````
433 433
434 434 (inserted)
435 435
436 436 Test that phase are pushed even when they are nothing to pus
437 437 (this might be tested later bu are very convenient to not alter too much test)
438 438
439 439 Push back to alpha
440 440
441 441 $ hg push ../alpha # from nu
442 442 pushing to ../alpha
443 443 searching for changes
444 444 no changes found
445 445 test-debug-phase: move rev 5: 1 -> 0
446 446 test-debug-phase: move rev 6: 1 -> 0
447 447 [1]
448 448 $ cd ..
449 449 $ cd alpha
450 450 $ hgph
451 451 o 6 public n-B - 145e75495359
452 452 |
453 453 o 5 public n-A - d6bcb4f74035
454 454 |
455 455 o 4 public b-A - f54f1bb90ff3
456 456 |
457 457 | @ 3 public a-D - b555f63b6063
458 458 | |
459 459 | o 2 public a-C - 54acac6f23ab
460 460 |/
461 461 o 1 public a-B - 548a3d25dbf0
462 462 |
463 463 o 0 public a-A - 054250a37db4
464 464
465 465
466 466 (end insertion)
467 467
468 468
469 469 initial setup
470 470
471 471 $ hg log -G # of alpha
472 472 o changeset: 6:145e75495359
473 473 | tag: tip
474 474 | user: test
475 475 | date: Thu Jan 01 00:00:00 1970 +0000
476 476 | summary: n-B
477 477 |
478 478 o changeset: 5:d6bcb4f74035
479 479 | user: test
480 480 | date: Thu Jan 01 00:00:00 1970 +0000
481 481 | summary: n-A
482 482 |
483 483 o changeset: 4:f54f1bb90ff3
484 484 | parent: 1:548a3d25dbf0
485 485 | user: test
486 486 | date: Thu Jan 01 00:00:00 1970 +0000
487 487 | summary: b-A
488 488 |
489 489 | @ changeset: 3:b555f63b6063
490 490 | | user: test
491 491 | | date: Thu Jan 01 00:00:00 1970 +0000
492 492 | | summary: a-D
493 493 | |
494 494 | o changeset: 2:54acac6f23ab
495 495 |/ user: test
496 496 | date: Thu Jan 01 00:00:00 1970 +0000
497 497 | summary: a-C
498 498 |
499 499 o changeset: 1:548a3d25dbf0
500 500 | user: test
501 501 | date: Thu Jan 01 00:00:00 1970 +0000
502 502 | summary: a-B
503 503 |
504 504 o changeset: 0:054250a37db4
505 505 user: test
506 506 date: Thu Jan 01 00:00:00 1970 +0000
507 507 summary: a-A
508 508
509 509 $ mkcommit a-E
510 510 test-debug-phase: new rev 7: x -> 1
511 511 $ mkcommit a-F
512 512 test-debug-phase: new rev 8: x -> 1
513 513 $ mkcommit a-G
514 514 test-debug-phase: new rev 9: x -> 1
515 515 $ hg up d6bcb4f74035 -q
516 516 $ mkcommit a-H
517 517 test-debug-phase: new rev 10: x -> 1
518 518 created new head
519 519 $ hgph
520 520 @ 10 draft a-H - 967b449fbc94
521 521 |
522 522 | o 9 draft a-G - 3e27b6f1eee1
523 523 | |
524 524 | o 8 draft a-F - b740e3e5c05d
525 525 | |
526 526 | o 7 draft a-E - e9f537e46dea
527 527 | |
528 528 +---o 6 public n-B - 145e75495359
529 529 | |
530 530 o | 5 public n-A - d6bcb4f74035
531 531 | |
532 532 o | 4 public b-A - f54f1bb90ff3
533 533 | |
534 534 | o 3 public a-D - b555f63b6063
535 535 | |
536 536 | o 2 public a-C - 54acac6f23ab
537 537 |/
538 538 o 1 public a-B - 548a3d25dbf0
539 539 |
540 540 o 0 public a-A - 054250a37db4
541 541
542 542
543 543 Pulling from bundle does not alter phases of changeset not present in the bundle
544 544
545 545 #if repobundlerepo
546 546 $ hg bundle --base 1 -r 6 -r 3 ../partial-bundle.hg
547 547 5 changesets found
548 548 $ hg pull ../partial-bundle.hg
549 549 pulling from ../partial-bundle.hg
550 550 searching for changes
551 551 no changes found
552 552 $ hgph
553 553 @ 10 draft a-H - 967b449fbc94
554 554 |
555 555 | o 9 draft a-G - 3e27b6f1eee1
556 556 | |
557 557 | o 8 draft a-F - b740e3e5c05d
558 558 | |
559 559 | o 7 draft a-E - e9f537e46dea
560 560 | |
561 561 +---o 6 public n-B - 145e75495359
562 562 | |
563 563 o | 5 public n-A - d6bcb4f74035
564 564 | |
565 565 o | 4 public b-A - f54f1bb90ff3
566 566 | |
567 567 | o 3 public a-D - b555f63b6063
568 568 | |
569 569 | o 2 public a-C - 54acac6f23ab
570 570 |/
571 571 o 1 public a-B - 548a3d25dbf0
572 572 |
573 573 o 0 public a-A - 054250a37db4
574 574
575 575 #endif
576 576
577 577 Pushing to Publish=False (unknown changeset)
578 578
579 579 $ hg push ../mu -r b740e3e5c05d # a-F
580 580 pushing to ../mu
581 581 searching for changes
582 582 adding changesets
583 583 adding manifests
584 584 adding file changes
585 585 added 2 changesets with 2 changes to 2 files
586 586 test-debug-phase: new rev 7: x -> 1
587 587 test-debug-phase: new rev 8: x -> 1
588 588 $ hgph
589 589 @ 10 draft a-H - 967b449fbc94
590 590 |
591 591 | o 9 draft a-G - 3e27b6f1eee1
592 592 | |
593 593 | o 8 draft a-F - b740e3e5c05d
594 594 | |
595 595 | o 7 draft a-E - e9f537e46dea
596 596 | |
597 597 +---o 6 public n-B - 145e75495359
598 598 | |
599 599 o | 5 public n-A - d6bcb4f74035
600 600 | |
601 601 o | 4 public b-A - f54f1bb90ff3
602 602 | |
603 603 | o 3 public a-D - b555f63b6063
604 604 | |
605 605 | o 2 public a-C - 54acac6f23ab
606 606 |/
607 607 o 1 public a-B - 548a3d25dbf0
608 608 |
609 609 o 0 public a-A - 054250a37db4
610 610
611 611
612 612 $ cd ../mu
613 613 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
614 614 > # not ancestor of -r
615 615 o 8 draft a-F - b740e3e5c05d
616 616 |
617 617 o 7 draft a-E - e9f537e46dea
618 618 |
619 619 | o 6 draft n-B - 145e75495359
620 620 | |
621 621 | o 5 draft n-A - d6bcb4f74035
622 622 | |
623 623 o | 4 public a-D - b555f63b6063
624 624 | |
625 625 o | 3 public a-C - 54acac6f23ab
626 626 | |
627 627 | o 2 draft b-A - f54f1bb90ff3
628 628 |/
629 629 o 1 public a-B - 548a3d25dbf0
630 630 |
631 631 o 0 public a-A - 054250a37db4
632 632
633 633
634 634 Pushing to Publish=True (unknown changeset)
635 635
636 636 $ hg push ../beta -r b740e3e5c05d
637 637 pushing to ../beta
638 638 searching for changes
639 639 adding changesets
640 640 adding manifests
641 641 adding file changes
642 642 added 2 changesets with 2 changes to 2 files
643 643 test-debug-phase: new rev 5: x -> 0
644 644 test-debug-phase: new rev 6: x -> 0
645 645 test-debug-phase: move rev 7: 1 -> 0
646 646 test-debug-phase: move rev 8: 1 -> 0
647 647 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
648 648 > # not ancestor of -r
649 649 o 8 public a-F - b740e3e5c05d
650 650 |
651 651 o 7 public a-E - e9f537e46dea
652 652 |
653 653 | o 6 draft n-B - 145e75495359
654 654 | |
655 655 | o 5 draft n-A - d6bcb4f74035
656 656 | |
657 657 o | 4 public a-D - b555f63b6063
658 658 | |
659 659 o | 3 public a-C - 54acac6f23ab
660 660 | |
661 661 | o 2 draft b-A - f54f1bb90ff3
662 662 |/
663 663 o 1 public a-B - 548a3d25dbf0
664 664 |
665 665 o 0 public a-A - 054250a37db4
666 666
667 667
668 668 Pushing to Publish=True (common changeset)
669 669
670 670 $ cd ../beta
671 671 $ hg push ../alpha
672 672 pushing to ../alpha
673 673 searching for changes
674 674 no changes found
675 675 test-debug-phase: move rev 7: 1 -> 0
676 676 test-debug-phase: move rev 8: 1 -> 0
677 677 [1]
678 678 $ hgph
679 679 o 6 public a-F - b740e3e5c05d
680 680 |
681 681 o 5 public a-E - e9f537e46dea
682 682 |
683 683 o 4 public a-D - b555f63b6063
684 684 |
685 685 o 3 public a-C - 54acac6f23ab
686 686 |
687 687 | @ 2 public b-A - f54f1bb90ff3
688 688 |/
689 689 o 1 public a-B - 548a3d25dbf0
690 690 |
691 691 o 0 public a-A - 054250a37db4
692 692
693 693 $ cd ../alpha
694 694 $ hgph
695 695 @ 10 draft a-H - 967b449fbc94
696 696 |
697 697 | o 9 draft a-G - 3e27b6f1eee1
698 698 | |
699 699 | o 8 public a-F - b740e3e5c05d
700 700 | |
701 701 | o 7 public a-E - e9f537e46dea
702 702 | |
703 703 +---o 6 public n-B - 145e75495359
704 704 | |
705 705 o | 5 public n-A - d6bcb4f74035
706 706 | |
707 707 o | 4 public b-A - f54f1bb90ff3
708 708 | |
709 709 | o 3 public a-D - b555f63b6063
710 710 | |
711 711 | o 2 public a-C - 54acac6f23ab
712 712 |/
713 713 o 1 public a-B - 548a3d25dbf0
714 714 |
715 715 o 0 public a-A - 054250a37db4
716 716
717 717
718 718 Pushing to Publish=False (common changeset that change phase + unknown one)
719 719
720 720 $ hg push ../mu -r 967b449fbc94 -f
721 721 pushing to ../mu
722 722 searching for changes
723 723 adding changesets
724 724 adding manifests
725 725 adding file changes
726 726 added 1 changesets with 1 changes to 1 files (+1 heads)
727 727 test-debug-phase: move rev 2: 1 -> 0
728 728 test-debug-phase: move rev 5: 1 -> 0
729 729 test-debug-phase: new rev 9: x -> 1
730 730 $ hgph
731 731 @ 10 draft a-H - 967b449fbc94
732 732 |
733 733 | o 9 draft a-G - 3e27b6f1eee1
734 734 | |
735 735 | o 8 public a-F - b740e3e5c05d
736 736 | |
737 737 | o 7 public a-E - e9f537e46dea
738 738 | |
739 739 +---o 6 public n-B - 145e75495359
740 740 | |
741 741 o | 5 public n-A - d6bcb4f74035
742 742 | |
743 743 o | 4 public b-A - f54f1bb90ff3
744 744 | |
745 745 | o 3 public a-D - b555f63b6063
746 746 | |
747 747 | o 2 public a-C - 54acac6f23ab
748 748 |/
749 749 o 1 public a-B - 548a3d25dbf0
750 750 |
751 751 o 0 public a-A - 054250a37db4
752 752
753 753 $ cd ../mu
754 754 $ hgph # d6bcb4f74035 should have changed phase
755 755 > # 145e75495359 is still draft. not ancestor of -r
756 756 o 9 draft a-H - 967b449fbc94
757 757 |
758 758 | o 8 public a-F - b740e3e5c05d
759 759 | |
760 760 | o 7 public a-E - e9f537e46dea
761 761 | |
762 762 +---o 6 draft n-B - 145e75495359
763 763 | |
764 764 o | 5 public n-A - d6bcb4f74035
765 765 | |
766 766 | o 4 public a-D - b555f63b6063
767 767 | |
768 768 | o 3 public a-C - 54acac6f23ab
769 769 | |
770 770 o | 2 public b-A - f54f1bb90ff3
771 771 |/
772 772 o 1 public a-B - 548a3d25dbf0
773 773 |
774 774 o 0 public a-A - 054250a37db4
775 775
776 776
777 777
778 778 Pushing to Publish=True (common changeset from publish=False)
779 779
780 780 (in mu)
781 781 $ hg push ../alpha
782 782 pushing to ../alpha
783 783 searching for changes
784 784 no changes found
785 785 test-debug-phase: move rev 10: 1 -> 0
786 786 test-debug-phase: move rev 6: 1 -> 0
787 787 test-debug-phase: move rev 9: 1 -> 0
788 788 [1]
789 789 $ hgph
790 790 o 9 public a-H - 967b449fbc94
791 791 |
792 792 | o 8 public a-F - b740e3e5c05d
793 793 | |
794 794 | o 7 public a-E - e9f537e46dea
795 795 | |
796 796 +---o 6 public n-B - 145e75495359
797 797 | |
798 798 o | 5 public n-A - d6bcb4f74035
799 799 | |
800 800 | o 4 public a-D - b555f63b6063
801 801 | |
802 802 | o 3 public a-C - 54acac6f23ab
803 803 | |
804 804 o | 2 public b-A - f54f1bb90ff3
805 805 |/
806 806 o 1 public a-B - 548a3d25dbf0
807 807 |
808 808 o 0 public a-A - 054250a37db4
809 809
810 810 $ hgph -R ../alpha # a-H should have been synced to 0
811 811 @ 10 public a-H - 967b449fbc94
812 812 |
813 813 | o 9 draft a-G - 3e27b6f1eee1
814 814 | |
815 815 | o 8 public a-F - b740e3e5c05d
816 816 | |
817 817 | o 7 public a-E - e9f537e46dea
818 818 | |
819 819 +---o 6 public n-B - 145e75495359
820 820 | |
821 821 o | 5 public n-A - d6bcb4f74035
822 822 | |
823 823 o | 4 public b-A - f54f1bb90ff3
824 824 | |
825 825 | o 3 public a-D - b555f63b6063
826 826 | |
827 827 | o 2 public a-C - 54acac6f23ab
828 828 |/
829 829 o 1 public a-B - 548a3d25dbf0
830 830 |
831 831 o 0 public a-A - 054250a37db4
832 832
833 833
834 834
835 835 Bare push with next changeset and common changeset needing sync (issue3575)
836 836
837 837 (reset some stat on remote repo to avoid confusing other tests)
838 838
839 839 $ hg -R ../alpha --config extensions.strip= strip --no-backup 967b449fbc94
840 840 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
841 841 $ hg phase --force --draft b740e3e5c05d 967b449fbc94
842 842 test-debug-phase: move rev 8: 0 -> 1
843 843 test-debug-phase: move rev 9: 0 -> 1
844 844 $ hg push -fv ../alpha
845 845 pushing to ../alpha
846 846 searching for changes
847 847 1 changesets found
848 848 uncompressed size of bundle content:
849 849 178 (changelog)
850 850 165 (manifests)
851 851 131 a-H
852 852 adding changesets
853 853 adding manifests
854 854 adding file changes
855 855 added 1 changesets with 1 changes to 1 files (+1 heads)
856 856 test-debug-phase: new rev 10: x -> 0
857 857 test-debug-phase: move rev 8: 1 -> 0
858 858 test-debug-phase: move rev 9: 1 -> 0
859 859 $ hgph
860 860 o 9 public a-H - 967b449fbc94
861 861 |
862 862 | o 8 public a-F - b740e3e5c05d
863 863 | |
864 864 | o 7 public a-E - e9f537e46dea
865 865 | |
866 866 +---o 6 public n-B - 145e75495359
867 867 | |
868 868 o | 5 public n-A - d6bcb4f74035
869 869 | |
870 870 | o 4 public a-D - b555f63b6063
871 871 | |
872 872 | o 3 public a-C - 54acac6f23ab
873 873 | |
874 874 o | 2 public b-A - f54f1bb90ff3
875 875 |/
876 876 o 1 public a-B - 548a3d25dbf0
877 877 |
878 878 o 0 public a-A - 054250a37db4
879 879
880 880
881 881 $ hg -R ../alpha update 967b449fbc94 #for latter test consistency
882 882 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
883 883 $ hgph -R ../alpha
884 884 @ 10 public a-H - 967b449fbc94
885 885 |
886 886 | o 9 draft a-G - 3e27b6f1eee1
887 887 | |
888 888 | o 8 public a-F - b740e3e5c05d
889 889 | |
890 890 | o 7 public a-E - e9f537e46dea
891 891 | |
892 892 +---o 6 public n-B - 145e75495359
893 893 | |
894 894 o | 5 public n-A - d6bcb4f74035
895 895 | |
896 896 o | 4 public b-A - f54f1bb90ff3
897 897 | |
898 898 | o 3 public a-D - b555f63b6063
899 899 | |
900 900 | o 2 public a-C - 54acac6f23ab
901 901 |/
902 902 o 1 public a-B - 548a3d25dbf0
903 903 |
904 904 o 0 public a-A - 054250a37db4
905 905
906 906
907 907 Discovery locally secret changeset on a remote repository:
908 908
909 909 - should make it non-secret
910 910
911 911 $ cd ../alpha
912 912 $ mkcommit A-secret --config phases.new-commit=2
913 913 test-debug-phase: new rev 11: x -> 2
914 914 $ hgph
915 915 @ 11 secret A-secret - 435b5d83910c
916 916 |
917 917 o 10 public a-H - 967b449fbc94
918 918 |
919 919 | o 9 draft a-G - 3e27b6f1eee1
920 920 | |
921 921 | o 8 public a-F - b740e3e5c05d
922 922 | |
923 923 | o 7 public a-E - e9f537e46dea
924 924 | |
925 925 +---o 6 public n-B - 145e75495359
926 926 | |
927 927 o | 5 public n-A - d6bcb4f74035
928 928 | |
929 929 o | 4 public b-A - f54f1bb90ff3
930 930 | |
931 931 | o 3 public a-D - b555f63b6063
932 932 | |
933 933 | o 2 public a-C - 54acac6f23ab
934 934 |/
935 935 o 1 public a-B - 548a3d25dbf0
936 936 |
937 937 o 0 public a-A - 054250a37db4
938 938
939 939 $ hg bundle --base 'parents(.)' -r . ../secret-bundle.hg
940 940 1 changesets found
941 941 $ hg -R ../mu unbundle ../secret-bundle.hg
942 942 adding changesets
943 943 adding manifests
944 944 adding file changes
945 945 added 1 changesets with 1 changes to 1 files
946 946 new changesets 435b5d83910c (1 drafts)
947 947 test-debug-phase: new rev 10: x -> 1
948 948 (run 'hg update' to get a working copy)
949 949 $ hgph -R ../mu
950 950 o 10 draft A-secret - 435b5d83910c
951 951 |
952 952 o 9 public a-H - 967b449fbc94
953 953 |
954 954 | o 8 public a-F - b740e3e5c05d
955 955 | |
956 956 | o 7 public a-E - e9f537e46dea
957 957 | |
958 958 +---o 6 public n-B - 145e75495359
959 959 | |
960 960 o | 5 public n-A - d6bcb4f74035
961 961 | |
962 962 | o 4 public a-D - b555f63b6063
963 963 | |
964 964 | o 3 public a-C - 54acac6f23ab
965 965 | |
966 966 o | 2 public b-A - f54f1bb90ff3
967 967 |/
968 968 o 1 public a-B - 548a3d25dbf0
969 969 |
970 970 o 0 public a-A - 054250a37db4
971 971
972 972 $ hg pull ../mu
973 973 pulling from ../mu
974 974 searching for changes
975 975 no changes found
976 976 test-debug-phase: move rev 11: 2 -> 1
977 977 $ hgph
978 978 @ 11 draft A-secret - 435b5d83910c
979 979 |
980 980 o 10 public a-H - 967b449fbc94
981 981 |
982 982 | o 9 draft a-G - 3e27b6f1eee1
983 983 | |
984 984 | o 8 public a-F - b740e3e5c05d
985 985 | |
986 986 | o 7 public a-E - e9f537e46dea
987 987 | |
988 988 +---o 6 public n-B - 145e75495359
989 989 | |
990 990 o | 5 public n-A - d6bcb4f74035
991 991 | |
992 992 o | 4 public b-A - f54f1bb90ff3
993 993 | |
994 994 | o 3 public a-D - b555f63b6063
995 995 | |
996 996 | o 2 public a-C - 54acac6f23ab
997 997 |/
998 998 o 1 public a-B - 548a3d25dbf0
999 999 |
1000 1000 o 0 public a-A - 054250a37db4
1001 1001
1002 1002
1003 1003 pushing a locally public and draft changesets remotely secret should make them
1004 1004 appear on the remote side.
1005 1005
1006 1006 $ hg -R ../mu phase --secret --force 967b449fbc94
1007 1007 test-debug-phase: move rev 9: 0 -> 2
1008 1008 test-debug-phase: move rev 10: 1 -> 2
1009 1009 $ hg push -r 435b5d83910c ../mu
1010 1010 pushing to ../mu
1011 1011 searching for changes
1012 1012 abort: push creates new remote head 435b5d83910c!
1013 1013 (merge or see 'hg help push' for details about pushing new heads)
1014 1014 [255]
1015 1015 $ hg push -fr 435b5d83910c ../mu # because the push will create new visible head
1016 1016 pushing to ../mu
1017 1017 searching for changes
1018 1018 adding changesets
1019 1019 adding manifests
1020 1020 adding file changes
1021 1021 added 0 changesets with 0 changes to 2 files
1022 1022 test-debug-phase: move rev 9: 2 -> 0
1023 1023 test-debug-phase: move rev 10: 2 -> 1
1024 1024 $ hgph -R ../mu
1025 1025 o 10 draft A-secret - 435b5d83910c
1026 1026 |
1027 1027 o 9 public a-H - 967b449fbc94
1028 1028 |
1029 1029 | o 8 public a-F - b740e3e5c05d
1030 1030 | |
1031 1031 | o 7 public a-E - e9f537e46dea
1032 1032 | |
1033 1033 +---o 6 public n-B - 145e75495359
1034 1034 | |
1035 1035 o | 5 public n-A - d6bcb4f74035
1036 1036 | |
1037 1037 | o 4 public a-D - b555f63b6063
1038 1038 | |
1039 1039 | o 3 public a-C - 54acac6f23ab
1040 1040 | |
1041 1041 o | 2 public b-A - f54f1bb90ff3
1042 1042 |/
1043 1043 o 1 public a-B - 548a3d25dbf0
1044 1044 |
1045 1045 o 0 public a-A - 054250a37db4
1046 1046
1047 1047
1048 1048 pull new changeset with common draft locally
1049 1049
1050 1050 $ hg up -q 967b449fbc94 # create a new root for draft
1051 1051 $ mkcommit 'alpha-more'
1052 1052 test-debug-phase: new rev 12: x -> 1
1053 1053 created new head
1054 1054 $ hg push -fr . ../mu
1055 1055 pushing to ../mu
1056 1056 searching for changes
1057 1057 adding changesets
1058 1058 adding manifests
1059 1059 adding file changes
1060 1060 added 1 changesets with 1 changes to 1 files (+1 heads)
1061 1061 test-debug-phase: new rev 11: x -> 1
1062 1062 $ cd ../mu
1063 1063 $ hg phase --secret --force 1c5cfd894796
1064 1064 test-debug-phase: move rev 11: 1 -> 2
1065 1065 $ hg up -q 435b5d83910c
1066 1066 $ mkcommit 'mu-more'
1067 1067 test-debug-phase: new rev 12: x -> 1
1068 1068 $ cd ../alpha
1069 1069 $ hg pull ../mu
1070 1070 pulling from ../mu
1071 1071 searching for changes
1072 1072 adding changesets
1073 1073 adding manifests
1074 1074 adding file changes
1075 1075 added 1 changesets with 1 changes to 1 files
1076 1076 new changesets 5237fb433fc8 (1 drafts)
1077 1077 test-debug-phase: new rev 13: x -> 1
1078 1078 (run 'hg update' to get a working copy)
1079 1079 $ hgph
1080 1080 o 13 draft mu-more - 5237fb433fc8
1081 1081 |
1082 1082 | @ 12 draft alpha-more - 1c5cfd894796
1083 1083 | |
1084 1084 o | 11 draft A-secret - 435b5d83910c
1085 1085 |/
1086 1086 o 10 public a-H - 967b449fbc94
1087 1087 |
1088 1088 | o 9 draft a-G - 3e27b6f1eee1
1089 1089 | |
1090 1090 | o 8 public a-F - b740e3e5c05d
1091 1091 | |
1092 1092 | o 7 public a-E - e9f537e46dea
1093 1093 | |
1094 1094 +---o 6 public n-B - 145e75495359
1095 1095 | |
1096 1096 o | 5 public n-A - d6bcb4f74035
1097 1097 | |
1098 1098 o | 4 public b-A - f54f1bb90ff3
1099 1099 | |
1100 1100 | o 3 public a-D - b555f63b6063
1101 1101 | |
1102 1102 | o 2 public a-C - 54acac6f23ab
1103 1103 |/
1104 1104 o 1 public a-B - 548a3d25dbf0
1105 1105 |
1106 1106 o 0 public a-A - 054250a37db4
1107 1107
1108 1108
1109 1109 Test that test are properly ignored on remote event when existing locally
1110 1110
1111 1111 $ cd ..
1112 1112 $ hg clone -qU -r b555f63b6063 -r f54f1bb90ff3 beta gamma
1113 1113 test-debug-phase: new rev 0: x -> 0
1114 1114 test-debug-phase: new rev 1: x -> 0
1115 1115 test-debug-phase: new rev 2: x -> 0
1116 1116 test-debug-phase: new rev 3: x -> 0
1117 1117 test-debug-phase: new rev 4: x -> 0
1118 1118
1119 1119 # pathological case are
1120 1120 #
1121 1121 # * secret remotely
1122 1122 # * known locally
1123 1123 # * repo have uncommon changeset
1124 1124
1125 1125 $ hg -R beta phase --secret --force f54f1bb90ff3
1126 1126 test-debug-phase: move rev 2: 0 -> 2
1127 1127 $ hg -R gamma phase --draft --force f54f1bb90ff3
1128 1128 test-debug-phase: move rev 2: 0 -> 1
1129 1129
1130 1130 $ cd gamma
1131 1131 $ hg pull ../beta
1132 1132 pulling from ../beta
1133 1133 searching for changes
1134 1134 adding changesets
1135 1135 adding manifests
1136 1136 adding file changes
1137 1137 added 2 changesets with 2 changes to 2 files
1138 1138 new changesets e9f537e46dea:b740e3e5c05d
1139 1139 test-debug-phase: new rev 5: x -> 0
1140 1140 test-debug-phase: new rev 6: x -> 0
1141 1141 (run 'hg update' to get a working copy)
1142 1142 $ hg phase f54f1bb90ff3
1143 1143 2: draft
1144 1144
1145 1145 same over the wire
1146 1146
1147 1147 $ cd ../beta
1148 1148 $ hg serve -p $HGPORT -d --pid-file=../beta.pid -E ../beta-error.log
1149 1149 $ cat ../beta.pid >> $DAEMON_PIDS
1150 1150 $ cd ../gamma
1151 1151
1152 1152 $ hg pull http://localhost:$HGPORT/ # bundle2+
1153 1153 pulling from http://localhost:$HGPORT/
1154 1154 searching for changes
1155 1155 no changes found
1156 1156 $ hg phase f54f1bb90ff3
1157 1157 2: draft
1158 1158
1159 1159 enforce bundle1
1160 1160
1161 1161 $ hg pull http://localhost:$HGPORT/ --config devel.legacy.exchange=bundle1
1162 1162 pulling from http://localhost:$HGPORT/
1163 1163 searching for changes
1164 1164 no changes found
1165 1165 $ hg phase f54f1bb90ff3
1166 1166 2: draft
1167 1167
1168 1168 check that secret local on both side are not synced to public
1169 1169
1170 1170 $ hg push -r b555f63b6063 http://localhost:$HGPORT/
1171 1171 pushing to http://localhost:$HGPORT/
1172 1172 searching for changes
1173 1173 no changes found
1174 1174 [1]
1175 1175 $ hg phase f54f1bb90ff3
1176 1176 2: draft
1177 1177
1178 1178 $ killdaemons.py
1179 1179
1180 1180 put the changeset in the draft state again
1181 1181 (first test after this one expect to be able to copy)
1182 1182
1183 1183 $ cd ..
1184 1184
1185 1185
1186 1186 Test Clone behavior
1187 1187
1188 1188 A. Clone without secret changeset
1189 1189
1190 1190 1. cloning non-publishing repository
1191 1191 (Phase should be preserved)
1192 1192
1193 1193 # make sure there is no secret so we can use a copy clone
1194 1194
1195 1195 $ hg -R mu phase --draft 'secret()'
1196 1196 test-debug-phase: move rev 11: 2 -> 1
1197 1197
1198 1198 $ hg clone -U mu Tau
1199 1199 $ hgph -R Tau
1200 1200 o 12 draft mu-more - 5237fb433fc8
1201 1201 |
1202 1202 | o 11 draft alpha-more - 1c5cfd894796
1203 1203 | |
1204 1204 o | 10 draft A-secret - 435b5d83910c
1205 1205 |/
1206 1206 o 9 public a-H - 967b449fbc94
1207 1207 |
1208 1208 | o 8 public a-F - b740e3e5c05d
1209 1209 | |
1210 1210 | o 7 public a-E - e9f537e46dea
1211 1211 | |
1212 1212 +---o 6 public n-B - 145e75495359
1213 1213 | |
1214 1214 o | 5 public n-A - d6bcb4f74035
1215 1215 | |
1216 1216 | o 4 public a-D - b555f63b6063
1217 1217 | |
1218 1218 | o 3 public a-C - 54acac6f23ab
1219 1219 | |
1220 1220 o | 2 public b-A - f54f1bb90ff3
1221 1221 |/
1222 1222 o 1 public a-B - 548a3d25dbf0
1223 1223 |
1224 1224 o 0 public a-A - 054250a37db4
1225 1225
1226 1226
1227 1227 2. cloning publishing repository
1228 1228
1229 1229 (everything should be public)
1230 1230
1231 1231 $ hg clone -U alpha Upsilon
1232 1232 $ hgph -R Upsilon
1233 1233 o 13 public mu-more - 5237fb433fc8
1234 1234 |
1235 1235 | o 12 public alpha-more - 1c5cfd894796
1236 1236 | |
1237 1237 o | 11 public A-secret - 435b5d83910c
1238 1238 |/
1239 1239 o 10 public a-H - 967b449fbc94
1240 1240 |
1241 1241 | o 9 public a-G - 3e27b6f1eee1
1242 1242 | |
1243 1243 | o 8 public a-F - b740e3e5c05d
1244 1244 | |
1245 1245 | o 7 public a-E - e9f537e46dea
1246 1246 | |
1247 1247 +---o 6 public n-B - 145e75495359
1248 1248 | |
1249 1249 o | 5 public n-A - d6bcb4f74035
1250 1250 | |
1251 1251 o | 4 public b-A - f54f1bb90ff3
1252 1252 | |
1253 1253 | o 3 public a-D - b555f63b6063
1254 1254 | |
1255 1255 | o 2 public a-C - 54acac6f23ab
1256 1256 |/
1257 1257 o 1 public a-B - 548a3d25dbf0
1258 1258 |
1259 1259 o 0 public a-A - 054250a37db4
1260 1260
1261 1261 #if unix-permissions no-root
1262 1262
1263 1263 Pushing From an unlockable repo
1264 1264 --------------------------------
1265 1265 (issue3684)
1266 1266
1267 1267 Unability to lock the source repo should not prevent the push. It will prevent
1268 1268 the retrieval of remote phase during push. For example, pushing to a publishing
1269 1269 server won't turn changeset public.
1270 1270
1271 1271 1. Test that push is not prevented
1272 1272
1273 1273 $ hg init Phi
1274 1274 $ cd Upsilon
1275 1275 $ chmod -R -w .hg
1276 1276 $ hg push ../Phi
1277 1277 pushing to ../Phi
1278 1278 searching for changes
1279 1279 adding changesets
1280 1280 adding manifests
1281 1281 adding file changes
1282 1282 added 14 changesets with 14 changes to 14 files (+3 heads)
1283 1283 test-debug-phase: new rev 0: x -> 0
1284 1284 test-debug-phase: new rev 1: x -> 0
1285 1285 test-debug-phase: new rev 2: x -> 0
1286 1286 test-debug-phase: new rev 3: x -> 0
1287 1287 test-debug-phase: new rev 4: x -> 0
1288 1288 test-debug-phase: new rev 5: x -> 0
1289 1289 test-debug-phase: new rev 6: x -> 0
1290 1290 test-debug-phase: new rev 7: x -> 0
1291 1291 test-debug-phase: new rev 8: x -> 0
1292 1292 test-debug-phase: new rev 9: x -> 0
1293 1293 test-debug-phase: new rev 10: x -> 0
1294 1294 test-debug-phase: new rev 11: x -> 0
1295 1295 test-debug-phase: new rev 12: x -> 0
1296 1296 test-debug-phase: new rev 13: x -> 0
1297 1297 $ chmod -R +w .hg
1298 1298
1299 1299 2. Test that failed phases movement are reported
1300 1300
1301 1301 $ hg phase --force --draft 3
1302 1302 test-debug-phase: move rev 3: 0 -> 1
1303 1303 test-debug-phase: move rev 7: 0 -> 1
1304 1304 test-debug-phase: move rev 8: 0 -> 1
1305 1305 test-debug-phase: move rev 9: 0 -> 1
1306 1306 $ chmod -R -w .hg
1307 1307 $ hg push ../Phi
1308 1308 pushing to ../Phi
1309 1309 searching for changes
1310 1310 no changes found
1311 1311 cannot lock source repo, skipping local public phase update
1312 1312 [1]
1313 1313 $ chmod -R +w .hg
1314 1314
1315 1315 3. Test that push is prevented if lock was already acquired (not a permission
1316 1316 error, but EEXIST)
1317 1317
1318 1318 $ touch .hg/store/lock
1319 1319 $ hg push ../Phi --config ui.timeout=1
1320 1320 pushing to ../Phi
1321 1321 waiting for lock on repository $TESTTMP/Upsilon held by ''
1322 1322 abort: repository $TESTTMP/Upsilon: timed out waiting for lock held by ''
1323 1323 (lock might be very busy)
1324 1324 [255]
1325 1325 $ rm .hg/store/lock
1326 1326
1327 1327 $ cd ..
1328 1328
1329 1329 #endif
1330 1330
1331 1331 Test that clone behaves like pull and doesn't publish changesets as plain push
1332 1332 does. The conditional output accounts for changes in the conditional block
1333 1333 above.
1334 1334
1335 1335 #if unix-permissions no-root
1336 1336 $ hg -R Upsilon phase -q --force --draft 2
1337 1337 test-debug-phase: move rev 2: 0 -> 1
1338 1338 #else
1339 1339 $ hg -R Upsilon phase -q --force --draft 2
1340 1340 test-debug-phase: move rev 2: 0 -> 1
1341 1341 test-debug-phase: move rev 3: 0 -> 1
1342 1342 test-debug-phase: move rev 7: 0 -> 1
1343 1343 test-debug-phase: move rev 8: 0 -> 1
1344 1344 test-debug-phase: move rev 9: 0 -> 1
1345 1345 #endif
1346 1346
1347 1347 $ hg clone -q Upsilon Pi -r 7
1348 1348 test-debug-phase: new rev 0: x -> 0
1349 1349 test-debug-phase: new rev 1: x -> 0
1350 1350 test-debug-phase: new rev 2: x -> 0
1351 1351 test-debug-phase: new rev 3: x -> 0
1352 1352 test-debug-phase: new rev 4: x -> 0
1353 1353 $ hgph Upsilon -r 'min(draft())'
1354 1354 o 2 draft a-C - 54acac6f23ab
1355 1355 |
1356 1356 ~
1357 1357
1358 1358 $ hg -R Upsilon push Pi -r 7
1359 1359 pushing to Pi
1360 1360 searching for changes
1361 1361 no changes found
1362 1362 test-debug-phase: move rev 2: 1 -> 0
1363 1363 test-debug-phase: move rev 3: 1 -> 0
1364 1364 test-debug-phase: move rev 7: 1 -> 0
1365 1365 [1]
1366 1366 $ hgph Upsilon -r 'min(draft())'
1367 1367 o 8 draft a-F - b740e3e5c05d
1368 1368 |
1369 1369 ~
1370 1370
1371 1371 $ hg -R Upsilon push Pi -r 8
1372 1372 pushing to Pi
1373 1373 searching for changes
1374 1374 adding changesets
1375 1375 adding manifests
1376 1376 adding file changes
1377 1377 added 1 changesets with 1 changes to 1 files
1378 1378 test-debug-phase: new rev 5: x -> 0
1379 1379 test-debug-phase: move rev 8: 1 -> 0
1380 1380
1381 1381 $ hgph Upsilon -r 'min(draft())'
1382 1382 o 9 draft a-G - 3e27b6f1eee1
1383 1383 |
1384 1384 ~
1385 1385
1386 1386 Test phases exchange when a phaseroot is on a merge
1387 1387
1388 1388 $ hg init mergetest
1389 1389 $ cd mergetest
1390 1390 > cat > .hg/hgrc << EOF
1391 1391 > [phases]
1392 1392 > publish = false
1393 1393 > EOF
1394 1394
1395 1395 $ hg debugdrawdag << EOF
1396 1396 > E Z
1397 1397 > |\|
1398 1398 > D Y
1399 1399 > | |
1400 1400 > C X
1401 1401 > |/
1402 1402 > B
1403 1403 > |
1404 1404 > A
1405 1405 > EOF
1406 1406 test-debug-phase: new rev 0: x -> 1
1407 1407 test-debug-phase: new rev 1: x -> 1
1408 1408 test-debug-phase: new rev 2: x -> 1
1409 1409 test-debug-phase: new rev 3: x -> 1
1410 1410 test-debug-phase: new rev 4: x -> 1
1411 1411 test-debug-phase: new rev 5: x -> 1
1412 1412 test-debug-phase: new rev 6: x -> 1
1413 1413 test-debug-phase: new rev 7: x -> 1
1414 1414
1415 1415 $ hg phase --public -r D
1416 1416 test-debug-phase: move rev 0: 1 -> 0
1417 1417 test-debug-phase: move rev 1: 1 -> 0
1418 1418 test-debug-phase: move rev 2: 1 -> 0
1419 1419 test-debug-phase: move rev 4: 1 -> 0
1420 1420
1421 1421 $ hg log -G -T '{shortest(node, 5)} {phase}'
1422 1422 o bb947 draft
1423 1423 |
1424 1424 | o 5ac28 draft
1425 1425 |/|
1426 1426 o | 13b7b draft
1427 1427 | |
1428 1428 | o f5853 public
1429 1429 | |
1430 1430 o | c67c4 draft
1431 1431 | |
1432 1432 | o 26805 public
1433 1433 |/
1434 1434 o 11247 public
1435 1435 |
1436 1436 o 426ba public
1437 1437
1438 1438 $ cd ..
1439 1439
1440 1440 Works with default settings
1441 1441
1442 1442 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1443 1443 $ cat hg.pid >> $DAEMON_PIDS
1444 1444
1445 1445 $ hg clone -U http://localhost:$HGPORT mergetest-normal
1446 1446 requesting all changes
1447 1447 adding changesets
1448 1448 adding manifests
1449 1449 adding file changes
1450 1450 added 8 changesets with 7 changes to 7 files (+1 heads)
1451 1451 new changesets 426bada5c675:bb94757e651a (4 drafts)
1452 1452 test-debug-phase: new rev 0: x -> 0
1453 1453 test-debug-phase: new rev 1: x -> 0
1454 1454 test-debug-phase: new rev 2: x -> 0
1455 1455 test-debug-phase: new rev 3: x -> 1
1456 1456 test-debug-phase: new rev 4: x -> 0
1457 1457 test-debug-phase: new rev 5: x -> 1
1458 1458 test-debug-phase: new rev 6: x -> 1
1459 1459 test-debug-phase: new rev 7: x -> 1
1460 1460
1461 1461 $ hg -R mergetest-normal log -G -T '{shortest(node, 5)} {phase}'
1462 1462 o bb947 draft
1463 1463 |
1464 1464 | o 5ac28 draft
1465 1465 |/|
1466 1466 o | 13b7b draft
1467 1467 | |
1468 1468 | o f5853 public
1469 1469 | |
1470 1470 o | c67c4 draft
1471 1471 | |
1472 1472 | o 26805 public
1473 1473 |/
1474 1474 o 11247 public
1475 1475 |
1476 1476 o 426ba public
1477 1477
1478 1478 $ killdaemons.py
1479 1479
1480 1480 With legacy listkeys over bundle2
1481 1481 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1482 1482 of phase heads computation)
1483 1483
1484 1484 $ hg -R mergetest --config devel.legacy.exchange=phases serve -p $HGPORT -d --pid-file=hg.pid
1485 1485 $ cat hg.pid >> $DAEMON_PIDS
1486 1486
1487 1487 $ hg clone -U http://localhost:$HGPORT mergetest-nobinarypart
1488 1488 requesting all changes
1489 1489 adding changesets
1490 1490 adding manifests
1491 1491 adding file changes
1492 1492 added 8 changesets with 7 changes to 7 files (+1 heads)
1493 1493 new changesets 426bada5c675:bb94757e651a (4 drafts)
1494 1494 test-debug-phase: new rev 0: x -> 0
1495 1495 test-debug-phase: new rev 1: x -> 0
1496 1496 test-debug-phase: new rev 2: x -> 0
1497 1497 test-debug-phase: new rev 3: x -> 1
1498 1498 test-debug-phase: new rev 4: x -> 0
1499 1499 test-debug-phase: new rev 5: x -> 1
1500 1500 test-debug-phase: new rev 6: x -> 1
1501 1501 test-debug-phase: new rev 7: x -> 1
1502 1502
1503 1503 $ hg -R mergetest-nobinarypart log -G -T '{shortest(node, 5)} {phase}'
1504 1504 o bb947 draft
1505 1505 |
1506 1506 | o 5ac28 draft
1507 1507 |/|
1508 1508 o | 13b7b draft
1509 1509 | |
1510 1510 | o f5853 public
1511 1511 | |
1512 1512 o | c67c4 draft
1513 1513 | |
1514 1514 | o 26805 public
1515 1515 |/
1516 1516 o 11247 public
1517 1517 |
1518 1518 o 426ba public
1519 1519
1520 1520 $ killdaemons.py
1521 1521
1522 1522 Without bundle2
1523 1523 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1524 1524 of phase heads computation)
1525 1525
1526 1526 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1527 1527 $ cat hg.pid >> $DAEMON_PIDS
1528 1528
1529 1529 $ hg --config devel.legacy.exchange=bundle1 clone -U http://localhost:$HGPORT mergetest-bundle1
1530 1530 requesting all changes
1531 1531 adding changesets
1532 1532 adding manifests
1533 1533 adding file changes
1534 1534 added 8 changesets with 7 changes to 7 files (+1 heads)
1535 1535 new changesets 426bada5c675:bb94757e651a (4 drafts)
1536 1536 test-debug-phase: new rev 0: x -> 0
1537 1537 test-debug-phase: new rev 1: x -> 0
1538 1538 test-debug-phase: new rev 2: x -> 0
1539 1539 test-debug-phase: new rev 3: x -> 1
1540 1540 test-debug-phase: new rev 4: x -> 0
1541 1541 test-debug-phase: new rev 5: x -> 1
1542 1542 test-debug-phase: new rev 6: x -> 1
1543 1543 test-debug-phase: new rev 7: x -> 1
1544 1544
1545 1545 $ hg -R mergetest-bundle1 log -G -T '{shortest(node, 5)} {phase}'
1546 1546 o bb947 draft
1547 1547 |
1548 1548 | o 5ac28 draft
1549 1549 |/|
1550 1550 o | 13b7b draft
1551 1551 | |
1552 1552 | o f5853 public
1553 1553 | |
1554 1554 o | c67c4 draft
1555 1555 | |
1556 1556 | o 26805 public
1557 1557 |/
1558 1558 o 11247 public
1559 1559 |
1560 1560 o 426ba public
1561 1561
1562 $ killdaemons.py
1563
1564
1565 --publish flag
1566 --------------
1567
1568 $ hg init doesnt-publish
1569 $ cd doesnt-publish
1570 $ cat > .hg/hgrc << EOF
1571 > [phases]
1572 > publish=0
1573 > EOF
1574 $ mkcommit orig-root
1575 test-debug-phase: new rev 0: x -> 1
1576 $ hg phase --public -r 'all()'
1577 test-debug-phase: move rev 0: 1 -> 0
1578 $ cd ..
1579
1580 $ hg clone -q doesnt-publish client
1581 $ cd client
1582
1583 pushing nothing
1584
1585 $ mkcommit new-A
1586 test-debug-phase: new rev 1: x -> 1
1587 $ mkcommit new-B
1588 test-debug-phase: new rev 2: x -> 1
1589 $ hg push --publish -r null
1590 pushing to $TESTTMP/doesnt-publish
1591 searching for changes
1592 no changes found
1593 [1]
1594 $ hgph
1595 @ 2 draft new-B - 89512e87d697
1596 |
1597 o 1 draft new-A - 4826e44e690e
1598 |
1599 o 0 public orig-root - c48edaf99a10
1600
1601
1602 pushing a new changeset (selective)
1603
1604 $ hg push --publish -r 'desc("new-A")'
1605 pushing to $TESTTMP/doesnt-publish
1606 searching for changes
1607 adding changesets
1608 adding manifests
1609 adding file changes
1610 added 1 changesets with 1 changes to 1 files
1611 test-debug-phase: new rev 1: x -> 0
1612 test-debug-phase: move rev 1: 1 -> 0
1613 $ hgph
1614 @ 2 draft new-B - 89512e87d697
1615 |
1616 o 1 public new-A - 4826e44e690e
1617 |
1618 o 0 public orig-root - c48edaf99a10
1619
1620
1621 pushing a new changeset (linear)
1622
1623 $ hg push --publish
1624 pushing to $TESTTMP/doesnt-publish
1625 searching for changes
1626 adding changesets
1627 adding manifests
1628 adding file changes
1629 added 1 changesets with 1 changes to 1 files
1630 test-debug-phase: new rev 2: x -> 0
1631 test-debug-phase: move rev 2: 1 -> 0
1632 $ hgph
1633 @ 2 public new-B - 89512e87d697
1634 |
1635 o 1 public new-A - 4826e44e690e
1636 |
1637 o 0 public orig-root - c48edaf99a10
1638
1639
1640 pushing new changesets (different branches)
1641
1642 $ mkcommit new-C
1643 test-debug-phase: new rev 3: x -> 1
1644 $ hg update -q '.^'
1645 $ hg branch -q another
1646 $ mkcommit new-D
1647 test-debug-phase: new rev 4: x -> 1
1648 $ hg push --new-branch --publish
1649 pushing to $TESTTMP/doesnt-publish
1650 searching for changes
1651 adding changesets
1652 adding manifests
1653 adding file changes
1654 added 2 changesets with 2 changes to 2 files (+1 heads)
1655 test-debug-phase: new rev 3: x -> 0
1656 test-debug-phase: new rev 4: x -> 0
1657 test-debug-phase: move rev 3: 1 -> 0
1658 test-debug-phase: move rev 4: 1 -> 0
1659 $ hgph
1660 @ 4 public new-D - 5e53dcafd13c
1661 |
1662 | o 3 public new-C - 1665482cc06d
1663 |/
1664 o 2 public new-B - 89512e87d697
1665 |
1666 o 1 public new-A - 4826e44e690e
1667 |
1668 o 0 public orig-root - c48edaf99a10
1669
1670
1671 pushing a shared changeset
1672
1673 $ mkcommit new-E
1674 test-debug-phase: new rev 5: x -> 1
1675 $ hg push
1676 pushing to $TESTTMP/doesnt-publish
1677 searching for changes
1678 adding changesets
1679 adding manifests
1680 adding file changes
1681 added 1 changesets with 1 changes to 1 files
1682 test-debug-phase: new rev 5: x -> 1
1683 $ hg push --publish
1684 pushing to $TESTTMP/doesnt-publish
1685 searching for changes
1686 no changes found
1687 test-debug-phase: move rev 5: 1 -> 0
1688 test-debug-phase: move rev 5: 1 -> 0
1689 [1]
1690 $ hgph
1691 @ 5 public new-E - 48931ee3529c
1692 |
1693 o 4 public new-D - 5e53dcafd13c
1694 |
1695 | o 3 public new-C - 1665482cc06d
1696 |/
1697 o 2 public new-B - 89512e87d697
1698 |
1699 o 1 public new-A - 4826e44e690e
1700 |
1701 o 0 public orig-root - c48edaf99a10
1702
1703 $ cd ..
1704
1705 --publish with subrepos (doesn't propagate to subrepos currently)
1706
1707 $ hg init with-subrepo
1708 $ cd with-subrepo
1709 $ cat > .hg/hgrc << EOF
1710 > [phases]
1711 > publish=0
1712 > EOF
1713 $ hg init subrepo
1714 $ cd subrepo
1715 $ cat > .hg/hgrc << EOF
1716 > [phases]
1717 > publish=0
1718 > EOF
1719 $ echo foo > foo
1720 $ hg ci -qAm0
1721 test-debug-phase: new rev 0: x -> 1
1722 $ cd ..
1723 $ echo 'subrepo = subrepo' > .hgsub
1724 $ hg add .hgsub
1725 $ hg ci -m 'Adding subrepo'
1726 test-debug-phase: new rev 0: x -> 1
1727 $ hgph
1728 @ 0 draft Adding subrepo - 74d5b62379c0
1729
1730 $ hgph -R subrepo
1731 @ 0 draft 0 - 4b3f578e3344
1732
1733 $ cd ..
1734 $ hg clone with-subrepo client-with-subrepo
1735 updating to branch default
1736 cloning subrepo subrepo from $TESTTMP/with-subrepo/subrepo
1737 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1738 $ cd client-with-subrepo
1739 $ hg push --publish
1740 pushing to $TESTTMP/with-subrepo
1741 no changes made to subrepo subrepo since last push to $TESTTMP/with-subrepo/subrepo
1742 searching for changes
1743 no changes found
1744 test-debug-phase: move rev 0: 1 -> 0
1745 test-debug-phase: move rev 0: 1 -> 0
1746 [1]
1747 $ hgph
1748 @ 0 public Adding subrepo - 74d5b62379c0
1749
1750 $ hgph -R subrepo
1751 @ 0 draft 0 - 4b3f578e3344
1752
General Comments 0
You need to be logged in to leave comments. Login now