##// END OF EJS Templates
branch: allow changing branch name to existing name if possible...
Pulkit Goyal -
r35764:e5b6ba78 default
parent child Browse files
Show More
@@ -1,4022 +1,4027
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import itertools
12 12 import os
13 13 import re
14 14 import tempfile
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 )
23 23
24 24 from . import (
25 25 bookmarks,
26 26 changelog,
27 27 copies,
28 28 crecord as crecordmod,
29 29 dagop,
30 30 dirstateguard,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 graphmod,
35 35 match as matchmod,
36 36 mdiff,
37 37 obsolete,
38 38 patch,
39 39 pathutil,
40 40 pycompat,
41 41 registrar,
42 42 revlog,
43 43 revset,
44 44 revsetlang,
45 45 rewriteutil,
46 46 scmutil,
47 47 smartset,
48 48 templatekw,
49 49 templater,
50 50 util,
51 51 vfs as vfsmod,
52 52 )
53 53 stringio = util.stringio
54 54
55 55 # templates of common command options
56 56
57 57 dryrunopts = [
58 58 ('n', 'dry-run', None,
59 59 _('do not perform actions, just print output')),
60 60 ]
61 61
62 62 remoteopts = [
63 63 ('e', 'ssh', '',
64 64 _('specify ssh command to use'), _('CMD')),
65 65 ('', 'remotecmd', '',
66 66 _('specify hg command to run on the remote side'), _('CMD')),
67 67 ('', 'insecure', None,
68 68 _('do not verify server certificate (ignoring web.cacerts config)')),
69 69 ]
70 70
71 71 walkopts = [
72 72 ('I', 'include', [],
73 73 _('include names matching the given patterns'), _('PATTERN')),
74 74 ('X', 'exclude', [],
75 75 _('exclude names matching the given patterns'), _('PATTERN')),
76 76 ]
77 77
78 78 commitopts = [
79 79 ('m', 'message', '',
80 80 _('use text as commit message'), _('TEXT')),
81 81 ('l', 'logfile', '',
82 82 _('read commit message from file'), _('FILE')),
83 83 ]
84 84
85 85 commitopts2 = [
86 86 ('d', 'date', '',
87 87 _('record the specified date as commit date'), _('DATE')),
88 88 ('u', 'user', '',
89 89 _('record the specified user as committer'), _('USER')),
90 90 ]
91 91
92 92 # hidden for now
93 93 formatteropts = [
94 94 ('T', 'template', '',
95 95 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
96 96 ]
97 97
98 98 templateopts = [
99 99 ('', 'style', '',
100 100 _('display using template map file (DEPRECATED)'), _('STYLE')),
101 101 ('T', 'template', '',
102 102 _('display with template'), _('TEMPLATE')),
103 103 ]
104 104
105 105 logopts = [
106 106 ('p', 'patch', None, _('show patch')),
107 107 ('g', 'git', None, _('use git extended diff format')),
108 108 ('l', 'limit', '',
109 109 _('limit number of changes displayed'), _('NUM')),
110 110 ('M', 'no-merges', None, _('do not show merges')),
111 111 ('', 'stat', None, _('output diffstat-style summary of changes')),
112 112 ('G', 'graph', None, _("show the revision DAG")),
113 113 ] + templateopts
114 114
115 115 diffopts = [
116 116 ('a', 'text', None, _('treat all files as text')),
117 117 ('g', 'git', None, _('use git extended diff format')),
118 118 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
119 119 ('', 'nodates', None, _('omit dates from diff headers'))
120 120 ]
121 121
122 122 diffwsopts = [
123 123 ('w', 'ignore-all-space', None,
124 124 _('ignore white space when comparing lines')),
125 125 ('b', 'ignore-space-change', None,
126 126 _('ignore changes in the amount of white space')),
127 127 ('B', 'ignore-blank-lines', None,
128 128 _('ignore changes whose lines are all blank')),
129 129 ('Z', 'ignore-space-at-eol', None,
130 130 _('ignore changes in whitespace at EOL')),
131 131 ]
132 132
133 133 diffopts2 = [
134 134 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
135 135 ('p', 'show-function', None, _('show which function each change is in')),
136 136 ('', 'reverse', None, _('produce a diff that undoes the changes')),
137 137 ] + diffwsopts + [
138 138 ('U', 'unified', '',
139 139 _('number of lines of context to show'), _('NUM')),
140 140 ('', 'stat', None, _('output diffstat-style summary of changes')),
141 141 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
142 142 ]
143 143
144 144 mergetoolopts = [
145 145 ('t', 'tool', '', _('specify merge tool')),
146 146 ]
147 147
148 148 similarityopts = [
149 149 ('s', 'similarity', '',
150 150 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
151 151 ]
152 152
153 153 subrepoopts = [
154 154 ('S', 'subrepos', None,
155 155 _('recurse into subrepositories'))
156 156 ]
157 157
158 158 debugrevlogopts = [
159 159 ('c', 'changelog', False, _('open changelog')),
160 160 ('m', 'manifest', False, _('open manifest')),
161 161 ('', 'dir', '', _('open directory manifest')),
162 162 ]
163 163
164 164 # special string such that everything below this line will be ingored in the
165 165 # editor text
166 166 _linebelow = "^HG: ------------------------ >8 ------------------------$"
167 167
168 168 def ishunk(x):
169 169 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
170 170 return isinstance(x, hunkclasses)
171 171
172 172 def newandmodified(chunks, originalchunks):
173 173 newlyaddedandmodifiedfiles = set()
174 174 for chunk in chunks:
175 175 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
176 176 originalchunks:
177 177 newlyaddedandmodifiedfiles.add(chunk.header.filename())
178 178 return newlyaddedandmodifiedfiles
179 179
180 180 def parsealiases(cmd):
181 181 return cmd.lstrip("^").split("|")
182 182
183 183 def setupwrapcolorwrite(ui):
184 184 # wrap ui.write so diff output can be labeled/colorized
185 185 def wrapwrite(orig, *args, **kw):
186 186 label = kw.pop(r'label', '')
187 187 for chunk, l in patch.difflabel(lambda: args):
188 188 orig(chunk, label=label + l)
189 189
190 190 oldwrite = ui.write
191 191 def wrap(*args, **kwargs):
192 192 return wrapwrite(oldwrite, *args, **kwargs)
193 193 setattr(ui, 'write', wrap)
194 194 return oldwrite
195 195
196 196 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
197 197 if usecurses:
198 198 if testfile:
199 199 recordfn = crecordmod.testdecorator(testfile,
200 200 crecordmod.testchunkselector)
201 201 else:
202 202 recordfn = crecordmod.chunkselector
203 203
204 204 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
205 205
206 206 else:
207 207 return patch.filterpatch(ui, originalhunks, operation)
208 208
209 209 def recordfilter(ui, originalhunks, operation=None):
210 210 """ Prompts the user to filter the originalhunks and return a list of
211 211 selected hunks.
212 212 *operation* is used for to build ui messages to indicate the user what
213 213 kind of filtering they are doing: reverting, committing, shelving, etc.
214 214 (see patch.filterpatch).
215 215 """
216 216 usecurses = crecordmod.checkcurses(ui)
217 217 testfile = ui.config('experimental', 'crecordtest')
218 218 oldwrite = setupwrapcolorwrite(ui)
219 219 try:
220 220 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
221 221 testfile, operation)
222 222 finally:
223 223 ui.write = oldwrite
224 224 return newchunks, newopts
225 225
226 226 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
227 227 filterfn, *pats, **opts):
228 228 from . import merge as mergemod
229 229 opts = pycompat.byteskwargs(opts)
230 230 if not ui.interactive():
231 231 if cmdsuggest:
232 232 msg = _('running non-interactively, use %s instead') % cmdsuggest
233 233 else:
234 234 msg = _('running non-interactively')
235 235 raise error.Abort(msg)
236 236
237 237 # make sure username is set before going interactive
238 238 if not opts.get('user'):
239 239 ui.username() # raise exception, username not provided
240 240
241 241 def recordfunc(ui, repo, message, match, opts):
242 242 """This is generic record driver.
243 243
244 244 Its job is to interactively filter local changes, and
245 245 accordingly prepare working directory into a state in which the
246 246 job can be delegated to a non-interactive commit command such as
247 247 'commit' or 'qrefresh'.
248 248
249 249 After the actual job is done by non-interactive command, the
250 250 working directory is restored to its original state.
251 251
252 252 In the end we'll record interesting changes, and everything else
253 253 will be left in place, so the user can continue working.
254 254 """
255 255
256 256 checkunfinished(repo, commit=True)
257 257 wctx = repo[None]
258 258 merge = len(wctx.parents()) > 1
259 259 if merge:
260 260 raise error.Abort(_('cannot partially commit a merge '
261 261 '(use "hg commit" instead)'))
262 262
263 263 def fail(f, msg):
264 264 raise error.Abort('%s: %s' % (f, msg))
265 265
266 266 force = opts.get('force')
267 267 if not force:
268 268 vdirs = []
269 269 match.explicitdir = vdirs.append
270 270 match.bad = fail
271 271
272 272 status = repo.status(match=match)
273 273 if not force:
274 274 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
275 275 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
276 276 diffopts.nodates = True
277 277 diffopts.git = True
278 278 diffopts.showfunc = True
279 279 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
280 280 originalchunks = patch.parsepatch(originaldiff)
281 281
282 282 # 1. filter patch, since we are intending to apply subset of it
283 283 try:
284 284 chunks, newopts = filterfn(ui, originalchunks)
285 285 except error.PatchError as err:
286 286 raise error.Abort(_('error parsing patch: %s') % err)
287 287 opts.update(newopts)
288 288
289 289 # We need to keep a backup of files that have been newly added and
290 290 # modified during the recording process because there is a previous
291 291 # version without the edit in the workdir
292 292 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
293 293 contenders = set()
294 294 for h in chunks:
295 295 try:
296 296 contenders.update(set(h.files()))
297 297 except AttributeError:
298 298 pass
299 299
300 300 changed = status.modified + status.added + status.removed
301 301 newfiles = [f for f in changed if f in contenders]
302 302 if not newfiles:
303 303 ui.status(_('no changes to record\n'))
304 304 return 0
305 305
306 306 modified = set(status.modified)
307 307
308 308 # 2. backup changed files, so we can restore them in the end
309 309
310 310 if backupall:
311 311 tobackup = changed
312 312 else:
313 313 tobackup = [f for f in newfiles if f in modified or f in \
314 314 newlyaddedandmodifiedfiles]
315 315 backups = {}
316 316 if tobackup:
317 317 backupdir = repo.vfs.join('record-backups')
318 318 try:
319 319 os.mkdir(backupdir)
320 320 except OSError as err:
321 321 if err.errno != errno.EEXIST:
322 322 raise
323 323 try:
324 324 # backup continues
325 325 for f in tobackup:
326 326 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
327 327 dir=backupdir)
328 328 os.close(fd)
329 329 ui.debug('backup %r as %r\n' % (f, tmpname))
330 330 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
331 331 backups[f] = tmpname
332 332
333 333 fp = stringio()
334 334 for c in chunks:
335 335 fname = c.filename()
336 336 if fname in backups:
337 337 c.write(fp)
338 338 dopatch = fp.tell()
339 339 fp.seek(0)
340 340
341 341 # 2.5 optionally review / modify patch in text editor
342 342 if opts.get('review', False):
343 343 patchtext = (crecordmod.diffhelptext
344 344 + crecordmod.patchhelptext
345 345 + fp.read())
346 346 reviewedpatch = ui.edit(patchtext, "",
347 347 action="diff",
348 348 repopath=repo.path)
349 349 fp.truncate(0)
350 350 fp.write(reviewedpatch)
351 351 fp.seek(0)
352 352
353 353 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
354 354 # 3a. apply filtered patch to clean repo (clean)
355 355 if backups:
356 356 # Equivalent to hg.revert
357 357 m = scmutil.matchfiles(repo, backups.keys())
358 358 mergemod.update(repo, repo.dirstate.p1(),
359 359 False, True, matcher=m)
360 360
361 361 # 3b. (apply)
362 362 if dopatch:
363 363 try:
364 364 ui.debug('applying patch\n')
365 365 ui.debug(fp.getvalue())
366 366 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
367 367 except error.PatchError as err:
368 368 raise error.Abort(str(err))
369 369 del fp
370 370
371 371 # 4. We prepared working directory according to filtered
372 372 # patch. Now is the time to delegate the job to
373 373 # commit/qrefresh or the like!
374 374
375 375 # Make all of the pathnames absolute.
376 376 newfiles = [repo.wjoin(nf) for nf in newfiles]
377 377 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
378 378 finally:
379 379 # 5. finally restore backed-up files
380 380 try:
381 381 dirstate = repo.dirstate
382 382 for realname, tmpname in backups.iteritems():
383 383 ui.debug('restoring %r to %r\n' % (tmpname, realname))
384 384
385 385 if dirstate[realname] == 'n':
386 386 # without normallookup, restoring timestamp
387 387 # may cause partially committed files
388 388 # to be treated as unmodified
389 389 dirstate.normallookup(realname)
390 390
391 391 # copystat=True here and above are a hack to trick any
392 392 # editors that have f open that we haven't modified them.
393 393 #
394 394 # Also note that this racy as an editor could notice the
395 395 # file's mtime before we've finished writing it.
396 396 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
397 397 os.unlink(tmpname)
398 398 if tobackup:
399 399 os.rmdir(backupdir)
400 400 except OSError:
401 401 pass
402 402
403 403 def recordinwlock(ui, repo, message, match, opts):
404 404 with repo.wlock():
405 405 return recordfunc(ui, repo, message, match, opts)
406 406
407 407 return commit(ui, repo, recordinwlock, pats, opts)
408 408
409 409 class dirnode(object):
410 410 """
411 411 Represent a directory in user working copy with information required for
412 412 the purpose of tersing its status.
413 413
414 414 path is the path to the directory
415 415
416 416 statuses is a set of statuses of all files in this directory (this includes
417 417 all the files in all the subdirectories too)
418 418
419 419 files is a list of files which are direct child of this directory
420 420
421 421 subdirs is a dictionary of sub-directory name as the key and it's own
422 422 dirnode object as the value
423 423 """
424 424
425 425 def __init__(self, dirpath):
426 426 self.path = dirpath
427 427 self.statuses = set([])
428 428 self.files = []
429 429 self.subdirs = {}
430 430
431 431 def _addfileindir(self, filename, status):
432 432 """Add a file in this directory as a direct child."""
433 433 self.files.append((filename, status))
434 434
435 435 def addfile(self, filename, status):
436 436 """
437 437 Add a file to this directory or to its direct parent directory.
438 438
439 439 If the file is not direct child of this directory, we traverse to the
440 440 directory of which this file is a direct child of and add the file
441 441 there.
442 442 """
443 443
444 444 # the filename contains a path separator, it means it's not the direct
445 445 # child of this directory
446 446 if '/' in filename:
447 447 subdir, filep = filename.split('/', 1)
448 448
449 449 # does the dirnode object for subdir exists
450 450 if subdir not in self.subdirs:
451 451 subdirpath = os.path.join(self.path, subdir)
452 452 self.subdirs[subdir] = dirnode(subdirpath)
453 453
454 454 # try adding the file in subdir
455 455 self.subdirs[subdir].addfile(filep, status)
456 456
457 457 else:
458 458 self._addfileindir(filename, status)
459 459
460 460 if status not in self.statuses:
461 461 self.statuses.add(status)
462 462
463 463 def iterfilepaths(self):
464 464 """Yield (status, path) for files directly under this directory."""
465 465 for f, st in self.files:
466 466 yield st, os.path.join(self.path, f)
467 467
468 468 def tersewalk(self, terseargs):
469 469 """
470 470 Yield (status, path) obtained by processing the status of this
471 471 dirnode.
472 472
473 473 terseargs is the string of arguments passed by the user with `--terse`
474 474 flag.
475 475
476 476 Following are the cases which can happen:
477 477
478 478 1) All the files in the directory (including all the files in its
479 479 subdirectories) share the same status and the user has asked us to terse
480 480 that status. -> yield (status, dirpath)
481 481
482 482 2) Otherwise, we do following:
483 483
484 484 a) Yield (status, filepath) for all the files which are in this
485 485 directory (only the ones in this directory, not the subdirs)
486 486
487 487 b) Recurse the function on all the subdirectories of this
488 488 directory
489 489 """
490 490
491 491 if len(self.statuses) == 1:
492 492 onlyst = self.statuses.pop()
493 493
494 494 # Making sure we terse only when the status abbreviation is
495 495 # passed as terse argument
496 496 if onlyst in terseargs:
497 497 yield onlyst, self.path + pycompat.ossep
498 498 return
499 499
500 500 # add the files to status list
501 501 for st, fpath in self.iterfilepaths():
502 502 yield st, fpath
503 503
504 504 #recurse on the subdirs
505 505 for dirobj in self.subdirs.values():
506 506 for st, fpath in dirobj.tersewalk(terseargs):
507 507 yield st, fpath
508 508
509 509 def tersedir(statuslist, terseargs):
510 510 """
511 511 Terse the status if all the files in a directory shares the same status.
512 512
513 513 statuslist is scmutil.status() object which contains a list of files for
514 514 each status.
515 515 terseargs is string which is passed by the user as the argument to `--terse`
516 516 flag.
517 517
518 518 The function makes a tree of objects of dirnode class, and at each node it
519 519 stores the information required to know whether we can terse a certain
520 520 directory or not.
521 521 """
522 522 # the order matters here as that is used to produce final list
523 523 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
524 524
525 525 # checking the argument validity
526 526 for s in pycompat.bytestr(terseargs):
527 527 if s not in allst:
528 528 raise error.Abort(_("'%s' not recognized") % s)
529 529
530 530 # creating a dirnode object for the root of the repo
531 531 rootobj = dirnode('')
532 532 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
533 533 'ignored', 'removed')
534 534
535 535 tersedict = {}
536 536 for attrname in pstatus:
537 537 statuschar = attrname[0:1]
538 538 for f in getattr(statuslist, attrname):
539 539 rootobj.addfile(f, statuschar)
540 540 tersedict[statuschar] = []
541 541
542 542 # we won't be tersing the root dir, so add files in it
543 543 for st, fpath in rootobj.iterfilepaths():
544 544 tersedict[st].append(fpath)
545 545
546 546 # process each sub-directory and build tersedict
547 547 for subdir in rootobj.subdirs.values():
548 548 for st, f in subdir.tersewalk(terseargs):
549 549 tersedict[st].append(f)
550 550
551 551 tersedlist = []
552 552 for st in allst:
553 553 tersedict[st].sort()
554 554 tersedlist.append(tersedict[st])
555 555
556 556 return tersedlist
557 557
558 558 def _commentlines(raw):
559 559 '''Surround lineswith a comment char and a new line'''
560 560 lines = raw.splitlines()
561 561 commentedlines = ['# %s' % line for line in lines]
562 562 return '\n'.join(commentedlines) + '\n'
563 563
564 564 def _conflictsmsg(repo):
565 565 # avoid merge cycle
566 566 from . import merge as mergemod
567 567 mergestate = mergemod.mergestate.read(repo)
568 568 if not mergestate.active():
569 569 return
570 570
571 571 m = scmutil.match(repo[None])
572 572 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
573 573 if unresolvedlist:
574 574 mergeliststr = '\n'.join(
575 575 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
576 576 for path in unresolvedlist])
577 577 msg = _('''Unresolved merge conflicts:
578 578
579 579 %s
580 580
581 581 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
582 582 else:
583 583 msg = _('No unresolved merge conflicts.')
584 584
585 585 return _commentlines(msg)
586 586
587 587 def _helpmessage(continuecmd, abortcmd):
588 588 msg = _('To continue: %s\n'
589 589 'To abort: %s') % (continuecmd, abortcmd)
590 590 return _commentlines(msg)
591 591
592 592 def _rebasemsg():
593 593 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
594 594
595 595 def _histeditmsg():
596 596 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
597 597
598 598 def _unshelvemsg():
599 599 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
600 600
601 601 def _updatecleanmsg(dest=None):
602 602 warning = _('warning: this will discard uncommitted changes')
603 603 return 'hg update --clean %s (%s)' % (dest or '.', warning)
604 604
605 605 def _graftmsg():
606 606 # tweakdefaults requires `update` to have a rev hence the `.`
607 607 return _helpmessage('hg graft --continue', _updatecleanmsg())
608 608
609 609 def _mergemsg():
610 610 # tweakdefaults requires `update` to have a rev hence the `.`
611 611 return _helpmessage('hg commit', _updatecleanmsg())
612 612
613 613 def _bisectmsg():
614 614 msg = _('To mark the changeset good: hg bisect --good\n'
615 615 'To mark the changeset bad: hg bisect --bad\n'
616 616 'To abort: hg bisect --reset\n')
617 617 return _commentlines(msg)
618 618
619 619 def fileexistspredicate(filename):
620 620 return lambda repo: repo.vfs.exists(filename)
621 621
622 622 def _mergepredicate(repo):
623 623 return len(repo[None].parents()) > 1
624 624
625 625 STATES = (
626 626 # (state, predicate to detect states, helpful message function)
627 627 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
628 628 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
629 629 ('graft', fileexistspredicate('graftstate'), _graftmsg),
630 630 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
631 631 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
632 632 # The merge state is part of a list that will be iterated over.
633 633 # They need to be last because some of the other unfinished states may also
634 634 # be in a merge or update state (eg. rebase, histedit, graft, etc).
635 635 # We want those to have priority.
636 636 ('merge', _mergepredicate, _mergemsg),
637 637 )
638 638
639 639 def _getrepostate(repo):
640 640 # experimental config: commands.status.skipstates
641 641 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
642 642 for state, statedetectionpredicate, msgfn in STATES:
643 643 if state in skip:
644 644 continue
645 645 if statedetectionpredicate(repo):
646 646 return (state, statedetectionpredicate, msgfn)
647 647
648 648 def morestatus(repo, fm):
649 649 statetuple = _getrepostate(repo)
650 650 label = 'status.morestatus'
651 651 if statetuple:
652 652 fm.startitem()
653 653 state, statedetectionpredicate, helpfulmsg = statetuple
654 654 statemsg = _('The repository is in an unfinished *%s* state.') % state
655 655 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
656 656 conmsg = _conflictsmsg(repo)
657 657 if conmsg:
658 658 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
659 659 if helpfulmsg:
660 660 helpmsg = helpfulmsg()
661 661 fm.write('helpmsg', '%s\n', helpmsg, label=label)
662 662
663 663 def findpossible(cmd, table, strict=False):
664 664 """
665 665 Return cmd -> (aliases, command table entry)
666 666 for each matching command.
667 667 Return debug commands (or their aliases) only if no normal command matches.
668 668 """
669 669 choice = {}
670 670 debugchoice = {}
671 671
672 672 if cmd in table:
673 673 # short-circuit exact matches, "log" alias beats "^log|history"
674 674 keys = [cmd]
675 675 else:
676 676 keys = table.keys()
677 677
678 678 allcmds = []
679 679 for e in keys:
680 680 aliases = parsealiases(e)
681 681 allcmds.extend(aliases)
682 682 found = None
683 683 if cmd in aliases:
684 684 found = cmd
685 685 elif not strict:
686 686 for a in aliases:
687 687 if a.startswith(cmd):
688 688 found = a
689 689 break
690 690 if found is not None:
691 691 if aliases[0].startswith("debug") or found.startswith("debug"):
692 692 debugchoice[found] = (aliases, table[e])
693 693 else:
694 694 choice[found] = (aliases, table[e])
695 695
696 696 if not choice and debugchoice:
697 697 choice = debugchoice
698 698
699 699 return choice, allcmds
700 700
701 701 def findcmd(cmd, table, strict=True):
702 702 """Return (aliases, command table entry) for command string."""
703 703 choice, allcmds = findpossible(cmd, table, strict)
704 704
705 705 if cmd in choice:
706 706 return choice[cmd]
707 707
708 708 if len(choice) > 1:
709 709 clist = sorted(choice)
710 710 raise error.AmbiguousCommand(cmd, clist)
711 711
712 712 if choice:
713 713 return list(choice.values())[0]
714 714
715 715 raise error.UnknownCommand(cmd, allcmds)
716 716
717 717 def changebranch(ui, repo, revs, label):
718 718 """ Change the branch name of given revs to label """
719 719
720 720 with repo.wlock(), repo.lock(), repo.transaction('branches'):
721 721 # abort in case of uncommitted merge or dirty wdir
722 722 bailifchanged(repo)
723 723 revs = scmutil.revrange(repo, revs)
724 724 if not revs:
725 725 raise error.Abort("empty revision set")
726 726 roots = repo.revs('roots(%ld)', revs)
727 727 if len(roots) > 1:
728 728 raise error.Abort(_("cannot change branch of non-linear revisions"))
729 729 rewriteutil.precheck(repo, revs, 'change branch of')
730
731 root = repo[roots.first()]
732 if not root.p1().branch() == label and label in repo.branchmap():
733 raise error.Abort(_("a branch of the same name already exists"))
734
730 735 if repo.revs('merge() and %ld', revs):
731 736 raise error.Abort(_("cannot change branch of a merge commit"))
732 737 if repo.revs('obsolete() and %ld', revs):
733 738 raise error.Abort(_("cannot change branch of a obsolete changeset"))
734 739
735 740 # make sure only topological heads
736 741 if repo.revs('heads(%ld) - head()', revs):
737 742 raise error.Abort(_("cannot change branch in middle of a stack"))
738 743
739 744 replacements = {}
740 745 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
741 746 # mercurial.subrepo -> mercurial.cmdutil
742 747 from . import context
743 748 for rev in revs:
744 749 ctx = repo[rev]
745 750 oldbranch = ctx.branch()
746 751 # check if ctx has same branch
747 752 if oldbranch == label:
748 753 continue
749 754
750 755 def filectxfn(repo, newctx, path):
751 756 try:
752 757 return ctx[path]
753 758 except error.ManifestLookupError:
754 759 return None
755 760
756 761 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
757 762 % (hex(ctx.node()), oldbranch, label))
758 763 extra = ctx.extra()
759 764 extra['branch_change'] = hex(ctx.node())
760 765 # While changing branch of set of linear commits, make sure that
761 766 # we base our commits on new parent rather than old parent which
762 767 # was obsoleted while changing the branch
763 768 p1 = ctx.p1().node()
764 769 p2 = ctx.p2().node()
765 770 if p1 in replacements:
766 771 p1 = replacements[p1][0]
767 772 if p2 in replacements:
768 773 p2 = replacements[p2][0]
769 774
770 775 mc = context.memctx(repo, (p1, p2),
771 776 ctx.description(),
772 777 ctx.files(),
773 778 filectxfn,
774 779 user=ctx.user(),
775 780 date=ctx.date(),
776 781 extra=extra,
777 782 branch=label)
778 783
779 784 commitphase = ctx.phase()
780 785 overrides = {('phases', 'new-commit'): commitphase}
781 786 with repo.ui.configoverride(overrides, 'branch-change'):
782 787 newnode = repo.commitctx(mc)
783 788
784 789 replacements[ctx.node()] = (newnode,)
785 790 ui.debug('new node id is %s\n' % hex(newnode))
786 791
787 792 # create obsmarkers and move bookmarks
788 793 scmutil.cleanupnodes(repo, replacements, 'branch-change')
789 794
790 795 # move the working copy too
791 796 wctx = repo[None]
792 797 # in-progress merge is a bit too complex for now.
793 798 if len(wctx.parents()) == 1:
794 799 newid = replacements.get(wctx.p1().node())
795 800 if newid is not None:
796 801 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
797 802 # mercurial.cmdutil
798 803 from . import hg
799 804 hg.update(repo, newid[0], quietempty=True)
800 805
801 806 ui.status(_("changed branch on %d changesets\n") % len(replacements))
802 807
803 808 def findrepo(p):
804 809 while not os.path.isdir(os.path.join(p, ".hg")):
805 810 oldp, p = p, os.path.dirname(p)
806 811 if p == oldp:
807 812 return None
808 813
809 814 return p
810 815
811 816 def bailifchanged(repo, merge=True, hint=None):
812 817 """ enforce the precondition that working directory must be clean.
813 818
814 819 'merge' can be set to false if a pending uncommitted merge should be
815 820 ignored (such as when 'update --check' runs).
816 821
817 822 'hint' is the usual hint given to Abort exception.
818 823 """
819 824
820 825 if merge and repo.dirstate.p2() != nullid:
821 826 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
822 827 modified, added, removed, deleted = repo.status()[:4]
823 828 if modified or added or removed or deleted:
824 829 raise error.Abort(_('uncommitted changes'), hint=hint)
825 830 ctx = repo[None]
826 831 for s in sorted(ctx.substate):
827 832 ctx.sub(s).bailifchanged(hint=hint)
828 833
829 834 def logmessage(ui, opts):
830 835 """ get the log message according to -m and -l option """
831 836 message = opts.get('message')
832 837 logfile = opts.get('logfile')
833 838
834 839 if message and logfile:
835 840 raise error.Abort(_('options --message and --logfile are mutually '
836 841 'exclusive'))
837 842 if not message and logfile:
838 843 try:
839 844 if isstdiofilename(logfile):
840 845 message = ui.fin.read()
841 846 else:
842 847 message = '\n'.join(util.readfile(logfile).splitlines())
843 848 except IOError as inst:
844 849 raise error.Abort(_("can't read commit message '%s': %s") %
845 850 (logfile, encoding.strtolocal(inst.strerror)))
846 851 return message
847 852
848 853 def mergeeditform(ctxorbool, baseformname):
849 854 """return appropriate editform name (referencing a committemplate)
850 855
851 856 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
852 857 merging is committed.
853 858
854 859 This returns baseformname with '.merge' appended if it is a merge,
855 860 otherwise '.normal' is appended.
856 861 """
857 862 if isinstance(ctxorbool, bool):
858 863 if ctxorbool:
859 864 return baseformname + ".merge"
860 865 elif 1 < len(ctxorbool.parents()):
861 866 return baseformname + ".merge"
862 867
863 868 return baseformname + ".normal"
864 869
865 870 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
866 871 editform='', **opts):
867 872 """get appropriate commit message editor according to '--edit' option
868 873
869 874 'finishdesc' is a function to be called with edited commit message
870 875 (= 'description' of the new changeset) just after editing, but
871 876 before checking empty-ness. It should return actual text to be
872 877 stored into history. This allows to change description before
873 878 storing.
874 879
875 880 'extramsg' is a extra message to be shown in the editor instead of
876 881 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
877 882 is automatically added.
878 883
879 884 'editform' is a dot-separated list of names, to distinguish
880 885 the purpose of commit text editing.
881 886
882 887 'getcommiteditor' returns 'commitforceeditor' regardless of
883 888 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
884 889 they are specific for usage in MQ.
885 890 """
886 891 if edit or finishdesc or extramsg:
887 892 return lambda r, c, s: commitforceeditor(r, c, s,
888 893 finishdesc=finishdesc,
889 894 extramsg=extramsg,
890 895 editform=editform)
891 896 elif editform:
892 897 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
893 898 else:
894 899 return commiteditor
895 900
896 901 def loglimit(opts):
897 902 """get the log limit according to option -l/--limit"""
898 903 limit = opts.get('limit')
899 904 if limit:
900 905 try:
901 906 limit = int(limit)
902 907 except ValueError:
903 908 raise error.Abort(_('limit must be a positive integer'))
904 909 if limit <= 0:
905 910 raise error.Abort(_('limit must be positive'))
906 911 else:
907 912 limit = None
908 913 return limit
909 914
910 915 def makefilename(repo, pat, node, desc=None,
911 916 total=None, seqno=None, revwidth=None, pathname=None):
912 917 node_expander = {
913 918 'H': lambda: hex(node),
914 919 'R': lambda: '%d' % repo.changelog.rev(node),
915 920 'h': lambda: short(node),
916 921 'm': lambda: re.sub('[^\w]', '_', desc or '')
917 922 }
918 923 expander = {
919 924 '%': lambda: '%',
920 925 'b': lambda: os.path.basename(repo.root),
921 926 }
922 927
923 928 try:
924 929 if node:
925 930 expander.update(node_expander)
926 931 if node:
927 932 expander['r'] = (lambda:
928 933 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
929 934 if total is not None:
930 935 expander['N'] = lambda: '%d' % total
931 936 if seqno is not None:
932 937 expander['n'] = lambda: '%d' % seqno
933 938 if total is not None and seqno is not None:
934 939 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
935 940 if pathname is not None:
936 941 expander['s'] = lambda: os.path.basename(pathname)
937 942 expander['d'] = lambda: os.path.dirname(pathname) or '.'
938 943 expander['p'] = lambda: pathname
939 944
940 945 newname = []
941 946 patlen = len(pat)
942 947 i = 0
943 948 while i < patlen:
944 949 c = pat[i:i + 1]
945 950 if c == '%':
946 951 i += 1
947 952 c = pat[i:i + 1]
948 953 c = expander[c]()
949 954 newname.append(c)
950 955 i += 1
951 956 return ''.join(newname)
952 957 except KeyError as inst:
953 958 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
954 959 inst.args[0])
955 960
956 961 def isstdiofilename(pat):
957 962 """True if the given pat looks like a filename denoting stdin/stdout"""
958 963 return not pat or pat == '-'
959 964
960 965 class _unclosablefile(object):
961 966 def __init__(self, fp):
962 967 self._fp = fp
963 968
964 969 def close(self):
965 970 pass
966 971
967 972 def __iter__(self):
968 973 return iter(self._fp)
969 974
970 975 def __getattr__(self, attr):
971 976 return getattr(self._fp, attr)
972 977
973 978 def __enter__(self):
974 979 return self
975 980
976 981 def __exit__(self, exc_type, exc_value, exc_tb):
977 982 pass
978 983
979 984 def makefileobj(repo, pat, node=None, desc=None, total=None,
980 985 seqno=None, revwidth=None, mode='wb', modemap=None,
981 986 pathname=None):
982 987
983 988 writable = mode not in ('r', 'rb')
984 989
985 990 if isstdiofilename(pat):
986 991 if writable:
987 992 fp = repo.ui.fout
988 993 else:
989 994 fp = repo.ui.fin
990 995 return _unclosablefile(fp)
991 996 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
992 997 if modemap is not None:
993 998 mode = modemap.get(fn, mode)
994 999 if mode == 'wb':
995 1000 modemap[fn] = 'ab'
996 1001 return open(fn, mode)
997 1002
998 1003 def openrevlog(repo, cmd, file_, opts):
999 1004 """opens the changelog, manifest, a filelog or a given revlog"""
1000 1005 cl = opts['changelog']
1001 1006 mf = opts['manifest']
1002 1007 dir = opts['dir']
1003 1008 msg = None
1004 1009 if cl and mf:
1005 1010 msg = _('cannot specify --changelog and --manifest at the same time')
1006 1011 elif cl and dir:
1007 1012 msg = _('cannot specify --changelog and --dir at the same time')
1008 1013 elif cl or mf or dir:
1009 1014 if file_:
1010 1015 msg = _('cannot specify filename with --changelog or --manifest')
1011 1016 elif not repo:
1012 1017 msg = _('cannot specify --changelog or --manifest or --dir '
1013 1018 'without a repository')
1014 1019 if msg:
1015 1020 raise error.Abort(msg)
1016 1021
1017 1022 r = None
1018 1023 if repo:
1019 1024 if cl:
1020 1025 r = repo.unfiltered().changelog
1021 1026 elif dir:
1022 1027 if 'treemanifest' not in repo.requirements:
1023 1028 raise error.Abort(_("--dir can only be used on repos with "
1024 1029 "treemanifest enabled"))
1025 1030 dirlog = repo.manifestlog._revlog.dirlog(dir)
1026 1031 if len(dirlog):
1027 1032 r = dirlog
1028 1033 elif mf:
1029 1034 r = repo.manifestlog._revlog
1030 1035 elif file_:
1031 1036 filelog = repo.file(file_)
1032 1037 if len(filelog):
1033 1038 r = filelog
1034 1039 if not r:
1035 1040 if not file_:
1036 1041 raise error.CommandError(cmd, _('invalid arguments'))
1037 1042 if not os.path.isfile(file_):
1038 1043 raise error.Abort(_("revlog '%s' not found") % file_)
1039 1044 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1040 1045 file_[:-2] + ".i")
1041 1046 return r
1042 1047
1043 1048 def copy(ui, repo, pats, opts, rename=False):
1044 1049 # called with the repo lock held
1045 1050 #
1046 1051 # hgsep => pathname that uses "/" to separate directories
1047 1052 # ossep => pathname that uses os.sep to separate directories
1048 1053 cwd = repo.getcwd()
1049 1054 targets = {}
1050 1055 after = opts.get("after")
1051 1056 dryrun = opts.get("dry_run")
1052 1057 wctx = repo[None]
1053 1058
1054 1059 def walkpat(pat):
1055 1060 srcs = []
1056 1061 if after:
1057 1062 badstates = '?'
1058 1063 else:
1059 1064 badstates = '?r'
1060 1065 m = scmutil.match(wctx, [pat], opts, globbed=True)
1061 1066 for abs in wctx.walk(m):
1062 1067 state = repo.dirstate[abs]
1063 1068 rel = m.rel(abs)
1064 1069 exact = m.exact(abs)
1065 1070 if state in badstates:
1066 1071 if exact and state == '?':
1067 1072 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1068 1073 if exact and state == 'r':
1069 1074 ui.warn(_('%s: not copying - file has been marked for'
1070 1075 ' remove\n') % rel)
1071 1076 continue
1072 1077 # abs: hgsep
1073 1078 # rel: ossep
1074 1079 srcs.append((abs, rel, exact))
1075 1080 return srcs
1076 1081
1077 1082 # abssrc: hgsep
1078 1083 # relsrc: ossep
1079 1084 # otarget: ossep
1080 1085 def copyfile(abssrc, relsrc, otarget, exact):
1081 1086 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1082 1087 if '/' in abstarget:
1083 1088 # We cannot normalize abstarget itself, this would prevent
1084 1089 # case only renames, like a => A.
1085 1090 abspath, absname = abstarget.rsplit('/', 1)
1086 1091 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1087 1092 reltarget = repo.pathto(abstarget, cwd)
1088 1093 target = repo.wjoin(abstarget)
1089 1094 src = repo.wjoin(abssrc)
1090 1095 state = repo.dirstate[abstarget]
1091 1096
1092 1097 scmutil.checkportable(ui, abstarget)
1093 1098
1094 1099 # check for collisions
1095 1100 prevsrc = targets.get(abstarget)
1096 1101 if prevsrc is not None:
1097 1102 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1098 1103 (reltarget, repo.pathto(abssrc, cwd),
1099 1104 repo.pathto(prevsrc, cwd)))
1100 1105 return
1101 1106
1102 1107 # check for overwrites
1103 1108 exists = os.path.lexists(target)
1104 1109 samefile = False
1105 1110 if exists and abssrc != abstarget:
1106 1111 if (repo.dirstate.normalize(abssrc) ==
1107 1112 repo.dirstate.normalize(abstarget)):
1108 1113 if not rename:
1109 1114 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1110 1115 return
1111 1116 exists = False
1112 1117 samefile = True
1113 1118
1114 1119 if not after and exists or after and state in 'mn':
1115 1120 if not opts['force']:
1116 1121 if state in 'mn':
1117 1122 msg = _('%s: not overwriting - file already committed\n')
1118 1123 if after:
1119 1124 flags = '--after --force'
1120 1125 else:
1121 1126 flags = '--force'
1122 1127 if rename:
1123 1128 hint = _('(hg rename %s to replace the file by '
1124 1129 'recording a rename)\n') % flags
1125 1130 else:
1126 1131 hint = _('(hg copy %s to replace the file by '
1127 1132 'recording a copy)\n') % flags
1128 1133 else:
1129 1134 msg = _('%s: not overwriting - file exists\n')
1130 1135 if rename:
1131 1136 hint = _('(hg rename --after to record the rename)\n')
1132 1137 else:
1133 1138 hint = _('(hg copy --after to record the copy)\n')
1134 1139 ui.warn(msg % reltarget)
1135 1140 ui.warn(hint)
1136 1141 return
1137 1142
1138 1143 if after:
1139 1144 if not exists:
1140 1145 if rename:
1141 1146 ui.warn(_('%s: not recording move - %s does not exist\n') %
1142 1147 (relsrc, reltarget))
1143 1148 else:
1144 1149 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1145 1150 (relsrc, reltarget))
1146 1151 return
1147 1152 elif not dryrun:
1148 1153 try:
1149 1154 if exists:
1150 1155 os.unlink(target)
1151 1156 targetdir = os.path.dirname(target) or '.'
1152 1157 if not os.path.isdir(targetdir):
1153 1158 os.makedirs(targetdir)
1154 1159 if samefile:
1155 1160 tmp = target + "~hgrename"
1156 1161 os.rename(src, tmp)
1157 1162 os.rename(tmp, target)
1158 1163 else:
1159 1164 util.copyfile(src, target)
1160 1165 srcexists = True
1161 1166 except IOError as inst:
1162 1167 if inst.errno == errno.ENOENT:
1163 1168 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1164 1169 srcexists = False
1165 1170 else:
1166 1171 ui.warn(_('%s: cannot copy - %s\n') %
1167 1172 (relsrc, encoding.strtolocal(inst.strerror)))
1168 1173 return True # report a failure
1169 1174
1170 1175 if ui.verbose or not exact:
1171 1176 if rename:
1172 1177 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1173 1178 else:
1174 1179 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1175 1180
1176 1181 targets[abstarget] = abssrc
1177 1182
1178 1183 # fix up dirstate
1179 1184 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1180 1185 dryrun=dryrun, cwd=cwd)
1181 1186 if rename and not dryrun:
1182 1187 if not after and srcexists and not samefile:
1183 1188 repo.wvfs.unlinkpath(abssrc)
1184 1189 wctx.forget([abssrc])
1185 1190
1186 1191 # pat: ossep
1187 1192 # dest ossep
1188 1193 # srcs: list of (hgsep, hgsep, ossep, bool)
1189 1194 # return: function that takes hgsep and returns ossep
1190 1195 def targetpathfn(pat, dest, srcs):
1191 1196 if os.path.isdir(pat):
1192 1197 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1193 1198 abspfx = util.localpath(abspfx)
1194 1199 if destdirexists:
1195 1200 striplen = len(os.path.split(abspfx)[0])
1196 1201 else:
1197 1202 striplen = len(abspfx)
1198 1203 if striplen:
1199 1204 striplen += len(pycompat.ossep)
1200 1205 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1201 1206 elif destdirexists:
1202 1207 res = lambda p: os.path.join(dest,
1203 1208 os.path.basename(util.localpath(p)))
1204 1209 else:
1205 1210 res = lambda p: dest
1206 1211 return res
1207 1212
1208 1213 # pat: ossep
1209 1214 # dest ossep
1210 1215 # srcs: list of (hgsep, hgsep, ossep, bool)
1211 1216 # return: function that takes hgsep and returns ossep
1212 1217 def targetpathafterfn(pat, dest, srcs):
1213 1218 if matchmod.patkind(pat):
1214 1219 # a mercurial pattern
1215 1220 res = lambda p: os.path.join(dest,
1216 1221 os.path.basename(util.localpath(p)))
1217 1222 else:
1218 1223 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1219 1224 if len(abspfx) < len(srcs[0][0]):
1220 1225 # A directory. Either the target path contains the last
1221 1226 # component of the source path or it does not.
1222 1227 def evalpath(striplen):
1223 1228 score = 0
1224 1229 for s in srcs:
1225 1230 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1226 1231 if os.path.lexists(t):
1227 1232 score += 1
1228 1233 return score
1229 1234
1230 1235 abspfx = util.localpath(abspfx)
1231 1236 striplen = len(abspfx)
1232 1237 if striplen:
1233 1238 striplen += len(pycompat.ossep)
1234 1239 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1235 1240 score = evalpath(striplen)
1236 1241 striplen1 = len(os.path.split(abspfx)[0])
1237 1242 if striplen1:
1238 1243 striplen1 += len(pycompat.ossep)
1239 1244 if evalpath(striplen1) > score:
1240 1245 striplen = striplen1
1241 1246 res = lambda p: os.path.join(dest,
1242 1247 util.localpath(p)[striplen:])
1243 1248 else:
1244 1249 # a file
1245 1250 if destdirexists:
1246 1251 res = lambda p: os.path.join(dest,
1247 1252 os.path.basename(util.localpath(p)))
1248 1253 else:
1249 1254 res = lambda p: dest
1250 1255 return res
1251 1256
1252 1257 pats = scmutil.expandpats(pats)
1253 1258 if not pats:
1254 1259 raise error.Abort(_('no source or destination specified'))
1255 1260 if len(pats) == 1:
1256 1261 raise error.Abort(_('no destination specified'))
1257 1262 dest = pats.pop()
1258 1263 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1259 1264 if not destdirexists:
1260 1265 if len(pats) > 1 or matchmod.patkind(pats[0]):
1261 1266 raise error.Abort(_('with multiple sources, destination must be an '
1262 1267 'existing directory'))
1263 1268 if util.endswithsep(dest):
1264 1269 raise error.Abort(_('destination %s is not a directory') % dest)
1265 1270
1266 1271 tfn = targetpathfn
1267 1272 if after:
1268 1273 tfn = targetpathafterfn
1269 1274 copylist = []
1270 1275 for pat in pats:
1271 1276 srcs = walkpat(pat)
1272 1277 if not srcs:
1273 1278 continue
1274 1279 copylist.append((tfn(pat, dest, srcs), srcs))
1275 1280 if not copylist:
1276 1281 raise error.Abort(_('no files to copy'))
1277 1282
1278 1283 errors = 0
1279 1284 for targetpath, srcs in copylist:
1280 1285 for abssrc, relsrc, exact in srcs:
1281 1286 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1282 1287 errors += 1
1283 1288
1284 1289 if errors:
1285 1290 ui.warn(_('(consider using --after)\n'))
1286 1291
1287 1292 return errors != 0
1288 1293
1289 1294 ## facility to let extension process additional data into an import patch
1290 1295 # list of identifier to be executed in order
1291 1296 extrapreimport = [] # run before commit
1292 1297 extrapostimport = [] # run after commit
1293 1298 # mapping from identifier to actual import function
1294 1299 #
1295 1300 # 'preimport' are run before the commit is made and are provided the following
1296 1301 # arguments:
1297 1302 # - repo: the localrepository instance,
1298 1303 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1299 1304 # - extra: the future extra dictionary of the changeset, please mutate it,
1300 1305 # - opts: the import options.
1301 1306 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1302 1307 # mutation of in memory commit and more. Feel free to rework the code to get
1303 1308 # there.
1304 1309 extrapreimportmap = {}
1305 1310 # 'postimport' are run after the commit is made and are provided the following
1306 1311 # argument:
1307 1312 # - ctx: the changectx created by import.
1308 1313 extrapostimportmap = {}
1309 1314
1310 1315 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1311 1316 """Utility function used by commands.import to import a single patch
1312 1317
1313 1318 This function is explicitly defined here to help the evolve extension to
1314 1319 wrap this part of the import logic.
1315 1320
1316 1321 The API is currently a bit ugly because it a simple code translation from
1317 1322 the import command. Feel free to make it better.
1318 1323
1319 1324 :hunk: a patch (as a binary string)
1320 1325 :parents: nodes that will be parent of the created commit
1321 1326 :opts: the full dict of option passed to the import command
1322 1327 :msgs: list to save commit message to.
1323 1328 (used in case we need to save it when failing)
1324 1329 :updatefunc: a function that update a repo to a given node
1325 1330 updatefunc(<repo>, <node>)
1326 1331 """
1327 1332 # avoid cycle context -> subrepo -> cmdutil
1328 1333 from . import context
1329 1334 extractdata = patch.extract(ui, hunk)
1330 1335 tmpname = extractdata.get('filename')
1331 1336 message = extractdata.get('message')
1332 1337 user = opts.get('user') or extractdata.get('user')
1333 1338 date = opts.get('date') or extractdata.get('date')
1334 1339 branch = extractdata.get('branch')
1335 1340 nodeid = extractdata.get('nodeid')
1336 1341 p1 = extractdata.get('p1')
1337 1342 p2 = extractdata.get('p2')
1338 1343
1339 1344 nocommit = opts.get('no_commit')
1340 1345 importbranch = opts.get('import_branch')
1341 1346 update = not opts.get('bypass')
1342 1347 strip = opts["strip"]
1343 1348 prefix = opts["prefix"]
1344 1349 sim = float(opts.get('similarity') or 0)
1345 1350 if not tmpname:
1346 1351 return (None, None, False)
1347 1352
1348 1353 rejects = False
1349 1354
1350 1355 try:
1351 1356 cmdline_message = logmessage(ui, opts)
1352 1357 if cmdline_message:
1353 1358 # pickup the cmdline msg
1354 1359 message = cmdline_message
1355 1360 elif message:
1356 1361 # pickup the patch msg
1357 1362 message = message.strip()
1358 1363 else:
1359 1364 # launch the editor
1360 1365 message = None
1361 1366 ui.debug('message:\n%s\n' % message)
1362 1367
1363 1368 if len(parents) == 1:
1364 1369 parents.append(repo[nullid])
1365 1370 if opts.get('exact'):
1366 1371 if not nodeid or not p1:
1367 1372 raise error.Abort(_('not a Mercurial patch'))
1368 1373 p1 = repo[p1]
1369 1374 p2 = repo[p2 or nullid]
1370 1375 elif p2:
1371 1376 try:
1372 1377 p1 = repo[p1]
1373 1378 p2 = repo[p2]
1374 1379 # Without any options, consider p2 only if the
1375 1380 # patch is being applied on top of the recorded
1376 1381 # first parent.
1377 1382 if p1 != parents[0]:
1378 1383 p1 = parents[0]
1379 1384 p2 = repo[nullid]
1380 1385 except error.RepoError:
1381 1386 p1, p2 = parents
1382 1387 if p2.node() == nullid:
1383 1388 ui.warn(_("warning: import the patch as a normal revision\n"
1384 1389 "(use --exact to import the patch as a merge)\n"))
1385 1390 else:
1386 1391 p1, p2 = parents
1387 1392
1388 1393 n = None
1389 1394 if update:
1390 1395 if p1 != parents[0]:
1391 1396 updatefunc(repo, p1.node())
1392 1397 if p2 != parents[1]:
1393 1398 repo.setparents(p1.node(), p2.node())
1394 1399
1395 1400 if opts.get('exact') or importbranch:
1396 1401 repo.dirstate.setbranch(branch or 'default')
1397 1402
1398 1403 partial = opts.get('partial', False)
1399 1404 files = set()
1400 1405 try:
1401 1406 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1402 1407 files=files, eolmode=None, similarity=sim / 100.0)
1403 1408 except error.PatchError as e:
1404 1409 if not partial:
1405 1410 raise error.Abort(str(e))
1406 1411 if partial:
1407 1412 rejects = True
1408 1413
1409 1414 files = list(files)
1410 1415 if nocommit:
1411 1416 if message:
1412 1417 msgs.append(message)
1413 1418 else:
1414 1419 if opts.get('exact') or p2:
1415 1420 # If you got here, you either use --force and know what
1416 1421 # you are doing or used --exact or a merge patch while
1417 1422 # being updated to its first parent.
1418 1423 m = None
1419 1424 else:
1420 1425 m = scmutil.matchfiles(repo, files or [])
1421 1426 editform = mergeeditform(repo[None], 'import.normal')
1422 1427 if opts.get('exact'):
1423 1428 editor = None
1424 1429 else:
1425 1430 editor = getcommiteditor(editform=editform,
1426 1431 **pycompat.strkwargs(opts))
1427 1432 extra = {}
1428 1433 for idfunc in extrapreimport:
1429 1434 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1430 1435 overrides = {}
1431 1436 if partial:
1432 1437 overrides[('ui', 'allowemptycommit')] = True
1433 1438 with repo.ui.configoverride(overrides, 'import'):
1434 1439 n = repo.commit(message, user,
1435 1440 date, match=m,
1436 1441 editor=editor, extra=extra)
1437 1442 for idfunc in extrapostimport:
1438 1443 extrapostimportmap[idfunc](repo[n])
1439 1444 else:
1440 1445 if opts.get('exact') or importbranch:
1441 1446 branch = branch or 'default'
1442 1447 else:
1443 1448 branch = p1.branch()
1444 1449 store = patch.filestore()
1445 1450 try:
1446 1451 files = set()
1447 1452 try:
1448 1453 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1449 1454 files, eolmode=None)
1450 1455 except error.PatchError as e:
1451 1456 raise error.Abort(str(e))
1452 1457 if opts.get('exact'):
1453 1458 editor = None
1454 1459 else:
1455 1460 editor = getcommiteditor(editform='import.bypass')
1456 1461 memctx = context.memctx(repo, (p1.node(), p2.node()),
1457 1462 message,
1458 1463 files=files,
1459 1464 filectxfn=store,
1460 1465 user=user,
1461 1466 date=date,
1462 1467 branch=branch,
1463 1468 editor=editor)
1464 1469 n = memctx.commit()
1465 1470 finally:
1466 1471 store.close()
1467 1472 if opts.get('exact') and nocommit:
1468 1473 # --exact with --no-commit is still useful in that it does merge
1469 1474 # and branch bits
1470 1475 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1471 1476 elif opts.get('exact') and hex(n) != nodeid:
1472 1477 raise error.Abort(_('patch is damaged or loses information'))
1473 1478 msg = _('applied to working directory')
1474 1479 if n:
1475 1480 # i18n: refers to a short changeset id
1476 1481 msg = _('created %s') % short(n)
1477 1482 return (msg, n, rejects)
1478 1483 finally:
1479 1484 os.unlink(tmpname)
1480 1485
1481 1486 # facility to let extensions include additional data in an exported patch
1482 1487 # list of identifiers to be executed in order
1483 1488 extraexport = []
1484 1489 # mapping from identifier to actual export function
1485 1490 # function as to return a string to be added to the header or None
1486 1491 # it is given two arguments (sequencenumber, changectx)
1487 1492 extraexportmap = {}
1488 1493
1489 1494 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1490 1495 node = scmutil.binnode(ctx)
1491 1496 parents = [p.node() for p in ctx.parents() if p]
1492 1497 branch = ctx.branch()
1493 1498 if switch_parent:
1494 1499 parents.reverse()
1495 1500
1496 1501 if parents:
1497 1502 prev = parents[0]
1498 1503 else:
1499 1504 prev = nullid
1500 1505
1501 1506 write("# HG changeset patch\n")
1502 1507 write("# User %s\n" % ctx.user())
1503 1508 write("# Date %d %d\n" % ctx.date())
1504 1509 write("# %s\n" % util.datestr(ctx.date()))
1505 1510 if branch and branch != 'default':
1506 1511 write("# Branch %s\n" % branch)
1507 1512 write("# Node ID %s\n" % hex(node))
1508 1513 write("# Parent %s\n" % hex(prev))
1509 1514 if len(parents) > 1:
1510 1515 write("# Parent %s\n" % hex(parents[1]))
1511 1516
1512 1517 for headerid in extraexport:
1513 1518 header = extraexportmap[headerid](seqno, ctx)
1514 1519 if header is not None:
1515 1520 write('# %s\n' % header)
1516 1521 write(ctx.description().rstrip())
1517 1522 write("\n\n")
1518 1523
1519 1524 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1520 1525 write(chunk, label=label)
1521 1526
1522 1527 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1523 1528 opts=None, match=None):
1524 1529 '''export changesets as hg patches
1525 1530
1526 1531 Args:
1527 1532 repo: The repository from which we're exporting revisions.
1528 1533 revs: A list of revisions to export as revision numbers.
1529 1534 fntemplate: An optional string to use for generating patch file names.
1530 1535 fp: An optional file-like object to which patches should be written.
1531 1536 switch_parent: If True, show diffs against second parent when not nullid.
1532 1537 Default is false, which always shows diff against p1.
1533 1538 opts: diff options to use for generating the patch.
1534 1539 match: If specified, only export changes to files matching this matcher.
1535 1540
1536 1541 Returns:
1537 1542 Nothing.
1538 1543
1539 1544 Side Effect:
1540 1545 "HG Changeset Patch" data is emitted to one of the following
1541 1546 destinations:
1542 1547 fp is specified: All revs are written to the specified
1543 1548 file-like object.
1544 1549 fntemplate specified: Each rev is written to a unique file named using
1545 1550 the given template.
1546 1551 Neither fp nor template specified: All revs written to repo.ui.write()
1547 1552 '''
1548 1553
1549 1554 total = len(revs)
1550 1555 revwidth = max(len(str(rev)) for rev in revs)
1551 1556 filemode = {}
1552 1557
1553 1558 write = None
1554 1559 dest = '<unnamed>'
1555 1560 if fp:
1556 1561 dest = getattr(fp, 'name', dest)
1557 1562 def write(s, **kw):
1558 1563 fp.write(s)
1559 1564 elif not fntemplate:
1560 1565 write = repo.ui.write
1561 1566
1562 1567 for seqno, rev in enumerate(revs, 1):
1563 1568 ctx = repo[rev]
1564 1569 fo = None
1565 1570 if not fp and fntemplate:
1566 1571 desc_lines = ctx.description().rstrip().split('\n')
1567 1572 desc = desc_lines[0] #Commit always has a first line.
1568 1573 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1569 1574 total=total, seqno=seqno, revwidth=revwidth,
1570 1575 mode='wb', modemap=filemode)
1571 1576 dest = fo.name
1572 1577 def write(s, **kw):
1573 1578 fo.write(s)
1574 1579 if not dest.startswith('<'):
1575 1580 repo.ui.note("%s\n" % dest)
1576 1581 _exportsingle(
1577 1582 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1578 1583 if fo is not None:
1579 1584 fo.close()
1580 1585
1581 1586 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1582 1587 changes=None, stat=False, fp=None, prefix='',
1583 1588 root='', listsubrepos=False, hunksfilterfn=None):
1584 1589 '''show diff or diffstat.'''
1585 1590 if fp is None:
1586 1591 write = ui.write
1587 1592 else:
1588 1593 def write(s, **kw):
1589 1594 fp.write(s)
1590 1595
1591 1596 if root:
1592 1597 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1593 1598 else:
1594 1599 relroot = ''
1595 1600 if relroot != '':
1596 1601 # XXX relative roots currently don't work if the root is within a
1597 1602 # subrepo
1598 1603 uirelroot = match.uipath(relroot)
1599 1604 relroot += '/'
1600 1605 for matchroot in match.files():
1601 1606 if not matchroot.startswith(relroot):
1602 1607 ui.warn(_('warning: %s not inside relative root %s\n') % (
1603 1608 match.uipath(matchroot), uirelroot))
1604 1609
1605 1610 if stat:
1606 1611 diffopts = diffopts.copy(context=0, noprefix=False)
1607 1612 width = 80
1608 1613 if not ui.plain():
1609 1614 width = ui.termwidth()
1610 1615 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
1611 1616 prefix=prefix, relroot=relroot,
1612 1617 hunksfilterfn=hunksfilterfn)
1613 1618 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1614 1619 width=width):
1615 1620 write(chunk, label=label)
1616 1621 else:
1617 1622 for chunk, label in patch.diffui(repo, node1, node2, match,
1618 1623 changes, opts=diffopts, prefix=prefix,
1619 1624 relroot=relroot,
1620 1625 hunksfilterfn=hunksfilterfn):
1621 1626 write(chunk, label=label)
1622 1627
1623 1628 if listsubrepos:
1624 1629 ctx1 = repo[node1]
1625 1630 ctx2 = repo[node2]
1626 1631 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1627 1632 tempnode2 = node2
1628 1633 try:
1629 1634 if node2 is not None:
1630 1635 tempnode2 = ctx2.substate[subpath][1]
1631 1636 except KeyError:
1632 1637 # A subrepo that existed in node1 was deleted between node1 and
1633 1638 # node2 (inclusive). Thus, ctx2's substate won't contain that
1634 1639 # subpath. The best we can do is to ignore it.
1635 1640 tempnode2 = None
1636 1641 submatch = matchmod.subdirmatcher(subpath, match)
1637 1642 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1638 1643 stat=stat, fp=fp, prefix=prefix)
1639 1644
1640 1645 def _changesetlabels(ctx):
1641 1646 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1642 1647 if ctx.obsolete():
1643 1648 labels.append('changeset.obsolete')
1644 1649 if ctx.isunstable():
1645 1650 labels.append('changeset.unstable')
1646 1651 for instability in ctx.instabilities():
1647 1652 labels.append('instability.%s' % instability)
1648 1653 return ' '.join(labels)
1649 1654
1650 1655 class changeset_printer(object):
1651 1656 '''show changeset information when templating not requested.'''
1652 1657
1653 1658 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1654 1659 self.ui = ui
1655 1660 self.repo = repo
1656 1661 self.buffered = buffered
1657 1662 self.matchfn = matchfn
1658 1663 self.diffopts = diffopts
1659 1664 self.header = {}
1660 1665 self.hunk = {}
1661 1666 self.lastheader = None
1662 1667 self.footer = None
1663 1668 self._columns = templatekw.getlogcolumns()
1664 1669
1665 1670 def flush(self, ctx):
1666 1671 rev = ctx.rev()
1667 1672 if rev in self.header:
1668 1673 h = self.header[rev]
1669 1674 if h != self.lastheader:
1670 1675 self.lastheader = h
1671 1676 self.ui.write(h)
1672 1677 del self.header[rev]
1673 1678 if rev in self.hunk:
1674 1679 self.ui.write(self.hunk[rev])
1675 1680 del self.hunk[rev]
1676 1681
1677 1682 def close(self):
1678 1683 if self.footer:
1679 1684 self.ui.write(self.footer)
1680 1685
1681 1686 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
1682 1687 **props):
1683 1688 props = pycompat.byteskwargs(props)
1684 1689 if self.buffered:
1685 1690 self.ui.pushbuffer(labeled=True)
1686 1691 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1687 1692 self.hunk[ctx.rev()] = self.ui.popbuffer()
1688 1693 else:
1689 1694 self._show(ctx, copies, matchfn, hunksfilterfn, props)
1690 1695
1691 1696 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1692 1697 '''show a single changeset or file revision'''
1693 1698 changenode = ctx.node()
1694 1699 rev = ctx.rev()
1695 1700
1696 1701 if self.ui.quiet:
1697 1702 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
1698 1703 label='log.node')
1699 1704 return
1700 1705
1701 1706 columns = self._columns
1702 1707 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
1703 1708 label=_changesetlabels(ctx))
1704 1709
1705 1710 # branches are shown first before any other names due to backwards
1706 1711 # compatibility
1707 1712 branch = ctx.branch()
1708 1713 # don't show the default branch name
1709 1714 if branch != 'default':
1710 1715 self.ui.write(columns['branch'] % branch, label='log.branch')
1711 1716
1712 1717 for nsname, ns in self.repo.names.iteritems():
1713 1718 # branches has special logic already handled above, so here we just
1714 1719 # skip it
1715 1720 if nsname == 'branches':
1716 1721 continue
1717 1722 # we will use the templatename as the color name since those two
1718 1723 # should be the same
1719 1724 for name in ns.names(self.repo, changenode):
1720 1725 self.ui.write(ns.logfmt % name,
1721 1726 label='log.%s' % ns.colorname)
1722 1727 if self.ui.debugflag:
1723 1728 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
1724 1729 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1725 1730 label = 'log.parent changeset.%s' % pctx.phasestr()
1726 1731 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
1727 1732 label=label)
1728 1733
1729 1734 if self.ui.debugflag and rev is not None:
1730 1735 mnode = ctx.manifestnode()
1731 1736 mrev = self.repo.manifestlog._revlog.rev(mnode)
1732 1737 self.ui.write(columns['manifest']
1733 1738 % scmutil.formatrevnode(self.ui, mrev, mnode),
1734 1739 label='ui.debug log.manifest')
1735 1740 self.ui.write(columns['user'] % ctx.user(), label='log.user')
1736 1741 self.ui.write(columns['date'] % util.datestr(ctx.date()),
1737 1742 label='log.date')
1738 1743
1739 1744 if ctx.isunstable():
1740 1745 instabilities = ctx.instabilities()
1741 1746 self.ui.write(columns['instability'] % ', '.join(instabilities),
1742 1747 label='log.instability')
1743 1748
1744 1749 elif ctx.obsolete():
1745 1750 self._showobsfate(ctx)
1746 1751
1747 1752 self._exthook(ctx)
1748 1753
1749 1754 if self.ui.debugflag:
1750 1755 files = ctx.p1().status(ctx)[:3]
1751 1756 for key, value in zip(['files', 'files+', 'files-'], files):
1752 1757 if value:
1753 1758 self.ui.write(columns[key] % " ".join(value),
1754 1759 label='ui.debug log.files')
1755 1760 elif ctx.files() and self.ui.verbose:
1756 1761 self.ui.write(columns['files'] % " ".join(ctx.files()),
1757 1762 label='ui.note log.files')
1758 1763 if copies and self.ui.verbose:
1759 1764 copies = ['%s (%s)' % c for c in copies]
1760 1765 self.ui.write(columns['copies'] % ' '.join(copies),
1761 1766 label='ui.note log.copies')
1762 1767
1763 1768 extra = ctx.extra()
1764 1769 if extra and self.ui.debugflag:
1765 1770 for key, value in sorted(extra.items()):
1766 1771 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
1767 1772 label='ui.debug log.extra')
1768 1773
1769 1774 description = ctx.description().strip()
1770 1775 if description:
1771 1776 if self.ui.verbose:
1772 1777 self.ui.write(_("description:\n"),
1773 1778 label='ui.note log.description')
1774 1779 self.ui.write(description,
1775 1780 label='ui.note log.description')
1776 1781 self.ui.write("\n\n")
1777 1782 else:
1778 1783 self.ui.write(columns['summary'] % description.splitlines()[0],
1779 1784 label='log.summary')
1780 1785 self.ui.write("\n")
1781 1786
1782 1787 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
1783 1788
1784 1789 def _showobsfate(self, ctx):
1785 1790 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
1786 1791
1787 1792 if obsfate:
1788 1793 for obsfateline in obsfate:
1789 1794 self.ui.write(self._columns['obsolete'] % obsfateline,
1790 1795 label='log.obsfate')
1791 1796
1792 1797 def _exthook(self, ctx):
1793 1798 '''empty method used by extension as a hook point
1794 1799 '''
1795 1800
1796 1801 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
1797 1802 if not matchfn:
1798 1803 matchfn = self.matchfn
1799 1804 if matchfn:
1800 1805 stat = self.diffopts.get('stat')
1801 1806 diff = self.diffopts.get('patch')
1802 1807 diffopts = patch.diffallopts(self.ui, self.diffopts)
1803 1808 node = ctx.node()
1804 1809 prev = ctx.p1().node()
1805 1810 if stat:
1806 1811 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1807 1812 match=matchfn, stat=True,
1808 1813 hunksfilterfn=hunksfilterfn)
1809 1814 if diff:
1810 1815 if stat:
1811 1816 self.ui.write("\n")
1812 1817 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1813 1818 match=matchfn, stat=False,
1814 1819 hunksfilterfn=hunksfilterfn)
1815 1820 if stat or diff:
1816 1821 self.ui.write("\n")
1817 1822
1818 1823 class jsonchangeset(changeset_printer):
1819 1824 '''format changeset information.'''
1820 1825
1821 1826 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1822 1827 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1823 1828 self.cache = {}
1824 1829 self._first = True
1825 1830
1826 1831 def close(self):
1827 1832 if not self._first:
1828 1833 self.ui.write("\n]\n")
1829 1834 else:
1830 1835 self.ui.write("[]\n")
1831 1836
1832 1837 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1833 1838 '''show a single changeset or file revision'''
1834 1839 rev = ctx.rev()
1835 1840 if rev is None:
1836 1841 jrev = jnode = 'null'
1837 1842 else:
1838 1843 jrev = '%d' % rev
1839 1844 jnode = '"%s"' % hex(ctx.node())
1840 1845 j = encoding.jsonescape
1841 1846
1842 1847 if self._first:
1843 1848 self.ui.write("[\n {")
1844 1849 self._first = False
1845 1850 else:
1846 1851 self.ui.write(",\n {")
1847 1852
1848 1853 if self.ui.quiet:
1849 1854 self.ui.write(('\n "rev": %s') % jrev)
1850 1855 self.ui.write((',\n "node": %s') % jnode)
1851 1856 self.ui.write('\n }')
1852 1857 return
1853 1858
1854 1859 self.ui.write(('\n "rev": %s') % jrev)
1855 1860 self.ui.write((',\n "node": %s') % jnode)
1856 1861 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1857 1862 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1858 1863 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1859 1864 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1860 1865 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1861 1866
1862 1867 self.ui.write((',\n "bookmarks": [%s]') %
1863 1868 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1864 1869 self.ui.write((',\n "tags": [%s]') %
1865 1870 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1866 1871 self.ui.write((',\n "parents": [%s]') %
1867 1872 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1868 1873
1869 1874 if self.ui.debugflag:
1870 1875 if rev is None:
1871 1876 jmanifestnode = 'null'
1872 1877 else:
1873 1878 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1874 1879 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1875 1880
1876 1881 self.ui.write((',\n "extra": {%s}') %
1877 1882 ", ".join('"%s": "%s"' % (j(k), j(v))
1878 1883 for k, v in ctx.extra().items()))
1879 1884
1880 1885 files = ctx.p1().status(ctx)
1881 1886 self.ui.write((',\n "modified": [%s]') %
1882 1887 ", ".join('"%s"' % j(f) for f in files[0]))
1883 1888 self.ui.write((',\n "added": [%s]') %
1884 1889 ", ".join('"%s"' % j(f) for f in files[1]))
1885 1890 self.ui.write((',\n "removed": [%s]') %
1886 1891 ", ".join('"%s"' % j(f) for f in files[2]))
1887 1892
1888 1893 elif self.ui.verbose:
1889 1894 self.ui.write((',\n "files": [%s]') %
1890 1895 ", ".join('"%s"' % j(f) for f in ctx.files()))
1891 1896
1892 1897 if copies:
1893 1898 self.ui.write((',\n "copies": {%s}') %
1894 1899 ", ".join('"%s": "%s"' % (j(k), j(v))
1895 1900 for k, v in copies))
1896 1901
1897 1902 matchfn = self.matchfn
1898 1903 if matchfn:
1899 1904 stat = self.diffopts.get('stat')
1900 1905 diff = self.diffopts.get('patch')
1901 1906 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1902 1907 node, prev = ctx.node(), ctx.p1().node()
1903 1908 if stat:
1904 1909 self.ui.pushbuffer()
1905 1910 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1906 1911 match=matchfn, stat=True)
1907 1912 self.ui.write((',\n "diffstat": "%s"')
1908 1913 % j(self.ui.popbuffer()))
1909 1914 if diff:
1910 1915 self.ui.pushbuffer()
1911 1916 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1912 1917 match=matchfn, stat=False)
1913 1918 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1914 1919
1915 1920 self.ui.write("\n }")
1916 1921
1917 1922 class changeset_templater(changeset_printer):
1918 1923 '''format changeset information.
1919 1924
1920 1925 Note: there are a variety of convenience functions to build a
1921 1926 changeset_templater for common cases. See functions such as:
1922 1927 makelogtemplater, show_changeset, buildcommittemplate, or other
1923 1928 functions that use changesest_templater.
1924 1929 '''
1925 1930
1926 1931 # Arguments before "buffered" used to be positional. Consider not
1927 1932 # adding/removing arguments before "buffered" to not break callers.
1928 1933 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1929 1934 buffered=False):
1930 1935 diffopts = diffopts or {}
1931 1936
1932 1937 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1933 1938 tres = formatter.templateresources(ui, repo)
1934 1939 self.t = formatter.loadtemplater(ui, tmplspec,
1935 1940 defaults=templatekw.keywords,
1936 1941 resources=tres,
1937 1942 cache=templatekw.defaulttempl)
1938 1943 self._counter = itertools.count()
1939 1944 self.cache = tres['cache'] # shared with _graphnodeformatter()
1940 1945
1941 1946 self._tref = tmplspec.ref
1942 1947 self._parts = {'header': '', 'footer': '',
1943 1948 tmplspec.ref: tmplspec.ref,
1944 1949 'docheader': '', 'docfooter': '',
1945 1950 'separator': ''}
1946 1951 if tmplspec.mapfile:
1947 1952 # find correct templates for current mode, for backward
1948 1953 # compatibility with 'log -v/-q/--debug' using a mapfile
1949 1954 tmplmodes = [
1950 1955 (True, ''),
1951 1956 (self.ui.verbose, '_verbose'),
1952 1957 (self.ui.quiet, '_quiet'),
1953 1958 (self.ui.debugflag, '_debug'),
1954 1959 ]
1955 1960 for mode, postfix in tmplmodes:
1956 1961 for t in self._parts:
1957 1962 cur = t + postfix
1958 1963 if mode and cur in self.t:
1959 1964 self._parts[t] = cur
1960 1965 else:
1961 1966 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1962 1967 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1963 1968 self._parts.update(m)
1964 1969
1965 1970 if self._parts['docheader']:
1966 1971 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1967 1972
1968 1973 def close(self):
1969 1974 if self._parts['docfooter']:
1970 1975 if not self.footer:
1971 1976 self.footer = ""
1972 1977 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1973 1978 return super(changeset_templater, self).close()
1974 1979
1975 1980 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
1976 1981 '''show a single changeset or file revision'''
1977 1982 props = props.copy()
1978 1983 props['ctx'] = ctx
1979 1984 props['index'] = index = next(self._counter)
1980 1985 props['revcache'] = {'copies': copies}
1981 1986 props = pycompat.strkwargs(props)
1982 1987
1983 1988 # write separator, which wouldn't work well with the header part below
1984 1989 # since there's inherently a conflict between header (across items) and
1985 1990 # separator (per item)
1986 1991 if self._parts['separator'] and index > 0:
1987 1992 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1988 1993
1989 1994 # write header
1990 1995 if self._parts['header']:
1991 1996 h = templater.stringify(self.t(self._parts['header'], **props))
1992 1997 if self.buffered:
1993 1998 self.header[ctx.rev()] = h
1994 1999 else:
1995 2000 if self.lastheader != h:
1996 2001 self.lastheader = h
1997 2002 self.ui.write(h)
1998 2003
1999 2004 # write changeset metadata, then patch if requested
2000 2005 key = self._parts[self._tref]
2001 2006 self.ui.write(templater.stringify(self.t(key, **props)))
2002 2007 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
2003 2008
2004 2009 if self._parts['footer']:
2005 2010 if not self.footer:
2006 2011 self.footer = templater.stringify(
2007 2012 self.t(self._parts['footer'], **props))
2008 2013
2009 2014 def logtemplatespec(tmpl, mapfile):
2010 2015 if mapfile:
2011 2016 return formatter.templatespec('changeset', tmpl, mapfile)
2012 2017 else:
2013 2018 return formatter.templatespec('', tmpl, None)
2014 2019
2015 2020 def _lookuplogtemplate(ui, tmpl, style):
2016 2021 """Find the template matching the given template spec or style
2017 2022
2018 2023 See formatter.lookuptemplate() for details.
2019 2024 """
2020 2025
2021 2026 # ui settings
2022 2027 if not tmpl and not style: # template are stronger than style
2023 2028 tmpl = ui.config('ui', 'logtemplate')
2024 2029 if tmpl:
2025 2030 return logtemplatespec(templater.unquotestring(tmpl), None)
2026 2031 else:
2027 2032 style = util.expandpath(ui.config('ui', 'style'))
2028 2033
2029 2034 if not tmpl and style:
2030 2035 mapfile = style
2031 2036 if not os.path.split(mapfile)[0]:
2032 2037 mapname = (templater.templatepath('map-cmdline.' + mapfile)
2033 2038 or templater.templatepath(mapfile))
2034 2039 if mapname:
2035 2040 mapfile = mapname
2036 2041 return logtemplatespec(None, mapfile)
2037 2042
2038 2043 if not tmpl:
2039 2044 return logtemplatespec(None, None)
2040 2045
2041 2046 return formatter.lookuptemplate(ui, 'changeset', tmpl)
2042 2047
2043 2048 def makelogtemplater(ui, repo, tmpl, buffered=False):
2044 2049 """Create a changeset_templater from a literal template 'tmpl'
2045 2050 byte-string."""
2046 2051 spec = logtemplatespec(tmpl, None)
2047 2052 return changeset_templater(ui, repo, spec, buffered=buffered)
2048 2053
2049 2054 def show_changeset(ui, repo, opts, buffered=False):
2050 2055 """show one changeset using template or regular display.
2051 2056
2052 2057 Display format will be the first non-empty hit of:
2053 2058 1. option 'template'
2054 2059 2. option 'style'
2055 2060 3. [ui] setting 'logtemplate'
2056 2061 4. [ui] setting 'style'
2057 2062 If all of these values are either the unset or the empty string,
2058 2063 regular display via changeset_printer() is done.
2059 2064 """
2060 2065 # options
2061 2066 match = None
2062 2067 if opts.get('patch') or opts.get('stat'):
2063 2068 match = scmutil.matchall(repo)
2064 2069
2065 2070 if opts.get('template') == 'json':
2066 2071 return jsonchangeset(ui, repo, match, opts, buffered)
2067 2072
2068 2073 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2069 2074
2070 2075 if not spec.ref and not spec.tmpl and not spec.mapfile:
2071 2076 return changeset_printer(ui, repo, match, opts, buffered)
2072 2077
2073 2078 return changeset_templater(ui, repo, spec, match, opts, buffered)
2074 2079
2075 2080 def showmarker(fm, marker, index=None):
2076 2081 """utility function to display obsolescence marker in a readable way
2077 2082
2078 2083 To be used by debug function."""
2079 2084 if index is not None:
2080 2085 fm.write('index', '%i ', index)
2081 2086 fm.write('prednode', '%s ', hex(marker.prednode()))
2082 2087 succs = marker.succnodes()
2083 2088 fm.condwrite(succs, 'succnodes', '%s ',
2084 2089 fm.formatlist(map(hex, succs), name='node'))
2085 2090 fm.write('flag', '%X ', marker.flags())
2086 2091 parents = marker.parentnodes()
2087 2092 if parents is not None:
2088 2093 fm.write('parentnodes', '{%s} ',
2089 2094 fm.formatlist(map(hex, parents), name='node', sep=', '))
2090 2095 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2091 2096 meta = marker.metadata().copy()
2092 2097 meta.pop('date', None)
2093 2098 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2094 2099 fm.plain('\n')
2095 2100
2096 2101 def finddate(ui, repo, date):
2097 2102 """Find the tipmost changeset that matches the given date spec"""
2098 2103
2099 2104 df = util.matchdate(date)
2100 2105 m = scmutil.matchall(repo)
2101 2106 results = {}
2102 2107
2103 2108 def prep(ctx, fns):
2104 2109 d = ctx.date()
2105 2110 if df(d[0]):
2106 2111 results[ctx.rev()] = d
2107 2112
2108 2113 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2109 2114 rev = ctx.rev()
2110 2115 if rev in results:
2111 2116 ui.status(_("found revision %s from %s\n") %
2112 2117 (rev, util.datestr(results[rev])))
2113 2118 return '%d' % rev
2114 2119
2115 2120 raise error.Abort(_("revision matching date not found"))
2116 2121
2117 2122 def increasingwindows(windowsize=8, sizelimit=512):
2118 2123 while True:
2119 2124 yield windowsize
2120 2125 if windowsize < sizelimit:
2121 2126 windowsize *= 2
2122 2127
2123 2128 def _walkrevs(repo, opts):
2124 2129 # Default --rev value depends on --follow but --follow behavior
2125 2130 # depends on revisions resolved from --rev...
2126 2131 follow = opts.get('follow') or opts.get('follow_first')
2127 2132 if opts.get('rev'):
2128 2133 revs = scmutil.revrange(repo, opts['rev'])
2129 2134 elif follow and repo.dirstate.p1() == nullid:
2130 2135 revs = smartset.baseset()
2131 2136 elif follow:
2132 2137 revs = repo.revs('reverse(:.)')
2133 2138 else:
2134 2139 revs = smartset.spanset(repo)
2135 2140 revs.reverse()
2136 2141 return revs
2137 2142
2138 2143 class FileWalkError(Exception):
2139 2144 pass
2140 2145
2141 2146 def walkfilerevs(repo, match, follow, revs, fncache):
2142 2147 '''Walks the file history for the matched files.
2143 2148
2144 2149 Returns the changeset revs that are involved in the file history.
2145 2150
2146 2151 Throws FileWalkError if the file history can't be walked using
2147 2152 filelogs alone.
2148 2153 '''
2149 2154 wanted = set()
2150 2155 copies = []
2151 2156 minrev, maxrev = min(revs), max(revs)
2152 2157 def filerevgen(filelog, last):
2153 2158 """
2154 2159 Only files, no patterns. Check the history of each file.
2155 2160
2156 2161 Examines filelog entries within minrev, maxrev linkrev range
2157 2162 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2158 2163 tuples in backwards order
2159 2164 """
2160 2165 cl_count = len(repo)
2161 2166 revs = []
2162 2167 for j in xrange(0, last + 1):
2163 2168 linkrev = filelog.linkrev(j)
2164 2169 if linkrev < minrev:
2165 2170 continue
2166 2171 # only yield rev for which we have the changelog, it can
2167 2172 # happen while doing "hg log" during a pull or commit
2168 2173 if linkrev >= cl_count:
2169 2174 break
2170 2175
2171 2176 parentlinkrevs = []
2172 2177 for p in filelog.parentrevs(j):
2173 2178 if p != nullrev:
2174 2179 parentlinkrevs.append(filelog.linkrev(p))
2175 2180 n = filelog.node(j)
2176 2181 revs.append((linkrev, parentlinkrevs,
2177 2182 follow and filelog.renamed(n)))
2178 2183
2179 2184 return reversed(revs)
2180 2185 def iterfiles():
2181 2186 pctx = repo['.']
2182 2187 for filename in match.files():
2183 2188 if follow:
2184 2189 if filename not in pctx:
2185 2190 raise error.Abort(_('cannot follow file not in parent '
2186 2191 'revision: "%s"') % filename)
2187 2192 yield filename, pctx[filename].filenode()
2188 2193 else:
2189 2194 yield filename, None
2190 2195 for filename_node in copies:
2191 2196 yield filename_node
2192 2197
2193 2198 for file_, node in iterfiles():
2194 2199 filelog = repo.file(file_)
2195 2200 if not len(filelog):
2196 2201 if node is None:
2197 2202 # A zero count may be a directory or deleted file, so
2198 2203 # try to find matching entries on the slow path.
2199 2204 if follow:
2200 2205 raise error.Abort(
2201 2206 _('cannot follow nonexistent file: "%s"') % file_)
2202 2207 raise FileWalkError("Cannot walk via filelog")
2203 2208 else:
2204 2209 continue
2205 2210
2206 2211 if node is None:
2207 2212 last = len(filelog) - 1
2208 2213 else:
2209 2214 last = filelog.rev(node)
2210 2215
2211 2216 # keep track of all ancestors of the file
2212 2217 ancestors = {filelog.linkrev(last)}
2213 2218
2214 2219 # iterate from latest to oldest revision
2215 2220 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2216 2221 if not follow:
2217 2222 if rev > maxrev:
2218 2223 continue
2219 2224 else:
2220 2225 # Note that last might not be the first interesting
2221 2226 # rev to us:
2222 2227 # if the file has been changed after maxrev, we'll
2223 2228 # have linkrev(last) > maxrev, and we still need
2224 2229 # to explore the file graph
2225 2230 if rev not in ancestors:
2226 2231 continue
2227 2232 # XXX insert 1327 fix here
2228 2233 if flparentlinkrevs:
2229 2234 ancestors.update(flparentlinkrevs)
2230 2235
2231 2236 fncache.setdefault(rev, []).append(file_)
2232 2237 wanted.add(rev)
2233 2238 if copied:
2234 2239 copies.append(copied)
2235 2240
2236 2241 return wanted
2237 2242
2238 2243 class _followfilter(object):
2239 2244 def __init__(self, repo, onlyfirst=False):
2240 2245 self.repo = repo
2241 2246 self.startrev = nullrev
2242 2247 self.roots = set()
2243 2248 self.onlyfirst = onlyfirst
2244 2249
2245 2250 def match(self, rev):
2246 2251 def realparents(rev):
2247 2252 if self.onlyfirst:
2248 2253 return self.repo.changelog.parentrevs(rev)[0:1]
2249 2254 else:
2250 2255 return filter(lambda x: x != nullrev,
2251 2256 self.repo.changelog.parentrevs(rev))
2252 2257
2253 2258 if self.startrev == nullrev:
2254 2259 self.startrev = rev
2255 2260 return True
2256 2261
2257 2262 if rev > self.startrev:
2258 2263 # forward: all descendants
2259 2264 if not self.roots:
2260 2265 self.roots.add(self.startrev)
2261 2266 for parent in realparents(rev):
2262 2267 if parent in self.roots:
2263 2268 self.roots.add(rev)
2264 2269 return True
2265 2270 else:
2266 2271 # backwards: all parents
2267 2272 if not self.roots:
2268 2273 self.roots.update(realparents(self.startrev))
2269 2274 if rev in self.roots:
2270 2275 self.roots.remove(rev)
2271 2276 self.roots.update(realparents(rev))
2272 2277 return True
2273 2278
2274 2279 return False
2275 2280
2276 2281 def walkchangerevs(repo, match, opts, prepare):
2277 2282 '''Iterate over files and the revs in which they changed.
2278 2283
2279 2284 Callers most commonly need to iterate backwards over the history
2280 2285 in which they are interested. Doing so has awful (quadratic-looking)
2281 2286 performance, so we use iterators in a "windowed" way.
2282 2287
2283 2288 We walk a window of revisions in the desired order. Within the
2284 2289 window, we first walk forwards to gather data, then in the desired
2285 2290 order (usually backwards) to display it.
2286 2291
2287 2292 This function returns an iterator yielding contexts. Before
2288 2293 yielding each context, the iterator will first call the prepare
2289 2294 function on each context in the window in forward order.'''
2290 2295
2291 2296 follow = opts.get('follow') or opts.get('follow_first')
2292 2297 revs = _walkrevs(repo, opts)
2293 2298 if not revs:
2294 2299 return []
2295 2300 wanted = set()
2296 2301 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2297 2302 fncache = {}
2298 2303 change = repo.changectx
2299 2304
2300 2305 # First step is to fill wanted, the set of revisions that we want to yield.
2301 2306 # When it does not induce extra cost, we also fill fncache for revisions in
2302 2307 # wanted: a cache of filenames that were changed (ctx.files()) and that
2303 2308 # match the file filtering conditions.
2304 2309
2305 2310 if match.always():
2306 2311 # No files, no patterns. Display all revs.
2307 2312 wanted = revs
2308 2313 elif not slowpath:
2309 2314 # We only have to read through the filelog to find wanted revisions
2310 2315
2311 2316 try:
2312 2317 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2313 2318 except FileWalkError:
2314 2319 slowpath = True
2315 2320
2316 2321 # We decided to fall back to the slowpath because at least one
2317 2322 # of the paths was not a file. Check to see if at least one of them
2318 2323 # existed in history, otherwise simply return
2319 2324 for path in match.files():
2320 2325 if path == '.' or path in repo.store:
2321 2326 break
2322 2327 else:
2323 2328 return []
2324 2329
2325 2330 if slowpath:
2326 2331 # We have to read the changelog to match filenames against
2327 2332 # changed files
2328 2333
2329 2334 if follow:
2330 2335 raise error.Abort(_('can only follow copies/renames for explicit '
2331 2336 'filenames'))
2332 2337
2333 2338 # The slow path checks files modified in every changeset.
2334 2339 # This is really slow on large repos, so compute the set lazily.
2335 2340 class lazywantedset(object):
2336 2341 def __init__(self):
2337 2342 self.set = set()
2338 2343 self.revs = set(revs)
2339 2344
2340 2345 # No need to worry about locality here because it will be accessed
2341 2346 # in the same order as the increasing window below.
2342 2347 def __contains__(self, value):
2343 2348 if value in self.set:
2344 2349 return True
2345 2350 elif not value in self.revs:
2346 2351 return False
2347 2352 else:
2348 2353 self.revs.discard(value)
2349 2354 ctx = change(value)
2350 2355 matches = filter(match, ctx.files())
2351 2356 if matches:
2352 2357 fncache[value] = matches
2353 2358 self.set.add(value)
2354 2359 return True
2355 2360 return False
2356 2361
2357 2362 def discard(self, value):
2358 2363 self.revs.discard(value)
2359 2364 self.set.discard(value)
2360 2365
2361 2366 wanted = lazywantedset()
2362 2367
2363 2368 # it might be worthwhile to do this in the iterator if the rev range
2364 2369 # is descending and the prune args are all within that range
2365 2370 for rev in opts.get('prune', ()):
2366 2371 rev = repo[rev].rev()
2367 2372 ff = _followfilter(repo)
2368 2373 stop = min(revs[0], revs[-1])
2369 2374 for x in xrange(rev, stop - 1, -1):
2370 2375 if ff.match(x):
2371 2376 wanted = wanted - [x]
2372 2377
2373 2378 # Now that wanted is correctly initialized, we can iterate over the
2374 2379 # revision range, yielding only revisions in wanted.
2375 2380 def iterate():
2376 2381 if follow and match.always():
2377 2382 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2378 2383 def want(rev):
2379 2384 return ff.match(rev) and rev in wanted
2380 2385 else:
2381 2386 def want(rev):
2382 2387 return rev in wanted
2383 2388
2384 2389 it = iter(revs)
2385 2390 stopiteration = False
2386 2391 for windowsize in increasingwindows():
2387 2392 nrevs = []
2388 2393 for i in xrange(windowsize):
2389 2394 rev = next(it, None)
2390 2395 if rev is None:
2391 2396 stopiteration = True
2392 2397 break
2393 2398 elif want(rev):
2394 2399 nrevs.append(rev)
2395 2400 for rev in sorted(nrevs):
2396 2401 fns = fncache.get(rev)
2397 2402 ctx = change(rev)
2398 2403 if not fns:
2399 2404 def fns_generator():
2400 2405 for f in ctx.files():
2401 2406 if match(f):
2402 2407 yield f
2403 2408 fns = fns_generator()
2404 2409 prepare(ctx, fns)
2405 2410 for rev in nrevs:
2406 2411 yield change(rev)
2407 2412
2408 2413 if stopiteration:
2409 2414 break
2410 2415
2411 2416 return iterate()
2412 2417
2413 2418 def _makelogmatcher(repo, revs, pats, opts):
2414 2419 """Build matcher and expanded patterns from log options
2415 2420
2416 2421 If --follow, revs are the revisions to follow from.
2417 2422
2418 2423 Returns (match, pats, slowpath) where
2419 2424 - match: a matcher built from the given pats and -I/-X opts
2420 2425 - pats: patterns used (globs are expanded on Windows)
2421 2426 - slowpath: True if patterns aren't as simple as scanning filelogs
2422 2427 """
2423 2428 # pats/include/exclude are passed to match.match() directly in
2424 2429 # _matchfiles() revset but walkchangerevs() builds its matcher with
2425 2430 # scmutil.match(). The difference is input pats are globbed on
2426 2431 # platforms without shell expansion (windows).
2427 2432 wctx = repo[None]
2428 2433 match, pats = scmutil.matchandpats(wctx, pats, opts)
2429 2434 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
2430 2435 if not slowpath:
2431 2436 follow = opts.get('follow') or opts.get('follow_first')
2432 2437 startctxs = []
2433 2438 if follow and opts.get('rev'):
2434 2439 startctxs = [repo[r] for r in revs]
2435 2440 for f in match.files():
2436 2441 if follow and startctxs:
2437 2442 # No idea if the path was a directory at that revision, so
2438 2443 # take the slow path.
2439 2444 if any(f not in c for c in startctxs):
2440 2445 slowpath = True
2441 2446 continue
2442 2447 elif follow and f not in wctx:
2443 2448 # If the file exists, it may be a directory, so let it
2444 2449 # take the slow path.
2445 2450 if os.path.exists(repo.wjoin(f)):
2446 2451 slowpath = True
2447 2452 continue
2448 2453 else:
2449 2454 raise error.Abort(_('cannot follow file not in parent '
2450 2455 'revision: "%s"') % f)
2451 2456 filelog = repo.file(f)
2452 2457 if not filelog:
2453 2458 # A zero count may be a directory or deleted file, so
2454 2459 # try to find matching entries on the slow path.
2455 2460 if follow:
2456 2461 raise error.Abort(
2457 2462 _('cannot follow nonexistent file: "%s"') % f)
2458 2463 slowpath = True
2459 2464
2460 2465 # We decided to fall back to the slowpath because at least one
2461 2466 # of the paths was not a file. Check to see if at least one of them
2462 2467 # existed in history - in that case, we'll continue down the
2463 2468 # slowpath; otherwise, we can turn off the slowpath
2464 2469 if slowpath:
2465 2470 for path in match.files():
2466 2471 if path == '.' or path in repo.store:
2467 2472 break
2468 2473 else:
2469 2474 slowpath = False
2470 2475
2471 2476 return match, pats, slowpath
2472 2477
2473 2478 def _fileancestors(repo, revs, match, followfirst):
2474 2479 fctxs = []
2475 2480 for r in revs:
2476 2481 ctx = repo[r]
2477 2482 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
2478 2483
2479 2484 # When displaying a revision with --patch --follow FILE, we have
2480 2485 # to know which file of the revision must be diffed. With
2481 2486 # --follow, we want the names of the ancestors of FILE in the
2482 2487 # revision, stored in "fcache". "fcache" is populated as a side effect
2483 2488 # of the graph traversal.
2484 2489 fcache = {}
2485 2490 def filematcher(rev):
2486 2491 return scmutil.matchfiles(repo, fcache.get(rev, []))
2487 2492
2488 2493 def revgen():
2489 2494 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
2490 2495 fcache[rev] = [c.path() for c in cs]
2491 2496 yield rev
2492 2497 return smartset.generatorset(revgen(), iterasc=False), filematcher
2493 2498
2494 2499 def _makenofollowlogfilematcher(repo, pats, opts):
2495 2500 '''hook for extensions to override the filematcher for non-follow cases'''
2496 2501 return None
2497 2502
2498 2503 _opt2logrevset = {
2499 2504 'no_merges': ('not merge()', None),
2500 2505 'only_merges': ('merge()', None),
2501 2506 '_matchfiles': (None, '_matchfiles(%ps)'),
2502 2507 'date': ('date(%s)', None),
2503 2508 'branch': ('branch(%s)', '%lr'),
2504 2509 '_patslog': ('filelog(%s)', '%lr'),
2505 2510 'keyword': ('keyword(%s)', '%lr'),
2506 2511 'prune': ('ancestors(%s)', 'not %lr'),
2507 2512 'user': ('user(%s)', '%lr'),
2508 2513 }
2509 2514
2510 2515 def _makelogrevset(repo, match, pats, slowpath, opts):
2511 2516 """Return a revset string built from log options and file patterns"""
2512 2517 opts = dict(opts)
2513 2518 # follow or not follow?
2514 2519 follow = opts.get('follow') or opts.get('follow_first')
2515 2520
2516 2521 # branch and only_branch are really aliases and must be handled at
2517 2522 # the same time
2518 2523 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2519 2524 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2520 2525
2521 2526 if slowpath:
2522 2527 # See walkchangerevs() slow path.
2523 2528 #
2524 2529 # pats/include/exclude cannot be represented as separate
2525 2530 # revset expressions as their filtering logic applies at file
2526 2531 # level. For instance "-I a -X b" matches a revision touching
2527 2532 # "a" and "b" while "file(a) and not file(b)" does
2528 2533 # not. Besides, filesets are evaluated against the working
2529 2534 # directory.
2530 2535 matchargs = ['r:', 'd:relpath']
2531 2536 for p in pats:
2532 2537 matchargs.append('p:' + p)
2533 2538 for p in opts.get('include', []):
2534 2539 matchargs.append('i:' + p)
2535 2540 for p in opts.get('exclude', []):
2536 2541 matchargs.append('x:' + p)
2537 2542 opts['_matchfiles'] = matchargs
2538 2543 elif not follow:
2539 2544 opts['_patslog'] = list(pats)
2540 2545
2541 2546 expr = []
2542 2547 for op, val in sorted(opts.iteritems()):
2543 2548 if not val:
2544 2549 continue
2545 2550 if op not in _opt2logrevset:
2546 2551 continue
2547 2552 revop, listop = _opt2logrevset[op]
2548 2553 if revop and '%' not in revop:
2549 2554 expr.append(revop)
2550 2555 elif not listop:
2551 2556 expr.append(revsetlang.formatspec(revop, val))
2552 2557 else:
2553 2558 if revop:
2554 2559 val = [revsetlang.formatspec(revop, v) for v in val]
2555 2560 expr.append(revsetlang.formatspec(listop, val))
2556 2561
2557 2562 if expr:
2558 2563 expr = '(' + ' and '.join(expr) + ')'
2559 2564 else:
2560 2565 expr = None
2561 2566 return expr
2562 2567
2563 2568 def _logrevs(repo, opts):
2564 2569 """Return the initial set of revisions to be filtered or followed"""
2565 2570 follow = opts.get('follow') or opts.get('follow_first')
2566 2571 if opts.get('rev'):
2567 2572 revs = scmutil.revrange(repo, opts['rev'])
2568 2573 elif follow and repo.dirstate.p1() == nullid:
2569 2574 revs = smartset.baseset()
2570 2575 elif follow:
2571 2576 revs = repo.revs('.')
2572 2577 else:
2573 2578 revs = smartset.spanset(repo)
2574 2579 revs.reverse()
2575 2580 return revs
2576 2581
2577 2582 def getlogrevs(repo, pats, opts):
2578 2583 """Return (revs, filematcher) where revs is a smartset
2579 2584
2580 2585 filematcher is a callable taking a revision number and returning a match
2581 2586 objects filtering the files to be detailed when displaying the revision.
2582 2587 """
2583 2588 follow = opts.get('follow') or opts.get('follow_first')
2584 2589 followfirst = opts.get('follow_first')
2585 2590 limit = loglimit(opts)
2586 2591 revs = _logrevs(repo, opts)
2587 2592 if not revs:
2588 2593 return smartset.baseset(), None
2589 2594 match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts)
2590 2595 filematcher = None
2591 2596 if follow:
2592 2597 if slowpath or match.always():
2593 2598 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
2594 2599 else:
2595 2600 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
2596 2601 revs.reverse()
2597 2602 if filematcher is None:
2598 2603 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2599 2604 if filematcher is None:
2600 2605 def filematcher(rev):
2601 2606 return match
2602 2607
2603 2608 expr = _makelogrevset(repo, match, pats, slowpath, opts)
2604 2609 if opts.get('graph') and opts.get('rev'):
2605 2610 # User-specified revs might be unsorted, but don't sort before
2606 2611 # _makelogrevset because it might depend on the order of revs
2607 2612 if not (revs.isdescending() or revs.istopo()):
2608 2613 revs.sort(reverse=True)
2609 2614 if expr:
2610 2615 matcher = revset.match(None, expr)
2611 2616 revs = matcher(repo, revs)
2612 2617 if limit is not None:
2613 2618 revs = revs.slice(0, limit)
2614 2619 return revs, filematcher
2615 2620
2616 2621 def _parselinerangelogopt(repo, opts):
2617 2622 """Parse --line-range log option and return a list of tuples (filename,
2618 2623 (fromline, toline)).
2619 2624 """
2620 2625 linerangebyfname = []
2621 2626 for pat in opts.get('line_range', []):
2622 2627 try:
2623 2628 pat, linerange = pat.rsplit(',', 1)
2624 2629 except ValueError:
2625 2630 raise error.Abort(_('malformatted line-range pattern %s') % pat)
2626 2631 try:
2627 2632 fromline, toline = map(int, linerange.split(':'))
2628 2633 except ValueError:
2629 2634 raise error.Abort(_("invalid line range for %s") % pat)
2630 2635 msg = _("line range pattern '%s' must match exactly one file") % pat
2631 2636 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
2632 2637 linerangebyfname.append(
2633 2638 (fname, util.processlinerange(fromline, toline)))
2634 2639 return linerangebyfname
2635 2640
2636 2641 def getloglinerangerevs(repo, userrevs, opts):
2637 2642 """Return (revs, filematcher, hunksfilter).
2638 2643
2639 2644 "revs" are revisions obtained by processing "line-range" log options and
2640 2645 walking block ancestors of each specified file/line-range.
2641 2646
2642 2647 "filematcher(rev) -> match" is a factory function returning a match object
2643 2648 for a given revision for file patterns specified in --line-range option.
2644 2649 If neither --stat nor --patch options are passed, "filematcher" is None.
2645 2650
2646 2651 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
2647 2652 returning a hunks filtering function.
2648 2653 If neither --stat nor --patch options are passed, "filterhunks" is None.
2649 2654 """
2650 2655 wctx = repo[None]
2651 2656
2652 2657 # Two-levels map of "rev -> file ctx -> [line range]".
2653 2658 linerangesbyrev = {}
2654 2659 for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
2655 2660 if fname not in wctx:
2656 2661 raise error.Abort(_('cannot follow file not in parent '
2657 2662 'revision: "%s"') % fname)
2658 2663 fctx = wctx.filectx(fname)
2659 2664 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
2660 2665 rev = fctx.introrev()
2661 2666 if rev not in userrevs:
2662 2667 continue
2663 2668 linerangesbyrev.setdefault(
2664 2669 rev, {}).setdefault(
2665 2670 fctx.path(), []).append(linerange)
2666 2671
2667 2672 filematcher = None
2668 2673 hunksfilter = None
2669 2674 if opts.get('patch') or opts.get('stat'):
2670 2675
2671 2676 def nofilterhunksfn(fctx, hunks):
2672 2677 return hunks
2673 2678
2674 2679 def hunksfilter(rev):
2675 2680 fctxlineranges = linerangesbyrev.get(rev)
2676 2681 if fctxlineranges is None:
2677 2682 return nofilterhunksfn
2678 2683
2679 2684 def filterfn(fctx, hunks):
2680 2685 lineranges = fctxlineranges.get(fctx.path())
2681 2686 if lineranges is not None:
2682 2687 for hr, lines in hunks:
2683 2688 if hr is None: # binary
2684 2689 yield hr, lines
2685 2690 continue
2686 2691 if any(mdiff.hunkinrange(hr[2:], lr)
2687 2692 for lr in lineranges):
2688 2693 yield hr, lines
2689 2694 else:
2690 2695 for hunk in hunks:
2691 2696 yield hunk
2692 2697
2693 2698 return filterfn
2694 2699
2695 2700 def filematcher(rev):
2696 2701 files = list(linerangesbyrev.get(rev, []))
2697 2702 return scmutil.matchfiles(repo, files)
2698 2703
2699 2704 revs = sorted(linerangesbyrev, reverse=True)
2700 2705
2701 2706 return revs, filematcher, hunksfilter
2702 2707
2703 2708 def _graphnodeformatter(ui, displayer):
2704 2709 spec = ui.config('ui', 'graphnodetemplate')
2705 2710 if not spec:
2706 2711 return templatekw.showgraphnode # fast path for "{graphnode}"
2707 2712
2708 2713 spec = templater.unquotestring(spec)
2709 2714 tres = formatter.templateresources(ui)
2710 2715 if isinstance(displayer, changeset_templater):
2711 2716 tres['cache'] = displayer.cache # reuse cache of slow templates
2712 2717 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
2713 2718 resources=tres)
2714 2719 def formatnode(repo, ctx):
2715 2720 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
2716 2721 return templ.render(props)
2717 2722 return formatnode
2718 2723
2719 2724 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2720 2725 filematcher=None, props=None):
2721 2726 props = props or {}
2722 2727 formatnode = _graphnodeformatter(ui, displayer)
2723 2728 state = graphmod.asciistate()
2724 2729 styles = state['styles']
2725 2730
2726 2731 # only set graph styling if HGPLAIN is not set.
2727 2732 if ui.plain('graph'):
2728 2733 # set all edge styles to |, the default pre-3.8 behaviour
2729 2734 styles.update(dict.fromkeys(styles, '|'))
2730 2735 else:
2731 2736 edgetypes = {
2732 2737 'parent': graphmod.PARENT,
2733 2738 'grandparent': graphmod.GRANDPARENT,
2734 2739 'missing': graphmod.MISSINGPARENT
2735 2740 }
2736 2741 for name, key in edgetypes.items():
2737 2742 # experimental config: experimental.graphstyle.*
2738 2743 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2739 2744 styles[key])
2740 2745 if not styles[key]:
2741 2746 styles[key] = None
2742 2747
2743 2748 # experimental config: experimental.graphshorten
2744 2749 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2745 2750
2746 2751 for rev, type, ctx, parents in dag:
2747 2752 char = formatnode(repo, ctx)
2748 2753 copies = None
2749 2754 if getrenamed and ctx.rev():
2750 2755 copies = []
2751 2756 for fn in ctx.files():
2752 2757 rename = getrenamed(fn, ctx.rev())
2753 2758 if rename:
2754 2759 copies.append((fn, rename[0]))
2755 2760 revmatchfn = None
2756 2761 if filematcher is not None:
2757 2762 revmatchfn = filematcher(ctx.rev())
2758 2763 edges = edgefn(type, char, state, rev, parents)
2759 2764 firstedge = next(edges)
2760 2765 width = firstedge[2]
2761 2766 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2762 2767 _graphwidth=width, **pycompat.strkwargs(props))
2763 2768 lines = displayer.hunk.pop(rev).split('\n')
2764 2769 if not lines[-1]:
2765 2770 del lines[-1]
2766 2771 displayer.flush(ctx)
2767 2772 for type, char, width, coldata in itertools.chain([firstedge], edges):
2768 2773 graphmod.ascii(ui, state, type, char, lines, coldata)
2769 2774 lines = []
2770 2775 displayer.close()
2771 2776
2772 2777 def graphlog(ui, repo, revs, filematcher, opts):
2773 2778 # Parameters are identical to log command ones
2774 2779 revdag = graphmod.dagwalker(repo, revs)
2775 2780
2776 2781 getrenamed = None
2777 2782 if opts.get('copies'):
2778 2783 endrev = None
2779 2784 if opts.get('rev'):
2780 2785 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2781 2786 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2782 2787
2783 2788 ui.pager('log')
2784 2789 displayer = show_changeset(ui, repo, opts, buffered=True)
2785 2790 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2786 2791 filematcher)
2787 2792
2788 2793 def checkunsupportedgraphflags(pats, opts):
2789 2794 for op in ["newest_first"]:
2790 2795 if op in opts and opts[op]:
2791 2796 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2792 2797 % op.replace("_", "-"))
2793 2798
2794 2799 def graphrevs(repo, nodes, opts):
2795 2800 limit = loglimit(opts)
2796 2801 nodes.reverse()
2797 2802 if limit is not None:
2798 2803 nodes = nodes[:limit]
2799 2804 return graphmod.nodes(repo, nodes)
2800 2805
2801 2806 def add(ui, repo, match, prefix, explicitonly, **opts):
2802 2807 join = lambda f: os.path.join(prefix, f)
2803 2808 bad = []
2804 2809
2805 2810 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2806 2811 names = []
2807 2812 wctx = repo[None]
2808 2813 cca = None
2809 2814 abort, warn = scmutil.checkportabilityalert(ui)
2810 2815 if abort or warn:
2811 2816 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2812 2817
2813 2818 badmatch = matchmod.badmatch(match, badfn)
2814 2819 dirstate = repo.dirstate
2815 2820 # We don't want to just call wctx.walk here, since it would return a lot of
2816 2821 # clean files, which we aren't interested in and takes time.
2817 2822 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2818 2823 unknown=True, ignored=False, full=False)):
2819 2824 exact = match.exact(f)
2820 2825 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2821 2826 if cca:
2822 2827 cca(f)
2823 2828 names.append(f)
2824 2829 if ui.verbose or not exact:
2825 2830 ui.status(_('adding %s\n') % match.rel(f))
2826 2831
2827 2832 for subpath in sorted(wctx.substate):
2828 2833 sub = wctx.sub(subpath)
2829 2834 try:
2830 2835 submatch = matchmod.subdirmatcher(subpath, match)
2831 2836 if opts.get(r'subrepos'):
2832 2837 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2833 2838 else:
2834 2839 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2835 2840 except error.LookupError:
2836 2841 ui.status(_("skipping missing subrepository: %s\n")
2837 2842 % join(subpath))
2838 2843
2839 2844 if not opts.get(r'dry_run'):
2840 2845 rejected = wctx.add(names, prefix)
2841 2846 bad.extend(f for f in rejected if f in match.files())
2842 2847 return bad
2843 2848
2844 2849 def addwebdirpath(repo, serverpath, webconf):
2845 2850 webconf[serverpath] = repo.root
2846 2851 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2847 2852
2848 2853 for r in repo.revs('filelog("path:.hgsub")'):
2849 2854 ctx = repo[r]
2850 2855 for subpath in ctx.substate:
2851 2856 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2852 2857
2853 2858 def forget(ui, repo, match, prefix, explicitonly):
2854 2859 join = lambda f: os.path.join(prefix, f)
2855 2860 bad = []
2856 2861 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2857 2862 wctx = repo[None]
2858 2863 forgot = []
2859 2864
2860 2865 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2861 2866 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2862 2867 if explicitonly:
2863 2868 forget = [f for f in forget if match.exact(f)]
2864 2869
2865 2870 for subpath in sorted(wctx.substate):
2866 2871 sub = wctx.sub(subpath)
2867 2872 try:
2868 2873 submatch = matchmod.subdirmatcher(subpath, match)
2869 2874 subbad, subforgot = sub.forget(submatch, prefix)
2870 2875 bad.extend([subpath + '/' + f for f in subbad])
2871 2876 forgot.extend([subpath + '/' + f for f in subforgot])
2872 2877 except error.LookupError:
2873 2878 ui.status(_("skipping missing subrepository: %s\n")
2874 2879 % join(subpath))
2875 2880
2876 2881 if not explicitonly:
2877 2882 for f in match.files():
2878 2883 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2879 2884 if f not in forgot:
2880 2885 if repo.wvfs.exists(f):
2881 2886 # Don't complain if the exact case match wasn't given.
2882 2887 # But don't do this until after checking 'forgot', so
2883 2888 # that subrepo files aren't normalized, and this op is
2884 2889 # purely from data cached by the status walk above.
2885 2890 if repo.dirstate.normalize(f) in repo.dirstate:
2886 2891 continue
2887 2892 ui.warn(_('not removing %s: '
2888 2893 'file is already untracked\n')
2889 2894 % match.rel(f))
2890 2895 bad.append(f)
2891 2896
2892 2897 for f in forget:
2893 2898 if ui.verbose or not match.exact(f):
2894 2899 ui.status(_('removing %s\n') % match.rel(f))
2895 2900
2896 2901 rejected = wctx.forget(forget, prefix)
2897 2902 bad.extend(f for f in rejected if f in match.files())
2898 2903 forgot.extend(f for f in forget if f not in rejected)
2899 2904 return bad, forgot
2900 2905
2901 2906 def files(ui, ctx, m, fm, fmt, subrepos):
2902 2907 rev = ctx.rev()
2903 2908 ret = 1
2904 2909 ds = ctx.repo().dirstate
2905 2910
2906 2911 for f in ctx.matches(m):
2907 2912 if rev is None and ds[f] == 'r':
2908 2913 continue
2909 2914 fm.startitem()
2910 2915 if ui.verbose:
2911 2916 fc = ctx[f]
2912 2917 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2913 2918 fm.data(abspath=f)
2914 2919 fm.write('path', fmt, m.rel(f))
2915 2920 ret = 0
2916 2921
2917 2922 for subpath in sorted(ctx.substate):
2918 2923 submatch = matchmod.subdirmatcher(subpath, m)
2919 2924 if (subrepos or m.exact(subpath) or any(submatch.files())):
2920 2925 sub = ctx.sub(subpath)
2921 2926 try:
2922 2927 recurse = m.exact(subpath) or subrepos
2923 2928 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2924 2929 ret = 0
2925 2930 except error.LookupError:
2926 2931 ui.status(_("skipping missing subrepository: %s\n")
2927 2932 % m.abs(subpath))
2928 2933
2929 2934 return ret
2930 2935
2931 2936 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2932 2937 join = lambda f: os.path.join(prefix, f)
2933 2938 ret = 0
2934 2939 s = repo.status(match=m, clean=True)
2935 2940 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2936 2941
2937 2942 wctx = repo[None]
2938 2943
2939 2944 if warnings is None:
2940 2945 warnings = []
2941 2946 warn = True
2942 2947 else:
2943 2948 warn = False
2944 2949
2945 2950 subs = sorted(wctx.substate)
2946 2951 total = len(subs)
2947 2952 count = 0
2948 2953 for subpath in subs:
2949 2954 count += 1
2950 2955 submatch = matchmod.subdirmatcher(subpath, m)
2951 2956 if subrepos or m.exact(subpath) or any(submatch.files()):
2952 2957 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2953 2958 sub = wctx.sub(subpath)
2954 2959 try:
2955 2960 if sub.removefiles(submatch, prefix, after, force, subrepos,
2956 2961 warnings):
2957 2962 ret = 1
2958 2963 except error.LookupError:
2959 2964 warnings.append(_("skipping missing subrepository: %s\n")
2960 2965 % join(subpath))
2961 2966 ui.progress(_('searching'), None)
2962 2967
2963 2968 # warn about failure to delete explicit files/dirs
2964 2969 deleteddirs = util.dirs(deleted)
2965 2970 files = m.files()
2966 2971 total = len(files)
2967 2972 count = 0
2968 2973 for f in files:
2969 2974 def insubrepo():
2970 2975 for subpath in wctx.substate:
2971 2976 if f.startswith(subpath + '/'):
2972 2977 return True
2973 2978 return False
2974 2979
2975 2980 count += 1
2976 2981 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2977 2982 isdir = f in deleteddirs or wctx.hasdir(f)
2978 2983 if (f in repo.dirstate or isdir or f == '.'
2979 2984 or insubrepo() or f in subs):
2980 2985 continue
2981 2986
2982 2987 if repo.wvfs.exists(f):
2983 2988 if repo.wvfs.isdir(f):
2984 2989 warnings.append(_('not removing %s: no tracked files\n')
2985 2990 % m.rel(f))
2986 2991 else:
2987 2992 warnings.append(_('not removing %s: file is untracked\n')
2988 2993 % m.rel(f))
2989 2994 # missing files will generate a warning elsewhere
2990 2995 ret = 1
2991 2996 ui.progress(_('deleting'), None)
2992 2997
2993 2998 if force:
2994 2999 list = modified + deleted + clean + added
2995 3000 elif after:
2996 3001 list = deleted
2997 3002 remaining = modified + added + clean
2998 3003 total = len(remaining)
2999 3004 count = 0
3000 3005 for f in remaining:
3001 3006 count += 1
3002 3007 ui.progress(_('skipping'), count, total=total, unit=_('files'))
3003 3008 if ui.verbose or (f in files):
3004 3009 warnings.append(_('not removing %s: file still exists\n')
3005 3010 % m.rel(f))
3006 3011 ret = 1
3007 3012 ui.progress(_('skipping'), None)
3008 3013 else:
3009 3014 list = deleted + clean
3010 3015 total = len(modified) + len(added)
3011 3016 count = 0
3012 3017 for f in modified:
3013 3018 count += 1
3014 3019 ui.progress(_('skipping'), count, total=total, unit=_('files'))
3015 3020 warnings.append(_('not removing %s: file is modified (use -f'
3016 3021 ' to force removal)\n') % m.rel(f))
3017 3022 ret = 1
3018 3023 for f in added:
3019 3024 count += 1
3020 3025 ui.progress(_('skipping'), count, total=total, unit=_('files'))
3021 3026 warnings.append(_("not removing %s: file has been marked for add"
3022 3027 " (use 'hg forget' to undo add)\n") % m.rel(f))
3023 3028 ret = 1
3024 3029 ui.progress(_('skipping'), None)
3025 3030
3026 3031 list = sorted(list)
3027 3032 total = len(list)
3028 3033 count = 0
3029 3034 for f in list:
3030 3035 count += 1
3031 3036 if ui.verbose or not m.exact(f):
3032 3037 ui.progress(_('deleting'), count, total=total, unit=_('files'))
3033 3038 ui.status(_('removing %s\n') % m.rel(f))
3034 3039 ui.progress(_('deleting'), None)
3035 3040
3036 3041 with repo.wlock():
3037 3042 if not after:
3038 3043 for f in list:
3039 3044 if f in added:
3040 3045 continue # we never unlink added files on remove
3041 3046 repo.wvfs.unlinkpath(f, ignoremissing=True)
3042 3047 repo[None].forget(list)
3043 3048
3044 3049 if warn:
3045 3050 for warning in warnings:
3046 3051 ui.warn(warning)
3047 3052
3048 3053 return ret
3049 3054
3050 3055 def _updatecatformatter(fm, ctx, matcher, path, decode):
3051 3056 """Hook for adding data to the formatter used by ``hg cat``.
3052 3057
3053 3058 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
3054 3059 this method first."""
3055 3060 data = ctx[path].data()
3056 3061 if decode:
3057 3062 data = ctx.repo().wwritedata(path, data)
3058 3063 fm.startitem()
3059 3064 fm.write('data', '%s', data)
3060 3065 fm.data(abspath=path, path=matcher.rel(path))
3061 3066
3062 3067 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
3063 3068 err = 1
3064 3069 opts = pycompat.byteskwargs(opts)
3065 3070
3066 3071 def write(path):
3067 3072 filename = None
3068 3073 if fntemplate:
3069 3074 filename = makefilename(repo, fntemplate, ctx.node(),
3070 3075 pathname=os.path.join(prefix, path))
3071 3076 # attempt to create the directory if it does not already exist
3072 3077 try:
3073 3078 os.makedirs(os.path.dirname(filename))
3074 3079 except OSError:
3075 3080 pass
3076 3081 with formatter.maybereopen(basefm, filename, opts) as fm:
3077 3082 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
3078 3083
3079 3084 # Automation often uses hg cat on single files, so special case it
3080 3085 # for performance to avoid the cost of parsing the manifest.
3081 3086 if len(matcher.files()) == 1 and not matcher.anypats():
3082 3087 file = matcher.files()[0]
3083 3088 mfl = repo.manifestlog
3084 3089 mfnode = ctx.manifestnode()
3085 3090 try:
3086 3091 if mfnode and mfl[mfnode].find(file)[0]:
3087 3092 write(file)
3088 3093 return 0
3089 3094 except KeyError:
3090 3095 pass
3091 3096
3092 3097 for abs in ctx.walk(matcher):
3093 3098 write(abs)
3094 3099 err = 0
3095 3100
3096 3101 for subpath in sorted(ctx.substate):
3097 3102 sub = ctx.sub(subpath)
3098 3103 try:
3099 3104 submatch = matchmod.subdirmatcher(subpath, matcher)
3100 3105
3101 3106 if not sub.cat(submatch, basefm, fntemplate,
3102 3107 os.path.join(prefix, sub._path),
3103 3108 **pycompat.strkwargs(opts)):
3104 3109 err = 0
3105 3110 except error.RepoLookupError:
3106 3111 ui.status(_("skipping missing subrepository: %s\n")
3107 3112 % os.path.join(prefix, subpath))
3108 3113
3109 3114 return err
3110 3115
3111 3116 def commit(ui, repo, commitfunc, pats, opts):
3112 3117 '''commit the specified files or all outstanding changes'''
3113 3118 date = opts.get('date')
3114 3119 if date:
3115 3120 opts['date'] = util.parsedate(date)
3116 3121 message = logmessage(ui, opts)
3117 3122 matcher = scmutil.match(repo[None], pats, opts)
3118 3123
3119 3124 dsguard = None
3120 3125 # extract addremove carefully -- this function can be called from a command
3121 3126 # that doesn't support addremove
3122 3127 if opts.get('addremove'):
3123 3128 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3124 3129 with dsguard or util.nullcontextmanager():
3125 3130 if dsguard:
3126 3131 if scmutil.addremove(repo, matcher, "", opts) != 0:
3127 3132 raise error.Abort(
3128 3133 _("failed to mark all new/missing files as added/removed"))
3129 3134
3130 3135 return commitfunc(ui, repo, message, matcher, opts)
3131 3136
3132 3137 def samefile(f, ctx1, ctx2):
3133 3138 if f in ctx1.manifest():
3134 3139 a = ctx1.filectx(f)
3135 3140 if f in ctx2.manifest():
3136 3141 b = ctx2.filectx(f)
3137 3142 return (not a.cmp(b)
3138 3143 and a.flags() == b.flags())
3139 3144 else:
3140 3145 return False
3141 3146 else:
3142 3147 return f not in ctx2.manifest()
3143 3148
3144 3149 def amend(ui, repo, old, extra, pats, opts):
3145 3150 # avoid cycle context -> subrepo -> cmdutil
3146 3151 from . import context
3147 3152
3148 3153 # amend will reuse the existing user if not specified, but the obsolete
3149 3154 # marker creation requires that the current user's name is specified.
3150 3155 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3151 3156 ui.username() # raise exception if username not set
3152 3157
3153 3158 ui.note(_('amending changeset %s\n') % old)
3154 3159 base = old.p1()
3155 3160
3156 3161 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3157 3162 # Participating changesets:
3158 3163 #
3159 3164 # wctx o - workingctx that contains changes from working copy
3160 3165 # | to go into amending commit
3161 3166 # |
3162 3167 # old o - changeset to amend
3163 3168 # |
3164 3169 # base o - first parent of the changeset to amend
3165 3170 wctx = repo[None]
3166 3171
3167 3172 # Copy to avoid mutating input
3168 3173 extra = extra.copy()
3169 3174 # Update extra dict from amended commit (e.g. to preserve graft
3170 3175 # source)
3171 3176 extra.update(old.extra())
3172 3177
3173 3178 # Also update it from the from the wctx
3174 3179 extra.update(wctx.extra())
3175 3180
3176 3181 user = opts.get('user') or old.user()
3177 3182 date = opts.get('date') or old.date()
3178 3183
3179 3184 # Parse the date to allow comparison between date and old.date()
3180 3185 date = util.parsedate(date)
3181 3186
3182 3187 if len(old.parents()) > 1:
3183 3188 # ctx.files() isn't reliable for merges, so fall back to the
3184 3189 # slower repo.status() method
3185 3190 files = set([fn for st in repo.status(base, old)[:3]
3186 3191 for fn in st])
3187 3192 else:
3188 3193 files = set(old.files())
3189 3194
3190 3195 # add/remove the files to the working copy if the "addremove" option
3191 3196 # was specified.
3192 3197 matcher = scmutil.match(wctx, pats, opts)
3193 3198 if (opts.get('addremove')
3194 3199 and scmutil.addremove(repo, matcher, "", opts)):
3195 3200 raise error.Abort(
3196 3201 _("failed to mark all new/missing files as added/removed"))
3197 3202
3198 3203 # Check subrepos. This depends on in-place wctx._status update in
3199 3204 # subrepo.precommit(). To minimize the risk of this hack, we do
3200 3205 # nothing if .hgsub does not exist.
3201 3206 if '.hgsub' in wctx or '.hgsub' in old:
3202 3207 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
3203 3208 subs, commitsubs, newsubstate = subrepo.precommit(
3204 3209 ui, wctx, wctx._status, matcher)
3205 3210 # amend should abort if commitsubrepos is enabled
3206 3211 assert not commitsubs
3207 3212 if subs:
3208 3213 subrepo.writestate(repo, newsubstate)
3209 3214
3210 3215 filestoamend = set(f for f in wctx.files() if matcher(f))
3211 3216
3212 3217 changes = (len(filestoamend) > 0)
3213 3218 if changes:
3214 3219 # Recompute copies (avoid recording a -> b -> a)
3215 3220 copied = copies.pathcopies(base, wctx, matcher)
3216 3221 if old.p2:
3217 3222 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3218 3223
3219 3224 # Prune files which were reverted by the updates: if old
3220 3225 # introduced file X and the file was renamed in the working
3221 3226 # copy, then those two files are the same and
3222 3227 # we can discard X from our list of files. Likewise if X
3223 3228 # was removed, it's no longer relevant. If X is missing (aka
3224 3229 # deleted), old X must be preserved.
3225 3230 files.update(filestoamend)
3226 3231 files = [f for f in files if (not samefile(f, wctx, base)
3227 3232 or f in wctx.deleted())]
3228 3233
3229 3234 def filectxfn(repo, ctx_, path):
3230 3235 try:
3231 3236 # If the file being considered is not amongst the files
3232 3237 # to be amended, we should return the file context from the
3233 3238 # old changeset. This avoids issues when only some files in
3234 3239 # the working copy are being amended but there are also
3235 3240 # changes to other files from the old changeset.
3236 3241 if path not in filestoamend:
3237 3242 return old.filectx(path)
3238 3243
3239 3244 # Return None for removed files.
3240 3245 if path in wctx.removed():
3241 3246 return None
3242 3247
3243 3248 fctx = wctx[path]
3244 3249 flags = fctx.flags()
3245 3250 mctx = context.memfilectx(repo, ctx_,
3246 3251 fctx.path(), fctx.data(),
3247 3252 islink='l' in flags,
3248 3253 isexec='x' in flags,
3249 3254 copied=copied.get(path))
3250 3255 return mctx
3251 3256 except KeyError:
3252 3257 return None
3253 3258 else:
3254 3259 ui.note(_('copying changeset %s to %s\n') % (old, base))
3255 3260
3256 3261 # Use version of files as in the old cset
3257 3262 def filectxfn(repo, ctx_, path):
3258 3263 try:
3259 3264 return old.filectx(path)
3260 3265 except KeyError:
3261 3266 return None
3262 3267
3263 3268 # See if we got a message from -m or -l, if not, open the editor with
3264 3269 # the message of the changeset to amend.
3265 3270 message = logmessage(ui, opts)
3266 3271
3267 3272 editform = mergeeditform(old, 'commit.amend')
3268 3273 editor = getcommiteditor(editform=editform,
3269 3274 **pycompat.strkwargs(opts))
3270 3275
3271 3276 if not message:
3272 3277 editor = getcommiteditor(edit=True, editform=editform)
3273 3278 message = old.description()
3274 3279
3275 3280 pureextra = extra.copy()
3276 3281 extra['amend_source'] = old.hex()
3277 3282
3278 3283 new = context.memctx(repo,
3279 3284 parents=[base.node(), old.p2().node()],
3280 3285 text=message,
3281 3286 files=files,
3282 3287 filectxfn=filectxfn,
3283 3288 user=user,
3284 3289 date=date,
3285 3290 extra=extra,
3286 3291 editor=editor)
3287 3292
3288 3293 newdesc = changelog.stripdesc(new.description())
3289 3294 if ((not changes)
3290 3295 and newdesc == old.description()
3291 3296 and user == old.user()
3292 3297 and date == old.date()
3293 3298 and pureextra == old.extra()):
3294 3299 # nothing changed. continuing here would create a new node
3295 3300 # anyway because of the amend_source noise.
3296 3301 #
3297 3302 # This not what we expect from amend.
3298 3303 return old.node()
3299 3304
3300 3305 if opts.get('secret'):
3301 3306 commitphase = 'secret'
3302 3307 else:
3303 3308 commitphase = old.phase()
3304 3309 overrides = {('phases', 'new-commit'): commitphase}
3305 3310 with ui.configoverride(overrides, 'amend'):
3306 3311 newid = repo.commitctx(new)
3307 3312
3308 3313 # Reroute the working copy parent to the new changeset
3309 3314 repo.setparents(newid, nullid)
3310 3315 mapping = {old.node(): (newid,)}
3311 3316 obsmetadata = None
3312 3317 if opts.get('note'):
3313 3318 obsmetadata = {'note': opts['note']}
3314 3319 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
3315 3320
3316 3321 # Fixing the dirstate because localrepo.commitctx does not update
3317 3322 # it. This is rather convenient because we did not need to update
3318 3323 # the dirstate for all the files in the new commit which commitctx
3319 3324 # could have done if it updated the dirstate. Now, we can
3320 3325 # selectively update the dirstate only for the amended files.
3321 3326 dirstate = repo.dirstate
3322 3327
3323 3328 # Update the state of the files which were added and
3324 3329 # and modified in the amend to "normal" in the dirstate.
3325 3330 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3326 3331 for f in normalfiles:
3327 3332 dirstate.normal(f)
3328 3333
3329 3334 # Update the state of files which were removed in the amend
3330 3335 # to "removed" in the dirstate.
3331 3336 removedfiles = set(wctx.removed()) & filestoamend
3332 3337 for f in removedfiles:
3333 3338 dirstate.drop(f)
3334 3339
3335 3340 return newid
3336 3341
3337 3342 def commiteditor(repo, ctx, subs, editform=''):
3338 3343 if ctx.description():
3339 3344 return ctx.description()
3340 3345 return commitforceeditor(repo, ctx, subs, editform=editform,
3341 3346 unchangedmessagedetection=True)
3342 3347
3343 3348 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3344 3349 editform='', unchangedmessagedetection=False):
3345 3350 if not extramsg:
3346 3351 extramsg = _("Leave message empty to abort commit.")
3347 3352
3348 3353 forms = [e for e in editform.split('.') if e]
3349 3354 forms.insert(0, 'changeset')
3350 3355 templatetext = None
3351 3356 while forms:
3352 3357 ref = '.'.join(forms)
3353 3358 if repo.ui.config('committemplate', ref):
3354 3359 templatetext = committext = buildcommittemplate(
3355 3360 repo, ctx, subs, extramsg, ref)
3356 3361 break
3357 3362 forms.pop()
3358 3363 else:
3359 3364 committext = buildcommittext(repo, ctx, subs, extramsg)
3360 3365
3361 3366 # run editor in the repository root
3362 3367 olddir = pycompat.getcwd()
3363 3368 os.chdir(repo.root)
3364 3369
3365 3370 # make in-memory changes visible to external process
3366 3371 tr = repo.currenttransaction()
3367 3372 repo.dirstate.write(tr)
3368 3373 pending = tr and tr.writepending() and repo.root
3369 3374
3370 3375 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3371 3376 editform=editform, pending=pending,
3372 3377 repopath=repo.path, action='commit')
3373 3378 text = editortext
3374 3379
3375 3380 # strip away anything below this special string (used for editors that want
3376 3381 # to display the diff)
3377 3382 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3378 3383 if stripbelow:
3379 3384 text = text[:stripbelow.start()]
3380 3385
3381 3386 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3382 3387 os.chdir(olddir)
3383 3388
3384 3389 if finishdesc:
3385 3390 text = finishdesc(text)
3386 3391 if not text.strip():
3387 3392 raise error.Abort(_("empty commit message"))
3388 3393 if unchangedmessagedetection and editortext == templatetext:
3389 3394 raise error.Abort(_("commit message unchanged"))
3390 3395
3391 3396 return text
3392 3397
3393 3398 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3394 3399 ui = repo.ui
3395 3400 spec = formatter.templatespec(ref, None, None)
3396 3401 t = changeset_templater(ui, repo, spec, None, {}, False)
3397 3402 t.t.cache.update((k, templater.unquotestring(v))
3398 3403 for k, v in repo.ui.configitems('committemplate'))
3399 3404
3400 3405 if not extramsg:
3401 3406 extramsg = '' # ensure that extramsg is string
3402 3407
3403 3408 ui.pushbuffer()
3404 3409 t.show(ctx, extramsg=extramsg)
3405 3410 return ui.popbuffer()
3406 3411
3407 3412 def hgprefix(msg):
3408 3413 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3409 3414
3410 3415 def buildcommittext(repo, ctx, subs, extramsg):
3411 3416 edittext = []
3412 3417 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3413 3418 if ctx.description():
3414 3419 edittext.append(ctx.description())
3415 3420 edittext.append("")
3416 3421 edittext.append("") # Empty line between message and comments.
3417 3422 edittext.append(hgprefix(_("Enter commit message."
3418 3423 " Lines beginning with 'HG:' are removed.")))
3419 3424 edittext.append(hgprefix(extramsg))
3420 3425 edittext.append("HG: --")
3421 3426 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3422 3427 if ctx.p2():
3423 3428 edittext.append(hgprefix(_("branch merge")))
3424 3429 if ctx.branch():
3425 3430 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3426 3431 if bookmarks.isactivewdirparent(repo):
3427 3432 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3428 3433 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3429 3434 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3430 3435 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3431 3436 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3432 3437 if not added and not modified and not removed:
3433 3438 edittext.append(hgprefix(_("no files changed")))
3434 3439 edittext.append("")
3435 3440
3436 3441 return "\n".join(edittext)
3437 3442
3438 3443 def commitstatus(repo, node, branch, bheads=None, opts=None):
3439 3444 if opts is None:
3440 3445 opts = {}
3441 3446 ctx = repo[node]
3442 3447 parents = ctx.parents()
3443 3448
3444 3449 if (not opts.get('amend') and bheads and node not in bheads and not
3445 3450 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3446 3451 repo.ui.status(_('created new head\n'))
3447 3452 # The message is not printed for initial roots. For the other
3448 3453 # changesets, it is printed in the following situations:
3449 3454 #
3450 3455 # Par column: for the 2 parents with ...
3451 3456 # N: null or no parent
3452 3457 # B: parent is on another named branch
3453 3458 # C: parent is a regular non head changeset
3454 3459 # H: parent was a branch head of the current branch
3455 3460 # Msg column: whether we print "created new head" message
3456 3461 # In the following, it is assumed that there already exists some
3457 3462 # initial branch heads of the current branch, otherwise nothing is
3458 3463 # printed anyway.
3459 3464 #
3460 3465 # Par Msg Comment
3461 3466 # N N y additional topo root
3462 3467 #
3463 3468 # B N y additional branch root
3464 3469 # C N y additional topo head
3465 3470 # H N n usual case
3466 3471 #
3467 3472 # B B y weird additional branch root
3468 3473 # C B y branch merge
3469 3474 # H B n merge with named branch
3470 3475 #
3471 3476 # C C y additional head from merge
3472 3477 # C H n merge with a head
3473 3478 #
3474 3479 # H H n head merge: head count decreases
3475 3480
3476 3481 if not opts.get('close_branch'):
3477 3482 for r in parents:
3478 3483 if r.closesbranch() and r.branch() == branch:
3479 3484 repo.ui.status(_('reopening closed branch head %d\n') % r)
3480 3485
3481 3486 if repo.ui.debugflag:
3482 3487 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3483 3488 elif repo.ui.verbose:
3484 3489 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3485 3490
3486 3491 def postcommitstatus(repo, pats, opts):
3487 3492 return repo.status(match=scmutil.match(repo[None], pats, opts))
3488 3493
3489 3494 def revert(ui, repo, ctx, parents, *pats, **opts):
3490 3495 opts = pycompat.byteskwargs(opts)
3491 3496 parent, p2 = parents
3492 3497 node = ctx.node()
3493 3498
3494 3499 mf = ctx.manifest()
3495 3500 if node == p2:
3496 3501 parent = p2
3497 3502
3498 3503 # need all matching names in dirstate and manifest of target rev,
3499 3504 # so have to walk both. do not print errors if files exist in one
3500 3505 # but not other. in both cases, filesets should be evaluated against
3501 3506 # workingctx to get consistent result (issue4497). this means 'set:**'
3502 3507 # cannot be used to select missing files from target rev.
3503 3508
3504 3509 # `names` is a mapping for all elements in working copy and target revision
3505 3510 # The mapping is in the form:
3506 3511 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3507 3512 names = {}
3508 3513
3509 3514 with repo.wlock():
3510 3515 ## filling of the `names` mapping
3511 3516 # walk dirstate to fill `names`
3512 3517
3513 3518 interactive = opts.get('interactive', False)
3514 3519 wctx = repo[None]
3515 3520 m = scmutil.match(wctx, pats, opts)
3516 3521
3517 3522 # we'll need this later
3518 3523 targetsubs = sorted(s for s in wctx.substate if m(s))
3519 3524
3520 3525 if not m.always():
3521 3526 matcher = matchmod.badmatch(m, lambda x, y: False)
3522 3527 for abs in wctx.walk(matcher):
3523 3528 names[abs] = m.rel(abs), m.exact(abs)
3524 3529
3525 3530 # walk target manifest to fill `names`
3526 3531
3527 3532 def badfn(path, msg):
3528 3533 if path in names:
3529 3534 return
3530 3535 if path in ctx.substate:
3531 3536 return
3532 3537 path_ = path + '/'
3533 3538 for f in names:
3534 3539 if f.startswith(path_):
3535 3540 return
3536 3541 ui.warn("%s: %s\n" % (m.rel(path), msg))
3537 3542
3538 3543 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3539 3544 if abs not in names:
3540 3545 names[abs] = m.rel(abs), m.exact(abs)
3541 3546
3542 3547 # Find status of all file in `names`.
3543 3548 m = scmutil.matchfiles(repo, names)
3544 3549
3545 3550 changes = repo.status(node1=node, match=m,
3546 3551 unknown=True, ignored=True, clean=True)
3547 3552 else:
3548 3553 changes = repo.status(node1=node, match=m)
3549 3554 for kind in changes:
3550 3555 for abs in kind:
3551 3556 names[abs] = m.rel(abs), m.exact(abs)
3552 3557
3553 3558 m = scmutil.matchfiles(repo, names)
3554 3559
3555 3560 modified = set(changes.modified)
3556 3561 added = set(changes.added)
3557 3562 removed = set(changes.removed)
3558 3563 _deleted = set(changes.deleted)
3559 3564 unknown = set(changes.unknown)
3560 3565 unknown.update(changes.ignored)
3561 3566 clean = set(changes.clean)
3562 3567 modadded = set()
3563 3568
3564 3569 # We need to account for the state of the file in the dirstate,
3565 3570 # even when we revert against something else than parent. This will
3566 3571 # slightly alter the behavior of revert (doing back up or not, delete
3567 3572 # or just forget etc).
3568 3573 if parent == node:
3569 3574 dsmodified = modified
3570 3575 dsadded = added
3571 3576 dsremoved = removed
3572 3577 # store all local modifications, useful later for rename detection
3573 3578 localchanges = dsmodified | dsadded
3574 3579 modified, added, removed = set(), set(), set()
3575 3580 else:
3576 3581 changes = repo.status(node1=parent, match=m)
3577 3582 dsmodified = set(changes.modified)
3578 3583 dsadded = set(changes.added)
3579 3584 dsremoved = set(changes.removed)
3580 3585 # store all local modifications, useful later for rename detection
3581 3586 localchanges = dsmodified | dsadded
3582 3587
3583 3588 # only take into account for removes between wc and target
3584 3589 clean |= dsremoved - removed
3585 3590 dsremoved &= removed
3586 3591 # distinct between dirstate remove and other
3587 3592 removed -= dsremoved
3588 3593
3589 3594 modadded = added & dsmodified
3590 3595 added -= modadded
3591 3596
3592 3597 # tell newly modified apart.
3593 3598 dsmodified &= modified
3594 3599 dsmodified |= modified & dsadded # dirstate added may need backup
3595 3600 modified -= dsmodified
3596 3601
3597 3602 # We need to wait for some post-processing to update this set
3598 3603 # before making the distinction. The dirstate will be used for
3599 3604 # that purpose.
3600 3605 dsadded = added
3601 3606
3602 3607 # in case of merge, files that are actually added can be reported as
3603 3608 # modified, we need to post process the result
3604 3609 if p2 != nullid:
3605 3610 mergeadd = set(dsmodified)
3606 3611 for path in dsmodified:
3607 3612 if path in mf:
3608 3613 mergeadd.remove(path)
3609 3614 dsadded |= mergeadd
3610 3615 dsmodified -= mergeadd
3611 3616
3612 3617 # if f is a rename, update `names` to also revert the source
3613 3618 cwd = repo.getcwd()
3614 3619 for f in localchanges:
3615 3620 src = repo.dirstate.copied(f)
3616 3621 # XXX should we check for rename down to target node?
3617 3622 if src and src not in names and repo.dirstate[src] == 'r':
3618 3623 dsremoved.add(src)
3619 3624 names[src] = (repo.pathto(src, cwd), True)
3620 3625
3621 3626 # determine the exact nature of the deleted changesets
3622 3627 deladded = set(_deleted)
3623 3628 for path in _deleted:
3624 3629 if path in mf:
3625 3630 deladded.remove(path)
3626 3631 deleted = _deleted - deladded
3627 3632
3628 3633 # distinguish between file to forget and the other
3629 3634 added = set()
3630 3635 for abs in dsadded:
3631 3636 if repo.dirstate[abs] != 'a':
3632 3637 added.add(abs)
3633 3638 dsadded -= added
3634 3639
3635 3640 for abs in deladded:
3636 3641 if repo.dirstate[abs] == 'a':
3637 3642 dsadded.add(abs)
3638 3643 deladded -= dsadded
3639 3644
3640 3645 # For files marked as removed, we check if an unknown file is present at
3641 3646 # the same path. If a such file exists it may need to be backed up.
3642 3647 # Making the distinction at this stage helps have simpler backup
3643 3648 # logic.
3644 3649 removunk = set()
3645 3650 for abs in removed:
3646 3651 target = repo.wjoin(abs)
3647 3652 if os.path.lexists(target):
3648 3653 removunk.add(abs)
3649 3654 removed -= removunk
3650 3655
3651 3656 dsremovunk = set()
3652 3657 for abs in dsremoved:
3653 3658 target = repo.wjoin(abs)
3654 3659 if os.path.lexists(target):
3655 3660 dsremovunk.add(abs)
3656 3661 dsremoved -= dsremovunk
3657 3662
3658 3663 # action to be actually performed by revert
3659 3664 # (<list of file>, message>) tuple
3660 3665 actions = {'revert': ([], _('reverting %s\n')),
3661 3666 'add': ([], _('adding %s\n')),
3662 3667 'remove': ([], _('removing %s\n')),
3663 3668 'drop': ([], _('removing %s\n')),
3664 3669 'forget': ([], _('forgetting %s\n')),
3665 3670 'undelete': ([], _('undeleting %s\n')),
3666 3671 'noop': (None, _('no changes needed to %s\n')),
3667 3672 'unknown': (None, _('file not managed: %s\n')),
3668 3673 }
3669 3674
3670 3675 # "constant" that convey the backup strategy.
3671 3676 # All set to `discard` if `no-backup` is set do avoid checking
3672 3677 # no_backup lower in the code.
3673 3678 # These values are ordered for comparison purposes
3674 3679 backupinteractive = 3 # do backup if interactively modified
3675 3680 backup = 2 # unconditionally do backup
3676 3681 check = 1 # check if the existing file differs from target
3677 3682 discard = 0 # never do backup
3678 3683 if opts.get('no_backup'):
3679 3684 backupinteractive = backup = check = discard
3680 3685 if interactive:
3681 3686 dsmodifiedbackup = backupinteractive
3682 3687 else:
3683 3688 dsmodifiedbackup = backup
3684 3689 tobackup = set()
3685 3690
3686 3691 backupanddel = actions['remove']
3687 3692 if not opts.get('no_backup'):
3688 3693 backupanddel = actions['drop']
3689 3694
3690 3695 disptable = (
3691 3696 # dispatch table:
3692 3697 # file state
3693 3698 # action
3694 3699 # make backup
3695 3700
3696 3701 ## Sets that results that will change file on disk
3697 3702 # Modified compared to target, no local change
3698 3703 (modified, actions['revert'], discard),
3699 3704 # Modified compared to target, but local file is deleted
3700 3705 (deleted, actions['revert'], discard),
3701 3706 # Modified compared to target, local change
3702 3707 (dsmodified, actions['revert'], dsmodifiedbackup),
3703 3708 # Added since target
3704 3709 (added, actions['remove'], discard),
3705 3710 # Added in working directory
3706 3711 (dsadded, actions['forget'], discard),
3707 3712 # Added since target, have local modification
3708 3713 (modadded, backupanddel, backup),
3709 3714 # Added since target but file is missing in working directory
3710 3715 (deladded, actions['drop'], discard),
3711 3716 # Removed since target, before working copy parent
3712 3717 (removed, actions['add'], discard),
3713 3718 # Same as `removed` but an unknown file exists at the same path
3714 3719 (removunk, actions['add'], check),
3715 3720 # Removed since targe, marked as such in working copy parent
3716 3721 (dsremoved, actions['undelete'], discard),
3717 3722 # Same as `dsremoved` but an unknown file exists at the same path
3718 3723 (dsremovunk, actions['undelete'], check),
3719 3724 ## the following sets does not result in any file changes
3720 3725 # File with no modification
3721 3726 (clean, actions['noop'], discard),
3722 3727 # Existing file, not tracked anywhere
3723 3728 (unknown, actions['unknown'], discard),
3724 3729 )
3725 3730
3726 3731 for abs, (rel, exact) in sorted(names.items()):
3727 3732 # target file to be touch on disk (relative to cwd)
3728 3733 target = repo.wjoin(abs)
3729 3734 # search the entry in the dispatch table.
3730 3735 # if the file is in any of these sets, it was touched in the working
3731 3736 # directory parent and we are sure it needs to be reverted.
3732 3737 for table, (xlist, msg), dobackup in disptable:
3733 3738 if abs not in table:
3734 3739 continue
3735 3740 if xlist is not None:
3736 3741 xlist.append(abs)
3737 3742 if dobackup:
3738 3743 # If in interactive mode, don't automatically create
3739 3744 # .orig files (issue4793)
3740 3745 if dobackup == backupinteractive:
3741 3746 tobackup.add(abs)
3742 3747 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3743 3748 bakname = scmutil.origpath(ui, repo, rel)
3744 3749 ui.note(_('saving current version of %s as %s\n') %
3745 3750 (rel, bakname))
3746 3751 if not opts.get('dry_run'):
3747 3752 if interactive:
3748 3753 util.copyfile(target, bakname)
3749 3754 else:
3750 3755 util.rename(target, bakname)
3751 3756 if ui.verbose or not exact:
3752 3757 if not isinstance(msg, bytes):
3753 3758 msg = msg(abs)
3754 3759 ui.status(msg % rel)
3755 3760 elif exact:
3756 3761 ui.warn(msg % rel)
3757 3762 break
3758 3763
3759 3764 if not opts.get('dry_run'):
3760 3765 needdata = ('revert', 'add', 'undelete')
3761 3766 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3762 3767 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3763 3768
3764 3769 if targetsubs:
3765 3770 # Revert the subrepos on the revert list
3766 3771 for sub in targetsubs:
3767 3772 try:
3768 3773 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3769 3774 **pycompat.strkwargs(opts))
3770 3775 except KeyError:
3771 3776 raise error.Abort("subrepository '%s' does not exist in %s!"
3772 3777 % (sub, short(ctx.node())))
3773 3778
3774 3779 def _revertprefetch(repo, ctx, *files):
3775 3780 """Let extension changing the storage layer prefetch content"""
3776 3781
3777 3782 def _performrevert(repo, parents, ctx, actions, interactive=False,
3778 3783 tobackup=None):
3779 3784 """function that actually perform all the actions computed for revert
3780 3785
3781 3786 This is an independent function to let extension to plug in and react to
3782 3787 the imminent revert.
3783 3788
3784 3789 Make sure you have the working directory locked when calling this function.
3785 3790 """
3786 3791 parent, p2 = parents
3787 3792 node = ctx.node()
3788 3793 excluded_files = []
3789 3794 matcher_opts = {"exclude": excluded_files}
3790 3795
3791 3796 def checkout(f):
3792 3797 fc = ctx[f]
3793 3798 repo.wwrite(f, fc.data(), fc.flags())
3794 3799
3795 3800 def doremove(f):
3796 3801 try:
3797 3802 repo.wvfs.unlinkpath(f)
3798 3803 except OSError:
3799 3804 pass
3800 3805 repo.dirstate.remove(f)
3801 3806
3802 3807 audit_path = pathutil.pathauditor(repo.root, cached=True)
3803 3808 for f in actions['forget'][0]:
3804 3809 if interactive:
3805 3810 choice = repo.ui.promptchoice(
3806 3811 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3807 3812 if choice == 0:
3808 3813 repo.dirstate.drop(f)
3809 3814 else:
3810 3815 excluded_files.append(repo.wjoin(f))
3811 3816 else:
3812 3817 repo.dirstate.drop(f)
3813 3818 for f in actions['remove'][0]:
3814 3819 audit_path(f)
3815 3820 if interactive:
3816 3821 choice = repo.ui.promptchoice(
3817 3822 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3818 3823 if choice == 0:
3819 3824 doremove(f)
3820 3825 else:
3821 3826 excluded_files.append(repo.wjoin(f))
3822 3827 else:
3823 3828 doremove(f)
3824 3829 for f in actions['drop'][0]:
3825 3830 audit_path(f)
3826 3831 repo.dirstate.remove(f)
3827 3832
3828 3833 normal = None
3829 3834 if node == parent:
3830 3835 # We're reverting to our parent. If possible, we'd like status
3831 3836 # to report the file as clean. We have to use normallookup for
3832 3837 # merges to avoid losing information about merged/dirty files.
3833 3838 if p2 != nullid:
3834 3839 normal = repo.dirstate.normallookup
3835 3840 else:
3836 3841 normal = repo.dirstate.normal
3837 3842
3838 3843 newlyaddedandmodifiedfiles = set()
3839 3844 if interactive:
3840 3845 # Prompt the user for changes to revert
3841 3846 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3842 3847 m = scmutil.match(ctx, torevert, matcher_opts)
3843 3848 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3844 3849 diffopts.nodates = True
3845 3850 diffopts.git = True
3846 3851 operation = 'discard'
3847 3852 reversehunks = True
3848 3853 if node != parent:
3849 3854 operation = 'apply'
3850 3855 reversehunks = False
3851 3856 if reversehunks:
3852 3857 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3853 3858 else:
3854 3859 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3855 3860 originalchunks = patch.parsepatch(diff)
3856 3861
3857 3862 try:
3858 3863
3859 3864 chunks, opts = recordfilter(repo.ui, originalchunks,
3860 3865 operation=operation)
3861 3866 if reversehunks:
3862 3867 chunks = patch.reversehunks(chunks)
3863 3868
3864 3869 except error.PatchError as err:
3865 3870 raise error.Abort(_('error parsing patch: %s') % err)
3866 3871
3867 3872 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3868 3873 if tobackup is None:
3869 3874 tobackup = set()
3870 3875 # Apply changes
3871 3876 fp = stringio()
3872 3877 for c in chunks:
3873 3878 # Create a backup file only if this hunk should be backed up
3874 3879 if ishunk(c) and c.header.filename() in tobackup:
3875 3880 abs = c.header.filename()
3876 3881 target = repo.wjoin(abs)
3877 3882 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3878 3883 util.copyfile(target, bakname)
3879 3884 tobackup.remove(abs)
3880 3885 c.write(fp)
3881 3886 dopatch = fp.tell()
3882 3887 fp.seek(0)
3883 3888 if dopatch:
3884 3889 try:
3885 3890 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3886 3891 except error.PatchError as err:
3887 3892 raise error.Abort(str(err))
3888 3893 del fp
3889 3894 else:
3890 3895 for f in actions['revert'][0]:
3891 3896 checkout(f)
3892 3897 if normal:
3893 3898 normal(f)
3894 3899
3895 3900 for f in actions['add'][0]:
3896 3901 # Don't checkout modified files, they are already created by the diff
3897 3902 if f not in newlyaddedandmodifiedfiles:
3898 3903 checkout(f)
3899 3904 repo.dirstate.add(f)
3900 3905
3901 3906 normal = repo.dirstate.normallookup
3902 3907 if node == parent and p2 == nullid:
3903 3908 normal = repo.dirstate.normal
3904 3909 for f in actions['undelete'][0]:
3905 3910 checkout(f)
3906 3911 normal(f)
3907 3912
3908 3913 copied = copies.pathcopies(repo[parent], ctx)
3909 3914
3910 3915 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3911 3916 if f in copied:
3912 3917 repo.dirstate.copy(copied[f], f)
3913 3918
3914 3919 class command(registrar.command):
3915 3920 """deprecated: used registrar.command instead"""
3916 3921 def _doregister(self, func, name, *args, **kwargs):
3917 3922 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3918 3923 return super(command, self)._doregister(func, name, *args, **kwargs)
3919 3924
3920 3925 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3921 3926 # commands.outgoing. "missing" is "missing" of the result of
3922 3927 # "findcommonoutgoing()"
3923 3928 outgoinghooks = util.hooks()
3924 3929
3925 3930 # a list of (ui, repo) functions called by commands.summary
3926 3931 summaryhooks = util.hooks()
3927 3932
3928 3933 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3929 3934 #
3930 3935 # functions should return tuple of booleans below, if 'changes' is None:
3931 3936 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3932 3937 #
3933 3938 # otherwise, 'changes' is a tuple of tuples below:
3934 3939 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3935 3940 # - (desturl, destbranch, destpeer, outgoing)
3936 3941 summaryremotehooks = util.hooks()
3937 3942
3938 3943 # A list of state files kept by multistep operations like graft.
3939 3944 # Since graft cannot be aborted, it is considered 'clearable' by update.
3940 3945 # note: bisect is intentionally excluded
3941 3946 # (state file, clearable, allowcommit, error, hint)
3942 3947 unfinishedstates = [
3943 3948 ('graftstate', True, False, _('graft in progress'),
3944 3949 _("use 'hg graft --continue' or 'hg update' to abort")),
3945 3950 ('updatestate', True, False, _('last update was interrupted'),
3946 3951 _("use 'hg update' to get a consistent checkout"))
3947 3952 ]
3948 3953
3949 3954 def checkunfinished(repo, commit=False):
3950 3955 '''Look for an unfinished multistep operation, like graft, and abort
3951 3956 if found. It's probably good to check this right before
3952 3957 bailifchanged().
3953 3958 '''
3954 3959 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3955 3960 if commit and allowcommit:
3956 3961 continue
3957 3962 if repo.vfs.exists(f):
3958 3963 raise error.Abort(msg, hint=hint)
3959 3964
3960 3965 def clearunfinished(repo):
3961 3966 '''Check for unfinished operations (as above), and clear the ones
3962 3967 that are clearable.
3963 3968 '''
3964 3969 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3965 3970 if not clearable and repo.vfs.exists(f):
3966 3971 raise error.Abort(msg, hint=hint)
3967 3972 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3968 3973 if clearable and repo.vfs.exists(f):
3969 3974 util.unlink(repo.vfs.join(f))
3970 3975
3971 3976 afterresolvedstates = [
3972 3977 ('graftstate',
3973 3978 _('hg graft --continue')),
3974 3979 ]
3975 3980
3976 3981 def howtocontinue(repo):
3977 3982 '''Check for an unfinished operation and return the command to finish
3978 3983 it.
3979 3984
3980 3985 afterresolvedstates tuples define a .hg/{file} and the corresponding
3981 3986 command needed to finish it.
3982 3987
3983 3988 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3984 3989 a boolean.
3985 3990 '''
3986 3991 contmsg = _("continue: %s")
3987 3992 for f, msg in afterresolvedstates:
3988 3993 if repo.vfs.exists(f):
3989 3994 return contmsg % msg, True
3990 3995 if repo[None].dirty(missing=True, merge=False, branch=False):
3991 3996 return contmsg % _("hg commit"), False
3992 3997 return None, None
3993 3998
3994 3999 def checkafterresolved(repo):
3995 4000 '''Inform the user about the next action after completing hg resolve
3996 4001
3997 4002 If there's a matching afterresolvedstates, howtocontinue will yield
3998 4003 repo.ui.warn as the reporter.
3999 4004
4000 4005 Otherwise, it will yield repo.ui.note.
4001 4006 '''
4002 4007 msg, warning = howtocontinue(repo)
4003 4008 if msg is not None:
4004 4009 if warning:
4005 4010 repo.ui.warn("%s\n" % msg)
4006 4011 else:
4007 4012 repo.ui.note("%s\n" % msg)
4008 4013
4009 4014 def wrongtooltocontinue(repo, task):
4010 4015 '''Raise an abort suggesting how to properly continue if there is an
4011 4016 active task.
4012 4017
4013 4018 Uses howtocontinue() to find the active task.
4014 4019
4015 4020 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4016 4021 a hint.
4017 4022 '''
4018 4023 after = howtocontinue(repo)
4019 4024 hint = None
4020 4025 if after[1]:
4021 4026 hint = after[0]
4022 4027 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5641 +1,5636
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 )
23 23 from . import (
24 24 archival,
25 25 bookmarks,
26 26 bundle2,
27 27 changegroup,
28 28 cmdutil,
29 29 copies,
30 30 debugcommands as debugcommandsmod,
31 31 destutil,
32 32 dirstateguard,
33 33 discovery,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 formatter,
39 39 graphmod,
40 40 hbisect,
41 41 help,
42 42 hg,
43 43 lock as lockmod,
44 44 merge as mergemod,
45 45 obsolete,
46 46 obsutil,
47 47 patch,
48 48 phases,
49 49 pycompat,
50 50 rcutil,
51 51 registrar,
52 52 revsetlang,
53 53 rewriteutil,
54 54 scmutil,
55 55 server,
56 56 sshserver,
57 57 streamclone,
58 58 tags as tagsmod,
59 59 templatekw,
60 60 ui as uimod,
61 61 util,
62 62 )
63 63
64 64 release = lockmod.release
65 65
66 66 table = {}
67 67 table.update(debugcommandsmod.command._table)
68 68
69 69 command = registrar.command(table)
70 70 readonly = registrar.command.readonly
71 71
72 72 # common command options
73 73
74 74 globalopts = [
75 75 ('R', 'repository', '',
76 76 _('repository root directory or name of overlay bundle file'),
77 77 _('REPO')),
78 78 ('', 'cwd', '',
79 79 _('change working directory'), _('DIR')),
80 80 ('y', 'noninteractive', None,
81 81 _('do not prompt, automatically pick the first choice for all prompts')),
82 82 ('q', 'quiet', None, _('suppress output')),
83 83 ('v', 'verbose', None, _('enable additional output')),
84 84 ('', 'color', '',
85 85 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
86 86 # and should not be translated
87 87 _("when to colorize (boolean, always, auto, never, or debug)"),
88 88 _('TYPE')),
89 89 ('', 'config', [],
90 90 _('set/override config option (use \'section.name=value\')'),
91 91 _('CONFIG')),
92 92 ('', 'debug', None, _('enable debugging output')),
93 93 ('', 'debugger', None, _('start debugger')),
94 94 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
95 95 _('ENCODE')),
96 96 ('', 'encodingmode', encoding.encodingmode,
97 97 _('set the charset encoding mode'), _('MODE')),
98 98 ('', 'traceback', None, _('always print a traceback on exception')),
99 99 ('', 'time', None, _('time how long the command takes')),
100 100 ('', 'profile', None, _('print command execution profile')),
101 101 ('', 'version', None, _('output version information and exit')),
102 102 ('h', 'help', None, _('display help and exit')),
103 103 ('', 'hidden', False, _('consider hidden changesets')),
104 104 ('', 'pager', 'auto',
105 105 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
106 106 ]
107 107
108 108 dryrunopts = cmdutil.dryrunopts
109 109 remoteopts = cmdutil.remoteopts
110 110 walkopts = cmdutil.walkopts
111 111 commitopts = cmdutil.commitopts
112 112 commitopts2 = cmdutil.commitopts2
113 113 formatteropts = cmdutil.formatteropts
114 114 templateopts = cmdutil.templateopts
115 115 logopts = cmdutil.logopts
116 116 diffopts = cmdutil.diffopts
117 117 diffwsopts = cmdutil.diffwsopts
118 118 diffopts2 = cmdutil.diffopts2
119 119 mergetoolopts = cmdutil.mergetoolopts
120 120 similarityopts = cmdutil.similarityopts
121 121 subrepoopts = cmdutil.subrepoopts
122 122 debugrevlogopts = cmdutil.debugrevlogopts
123 123
124 124 # Commands start here, listed alphabetically
125 125
126 126 @command('^add',
127 127 walkopts + subrepoopts + dryrunopts,
128 128 _('[OPTION]... [FILE]...'),
129 129 inferrepo=True)
130 130 def add(ui, repo, *pats, **opts):
131 131 """add the specified files on the next commit
132 132
133 133 Schedule files to be version controlled and added to the
134 134 repository.
135 135
136 136 The files will be added to the repository at the next commit. To
137 137 undo an add before that, see :hg:`forget`.
138 138
139 139 If no names are given, add all files to the repository (except
140 140 files matching ``.hgignore``).
141 141
142 142 .. container:: verbose
143 143
144 144 Examples:
145 145
146 146 - New (unknown) files are added
147 147 automatically by :hg:`add`::
148 148
149 149 $ ls
150 150 foo.c
151 151 $ hg status
152 152 ? foo.c
153 153 $ hg add
154 154 adding foo.c
155 155 $ hg status
156 156 A foo.c
157 157
158 158 - Specific files to be added can be specified::
159 159
160 160 $ ls
161 161 bar.c foo.c
162 162 $ hg status
163 163 ? bar.c
164 164 ? foo.c
165 165 $ hg add bar.c
166 166 $ hg status
167 167 A bar.c
168 168 ? foo.c
169 169
170 170 Returns 0 if all files are successfully added.
171 171 """
172 172
173 173 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
174 174 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
175 175 return rejected and 1 or 0
176 176
177 177 @command('addremove',
178 178 similarityopts + subrepoopts + walkopts + dryrunopts,
179 179 _('[OPTION]... [FILE]...'),
180 180 inferrepo=True)
181 181 def addremove(ui, repo, *pats, **opts):
182 182 """add all new files, delete all missing files
183 183
184 184 Add all new files and remove all missing files from the
185 185 repository.
186 186
187 187 Unless names are given, new files are ignored if they match any of
188 188 the patterns in ``.hgignore``. As with add, these changes take
189 189 effect at the next commit.
190 190
191 191 Use the -s/--similarity option to detect renamed files. This
192 192 option takes a percentage between 0 (disabled) and 100 (files must
193 193 be identical) as its parameter. With a parameter greater than 0,
194 194 this compares every removed file with every added file and records
195 195 those similar enough as renames. Detecting renamed files this way
196 196 can be expensive. After using this option, :hg:`status -C` can be
197 197 used to check which files were identified as moved or renamed. If
198 198 not specified, -s/--similarity defaults to 100 and only renames of
199 199 identical files are detected.
200 200
201 201 .. container:: verbose
202 202
203 203 Examples:
204 204
205 205 - A number of files (bar.c and foo.c) are new,
206 206 while foobar.c has been removed (without using :hg:`remove`)
207 207 from the repository::
208 208
209 209 $ ls
210 210 bar.c foo.c
211 211 $ hg status
212 212 ! foobar.c
213 213 ? bar.c
214 214 ? foo.c
215 215 $ hg addremove
216 216 adding bar.c
217 217 adding foo.c
218 218 removing foobar.c
219 219 $ hg status
220 220 A bar.c
221 221 A foo.c
222 222 R foobar.c
223 223
224 224 - A file foobar.c was moved to foo.c without using :hg:`rename`.
225 225 Afterwards, it was edited slightly::
226 226
227 227 $ ls
228 228 foo.c
229 229 $ hg status
230 230 ! foobar.c
231 231 ? foo.c
232 232 $ hg addremove --similarity 90
233 233 removing foobar.c
234 234 adding foo.c
235 235 recording removal of foobar.c as rename to foo.c (94% similar)
236 236 $ hg status -C
237 237 A foo.c
238 238 foobar.c
239 239 R foobar.c
240 240
241 241 Returns 0 if all files are successfully added.
242 242 """
243 243 opts = pycompat.byteskwargs(opts)
244 244 try:
245 245 sim = float(opts.get('similarity') or 100)
246 246 except ValueError:
247 247 raise error.Abort(_('similarity must be a number'))
248 248 if sim < 0 or sim > 100:
249 249 raise error.Abort(_('similarity must be between 0 and 100'))
250 250 matcher = scmutil.match(repo[None], pats, opts)
251 251 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
252 252
253 253 @command('^annotate|blame',
254 254 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
255 255 ('', 'follow', None,
256 256 _('follow copies/renames and list the filename (DEPRECATED)')),
257 257 ('', 'no-follow', None, _("don't follow copies and renames")),
258 258 ('a', 'text', None, _('treat all files as text')),
259 259 ('u', 'user', None, _('list the author (long with -v)')),
260 260 ('f', 'file', None, _('list the filename')),
261 261 ('d', 'date', None, _('list the date (short with -q)')),
262 262 ('n', 'number', None, _('list the revision number (default)')),
263 263 ('c', 'changeset', None, _('list the changeset')),
264 264 ('l', 'line-number', None, _('show line number at the first appearance')),
265 265 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
266 266 ] + diffwsopts + walkopts + formatteropts,
267 267 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
268 268 inferrepo=True)
269 269 def annotate(ui, repo, *pats, **opts):
270 270 """show changeset information by line for each file
271 271
272 272 List changes in files, showing the revision id responsible for
273 273 each line.
274 274
275 275 This command is useful for discovering when a change was made and
276 276 by whom.
277 277
278 278 If you include --file, --user, or --date, the revision number is
279 279 suppressed unless you also include --number.
280 280
281 281 Without the -a/--text option, annotate will avoid processing files
282 282 it detects as binary. With -a, annotate will annotate the file
283 283 anyway, although the results will probably be neither useful
284 284 nor desirable.
285 285
286 286 Returns 0 on success.
287 287 """
288 288 opts = pycompat.byteskwargs(opts)
289 289 if not pats:
290 290 raise error.Abort(_('at least one filename or pattern is required'))
291 291
292 292 if opts.get('follow'):
293 293 # --follow is deprecated and now just an alias for -f/--file
294 294 # to mimic the behavior of Mercurial before version 1.5
295 295 opts['file'] = True
296 296
297 297 rev = opts.get('rev')
298 298 if rev:
299 299 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
300 300 ctx = scmutil.revsingle(repo, rev)
301 301
302 302 rootfm = ui.formatter('annotate', opts)
303 303 if ui.quiet:
304 304 datefunc = util.shortdate
305 305 else:
306 306 datefunc = util.datestr
307 307 if ctx.rev() is None:
308 308 def hexfn(node):
309 309 if node is None:
310 310 return None
311 311 else:
312 312 return rootfm.hexfunc(node)
313 313 if opts.get('changeset'):
314 314 # omit "+" suffix which is appended to node hex
315 315 def formatrev(rev):
316 316 if rev is None:
317 317 return '%d' % ctx.p1().rev()
318 318 else:
319 319 return '%d' % rev
320 320 else:
321 321 def formatrev(rev):
322 322 if rev is None:
323 323 return '%d+' % ctx.p1().rev()
324 324 else:
325 325 return '%d ' % rev
326 326 def formathex(hex):
327 327 if hex is None:
328 328 return '%s+' % rootfm.hexfunc(ctx.p1().node())
329 329 else:
330 330 return '%s ' % hex
331 331 else:
332 332 hexfn = rootfm.hexfunc
333 333 formatrev = formathex = pycompat.bytestr
334 334
335 335 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
336 336 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
337 337 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
338 338 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
339 339 ('file', ' ', lambda x: x.fctx.path(), str),
340 340 ('line_number', ':', lambda x: x.lineno, str),
341 341 ]
342 342 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
343 343
344 344 if (not opts.get('user') and not opts.get('changeset')
345 345 and not opts.get('date') and not opts.get('file')):
346 346 opts['number'] = True
347 347
348 348 linenumber = opts.get('line_number') is not None
349 349 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
350 350 raise error.Abort(_('at least one of -n/-c is required for -l'))
351 351
352 352 ui.pager('annotate')
353 353
354 354 if rootfm.isplain():
355 355 def makefunc(get, fmt):
356 356 return lambda x: fmt(get(x))
357 357 else:
358 358 def makefunc(get, fmt):
359 359 return get
360 360 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
361 361 if opts.get(op)]
362 362 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
363 363 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
364 364 if opts.get(op))
365 365
366 366 def bad(x, y):
367 367 raise error.Abort("%s: %s" % (x, y))
368 368
369 369 m = scmutil.match(ctx, pats, opts, badfn=bad)
370 370
371 371 follow = not opts.get('no_follow')
372 372 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
373 373 whitespace=True)
374 374 skiprevs = opts.get('skip')
375 375 if skiprevs:
376 376 skiprevs = scmutil.revrange(repo, skiprevs)
377 377
378 378 for abs in ctx.walk(m):
379 379 fctx = ctx[abs]
380 380 rootfm.startitem()
381 381 rootfm.data(abspath=abs, path=m.rel(abs))
382 382 if not opts.get('text') and fctx.isbinary():
383 383 rootfm.plain(_("%s: binary file\n")
384 384 % ((pats and m.rel(abs)) or abs))
385 385 continue
386 386
387 387 fm = rootfm.nested('lines')
388 388 lines = fctx.annotate(follow=follow, linenumber=linenumber,
389 389 skiprevs=skiprevs, diffopts=diffopts)
390 390 if not lines:
391 391 fm.end()
392 392 continue
393 393 formats = []
394 394 pieces = []
395 395
396 396 for f, sep in funcmap:
397 397 l = [f(n) for n, dummy in lines]
398 398 if fm.isplain():
399 399 sizes = [encoding.colwidth(x) for x in l]
400 400 ml = max(sizes)
401 401 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
402 402 else:
403 403 formats.append(['%s' for x in l])
404 404 pieces.append(l)
405 405
406 406 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
407 407 fm.startitem()
408 408 fm.write(fields, "".join(f), *p)
409 409 if l[0].skip:
410 410 fmt = "* %s"
411 411 else:
412 412 fmt = ": %s"
413 413 fm.write('line', fmt, l[1])
414 414
415 415 if not lines[-1][1].endswith('\n'):
416 416 fm.plain('\n')
417 417 fm.end()
418 418
419 419 rootfm.end()
420 420
421 421 @command('archive',
422 422 [('', 'no-decode', None, _('do not pass files through decoders')),
423 423 ('p', 'prefix', '', _('directory prefix for files in archive'),
424 424 _('PREFIX')),
425 425 ('r', 'rev', '', _('revision to distribute'), _('REV')),
426 426 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
427 427 ] + subrepoopts + walkopts,
428 428 _('[OPTION]... DEST'))
429 429 def archive(ui, repo, dest, **opts):
430 430 '''create an unversioned archive of a repository revision
431 431
432 432 By default, the revision used is the parent of the working
433 433 directory; use -r/--rev to specify a different revision.
434 434
435 435 The archive type is automatically detected based on file
436 436 extension (to override, use -t/--type).
437 437
438 438 .. container:: verbose
439 439
440 440 Examples:
441 441
442 442 - create a zip file containing the 1.0 release::
443 443
444 444 hg archive -r 1.0 project-1.0.zip
445 445
446 446 - create a tarball excluding .hg files::
447 447
448 448 hg archive project.tar.gz -X ".hg*"
449 449
450 450 Valid types are:
451 451
452 452 :``files``: a directory full of files (default)
453 453 :``tar``: tar archive, uncompressed
454 454 :``tbz2``: tar archive, compressed using bzip2
455 455 :``tgz``: tar archive, compressed using gzip
456 456 :``uzip``: zip archive, uncompressed
457 457 :``zip``: zip archive, compressed using deflate
458 458
459 459 The exact name of the destination archive or directory is given
460 460 using a format string; see :hg:`help export` for details.
461 461
462 462 Each member added to an archive file has a directory prefix
463 463 prepended. Use -p/--prefix to specify a format string for the
464 464 prefix. The default is the basename of the archive, with suffixes
465 465 removed.
466 466
467 467 Returns 0 on success.
468 468 '''
469 469
470 470 opts = pycompat.byteskwargs(opts)
471 471 rev = opts.get('rev')
472 472 if rev:
473 473 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
474 474 ctx = scmutil.revsingle(repo, rev)
475 475 if not ctx:
476 476 raise error.Abort(_('no working directory: please specify a revision'))
477 477 node = ctx.node()
478 478 dest = cmdutil.makefilename(repo, dest, node)
479 479 if os.path.realpath(dest) == repo.root:
480 480 raise error.Abort(_('repository root cannot be destination'))
481 481
482 482 kind = opts.get('type') or archival.guesskind(dest) or 'files'
483 483 prefix = opts.get('prefix')
484 484
485 485 if dest == '-':
486 486 if kind == 'files':
487 487 raise error.Abort(_('cannot archive plain files to stdout'))
488 488 dest = cmdutil.makefileobj(repo, dest)
489 489 if not prefix:
490 490 prefix = os.path.basename(repo.root) + '-%h'
491 491
492 492 prefix = cmdutil.makefilename(repo, prefix, node)
493 493 match = scmutil.match(ctx, [], opts)
494 494 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
495 495 match, prefix, subrepos=opts.get('subrepos'))
496 496
497 497 @command('backout',
498 498 [('', 'merge', None, _('merge with old dirstate parent after backout')),
499 499 ('', 'commit', None,
500 500 _('commit if no conflicts were encountered (DEPRECATED)')),
501 501 ('', 'no-commit', None, _('do not commit')),
502 502 ('', 'parent', '',
503 503 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
504 504 ('r', 'rev', '', _('revision to backout'), _('REV')),
505 505 ('e', 'edit', False, _('invoke editor on commit messages')),
506 506 ] + mergetoolopts + walkopts + commitopts + commitopts2,
507 507 _('[OPTION]... [-r] REV'))
508 508 def backout(ui, repo, node=None, rev=None, **opts):
509 509 '''reverse effect of earlier changeset
510 510
511 511 Prepare a new changeset with the effect of REV undone in the
512 512 current working directory. If no conflicts were encountered,
513 513 it will be committed immediately.
514 514
515 515 If REV is the parent of the working directory, then this new changeset
516 516 is committed automatically (unless --no-commit is specified).
517 517
518 518 .. note::
519 519
520 520 :hg:`backout` cannot be used to fix either an unwanted or
521 521 incorrect merge.
522 522
523 523 .. container:: verbose
524 524
525 525 Examples:
526 526
527 527 - Reverse the effect of the parent of the working directory.
528 528 This backout will be committed immediately::
529 529
530 530 hg backout -r .
531 531
532 532 - Reverse the effect of previous bad revision 23::
533 533
534 534 hg backout -r 23
535 535
536 536 - Reverse the effect of previous bad revision 23 and
537 537 leave changes uncommitted::
538 538
539 539 hg backout -r 23 --no-commit
540 540 hg commit -m "Backout revision 23"
541 541
542 542 By default, the pending changeset will have one parent,
543 543 maintaining a linear history. With --merge, the pending
544 544 changeset will instead have two parents: the old parent of the
545 545 working directory and a new child of REV that simply undoes REV.
546 546
547 547 Before version 1.7, the behavior without --merge was equivalent
548 548 to specifying --merge followed by :hg:`update --clean .` to
549 549 cancel the merge and leave the child of REV as a head to be
550 550 merged separately.
551 551
552 552 See :hg:`help dates` for a list of formats valid for -d/--date.
553 553
554 554 See :hg:`help revert` for a way to restore files to the state
555 555 of another revision.
556 556
557 557 Returns 0 on success, 1 if nothing to backout or there are unresolved
558 558 files.
559 559 '''
560 560 wlock = lock = None
561 561 try:
562 562 wlock = repo.wlock()
563 563 lock = repo.lock()
564 564 return _dobackout(ui, repo, node, rev, **opts)
565 565 finally:
566 566 release(lock, wlock)
567 567
568 568 def _dobackout(ui, repo, node=None, rev=None, **opts):
569 569 opts = pycompat.byteskwargs(opts)
570 570 if opts.get('commit') and opts.get('no_commit'):
571 571 raise error.Abort(_("cannot use --commit with --no-commit"))
572 572 if opts.get('merge') and opts.get('no_commit'):
573 573 raise error.Abort(_("cannot use --merge with --no-commit"))
574 574
575 575 if rev and node:
576 576 raise error.Abort(_("please specify just one revision"))
577 577
578 578 if not rev:
579 579 rev = node
580 580
581 581 if not rev:
582 582 raise error.Abort(_("please specify a revision to backout"))
583 583
584 584 date = opts.get('date')
585 585 if date:
586 586 opts['date'] = util.parsedate(date)
587 587
588 588 cmdutil.checkunfinished(repo)
589 589 cmdutil.bailifchanged(repo)
590 590 node = scmutil.revsingle(repo, rev).node()
591 591
592 592 op1, op2 = repo.dirstate.parents()
593 593 if not repo.changelog.isancestor(node, op1):
594 594 raise error.Abort(_('cannot backout change that is not an ancestor'))
595 595
596 596 p1, p2 = repo.changelog.parents(node)
597 597 if p1 == nullid:
598 598 raise error.Abort(_('cannot backout a change with no parents'))
599 599 if p2 != nullid:
600 600 if not opts.get('parent'):
601 601 raise error.Abort(_('cannot backout a merge changeset'))
602 602 p = repo.lookup(opts['parent'])
603 603 if p not in (p1, p2):
604 604 raise error.Abort(_('%s is not a parent of %s') %
605 605 (short(p), short(node)))
606 606 parent = p
607 607 else:
608 608 if opts.get('parent'):
609 609 raise error.Abort(_('cannot use --parent on non-merge changeset'))
610 610 parent = p1
611 611
612 612 # the backout should appear on the same branch
613 613 branch = repo.dirstate.branch()
614 614 bheads = repo.branchheads(branch)
615 615 rctx = scmutil.revsingle(repo, hex(parent))
616 616 if not opts.get('merge') and op1 != node:
617 617 dsguard = dirstateguard.dirstateguard(repo, 'backout')
618 618 try:
619 619 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
620 620 'backout')
621 621 stats = mergemod.update(repo, parent, True, True, node, False)
622 622 repo.setparents(op1, op2)
623 623 dsguard.close()
624 624 hg._showstats(repo, stats)
625 625 if stats[3]:
626 626 repo.ui.status(_("use 'hg resolve' to retry unresolved "
627 627 "file merges\n"))
628 628 return 1
629 629 finally:
630 630 ui.setconfig('ui', 'forcemerge', '', '')
631 631 lockmod.release(dsguard)
632 632 else:
633 633 hg.clean(repo, node, show_stats=False)
634 634 repo.dirstate.setbranch(branch)
635 635 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
636 636
637 637 if opts.get('no_commit'):
638 638 msg = _("changeset %s backed out, "
639 639 "don't forget to commit.\n")
640 640 ui.status(msg % short(node))
641 641 return 0
642 642
643 643 def commitfunc(ui, repo, message, match, opts):
644 644 editform = 'backout'
645 645 e = cmdutil.getcommiteditor(editform=editform,
646 646 **pycompat.strkwargs(opts))
647 647 if not message:
648 648 # we don't translate commit messages
649 649 message = "Backed out changeset %s" % short(node)
650 650 e = cmdutil.getcommiteditor(edit=True, editform=editform)
651 651 return repo.commit(message, opts.get('user'), opts.get('date'),
652 652 match, editor=e)
653 653 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
654 654 if not newnode:
655 655 ui.status(_("nothing changed\n"))
656 656 return 1
657 657 cmdutil.commitstatus(repo, newnode, branch, bheads)
658 658
659 659 def nice(node):
660 660 return '%d:%s' % (repo.changelog.rev(node), short(node))
661 661 ui.status(_('changeset %s backs out changeset %s\n') %
662 662 (nice(repo.changelog.tip()), nice(node)))
663 663 if opts.get('merge') and op1 != node:
664 664 hg.clean(repo, op1, show_stats=False)
665 665 ui.status(_('merging with changeset %s\n')
666 666 % nice(repo.changelog.tip()))
667 667 try:
668 668 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
669 669 'backout')
670 670 return hg.merge(repo, hex(repo.changelog.tip()))
671 671 finally:
672 672 ui.setconfig('ui', 'forcemerge', '', '')
673 673 return 0
674 674
675 675 @command('bisect',
676 676 [('r', 'reset', False, _('reset bisect state')),
677 677 ('g', 'good', False, _('mark changeset good')),
678 678 ('b', 'bad', False, _('mark changeset bad')),
679 679 ('s', 'skip', False, _('skip testing changeset')),
680 680 ('e', 'extend', False, _('extend the bisect range')),
681 681 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
682 682 ('U', 'noupdate', False, _('do not update to target'))],
683 683 _("[-gbsr] [-U] [-c CMD] [REV]"))
684 684 def bisect(ui, repo, rev=None, extra=None, command=None,
685 685 reset=None, good=None, bad=None, skip=None, extend=None,
686 686 noupdate=None):
687 687 """subdivision search of changesets
688 688
689 689 This command helps to find changesets which introduce problems. To
690 690 use, mark the earliest changeset you know exhibits the problem as
691 691 bad, then mark the latest changeset which is free from the problem
692 692 as good. Bisect will update your working directory to a revision
693 693 for testing (unless the -U/--noupdate option is specified). Once
694 694 you have performed tests, mark the working directory as good or
695 695 bad, and bisect will either update to another candidate changeset
696 696 or announce that it has found the bad revision.
697 697
698 698 As a shortcut, you can also use the revision argument to mark a
699 699 revision as good or bad without checking it out first.
700 700
701 701 If you supply a command, it will be used for automatic bisection.
702 702 The environment variable HG_NODE will contain the ID of the
703 703 changeset being tested. The exit status of the command will be
704 704 used to mark revisions as good or bad: status 0 means good, 125
705 705 means to skip the revision, 127 (command not found) will abort the
706 706 bisection, and any other non-zero exit status means the revision
707 707 is bad.
708 708
709 709 .. container:: verbose
710 710
711 711 Some examples:
712 712
713 713 - start a bisection with known bad revision 34, and good revision 12::
714 714
715 715 hg bisect --bad 34
716 716 hg bisect --good 12
717 717
718 718 - advance the current bisection by marking current revision as good or
719 719 bad::
720 720
721 721 hg bisect --good
722 722 hg bisect --bad
723 723
724 724 - mark the current revision, or a known revision, to be skipped (e.g. if
725 725 that revision is not usable because of another issue)::
726 726
727 727 hg bisect --skip
728 728 hg bisect --skip 23
729 729
730 730 - skip all revisions that do not touch directories ``foo`` or ``bar``::
731 731
732 732 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
733 733
734 734 - forget the current bisection::
735 735
736 736 hg bisect --reset
737 737
738 738 - use 'make && make tests' to automatically find the first broken
739 739 revision::
740 740
741 741 hg bisect --reset
742 742 hg bisect --bad 34
743 743 hg bisect --good 12
744 744 hg bisect --command "make && make tests"
745 745
746 746 - see all changesets whose states are already known in the current
747 747 bisection::
748 748
749 749 hg log -r "bisect(pruned)"
750 750
751 751 - see the changeset currently being bisected (especially useful
752 752 if running with -U/--noupdate)::
753 753
754 754 hg log -r "bisect(current)"
755 755
756 756 - see all changesets that took part in the current bisection::
757 757
758 758 hg log -r "bisect(range)"
759 759
760 760 - you can even get a nice graph::
761 761
762 762 hg log --graph -r "bisect(range)"
763 763
764 764 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
765 765
766 766 Returns 0 on success.
767 767 """
768 768 # backward compatibility
769 769 if rev in "good bad reset init".split():
770 770 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
771 771 cmd, rev, extra = rev, extra, None
772 772 if cmd == "good":
773 773 good = True
774 774 elif cmd == "bad":
775 775 bad = True
776 776 else:
777 777 reset = True
778 778 elif extra:
779 779 raise error.Abort(_('incompatible arguments'))
780 780
781 781 incompatibles = {
782 782 '--bad': bad,
783 783 '--command': bool(command),
784 784 '--extend': extend,
785 785 '--good': good,
786 786 '--reset': reset,
787 787 '--skip': skip,
788 788 }
789 789
790 790 enabled = [x for x in incompatibles if incompatibles[x]]
791 791
792 792 if len(enabled) > 1:
793 793 raise error.Abort(_('%s and %s are incompatible') %
794 794 tuple(sorted(enabled)[0:2]))
795 795
796 796 if reset:
797 797 hbisect.resetstate(repo)
798 798 return
799 799
800 800 state = hbisect.load_state(repo)
801 801
802 802 # update state
803 803 if good or bad or skip:
804 804 if rev:
805 805 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
806 806 else:
807 807 nodes = [repo.lookup('.')]
808 808 if good:
809 809 state['good'] += nodes
810 810 elif bad:
811 811 state['bad'] += nodes
812 812 elif skip:
813 813 state['skip'] += nodes
814 814 hbisect.save_state(repo, state)
815 815 if not (state['good'] and state['bad']):
816 816 return
817 817
818 818 def mayupdate(repo, node, show_stats=True):
819 819 """common used update sequence"""
820 820 if noupdate:
821 821 return
822 822 cmdutil.checkunfinished(repo)
823 823 cmdutil.bailifchanged(repo)
824 824 return hg.clean(repo, node, show_stats=show_stats)
825 825
826 826 displayer = cmdutil.show_changeset(ui, repo, {})
827 827
828 828 if command:
829 829 changesets = 1
830 830 if noupdate:
831 831 try:
832 832 node = state['current'][0]
833 833 except LookupError:
834 834 raise error.Abort(_('current bisect revision is unknown - '
835 835 'start a new bisect to fix'))
836 836 else:
837 837 node, p2 = repo.dirstate.parents()
838 838 if p2 != nullid:
839 839 raise error.Abort(_('current bisect revision is a merge'))
840 840 if rev:
841 841 node = repo[scmutil.revsingle(repo, rev, node)].node()
842 842 try:
843 843 while changesets:
844 844 # update state
845 845 state['current'] = [node]
846 846 hbisect.save_state(repo, state)
847 847 status = ui.system(command, environ={'HG_NODE': hex(node)},
848 848 blockedtag='bisect_check')
849 849 if status == 125:
850 850 transition = "skip"
851 851 elif status == 0:
852 852 transition = "good"
853 853 # status < 0 means process was killed
854 854 elif status == 127:
855 855 raise error.Abort(_("failed to execute %s") % command)
856 856 elif status < 0:
857 857 raise error.Abort(_("%s killed") % command)
858 858 else:
859 859 transition = "bad"
860 860 state[transition].append(node)
861 861 ctx = repo[node]
862 862 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
863 863 hbisect.checkstate(state)
864 864 # bisect
865 865 nodes, changesets, bgood = hbisect.bisect(repo, state)
866 866 # update to next check
867 867 node = nodes[0]
868 868 mayupdate(repo, node, show_stats=False)
869 869 finally:
870 870 state['current'] = [node]
871 871 hbisect.save_state(repo, state)
872 872 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
873 873 return
874 874
875 875 hbisect.checkstate(state)
876 876
877 877 # actually bisect
878 878 nodes, changesets, good = hbisect.bisect(repo, state)
879 879 if extend:
880 880 if not changesets:
881 881 extendnode = hbisect.extendrange(repo, state, nodes, good)
882 882 if extendnode is not None:
883 883 ui.write(_("Extending search to changeset %d:%s\n")
884 884 % (extendnode.rev(), extendnode))
885 885 state['current'] = [extendnode.node()]
886 886 hbisect.save_state(repo, state)
887 887 return mayupdate(repo, extendnode.node())
888 888 raise error.Abort(_("nothing to extend"))
889 889
890 890 if changesets == 0:
891 891 hbisect.printresult(ui, repo, state, displayer, nodes, good)
892 892 else:
893 893 assert len(nodes) == 1 # only a single node can be tested next
894 894 node = nodes[0]
895 895 # compute the approximate number of remaining tests
896 896 tests, size = 0, 2
897 897 while size <= changesets:
898 898 tests, size = tests + 1, size * 2
899 899 rev = repo.changelog.rev(node)
900 900 ui.write(_("Testing changeset %d:%s "
901 901 "(%d changesets remaining, ~%d tests)\n")
902 902 % (rev, short(node), changesets, tests))
903 903 state['current'] = [node]
904 904 hbisect.save_state(repo, state)
905 905 return mayupdate(repo, node)
906 906
907 907 @command('bookmarks|bookmark',
908 908 [('f', 'force', False, _('force')),
909 909 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
910 910 ('d', 'delete', False, _('delete a given bookmark')),
911 911 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
912 912 ('i', 'inactive', False, _('mark a bookmark inactive')),
913 913 ] + formatteropts,
914 914 _('hg bookmarks [OPTIONS]... [NAME]...'))
915 915 def bookmark(ui, repo, *names, **opts):
916 916 '''create a new bookmark or list existing bookmarks
917 917
918 918 Bookmarks are labels on changesets to help track lines of development.
919 919 Bookmarks are unversioned and can be moved, renamed and deleted.
920 920 Deleting or moving a bookmark has no effect on the associated changesets.
921 921
922 922 Creating or updating to a bookmark causes it to be marked as 'active'.
923 923 The active bookmark is indicated with a '*'.
924 924 When a commit is made, the active bookmark will advance to the new commit.
925 925 A plain :hg:`update` will also advance an active bookmark, if possible.
926 926 Updating away from a bookmark will cause it to be deactivated.
927 927
928 928 Bookmarks can be pushed and pulled between repositories (see
929 929 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
930 930 diverged, a new 'divergent bookmark' of the form 'name@path' will
931 931 be created. Using :hg:`merge` will resolve the divergence.
932 932
933 933 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
934 934 the active bookmark's name.
935 935
936 936 A bookmark named '@' has the special property that :hg:`clone` will
937 937 check it out by default if it exists.
938 938
939 939 .. container:: verbose
940 940
941 941 Examples:
942 942
943 943 - create an active bookmark for a new line of development::
944 944
945 945 hg book new-feature
946 946
947 947 - create an inactive bookmark as a place marker::
948 948
949 949 hg book -i reviewed
950 950
951 951 - create an inactive bookmark on another changeset::
952 952
953 953 hg book -r .^ tested
954 954
955 955 - rename bookmark turkey to dinner::
956 956
957 957 hg book -m turkey dinner
958 958
959 959 - move the '@' bookmark from another branch::
960 960
961 961 hg book -f @
962 962 '''
963 963 force = opts.get(r'force')
964 964 rev = opts.get(r'rev')
965 965 delete = opts.get(r'delete')
966 966 rename = opts.get(r'rename')
967 967 inactive = opts.get(r'inactive')
968 968
969 969 if delete and rename:
970 970 raise error.Abort(_("--delete and --rename are incompatible"))
971 971 if delete and rev:
972 972 raise error.Abort(_("--rev is incompatible with --delete"))
973 973 if rename and rev:
974 974 raise error.Abort(_("--rev is incompatible with --rename"))
975 975 if not names and (delete or rev):
976 976 raise error.Abort(_("bookmark name required"))
977 977
978 978 if delete or rename or names or inactive:
979 979 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
980 980 if delete:
981 981 names = pycompat.maplist(repo._bookmarks.expandname, names)
982 982 bookmarks.delete(repo, tr, names)
983 983 elif rename:
984 984 if not names:
985 985 raise error.Abort(_("new bookmark name required"))
986 986 elif len(names) > 1:
987 987 raise error.Abort(_("only one new bookmark name allowed"))
988 988 rename = repo._bookmarks.expandname(rename)
989 989 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
990 990 elif names:
991 991 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
992 992 elif inactive:
993 993 if len(repo._bookmarks) == 0:
994 994 ui.status(_("no bookmarks set\n"))
995 995 elif not repo._activebookmark:
996 996 ui.status(_("no active bookmark\n"))
997 997 else:
998 998 bookmarks.deactivate(repo)
999 999 else: # show bookmarks
1000 1000 bookmarks.printbookmarks(ui, repo, **opts)
1001 1001
1002 1002 @command('branch',
1003 1003 [('f', 'force', None,
1004 1004 _('set branch name even if it shadows an existing branch')),
1005 1005 ('C', 'clean', None, _('reset branch name to parent branch name')),
1006 1006 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1007 1007 ],
1008 1008 _('[-fC] [NAME]'))
1009 1009 def branch(ui, repo, label=None, **opts):
1010 1010 """set or show the current branch name
1011 1011
1012 1012 .. note::
1013 1013
1014 1014 Branch names are permanent and global. Use :hg:`bookmark` to create a
1015 1015 light-weight bookmark instead. See :hg:`help glossary` for more
1016 1016 information about named branches and bookmarks.
1017 1017
1018 1018 With no argument, show the current branch name. With one argument,
1019 1019 set the working directory branch name (the branch will not exist
1020 1020 in the repository until the next commit). Standard practice
1021 1021 recommends that primary development take place on the 'default'
1022 1022 branch.
1023 1023
1024 1024 Unless -f/--force is specified, branch will not let you set a
1025 1025 branch name that already exists.
1026 1026
1027 1027 Use -C/--clean to reset the working directory branch to that of
1028 1028 the parent of the working directory, negating a previous branch
1029 1029 change.
1030 1030
1031 1031 Use the command :hg:`update` to switch to an existing branch. Use
1032 1032 :hg:`commit --close-branch` to mark this branch head as closed.
1033 1033 When all heads of a branch are closed, the branch will be
1034 1034 considered closed.
1035 1035
1036 1036 Returns 0 on success.
1037 1037 """
1038 1038 opts = pycompat.byteskwargs(opts)
1039 1039 revs = opts.get('rev')
1040 1040 if label:
1041 1041 label = label.strip()
1042 1042
1043 1043 if not opts.get('clean') and not label:
1044 1044 if revs:
1045 1045 raise error.Abort(_("no branch name specified for the revisions"))
1046 1046 ui.write("%s\n" % repo.dirstate.branch())
1047 1047 return
1048 1048
1049 1049 with repo.wlock():
1050 1050 if opts.get('clean'):
1051 1051 label = repo[None].p1().branch()
1052 1052 repo.dirstate.setbranch(label)
1053 1053 ui.status(_('reset working directory to branch %s\n') % label)
1054 1054 elif label:
1055 1055
1056 1056 scmutil.checknewlabel(repo, label, 'branch')
1057 1057 if revs:
1058 # XXX: we should allow setting name to existing branch if the
1059 # branch of root of the revs is same as the new branch name
1060 if label in repo.branchmap():
1061 raise error.Abort(_('a branch of the same'
1062 ' name already exists'))
1063 1058 return cmdutil.changebranch(ui, repo, revs, label)
1064 1059
1065 1060 if not opts.get('force') and label in repo.branchmap():
1066 1061 if label not in [p.branch() for p in repo[None].parents()]:
1067 1062 raise error.Abort(_('a branch of the same name already'
1068 1063 ' exists'),
1069 1064 # i18n: "it" refers to an existing branch
1070 1065 hint=_("use 'hg update' to switch to it"))
1071 1066
1072 1067 repo.dirstate.setbranch(label)
1073 1068 ui.status(_('marked working directory as branch %s\n') % label)
1074 1069
1075 1070 # find any open named branches aside from default
1076 1071 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1077 1072 if n != "default" and not c]
1078 1073 if not others:
1079 1074 ui.status(_('(branches are permanent and global, '
1080 1075 'did you want a bookmark?)\n'))
1081 1076
1082 1077 @command('branches',
1083 1078 [('a', 'active', False,
1084 1079 _('show only branches that have unmerged heads (DEPRECATED)')),
1085 1080 ('c', 'closed', False, _('show normal and closed branches')),
1086 1081 ] + formatteropts,
1087 1082 _('[-c]'), cmdtype=readonly)
1088 1083 def branches(ui, repo, active=False, closed=False, **opts):
1089 1084 """list repository named branches
1090 1085
1091 1086 List the repository's named branches, indicating which ones are
1092 1087 inactive. If -c/--closed is specified, also list branches which have
1093 1088 been marked closed (see :hg:`commit --close-branch`).
1094 1089
1095 1090 Use the command :hg:`update` to switch to an existing branch.
1096 1091
1097 1092 Returns 0.
1098 1093 """
1099 1094
1100 1095 opts = pycompat.byteskwargs(opts)
1101 1096 ui.pager('branches')
1102 1097 fm = ui.formatter('branches', opts)
1103 1098 hexfunc = fm.hexfunc
1104 1099
1105 1100 allheads = set(repo.heads())
1106 1101 branches = []
1107 1102 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1108 1103 isactive = False
1109 1104 if not isclosed:
1110 1105 openheads = set(repo.branchmap().iteropen(heads))
1111 1106 isactive = bool(openheads & allheads)
1112 1107 branches.append((tag, repo[tip], isactive, not isclosed))
1113 1108 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1114 1109 reverse=True)
1115 1110
1116 1111 for tag, ctx, isactive, isopen in branches:
1117 1112 if active and not isactive:
1118 1113 continue
1119 1114 if isactive:
1120 1115 label = 'branches.active'
1121 1116 notice = ''
1122 1117 elif not isopen:
1123 1118 if not closed:
1124 1119 continue
1125 1120 label = 'branches.closed'
1126 1121 notice = _(' (closed)')
1127 1122 else:
1128 1123 label = 'branches.inactive'
1129 1124 notice = _(' (inactive)')
1130 1125 current = (tag == repo.dirstate.branch())
1131 1126 if current:
1132 1127 label = 'branches.current'
1133 1128
1134 1129 fm.startitem()
1135 1130 fm.write('branch', '%s', tag, label=label)
1136 1131 rev = ctx.rev()
1137 1132 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1138 1133 fmt = ' ' * padsize + ' %d:%s'
1139 1134 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1140 1135 label='log.changeset changeset.%s' % ctx.phasestr())
1141 1136 fm.context(ctx=ctx)
1142 1137 fm.data(active=isactive, closed=not isopen, current=current)
1143 1138 if not ui.quiet:
1144 1139 fm.plain(notice)
1145 1140 fm.plain('\n')
1146 1141 fm.end()
1147 1142
1148 1143 @command('bundle',
1149 1144 [('f', 'force', None, _('run even when the destination is unrelated')),
1150 1145 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1151 1146 _('REV')),
1152 1147 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1153 1148 _('BRANCH')),
1154 1149 ('', 'base', [],
1155 1150 _('a base changeset assumed to be available at the destination'),
1156 1151 _('REV')),
1157 1152 ('a', 'all', None, _('bundle all changesets in the repository')),
1158 1153 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1159 1154 ] + remoteopts,
1160 1155 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1161 1156 def bundle(ui, repo, fname, dest=None, **opts):
1162 1157 """create a bundle file
1163 1158
1164 1159 Generate a bundle file containing data to be added to a repository.
1165 1160
1166 1161 To create a bundle containing all changesets, use -a/--all
1167 1162 (or --base null). Otherwise, hg assumes the destination will have
1168 1163 all the nodes you specify with --base parameters. Otherwise, hg
1169 1164 will assume the repository has all the nodes in destination, or
1170 1165 default-push/default if no destination is specified.
1171 1166
1172 1167 You can change bundle format with the -t/--type option. See
1173 1168 :hg:`help bundlespec` for documentation on this format. By default,
1174 1169 the most appropriate format is used and compression defaults to
1175 1170 bzip2.
1176 1171
1177 1172 The bundle file can then be transferred using conventional means
1178 1173 and applied to another repository with the unbundle or pull
1179 1174 command. This is useful when direct push and pull are not
1180 1175 available or when exporting an entire repository is undesirable.
1181 1176
1182 1177 Applying bundles preserves all changeset contents including
1183 1178 permissions, copy/rename information, and revision history.
1184 1179
1185 1180 Returns 0 on success, 1 if no changes found.
1186 1181 """
1187 1182 opts = pycompat.byteskwargs(opts)
1188 1183 revs = None
1189 1184 if 'rev' in opts:
1190 1185 revstrings = opts['rev']
1191 1186 revs = scmutil.revrange(repo, revstrings)
1192 1187 if revstrings and not revs:
1193 1188 raise error.Abort(_('no commits to bundle'))
1194 1189
1195 1190 bundletype = opts.get('type', 'bzip2').lower()
1196 1191 try:
1197 1192 bcompression, cgversion, params = exchange.parsebundlespec(
1198 1193 repo, bundletype, strict=False)
1199 1194 except error.UnsupportedBundleSpecification as e:
1200 1195 raise error.Abort(str(e),
1201 1196 hint=_("see 'hg help bundlespec' for supported "
1202 1197 "values for --type"))
1203 1198
1204 1199 # Packed bundles are a pseudo bundle format for now.
1205 1200 if cgversion == 's1':
1206 1201 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1207 1202 hint=_("use 'hg debugcreatestreamclonebundle'"))
1208 1203
1209 1204 if opts.get('all'):
1210 1205 if dest:
1211 1206 raise error.Abort(_("--all is incompatible with specifying "
1212 1207 "a destination"))
1213 1208 if opts.get('base'):
1214 1209 ui.warn(_("ignoring --base because --all was specified\n"))
1215 1210 base = ['null']
1216 1211 else:
1217 1212 base = scmutil.revrange(repo, opts.get('base'))
1218 1213 if cgversion not in changegroup.supportedoutgoingversions(repo):
1219 1214 raise error.Abort(_("repository does not support bundle version %s") %
1220 1215 cgversion)
1221 1216
1222 1217 if base:
1223 1218 if dest:
1224 1219 raise error.Abort(_("--base is incompatible with specifying "
1225 1220 "a destination"))
1226 1221 common = [repo.lookup(rev) for rev in base]
1227 1222 heads = revs and map(repo.lookup, revs) or None
1228 1223 outgoing = discovery.outgoing(repo, common, heads)
1229 1224 else:
1230 1225 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1231 1226 dest, branches = hg.parseurl(dest, opts.get('branch'))
1232 1227 other = hg.peer(repo, opts, dest)
1233 1228 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1234 1229 heads = revs and map(repo.lookup, revs) or revs
1235 1230 outgoing = discovery.findcommonoutgoing(repo, other,
1236 1231 onlyheads=heads,
1237 1232 force=opts.get('force'),
1238 1233 portable=True)
1239 1234
1240 1235 if not outgoing.missing:
1241 1236 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1242 1237 return 1
1243 1238
1244 1239 if cgversion == '01': #bundle1
1245 1240 if bcompression is None:
1246 1241 bcompression = 'UN'
1247 1242 bversion = 'HG10' + bcompression
1248 1243 bcompression = None
1249 1244 elif cgversion in ('02', '03'):
1250 1245 bversion = 'HG20'
1251 1246 else:
1252 1247 raise error.ProgrammingError(
1253 1248 'bundle: unexpected changegroup version %s' % cgversion)
1254 1249
1255 1250 # TODO compression options should be derived from bundlespec parsing.
1256 1251 # This is a temporary hack to allow adjusting bundle compression
1257 1252 # level without a) formalizing the bundlespec changes to declare it
1258 1253 # b) introducing a command flag.
1259 1254 compopts = {}
1260 1255 complevel = ui.configint('experimental', 'bundlecomplevel')
1261 1256 if complevel is not None:
1262 1257 compopts['level'] = complevel
1263 1258
1264 1259
1265 1260 contentopts = {'cg.version': cgversion}
1266 1261 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1267 1262 contentopts['obsolescence'] = True
1268 1263 if repo.ui.configbool('experimental', 'bundle-phases'):
1269 1264 contentopts['phases'] = True
1270 1265 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1271 1266 contentopts, compression=bcompression,
1272 1267 compopts=compopts)
1273 1268
1274 1269 @command('cat',
1275 1270 [('o', 'output', '',
1276 1271 _('print output to file with formatted name'), _('FORMAT')),
1277 1272 ('r', 'rev', '', _('print the given revision'), _('REV')),
1278 1273 ('', 'decode', None, _('apply any matching decode filter')),
1279 1274 ] + walkopts + formatteropts,
1280 1275 _('[OPTION]... FILE...'),
1281 1276 inferrepo=True, cmdtype=readonly)
1282 1277 def cat(ui, repo, file1, *pats, **opts):
1283 1278 """output the current or given revision of files
1284 1279
1285 1280 Print the specified files as they were at the given revision. If
1286 1281 no revision is given, the parent of the working directory is used.
1287 1282
1288 1283 Output may be to a file, in which case the name of the file is
1289 1284 given using a format string. The formatting rules as follows:
1290 1285
1291 1286 :``%%``: literal "%" character
1292 1287 :``%s``: basename of file being printed
1293 1288 :``%d``: dirname of file being printed, or '.' if in repository root
1294 1289 :``%p``: root-relative path name of file being printed
1295 1290 :``%H``: changeset hash (40 hexadecimal digits)
1296 1291 :``%R``: changeset revision number
1297 1292 :``%h``: short-form changeset hash (12 hexadecimal digits)
1298 1293 :``%r``: zero-padded changeset revision number
1299 1294 :``%b``: basename of the exporting repository
1300 1295
1301 1296 Returns 0 on success.
1302 1297 """
1303 1298 opts = pycompat.byteskwargs(opts)
1304 1299 rev = opts.get('rev')
1305 1300 if rev:
1306 1301 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1307 1302 ctx = scmutil.revsingle(repo, rev)
1308 1303 m = scmutil.match(ctx, (file1,) + pats, opts)
1309 1304 fntemplate = opts.pop('output', '')
1310 1305 if cmdutil.isstdiofilename(fntemplate):
1311 1306 fntemplate = ''
1312 1307
1313 1308 if fntemplate:
1314 1309 fm = formatter.nullformatter(ui, 'cat')
1315 1310 else:
1316 1311 ui.pager('cat')
1317 1312 fm = ui.formatter('cat', opts)
1318 1313 with fm:
1319 1314 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1320 1315 **pycompat.strkwargs(opts))
1321 1316
1322 1317 @command('^clone',
1323 1318 [('U', 'noupdate', None, _('the clone will include an empty working '
1324 1319 'directory (only a repository)')),
1325 1320 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1326 1321 _('REV')),
1327 1322 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1328 1323 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1329 1324 ('', 'pull', None, _('use pull protocol to copy metadata')),
1330 1325 ('', 'uncompressed', None,
1331 1326 _('an alias to --stream (DEPRECATED)')),
1332 1327 ('', 'stream', None,
1333 1328 _('clone with minimal data processing')),
1334 1329 ] + remoteopts,
1335 1330 _('[OPTION]... SOURCE [DEST]'),
1336 1331 norepo=True)
1337 1332 def clone(ui, source, dest=None, **opts):
1338 1333 """make a copy of an existing repository
1339 1334
1340 1335 Create a copy of an existing repository in a new directory.
1341 1336
1342 1337 If no destination directory name is specified, it defaults to the
1343 1338 basename of the source.
1344 1339
1345 1340 The location of the source is added to the new repository's
1346 1341 ``.hg/hgrc`` file, as the default to be used for future pulls.
1347 1342
1348 1343 Only local paths and ``ssh://`` URLs are supported as
1349 1344 destinations. For ``ssh://`` destinations, no working directory or
1350 1345 ``.hg/hgrc`` will be created on the remote side.
1351 1346
1352 1347 If the source repository has a bookmark called '@' set, that
1353 1348 revision will be checked out in the new repository by default.
1354 1349
1355 1350 To check out a particular version, use -u/--update, or
1356 1351 -U/--noupdate to create a clone with no working directory.
1357 1352
1358 1353 To pull only a subset of changesets, specify one or more revisions
1359 1354 identifiers with -r/--rev or branches with -b/--branch. The
1360 1355 resulting clone will contain only the specified changesets and
1361 1356 their ancestors. These options (or 'clone src#rev dest') imply
1362 1357 --pull, even for local source repositories.
1363 1358
1364 1359 In normal clone mode, the remote normalizes repository data into a common
1365 1360 exchange format and the receiving end translates this data into its local
1366 1361 storage format. --stream activates a different clone mode that essentially
1367 1362 copies repository files from the remote with minimal data processing. This
1368 1363 significantly reduces the CPU cost of a clone both remotely and locally.
1369 1364 However, it often increases the transferred data size by 30-40%. This can
1370 1365 result in substantially faster clones where I/O throughput is plentiful,
1371 1366 especially for larger repositories. A side-effect of --stream clones is
1372 1367 that storage settings and requirements on the remote are applied locally:
1373 1368 a modern client may inherit legacy or inefficient storage used by the
1374 1369 remote or a legacy Mercurial client may not be able to clone from a
1375 1370 modern Mercurial remote.
1376 1371
1377 1372 .. note::
1378 1373
1379 1374 Specifying a tag will include the tagged changeset but not the
1380 1375 changeset containing the tag.
1381 1376
1382 1377 .. container:: verbose
1383 1378
1384 1379 For efficiency, hardlinks are used for cloning whenever the
1385 1380 source and destination are on the same filesystem (note this
1386 1381 applies only to the repository data, not to the working
1387 1382 directory). Some filesystems, such as AFS, implement hardlinking
1388 1383 incorrectly, but do not report errors. In these cases, use the
1389 1384 --pull option to avoid hardlinking.
1390 1385
1391 1386 Mercurial will update the working directory to the first applicable
1392 1387 revision from this list:
1393 1388
1394 1389 a) null if -U or the source repository has no changesets
1395 1390 b) if -u . and the source repository is local, the first parent of
1396 1391 the source repository's working directory
1397 1392 c) the changeset specified with -u (if a branch name, this means the
1398 1393 latest head of that branch)
1399 1394 d) the changeset specified with -r
1400 1395 e) the tipmost head specified with -b
1401 1396 f) the tipmost head specified with the url#branch source syntax
1402 1397 g) the revision marked with the '@' bookmark, if present
1403 1398 h) the tipmost head of the default branch
1404 1399 i) tip
1405 1400
1406 1401 When cloning from servers that support it, Mercurial may fetch
1407 1402 pre-generated data from a server-advertised URL. When this is done,
1408 1403 hooks operating on incoming changesets and changegroups may fire twice,
1409 1404 once for the bundle fetched from the URL and another for any additional
1410 1405 data not fetched from this URL. In addition, if an error occurs, the
1411 1406 repository may be rolled back to a partial clone. This behavior may
1412 1407 change in future releases. See :hg:`help -e clonebundles` for more.
1413 1408
1414 1409 Examples:
1415 1410
1416 1411 - clone a remote repository to a new directory named hg/::
1417 1412
1418 1413 hg clone https://www.mercurial-scm.org/repo/hg/
1419 1414
1420 1415 - create a lightweight local clone::
1421 1416
1422 1417 hg clone project/ project-feature/
1423 1418
1424 1419 - clone from an absolute path on an ssh server (note double-slash)::
1425 1420
1426 1421 hg clone ssh://user@server//home/projects/alpha/
1427 1422
1428 1423 - do a streaming clone while checking out a specified version::
1429 1424
1430 1425 hg clone --stream http://server/repo -u 1.5
1431 1426
1432 1427 - create a repository without changesets after a particular revision::
1433 1428
1434 1429 hg clone -r 04e544 experimental/ good/
1435 1430
1436 1431 - clone (and track) a particular named branch::
1437 1432
1438 1433 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1439 1434
1440 1435 See :hg:`help urls` for details on specifying URLs.
1441 1436
1442 1437 Returns 0 on success.
1443 1438 """
1444 1439 opts = pycompat.byteskwargs(opts)
1445 1440 if opts.get('noupdate') and opts.get('updaterev'):
1446 1441 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1447 1442
1448 1443 r = hg.clone(ui, opts, source, dest,
1449 1444 pull=opts.get('pull'),
1450 1445 stream=opts.get('stream') or opts.get('uncompressed'),
1451 1446 rev=opts.get('rev'),
1452 1447 update=opts.get('updaterev') or not opts.get('noupdate'),
1453 1448 branch=opts.get('branch'),
1454 1449 shareopts=opts.get('shareopts'))
1455 1450
1456 1451 return r is None
1457 1452
1458 1453 @command('^commit|ci',
1459 1454 [('A', 'addremove', None,
1460 1455 _('mark new/missing files as added/removed before committing')),
1461 1456 ('', 'close-branch', None,
1462 1457 _('mark a branch head as closed')),
1463 1458 ('', 'amend', None, _('amend the parent of the working directory')),
1464 1459 ('s', 'secret', None, _('use the secret phase for committing')),
1465 1460 ('e', 'edit', None, _('invoke editor on commit messages')),
1466 1461 ('i', 'interactive', None, _('use interactive mode')),
1467 1462 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1468 1463 _('[OPTION]... [FILE]...'),
1469 1464 inferrepo=True)
1470 1465 def commit(ui, repo, *pats, **opts):
1471 1466 """commit the specified files or all outstanding changes
1472 1467
1473 1468 Commit changes to the given files into the repository. Unlike a
1474 1469 centralized SCM, this operation is a local operation. See
1475 1470 :hg:`push` for a way to actively distribute your changes.
1476 1471
1477 1472 If a list of files is omitted, all changes reported by :hg:`status`
1478 1473 will be committed.
1479 1474
1480 1475 If you are committing the result of a merge, do not provide any
1481 1476 filenames or -I/-X filters.
1482 1477
1483 1478 If no commit message is specified, Mercurial starts your
1484 1479 configured editor where you can enter a message. In case your
1485 1480 commit fails, you will find a backup of your message in
1486 1481 ``.hg/last-message.txt``.
1487 1482
1488 1483 The --close-branch flag can be used to mark the current branch
1489 1484 head closed. When all heads of a branch are closed, the branch
1490 1485 will be considered closed and no longer listed.
1491 1486
1492 1487 The --amend flag can be used to amend the parent of the
1493 1488 working directory with a new commit that contains the changes
1494 1489 in the parent in addition to those currently reported by :hg:`status`,
1495 1490 if there are any. The old commit is stored in a backup bundle in
1496 1491 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1497 1492 on how to restore it).
1498 1493
1499 1494 Message, user and date are taken from the amended commit unless
1500 1495 specified. When a message isn't specified on the command line,
1501 1496 the editor will open with the message of the amended commit.
1502 1497
1503 1498 It is not possible to amend public changesets (see :hg:`help phases`)
1504 1499 or changesets that have children.
1505 1500
1506 1501 See :hg:`help dates` for a list of formats valid for -d/--date.
1507 1502
1508 1503 Returns 0 on success, 1 if nothing changed.
1509 1504
1510 1505 .. container:: verbose
1511 1506
1512 1507 Examples:
1513 1508
1514 1509 - commit all files ending in .py::
1515 1510
1516 1511 hg commit --include "set:**.py"
1517 1512
1518 1513 - commit all non-binary files::
1519 1514
1520 1515 hg commit --exclude "set:binary()"
1521 1516
1522 1517 - amend the current commit and set the date to now::
1523 1518
1524 1519 hg commit --amend --date now
1525 1520 """
1526 1521 wlock = lock = None
1527 1522 try:
1528 1523 wlock = repo.wlock()
1529 1524 lock = repo.lock()
1530 1525 return _docommit(ui, repo, *pats, **opts)
1531 1526 finally:
1532 1527 release(lock, wlock)
1533 1528
1534 1529 def _docommit(ui, repo, *pats, **opts):
1535 1530 if opts.get(r'interactive'):
1536 1531 opts.pop(r'interactive')
1537 1532 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1538 1533 cmdutil.recordfilter, *pats,
1539 1534 **opts)
1540 1535 # ret can be 0 (no changes to record) or the value returned by
1541 1536 # commit(), 1 if nothing changed or None on success.
1542 1537 return 1 if ret == 0 else ret
1543 1538
1544 1539 opts = pycompat.byteskwargs(opts)
1545 1540 if opts.get('subrepos'):
1546 1541 if opts.get('amend'):
1547 1542 raise error.Abort(_('cannot amend with --subrepos'))
1548 1543 # Let --subrepos on the command line override config setting.
1549 1544 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1550 1545
1551 1546 cmdutil.checkunfinished(repo, commit=True)
1552 1547
1553 1548 branch = repo[None].branch()
1554 1549 bheads = repo.branchheads(branch)
1555 1550
1556 1551 extra = {}
1557 1552 if opts.get('close_branch'):
1558 1553 extra['close'] = 1
1559 1554
1560 1555 if not bheads:
1561 1556 raise error.Abort(_('can only close branch heads'))
1562 1557 elif opts.get('amend'):
1563 1558 if repo[None].parents()[0].p1().branch() != branch and \
1564 1559 repo[None].parents()[0].p2().branch() != branch:
1565 1560 raise error.Abort(_('can only close branch heads'))
1566 1561
1567 1562 if opts.get('amend'):
1568 1563 if ui.configbool('ui', 'commitsubrepos'):
1569 1564 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1570 1565
1571 1566 old = repo['.']
1572 1567 rewriteutil.precheck(repo, [old.rev()], 'amend')
1573 1568
1574 1569 # Currently histedit gets confused if an amend happens while histedit
1575 1570 # is in progress. Since we have a checkunfinished command, we are
1576 1571 # temporarily honoring it.
1577 1572 #
1578 1573 # Note: eventually this guard will be removed. Please do not expect
1579 1574 # this behavior to remain.
1580 1575 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1581 1576 cmdutil.checkunfinished(repo)
1582 1577
1583 1578 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1584 1579 if node == old.node():
1585 1580 ui.status(_("nothing changed\n"))
1586 1581 return 1
1587 1582 else:
1588 1583 def commitfunc(ui, repo, message, match, opts):
1589 1584 overrides = {}
1590 1585 if opts.get('secret'):
1591 1586 overrides[('phases', 'new-commit')] = 'secret'
1592 1587
1593 1588 baseui = repo.baseui
1594 1589 with baseui.configoverride(overrides, 'commit'):
1595 1590 with ui.configoverride(overrides, 'commit'):
1596 1591 editform = cmdutil.mergeeditform(repo[None],
1597 1592 'commit.normal')
1598 1593 editor = cmdutil.getcommiteditor(
1599 1594 editform=editform, **pycompat.strkwargs(opts))
1600 1595 return repo.commit(message,
1601 1596 opts.get('user'),
1602 1597 opts.get('date'),
1603 1598 match,
1604 1599 editor=editor,
1605 1600 extra=extra)
1606 1601
1607 1602 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1608 1603
1609 1604 if not node:
1610 1605 stat = cmdutil.postcommitstatus(repo, pats, opts)
1611 1606 if stat[3]:
1612 1607 ui.status(_("nothing changed (%d missing files, see "
1613 1608 "'hg status')\n") % len(stat[3]))
1614 1609 else:
1615 1610 ui.status(_("nothing changed\n"))
1616 1611 return 1
1617 1612
1618 1613 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1619 1614
1620 1615 @command('config|showconfig|debugconfig',
1621 1616 [('u', 'untrusted', None, _('show untrusted configuration options')),
1622 1617 ('e', 'edit', None, _('edit user config')),
1623 1618 ('l', 'local', None, _('edit repository config')),
1624 1619 ('g', 'global', None, _('edit global config'))] + formatteropts,
1625 1620 _('[-u] [NAME]...'),
1626 1621 optionalrepo=True, cmdtype=readonly)
1627 1622 def config(ui, repo, *values, **opts):
1628 1623 """show combined config settings from all hgrc files
1629 1624
1630 1625 With no arguments, print names and values of all config items.
1631 1626
1632 1627 With one argument of the form section.name, print just the value
1633 1628 of that config item.
1634 1629
1635 1630 With multiple arguments, print names and values of all config
1636 1631 items with matching section names.
1637 1632
1638 1633 With --edit, start an editor on the user-level config file. With
1639 1634 --global, edit the system-wide config file. With --local, edit the
1640 1635 repository-level config file.
1641 1636
1642 1637 With --debug, the source (filename and line number) is printed
1643 1638 for each config item.
1644 1639
1645 1640 See :hg:`help config` for more information about config files.
1646 1641
1647 1642 Returns 0 on success, 1 if NAME does not exist.
1648 1643
1649 1644 """
1650 1645
1651 1646 opts = pycompat.byteskwargs(opts)
1652 1647 if opts.get('edit') or opts.get('local') or opts.get('global'):
1653 1648 if opts.get('local') and opts.get('global'):
1654 1649 raise error.Abort(_("can't use --local and --global together"))
1655 1650
1656 1651 if opts.get('local'):
1657 1652 if not repo:
1658 1653 raise error.Abort(_("can't use --local outside a repository"))
1659 1654 paths = [repo.vfs.join('hgrc')]
1660 1655 elif opts.get('global'):
1661 1656 paths = rcutil.systemrcpath()
1662 1657 else:
1663 1658 paths = rcutil.userrcpath()
1664 1659
1665 1660 for f in paths:
1666 1661 if os.path.exists(f):
1667 1662 break
1668 1663 else:
1669 1664 if opts.get('global'):
1670 1665 samplehgrc = uimod.samplehgrcs['global']
1671 1666 elif opts.get('local'):
1672 1667 samplehgrc = uimod.samplehgrcs['local']
1673 1668 else:
1674 1669 samplehgrc = uimod.samplehgrcs['user']
1675 1670
1676 1671 f = paths[0]
1677 1672 fp = open(f, "wb")
1678 1673 fp.write(util.tonativeeol(samplehgrc))
1679 1674 fp.close()
1680 1675
1681 1676 editor = ui.geteditor()
1682 1677 ui.system("%s \"%s\"" % (editor, f),
1683 1678 onerr=error.Abort, errprefix=_("edit failed"),
1684 1679 blockedtag='config_edit')
1685 1680 return
1686 1681 ui.pager('config')
1687 1682 fm = ui.formatter('config', opts)
1688 1683 for t, f in rcutil.rccomponents():
1689 1684 if t == 'path':
1690 1685 ui.debug('read config from: %s\n' % f)
1691 1686 elif t == 'items':
1692 1687 for section, name, value, source in f:
1693 1688 ui.debug('set config by: %s\n' % source)
1694 1689 else:
1695 1690 raise error.ProgrammingError('unknown rctype: %s' % t)
1696 1691 untrusted = bool(opts.get('untrusted'))
1697 1692 if values:
1698 1693 sections = [v for v in values if '.' not in v]
1699 1694 items = [v for v in values if '.' in v]
1700 1695 if len(items) > 1 or items and sections:
1701 1696 raise error.Abort(_('only one config item permitted'))
1702 1697 matched = False
1703 1698 for section, name, value in ui.walkconfig(untrusted=untrusted):
1704 1699 source = ui.configsource(section, name, untrusted)
1705 1700 value = pycompat.bytestr(value)
1706 1701 if fm.isplain():
1707 1702 source = source or 'none'
1708 1703 value = value.replace('\n', '\\n')
1709 1704 entryname = section + '.' + name
1710 1705 if values:
1711 1706 for v in values:
1712 1707 if v == section:
1713 1708 fm.startitem()
1714 1709 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1715 1710 fm.write('name value', '%s=%s\n', entryname, value)
1716 1711 matched = True
1717 1712 elif v == entryname:
1718 1713 fm.startitem()
1719 1714 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1720 1715 fm.write('value', '%s\n', value)
1721 1716 fm.data(name=entryname)
1722 1717 matched = True
1723 1718 else:
1724 1719 fm.startitem()
1725 1720 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1726 1721 fm.write('name value', '%s=%s\n', entryname, value)
1727 1722 matched = True
1728 1723 fm.end()
1729 1724 if matched:
1730 1725 return 0
1731 1726 return 1
1732 1727
1733 1728 @command('copy|cp',
1734 1729 [('A', 'after', None, _('record a copy that has already occurred')),
1735 1730 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1736 1731 ] + walkopts + dryrunopts,
1737 1732 _('[OPTION]... [SOURCE]... DEST'))
1738 1733 def copy(ui, repo, *pats, **opts):
1739 1734 """mark files as copied for the next commit
1740 1735
1741 1736 Mark dest as having copies of source files. If dest is a
1742 1737 directory, copies are put in that directory. If dest is a file,
1743 1738 the source must be a single file.
1744 1739
1745 1740 By default, this command copies the contents of files as they
1746 1741 exist in the working directory. If invoked with -A/--after, the
1747 1742 operation is recorded, but no copying is performed.
1748 1743
1749 1744 This command takes effect with the next commit. To undo a copy
1750 1745 before that, see :hg:`revert`.
1751 1746
1752 1747 Returns 0 on success, 1 if errors are encountered.
1753 1748 """
1754 1749 opts = pycompat.byteskwargs(opts)
1755 1750 with repo.wlock(False):
1756 1751 return cmdutil.copy(ui, repo, pats, opts)
1757 1752
1758 1753 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1759 1754 def debugcommands(ui, cmd='', *args):
1760 1755 """list all available commands and options"""
1761 1756 for cmd, vals in sorted(table.iteritems()):
1762 1757 cmd = cmd.split('|')[0].strip('^')
1763 1758 opts = ', '.join([i[1] for i in vals[1]])
1764 1759 ui.write('%s: %s\n' % (cmd, opts))
1765 1760
1766 1761 @command('debugcomplete',
1767 1762 [('o', 'options', None, _('show the command options'))],
1768 1763 _('[-o] CMD'),
1769 1764 norepo=True)
1770 1765 def debugcomplete(ui, cmd='', **opts):
1771 1766 """returns the completion list associated with the given command"""
1772 1767
1773 1768 if opts.get(r'options'):
1774 1769 options = []
1775 1770 otables = [globalopts]
1776 1771 if cmd:
1777 1772 aliases, entry = cmdutil.findcmd(cmd, table, False)
1778 1773 otables.append(entry[1])
1779 1774 for t in otables:
1780 1775 for o in t:
1781 1776 if "(DEPRECATED)" in o[3]:
1782 1777 continue
1783 1778 if o[0]:
1784 1779 options.append('-%s' % o[0])
1785 1780 options.append('--%s' % o[1])
1786 1781 ui.write("%s\n" % "\n".join(options))
1787 1782 return
1788 1783
1789 1784 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1790 1785 if ui.verbose:
1791 1786 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1792 1787 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1793 1788
1794 1789 @command('^diff',
1795 1790 [('r', 'rev', [], _('revision'), _('REV')),
1796 1791 ('c', 'change', '', _('change made by revision'), _('REV'))
1797 1792 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1798 1793 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1799 1794 inferrepo=True, cmdtype=readonly)
1800 1795 def diff(ui, repo, *pats, **opts):
1801 1796 """diff repository (or selected files)
1802 1797
1803 1798 Show differences between revisions for the specified files.
1804 1799
1805 1800 Differences between files are shown using the unified diff format.
1806 1801
1807 1802 .. note::
1808 1803
1809 1804 :hg:`diff` may generate unexpected results for merges, as it will
1810 1805 default to comparing against the working directory's first
1811 1806 parent changeset if no revisions are specified.
1812 1807
1813 1808 When two revision arguments are given, then changes are shown
1814 1809 between those revisions. If only one revision is specified then
1815 1810 that revision is compared to the working directory, and, when no
1816 1811 revisions are specified, the working directory files are compared
1817 1812 to its first parent.
1818 1813
1819 1814 Alternatively you can specify -c/--change with a revision to see
1820 1815 the changes in that changeset relative to its first parent.
1821 1816
1822 1817 Without the -a/--text option, diff will avoid generating diffs of
1823 1818 files it detects as binary. With -a, diff will generate a diff
1824 1819 anyway, probably with undesirable results.
1825 1820
1826 1821 Use the -g/--git option to generate diffs in the git extended diff
1827 1822 format. For more information, read :hg:`help diffs`.
1828 1823
1829 1824 .. container:: verbose
1830 1825
1831 1826 Examples:
1832 1827
1833 1828 - compare a file in the current working directory to its parent::
1834 1829
1835 1830 hg diff foo.c
1836 1831
1837 1832 - compare two historical versions of a directory, with rename info::
1838 1833
1839 1834 hg diff --git -r 1.0:1.2 lib/
1840 1835
1841 1836 - get change stats relative to the last change on some date::
1842 1837
1843 1838 hg diff --stat -r "date('may 2')"
1844 1839
1845 1840 - diff all newly-added files that contain a keyword::
1846 1841
1847 1842 hg diff "set:added() and grep(GNU)"
1848 1843
1849 1844 - compare a revision and its parents::
1850 1845
1851 1846 hg diff -c 9353 # compare against first parent
1852 1847 hg diff -r 9353^:9353 # same using revset syntax
1853 1848 hg diff -r 9353^2:9353 # compare against the second parent
1854 1849
1855 1850 Returns 0 on success.
1856 1851 """
1857 1852
1858 1853 opts = pycompat.byteskwargs(opts)
1859 1854 revs = opts.get('rev')
1860 1855 change = opts.get('change')
1861 1856 stat = opts.get('stat')
1862 1857 reverse = opts.get('reverse')
1863 1858
1864 1859 if revs and change:
1865 1860 msg = _('cannot specify --rev and --change at the same time')
1866 1861 raise error.Abort(msg)
1867 1862 elif change:
1868 1863 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1869 1864 node2 = scmutil.revsingle(repo, change, None).node()
1870 1865 node1 = repo[node2].p1().node()
1871 1866 else:
1872 1867 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1873 1868 node1, node2 = scmutil.revpair(repo, revs)
1874 1869
1875 1870 if reverse:
1876 1871 node1, node2 = node2, node1
1877 1872
1878 1873 diffopts = patch.diffallopts(ui, opts)
1879 1874 m = scmutil.match(repo[node2], pats, opts)
1880 1875 ui.pager('diff')
1881 1876 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1882 1877 listsubrepos=opts.get('subrepos'),
1883 1878 root=opts.get('root'))
1884 1879
1885 1880 @command('^export',
1886 1881 [('o', 'output', '',
1887 1882 _('print output to file with formatted name'), _('FORMAT')),
1888 1883 ('', 'switch-parent', None, _('diff against the second parent')),
1889 1884 ('r', 'rev', [], _('revisions to export'), _('REV')),
1890 1885 ] + diffopts,
1891 1886 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1892 1887 def export(ui, repo, *changesets, **opts):
1893 1888 """dump the header and diffs for one or more changesets
1894 1889
1895 1890 Print the changeset header and diffs for one or more revisions.
1896 1891 If no revision is given, the parent of the working directory is used.
1897 1892
1898 1893 The information shown in the changeset header is: author, date,
1899 1894 branch name (if non-default), changeset hash, parent(s) and commit
1900 1895 comment.
1901 1896
1902 1897 .. note::
1903 1898
1904 1899 :hg:`export` may generate unexpected diff output for merge
1905 1900 changesets, as it will compare the merge changeset against its
1906 1901 first parent only.
1907 1902
1908 1903 Output may be to a file, in which case the name of the file is
1909 1904 given using a format string. The formatting rules are as follows:
1910 1905
1911 1906 :``%%``: literal "%" character
1912 1907 :``%H``: changeset hash (40 hexadecimal digits)
1913 1908 :``%N``: number of patches being generated
1914 1909 :``%R``: changeset revision number
1915 1910 :``%b``: basename of the exporting repository
1916 1911 :``%h``: short-form changeset hash (12 hexadecimal digits)
1917 1912 :``%m``: first line of the commit message (only alphanumeric characters)
1918 1913 :``%n``: zero-padded sequence number, starting at 1
1919 1914 :``%r``: zero-padded changeset revision number
1920 1915
1921 1916 Without the -a/--text option, export will avoid generating diffs
1922 1917 of files it detects as binary. With -a, export will generate a
1923 1918 diff anyway, probably with undesirable results.
1924 1919
1925 1920 Use the -g/--git option to generate diffs in the git extended diff
1926 1921 format. See :hg:`help diffs` for more information.
1927 1922
1928 1923 With the --switch-parent option, the diff will be against the
1929 1924 second parent. It can be useful to review a merge.
1930 1925
1931 1926 .. container:: verbose
1932 1927
1933 1928 Examples:
1934 1929
1935 1930 - use export and import to transplant a bugfix to the current
1936 1931 branch::
1937 1932
1938 1933 hg export -r 9353 | hg import -
1939 1934
1940 1935 - export all the changesets between two revisions to a file with
1941 1936 rename information::
1942 1937
1943 1938 hg export --git -r 123:150 > changes.txt
1944 1939
1945 1940 - split outgoing changes into a series of patches with
1946 1941 descriptive names::
1947 1942
1948 1943 hg export -r "outgoing()" -o "%n-%m.patch"
1949 1944
1950 1945 Returns 0 on success.
1951 1946 """
1952 1947 opts = pycompat.byteskwargs(opts)
1953 1948 changesets += tuple(opts.get('rev', []))
1954 1949 if not changesets:
1955 1950 changesets = ['.']
1956 1951 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1957 1952 revs = scmutil.revrange(repo, changesets)
1958 1953 if not revs:
1959 1954 raise error.Abort(_("export requires at least one changeset"))
1960 1955 if len(revs) > 1:
1961 1956 ui.note(_('exporting patches:\n'))
1962 1957 else:
1963 1958 ui.note(_('exporting patch:\n'))
1964 1959 ui.pager('export')
1965 1960 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1966 1961 switch_parent=opts.get('switch_parent'),
1967 1962 opts=patch.diffallopts(ui, opts))
1968 1963
1969 1964 @command('files',
1970 1965 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1971 1966 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1972 1967 ] + walkopts + formatteropts + subrepoopts,
1973 1968 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1974 1969 def files(ui, repo, *pats, **opts):
1975 1970 """list tracked files
1976 1971
1977 1972 Print files under Mercurial control in the working directory or
1978 1973 specified revision for given files (excluding removed files).
1979 1974 Files can be specified as filenames or filesets.
1980 1975
1981 1976 If no files are given to match, this command prints the names
1982 1977 of all files under Mercurial control.
1983 1978
1984 1979 .. container:: verbose
1985 1980
1986 1981 Examples:
1987 1982
1988 1983 - list all files under the current directory::
1989 1984
1990 1985 hg files .
1991 1986
1992 1987 - shows sizes and flags for current revision::
1993 1988
1994 1989 hg files -vr .
1995 1990
1996 1991 - list all files named README::
1997 1992
1998 1993 hg files -I "**/README"
1999 1994
2000 1995 - list all binary files::
2001 1996
2002 1997 hg files "set:binary()"
2003 1998
2004 1999 - find files containing a regular expression::
2005 2000
2006 2001 hg files "set:grep('bob')"
2007 2002
2008 2003 - search tracked file contents with xargs and grep::
2009 2004
2010 2005 hg files -0 | xargs -0 grep foo
2011 2006
2012 2007 See :hg:`help patterns` and :hg:`help filesets` for more information
2013 2008 on specifying file patterns.
2014 2009
2015 2010 Returns 0 if a match is found, 1 otherwise.
2016 2011
2017 2012 """
2018 2013
2019 2014 opts = pycompat.byteskwargs(opts)
2020 2015 rev = opts.get('rev')
2021 2016 if rev:
2022 2017 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2023 2018 ctx = scmutil.revsingle(repo, rev, None)
2024 2019
2025 2020 end = '\n'
2026 2021 if opts.get('print0'):
2027 2022 end = '\0'
2028 2023 fmt = '%s' + end
2029 2024
2030 2025 m = scmutil.match(ctx, pats, opts)
2031 2026 ui.pager('files')
2032 2027 with ui.formatter('files', opts) as fm:
2033 2028 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2034 2029
2035 2030 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2036 2031 def forget(ui, repo, *pats, **opts):
2037 2032 """forget the specified files on the next commit
2038 2033
2039 2034 Mark the specified files so they will no longer be tracked
2040 2035 after the next commit.
2041 2036
2042 2037 This only removes files from the current branch, not from the
2043 2038 entire project history, and it does not delete them from the
2044 2039 working directory.
2045 2040
2046 2041 To delete the file from the working directory, see :hg:`remove`.
2047 2042
2048 2043 To undo a forget before the next commit, see :hg:`add`.
2049 2044
2050 2045 .. container:: verbose
2051 2046
2052 2047 Examples:
2053 2048
2054 2049 - forget newly-added binary files::
2055 2050
2056 2051 hg forget "set:added() and binary()"
2057 2052
2058 2053 - forget files that would be excluded by .hgignore::
2059 2054
2060 2055 hg forget "set:hgignore()"
2061 2056
2062 2057 Returns 0 on success.
2063 2058 """
2064 2059
2065 2060 opts = pycompat.byteskwargs(opts)
2066 2061 if not pats:
2067 2062 raise error.Abort(_('no files specified'))
2068 2063
2069 2064 m = scmutil.match(repo[None], pats, opts)
2070 2065 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2071 2066 return rejected and 1 or 0
2072 2067
2073 2068 @command(
2074 2069 'graft',
2075 2070 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2076 2071 ('c', 'continue', False, _('resume interrupted graft')),
2077 2072 ('e', 'edit', False, _('invoke editor on commit messages')),
2078 2073 ('', 'log', None, _('append graft info to log message')),
2079 2074 ('f', 'force', False, _('force graft')),
2080 2075 ('D', 'currentdate', False,
2081 2076 _('record the current date as commit date')),
2082 2077 ('U', 'currentuser', False,
2083 2078 _('record the current user as committer'), _('DATE'))]
2084 2079 + commitopts2 + mergetoolopts + dryrunopts,
2085 2080 _('[OPTION]... [-r REV]... REV...'))
2086 2081 def graft(ui, repo, *revs, **opts):
2087 2082 '''copy changes from other branches onto the current branch
2088 2083
2089 2084 This command uses Mercurial's merge logic to copy individual
2090 2085 changes from other branches without merging branches in the
2091 2086 history graph. This is sometimes known as 'backporting' or
2092 2087 'cherry-picking'. By default, graft will copy user, date, and
2093 2088 description from the source changesets.
2094 2089
2095 2090 Changesets that are ancestors of the current revision, that have
2096 2091 already been grafted, or that are merges will be skipped.
2097 2092
2098 2093 If --log is specified, log messages will have a comment appended
2099 2094 of the form::
2100 2095
2101 2096 (grafted from CHANGESETHASH)
2102 2097
2103 2098 If --force is specified, revisions will be grafted even if they
2104 2099 are already ancestors of, or have been grafted to, the destination.
2105 2100 This is useful when the revisions have since been backed out.
2106 2101
2107 2102 If a graft merge results in conflicts, the graft process is
2108 2103 interrupted so that the current merge can be manually resolved.
2109 2104 Once all conflicts are addressed, the graft process can be
2110 2105 continued with the -c/--continue option.
2111 2106
2112 2107 .. note::
2113 2108
2114 2109 The -c/--continue option does not reapply earlier options, except
2115 2110 for --force.
2116 2111
2117 2112 .. container:: verbose
2118 2113
2119 2114 Examples:
2120 2115
2121 2116 - copy a single change to the stable branch and edit its description::
2122 2117
2123 2118 hg update stable
2124 2119 hg graft --edit 9393
2125 2120
2126 2121 - graft a range of changesets with one exception, updating dates::
2127 2122
2128 2123 hg graft -D "2085::2093 and not 2091"
2129 2124
2130 2125 - continue a graft after resolving conflicts::
2131 2126
2132 2127 hg graft -c
2133 2128
2134 2129 - show the source of a grafted changeset::
2135 2130
2136 2131 hg log --debug -r .
2137 2132
2138 2133 - show revisions sorted by date::
2139 2134
2140 2135 hg log -r "sort(all(), date)"
2141 2136
2142 2137 See :hg:`help revisions` for more about specifying revisions.
2143 2138
2144 2139 Returns 0 on successful completion.
2145 2140 '''
2146 2141 with repo.wlock():
2147 2142 return _dograft(ui, repo, *revs, **opts)
2148 2143
2149 2144 def _dograft(ui, repo, *revs, **opts):
2150 2145 opts = pycompat.byteskwargs(opts)
2151 2146 if revs and opts.get('rev'):
2152 2147 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2153 2148 'revision ordering!\n'))
2154 2149
2155 2150 revs = list(revs)
2156 2151 revs.extend(opts.get('rev'))
2157 2152
2158 2153 if not opts.get('user') and opts.get('currentuser'):
2159 2154 opts['user'] = ui.username()
2160 2155 if not opts.get('date') and opts.get('currentdate'):
2161 2156 opts['date'] = "%d %d" % util.makedate()
2162 2157
2163 2158 editor = cmdutil.getcommiteditor(editform='graft',
2164 2159 **pycompat.strkwargs(opts))
2165 2160
2166 2161 cont = False
2167 2162 if opts.get('continue'):
2168 2163 cont = True
2169 2164 if revs:
2170 2165 raise error.Abort(_("can't specify --continue and revisions"))
2171 2166 # read in unfinished revisions
2172 2167 try:
2173 2168 nodes = repo.vfs.read('graftstate').splitlines()
2174 2169 revs = [repo[node].rev() for node in nodes]
2175 2170 except IOError as inst:
2176 2171 if inst.errno != errno.ENOENT:
2177 2172 raise
2178 2173 cmdutil.wrongtooltocontinue(repo, _('graft'))
2179 2174 else:
2180 2175 cmdutil.checkunfinished(repo)
2181 2176 cmdutil.bailifchanged(repo)
2182 2177 if not revs:
2183 2178 raise error.Abort(_('no revisions specified'))
2184 2179 revs = scmutil.revrange(repo, revs)
2185 2180
2186 2181 skipped = set()
2187 2182 # check for merges
2188 2183 for rev in repo.revs('%ld and merge()', revs):
2189 2184 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2190 2185 skipped.add(rev)
2191 2186 revs = [r for r in revs if r not in skipped]
2192 2187 if not revs:
2193 2188 return -1
2194 2189
2195 2190 # Don't check in the --continue case, in effect retaining --force across
2196 2191 # --continues. That's because without --force, any revisions we decided to
2197 2192 # skip would have been filtered out here, so they wouldn't have made their
2198 2193 # way to the graftstate. With --force, any revisions we would have otherwise
2199 2194 # skipped would not have been filtered out, and if they hadn't been applied
2200 2195 # already, they'd have been in the graftstate.
2201 2196 if not (cont or opts.get('force')):
2202 2197 # check for ancestors of dest branch
2203 2198 crev = repo['.'].rev()
2204 2199 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2205 2200 # XXX make this lazy in the future
2206 2201 # don't mutate while iterating, create a copy
2207 2202 for rev in list(revs):
2208 2203 if rev in ancestors:
2209 2204 ui.warn(_('skipping ancestor revision %d:%s\n') %
2210 2205 (rev, repo[rev]))
2211 2206 # XXX remove on list is slow
2212 2207 revs.remove(rev)
2213 2208 if not revs:
2214 2209 return -1
2215 2210
2216 2211 # analyze revs for earlier grafts
2217 2212 ids = {}
2218 2213 for ctx in repo.set("%ld", revs):
2219 2214 ids[ctx.hex()] = ctx.rev()
2220 2215 n = ctx.extra().get('source')
2221 2216 if n:
2222 2217 ids[n] = ctx.rev()
2223 2218
2224 2219 # check ancestors for earlier grafts
2225 2220 ui.debug('scanning for duplicate grafts\n')
2226 2221
2227 2222 # The only changesets we can be sure doesn't contain grafts of any
2228 2223 # revs, are the ones that are common ancestors of *all* revs:
2229 2224 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2230 2225 ctx = repo[rev]
2231 2226 n = ctx.extra().get('source')
2232 2227 if n in ids:
2233 2228 try:
2234 2229 r = repo[n].rev()
2235 2230 except error.RepoLookupError:
2236 2231 r = None
2237 2232 if r in revs:
2238 2233 ui.warn(_('skipping revision %d:%s '
2239 2234 '(already grafted to %d:%s)\n')
2240 2235 % (r, repo[r], rev, ctx))
2241 2236 revs.remove(r)
2242 2237 elif ids[n] in revs:
2243 2238 if r is None:
2244 2239 ui.warn(_('skipping already grafted revision %d:%s '
2245 2240 '(%d:%s also has unknown origin %s)\n')
2246 2241 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2247 2242 else:
2248 2243 ui.warn(_('skipping already grafted revision %d:%s '
2249 2244 '(%d:%s also has origin %d:%s)\n')
2250 2245 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2251 2246 revs.remove(ids[n])
2252 2247 elif ctx.hex() in ids:
2253 2248 r = ids[ctx.hex()]
2254 2249 ui.warn(_('skipping already grafted revision %d:%s '
2255 2250 '(was grafted from %d:%s)\n') %
2256 2251 (r, repo[r], rev, ctx))
2257 2252 revs.remove(r)
2258 2253 if not revs:
2259 2254 return -1
2260 2255
2261 2256 for pos, ctx in enumerate(repo.set("%ld", revs)):
2262 2257 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2263 2258 ctx.description().split('\n', 1)[0])
2264 2259 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2265 2260 if names:
2266 2261 desc += ' (%s)' % ' '.join(names)
2267 2262 ui.status(_('grafting %s\n') % desc)
2268 2263 if opts.get('dry_run'):
2269 2264 continue
2270 2265
2271 2266 source = ctx.extra().get('source')
2272 2267 extra = {}
2273 2268 if source:
2274 2269 extra['source'] = source
2275 2270 extra['intermediate-source'] = ctx.hex()
2276 2271 else:
2277 2272 extra['source'] = ctx.hex()
2278 2273 user = ctx.user()
2279 2274 if opts.get('user'):
2280 2275 user = opts['user']
2281 2276 date = ctx.date()
2282 2277 if opts.get('date'):
2283 2278 date = opts['date']
2284 2279 message = ctx.description()
2285 2280 if opts.get('log'):
2286 2281 message += '\n(grafted from %s)' % ctx.hex()
2287 2282
2288 2283 # we don't merge the first commit when continuing
2289 2284 if not cont:
2290 2285 # perform the graft merge with p1(rev) as 'ancestor'
2291 2286 try:
2292 2287 # ui.forcemerge is an internal variable, do not document
2293 2288 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2294 2289 'graft')
2295 2290 stats = mergemod.graft(repo, ctx, ctx.p1(),
2296 2291 ['local', 'graft'])
2297 2292 finally:
2298 2293 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2299 2294 # report any conflicts
2300 2295 if stats and stats[3] > 0:
2301 2296 # write out state for --continue
2302 2297 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2303 2298 repo.vfs.write('graftstate', ''.join(nodelines))
2304 2299 extra = ''
2305 2300 if opts.get('user'):
2306 2301 extra += ' --user %s' % util.shellquote(opts['user'])
2307 2302 if opts.get('date'):
2308 2303 extra += ' --date %s' % util.shellquote(opts['date'])
2309 2304 if opts.get('log'):
2310 2305 extra += ' --log'
2311 2306 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2312 2307 raise error.Abort(
2313 2308 _("unresolved conflicts, can't continue"),
2314 2309 hint=hint)
2315 2310 else:
2316 2311 cont = False
2317 2312
2318 2313 # commit
2319 2314 node = repo.commit(text=message, user=user,
2320 2315 date=date, extra=extra, editor=editor)
2321 2316 if node is None:
2322 2317 ui.warn(
2323 2318 _('note: graft of %d:%s created no changes to commit\n') %
2324 2319 (ctx.rev(), ctx))
2325 2320
2326 2321 # remove state when we complete successfully
2327 2322 if not opts.get('dry_run'):
2328 2323 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2329 2324
2330 2325 return 0
2331 2326
2332 2327 @command('grep',
2333 2328 [('0', 'print0', None, _('end fields with NUL')),
2334 2329 ('', 'all', None, _('print all revisions that match')),
2335 2330 ('a', 'text', None, _('treat all files as text')),
2336 2331 ('f', 'follow', None,
2337 2332 _('follow changeset history,'
2338 2333 ' or file history across copies and renames')),
2339 2334 ('i', 'ignore-case', None, _('ignore case when matching')),
2340 2335 ('l', 'files-with-matches', None,
2341 2336 _('print only filenames and revisions that match')),
2342 2337 ('n', 'line-number', None, _('print matching line numbers')),
2343 2338 ('r', 'rev', [],
2344 2339 _('only search files changed within revision range'), _('REV')),
2345 2340 ('u', 'user', None, _('list the author (long with -v)')),
2346 2341 ('d', 'date', None, _('list the date (short with -q)')),
2347 2342 ] + formatteropts + walkopts,
2348 2343 _('[OPTION]... PATTERN [FILE]...'),
2349 2344 inferrepo=True, cmdtype=readonly)
2350 2345 def grep(ui, repo, pattern, *pats, **opts):
2351 2346 """search revision history for a pattern in specified files
2352 2347
2353 2348 Search revision history for a regular expression in the specified
2354 2349 files or the entire project.
2355 2350
2356 2351 By default, grep prints the most recent revision number for each
2357 2352 file in which it finds a match. To get it to print every revision
2358 2353 that contains a change in match status ("-" for a match that becomes
2359 2354 a non-match, or "+" for a non-match that becomes a match), use the
2360 2355 --all flag.
2361 2356
2362 2357 PATTERN can be any Python (roughly Perl-compatible) regular
2363 2358 expression.
2364 2359
2365 2360 If no FILEs are specified (and -f/--follow isn't set), all files in
2366 2361 the repository are searched, including those that don't exist in the
2367 2362 current branch or have been deleted in a prior changeset.
2368 2363
2369 2364 Returns 0 if a match is found, 1 otherwise.
2370 2365 """
2371 2366 opts = pycompat.byteskwargs(opts)
2372 2367 reflags = re.M
2373 2368 if opts.get('ignore_case'):
2374 2369 reflags |= re.I
2375 2370 try:
2376 2371 regexp = util.re.compile(pattern, reflags)
2377 2372 except re.error as inst:
2378 2373 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2379 2374 return 1
2380 2375 sep, eol = ':', '\n'
2381 2376 if opts.get('print0'):
2382 2377 sep = eol = '\0'
2383 2378
2384 2379 getfile = util.lrucachefunc(repo.file)
2385 2380
2386 2381 def matchlines(body):
2387 2382 begin = 0
2388 2383 linenum = 0
2389 2384 while begin < len(body):
2390 2385 match = regexp.search(body, begin)
2391 2386 if not match:
2392 2387 break
2393 2388 mstart, mend = match.span()
2394 2389 linenum += body.count('\n', begin, mstart) + 1
2395 2390 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2396 2391 begin = body.find('\n', mend) + 1 or len(body) + 1
2397 2392 lend = begin - 1
2398 2393 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2399 2394
2400 2395 class linestate(object):
2401 2396 def __init__(self, line, linenum, colstart, colend):
2402 2397 self.line = line
2403 2398 self.linenum = linenum
2404 2399 self.colstart = colstart
2405 2400 self.colend = colend
2406 2401
2407 2402 def __hash__(self):
2408 2403 return hash((self.linenum, self.line))
2409 2404
2410 2405 def __eq__(self, other):
2411 2406 return self.line == other.line
2412 2407
2413 2408 def findpos(self):
2414 2409 """Iterate all (start, end) indices of matches"""
2415 2410 yield self.colstart, self.colend
2416 2411 p = self.colend
2417 2412 while p < len(self.line):
2418 2413 m = regexp.search(self.line, p)
2419 2414 if not m:
2420 2415 break
2421 2416 yield m.span()
2422 2417 p = m.end()
2423 2418
2424 2419 matches = {}
2425 2420 copies = {}
2426 2421 def grepbody(fn, rev, body):
2427 2422 matches[rev].setdefault(fn, [])
2428 2423 m = matches[rev][fn]
2429 2424 for lnum, cstart, cend, line in matchlines(body):
2430 2425 s = linestate(line, lnum, cstart, cend)
2431 2426 m.append(s)
2432 2427
2433 2428 def difflinestates(a, b):
2434 2429 sm = difflib.SequenceMatcher(None, a, b)
2435 2430 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2436 2431 if tag == 'insert':
2437 2432 for i in xrange(blo, bhi):
2438 2433 yield ('+', b[i])
2439 2434 elif tag == 'delete':
2440 2435 for i in xrange(alo, ahi):
2441 2436 yield ('-', a[i])
2442 2437 elif tag == 'replace':
2443 2438 for i in xrange(alo, ahi):
2444 2439 yield ('-', a[i])
2445 2440 for i in xrange(blo, bhi):
2446 2441 yield ('+', b[i])
2447 2442
2448 2443 def display(fm, fn, ctx, pstates, states):
2449 2444 rev = ctx.rev()
2450 2445 if fm.isplain():
2451 2446 formatuser = ui.shortuser
2452 2447 else:
2453 2448 formatuser = str
2454 2449 if ui.quiet:
2455 2450 datefmt = '%Y-%m-%d'
2456 2451 else:
2457 2452 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2458 2453 found = False
2459 2454 @util.cachefunc
2460 2455 def binary():
2461 2456 flog = getfile(fn)
2462 2457 return util.binary(flog.read(ctx.filenode(fn)))
2463 2458
2464 2459 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2465 2460 if opts.get('all'):
2466 2461 iter = difflinestates(pstates, states)
2467 2462 else:
2468 2463 iter = [('', l) for l in states]
2469 2464 for change, l in iter:
2470 2465 fm.startitem()
2471 2466 fm.data(node=fm.hexfunc(ctx.node()))
2472 2467 cols = [
2473 2468 ('filename', fn, True),
2474 2469 ('rev', rev, True),
2475 2470 ('linenumber', l.linenum, opts.get('line_number')),
2476 2471 ]
2477 2472 if opts.get('all'):
2478 2473 cols.append(('change', change, True))
2479 2474 cols.extend([
2480 2475 ('user', formatuser(ctx.user()), opts.get('user')),
2481 2476 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2482 2477 ])
2483 2478 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2484 2479 for name, data, cond in cols:
2485 2480 field = fieldnamemap.get(name, name)
2486 2481 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2487 2482 if cond and name != lastcol:
2488 2483 fm.plain(sep, label='grep.sep')
2489 2484 if not opts.get('files_with_matches'):
2490 2485 fm.plain(sep, label='grep.sep')
2491 2486 if not opts.get('text') and binary():
2492 2487 fm.plain(_(" Binary file matches"))
2493 2488 else:
2494 2489 displaymatches(fm.nested('texts'), l)
2495 2490 fm.plain(eol)
2496 2491 found = True
2497 2492 if opts.get('files_with_matches'):
2498 2493 break
2499 2494 return found
2500 2495
2501 2496 def displaymatches(fm, l):
2502 2497 p = 0
2503 2498 for s, e in l.findpos():
2504 2499 if p < s:
2505 2500 fm.startitem()
2506 2501 fm.write('text', '%s', l.line[p:s])
2507 2502 fm.data(matched=False)
2508 2503 fm.startitem()
2509 2504 fm.write('text', '%s', l.line[s:e], label='grep.match')
2510 2505 fm.data(matched=True)
2511 2506 p = e
2512 2507 if p < len(l.line):
2513 2508 fm.startitem()
2514 2509 fm.write('text', '%s', l.line[p:])
2515 2510 fm.data(matched=False)
2516 2511 fm.end()
2517 2512
2518 2513 skip = {}
2519 2514 revfiles = {}
2520 2515 match = scmutil.match(repo[None], pats, opts)
2521 2516 found = False
2522 2517 follow = opts.get('follow')
2523 2518
2524 2519 def prep(ctx, fns):
2525 2520 rev = ctx.rev()
2526 2521 pctx = ctx.p1()
2527 2522 parent = pctx.rev()
2528 2523 matches.setdefault(rev, {})
2529 2524 matches.setdefault(parent, {})
2530 2525 files = revfiles.setdefault(rev, [])
2531 2526 for fn in fns:
2532 2527 flog = getfile(fn)
2533 2528 try:
2534 2529 fnode = ctx.filenode(fn)
2535 2530 except error.LookupError:
2536 2531 continue
2537 2532
2538 2533 copied = flog.renamed(fnode)
2539 2534 copy = follow and copied and copied[0]
2540 2535 if copy:
2541 2536 copies.setdefault(rev, {})[fn] = copy
2542 2537 if fn in skip:
2543 2538 if copy:
2544 2539 skip[copy] = True
2545 2540 continue
2546 2541 files.append(fn)
2547 2542
2548 2543 if fn not in matches[rev]:
2549 2544 grepbody(fn, rev, flog.read(fnode))
2550 2545
2551 2546 pfn = copy or fn
2552 2547 if pfn not in matches[parent]:
2553 2548 try:
2554 2549 fnode = pctx.filenode(pfn)
2555 2550 grepbody(pfn, parent, flog.read(fnode))
2556 2551 except error.LookupError:
2557 2552 pass
2558 2553
2559 2554 ui.pager('grep')
2560 2555 fm = ui.formatter('grep', opts)
2561 2556 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2562 2557 rev = ctx.rev()
2563 2558 parent = ctx.p1().rev()
2564 2559 for fn in sorted(revfiles.get(rev, [])):
2565 2560 states = matches[rev][fn]
2566 2561 copy = copies.get(rev, {}).get(fn)
2567 2562 if fn in skip:
2568 2563 if copy:
2569 2564 skip[copy] = True
2570 2565 continue
2571 2566 pstates = matches.get(parent, {}).get(copy or fn, [])
2572 2567 if pstates or states:
2573 2568 r = display(fm, fn, ctx, pstates, states)
2574 2569 found = found or r
2575 2570 if r and not opts.get('all'):
2576 2571 skip[fn] = True
2577 2572 if copy:
2578 2573 skip[copy] = True
2579 2574 del matches[rev]
2580 2575 del revfiles[rev]
2581 2576 fm.end()
2582 2577
2583 2578 return not found
2584 2579
2585 2580 @command('heads',
2586 2581 [('r', 'rev', '',
2587 2582 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2588 2583 ('t', 'topo', False, _('show topological heads only')),
2589 2584 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2590 2585 ('c', 'closed', False, _('show normal and closed branch heads')),
2591 2586 ] + templateopts,
2592 2587 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2593 2588 def heads(ui, repo, *branchrevs, **opts):
2594 2589 """show branch heads
2595 2590
2596 2591 With no arguments, show all open branch heads in the repository.
2597 2592 Branch heads are changesets that have no descendants on the
2598 2593 same branch. They are where development generally takes place and
2599 2594 are the usual targets for update and merge operations.
2600 2595
2601 2596 If one or more REVs are given, only open branch heads on the
2602 2597 branches associated with the specified changesets are shown. This
2603 2598 means that you can use :hg:`heads .` to see the heads on the
2604 2599 currently checked-out branch.
2605 2600
2606 2601 If -c/--closed is specified, also show branch heads marked closed
2607 2602 (see :hg:`commit --close-branch`).
2608 2603
2609 2604 If STARTREV is specified, only those heads that are descendants of
2610 2605 STARTREV will be displayed.
2611 2606
2612 2607 If -t/--topo is specified, named branch mechanics will be ignored and only
2613 2608 topological heads (changesets with no children) will be shown.
2614 2609
2615 2610 Returns 0 if matching heads are found, 1 if not.
2616 2611 """
2617 2612
2618 2613 opts = pycompat.byteskwargs(opts)
2619 2614 start = None
2620 2615 rev = opts.get('rev')
2621 2616 if rev:
2622 2617 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2623 2618 start = scmutil.revsingle(repo, rev, None).node()
2624 2619
2625 2620 if opts.get('topo'):
2626 2621 heads = [repo[h] for h in repo.heads(start)]
2627 2622 else:
2628 2623 heads = []
2629 2624 for branch in repo.branchmap():
2630 2625 heads += repo.branchheads(branch, start, opts.get('closed'))
2631 2626 heads = [repo[h] for h in heads]
2632 2627
2633 2628 if branchrevs:
2634 2629 branches = set(repo[br].branch() for br in branchrevs)
2635 2630 heads = [h for h in heads if h.branch() in branches]
2636 2631
2637 2632 if opts.get('active') and branchrevs:
2638 2633 dagheads = repo.heads(start)
2639 2634 heads = [h for h in heads if h.node() in dagheads]
2640 2635
2641 2636 if branchrevs:
2642 2637 haveheads = set(h.branch() for h in heads)
2643 2638 if branches - haveheads:
2644 2639 headless = ', '.join(b for b in branches - haveheads)
2645 2640 msg = _('no open branch heads found on branches %s')
2646 2641 if opts.get('rev'):
2647 2642 msg += _(' (started at %s)') % opts['rev']
2648 2643 ui.warn((msg + '\n') % headless)
2649 2644
2650 2645 if not heads:
2651 2646 return 1
2652 2647
2653 2648 ui.pager('heads')
2654 2649 heads = sorted(heads, key=lambda x: -x.rev())
2655 2650 displayer = cmdutil.show_changeset(ui, repo, opts)
2656 2651 for ctx in heads:
2657 2652 displayer.show(ctx)
2658 2653 displayer.close()
2659 2654
2660 2655 @command('help',
2661 2656 [('e', 'extension', None, _('show only help for extensions')),
2662 2657 ('c', 'command', None, _('show only help for commands')),
2663 2658 ('k', 'keyword', None, _('show topics matching keyword')),
2664 2659 ('s', 'system', [], _('show help for specific platform(s)')),
2665 2660 ],
2666 2661 _('[-ecks] [TOPIC]'),
2667 2662 norepo=True, cmdtype=readonly)
2668 2663 def help_(ui, name=None, **opts):
2669 2664 """show help for a given topic or a help overview
2670 2665
2671 2666 With no arguments, print a list of commands with short help messages.
2672 2667
2673 2668 Given a topic, extension, or command name, print help for that
2674 2669 topic.
2675 2670
2676 2671 Returns 0 if successful.
2677 2672 """
2678 2673
2679 2674 keep = opts.get(r'system') or []
2680 2675 if len(keep) == 0:
2681 2676 if pycompat.sysplatform.startswith('win'):
2682 2677 keep.append('windows')
2683 2678 elif pycompat.sysplatform == 'OpenVMS':
2684 2679 keep.append('vms')
2685 2680 elif pycompat.sysplatform == 'plan9':
2686 2681 keep.append('plan9')
2687 2682 else:
2688 2683 keep.append('unix')
2689 2684 keep.append(pycompat.sysplatform.lower())
2690 2685 if ui.verbose:
2691 2686 keep.append('verbose')
2692 2687
2693 2688 commands = sys.modules[__name__]
2694 2689 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2695 2690 ui.pager('help')
2696 2691 ui.write(formatted)
2697 2692
2698 2693
2699 2694 @command('identify|id',
2700 2695 [('r', 'rev', '',
2701 2696 _('identify the specified revision'), _('REV')),
2702 2697 ('n', 'num', None, _('show local revision number')),
2703 2698 ('i', 'id', None, _('show global revision id')),
2704 2699 ('b', 'branch', None, _('show branch')),
2705 2700 ('t', 'tags', None, _('show tags')),
2706 2701 ('B', 'bookmarks', None, _('show bookmarks')),
2707 2702 ] + remoteopts + formatteropts,
2708 2703 _('[-nibtB] [-r REV] [SOURCE]'),
2709 2704 optionalrepo=True, cmdtype=readonly)
2710 2705 def identify(ui, repo, source=None, rev=None,
2711 2706 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2712 2707 """identify the working directory or specified revision
2713 2708
2714 2709 Print a summary identifying the repository state at REV using one or
2715 2710 two parent hash identifiers, followed by a "+" if the working
2716 2711 directory has uncommitted changes, the branch name (if not default),
2717 2712 a list of tags, and a list of bookmarks.
2718 2713
2719 2714 When REV is not given, print a summary of the current state of the
2720 2715 repository.
2721 2716
2722 2717 Specifying a path to a repository root or Mercurial bundle will
2723 2718 cause lookup to operate on that repository/bundle.
2724 2719
2725 2720 .. container:: verbose
2726 2721
2727 2722 Examples:
2728 2723
2729 2724 - generate a build identifier for the working directory::
2730 2725
2731 2726 hg id --id > build-id.dat
2732 2727
2733 2728 - find the revision corresponding to a tag::
2734 2729
2735 2730 hg id -n -r 1.3
2736 2731
2737 2732 - check the most recent revision of a remote repository::
2738 2733
2739 2734 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2740 2735
2741 2736 See :hg:`log` for generating more information about specific revisions,
2742 2737 including full hash identifiers.
2743 2738
2744 2739 Returns 0 if successful.
2745 2740 """
2746 2741
2747 2742 opts = pycompat.byteskwargs(opts)
2748 2743 if not repo and not source:
2749 2744 raise error.Abort(_("there is no Mercurial repository here "
2750 2745 "(.hg not found)"))
2751 2746
2752 2747 if ui.debugflag:
2753 2748 hexfunc = hex
2754 2749 else:
2755 2750 hexfunc = short
2756 2751 default = not (num or id or branch or tags or bookmarks)
2757 2752 output = []
2758 2753 revs = []
2759 2754
2760 2755 if source:
2761 2756 source, branches = hg.parseurl(ui.expandpath(source))
2762 2757 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2763 2758 repo = peer.local()
2764 2759 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2765 2760
2766 2761 fm = ui.formatter('identify', opts)
2767 2762 fm.startitem()
2768 2763
2769 2764 if not repo:
2770 2765 if num or branch or tags:
2771 2766 raise error.Abort(
2772 2767 _("can't query remote revision number, branch, or tags"))
2773 2768 if not rev and revs:
2774 2769 rev = revs[0]
2775 2770 if not rev:
2776 2771 rev = "tip"
2777 2772
2778 2773 remoterev = peer.lookup(rev)
2779 2774 hexrev = hexfunc(remoterev)
2780 2775 if default or id:
2781 2776 output = [hexrev]
2782 2777 fm.data(id=hexrev)
2783 2778
2784 2779 def getbms():
2785 2780 bms = []
2786 2781
2787 2782 if 'bookmarks' in peer.listkeys('namespaces'):
2788 2783 hexremoterev = hex(remoterev)
2789 2784 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2790 2785 if bmr == hexremoterev]
2791 2786
2792 2787 return sorted(bms)
2793 2788
2794 2789 bms = getbms()
2795 2790 if bookmarks:
2796 2791 output.extend(bms)
2797 2792 elif default and not ui.quiet:
2798 2793 # multiple bookmarks for a single parent separated by '/'
2799 2794 bm = '/'.join(bms)
2800 2795 if bm:
2801 2796 output.append(bm)
2802 2797
2803 2798 fm.data(node=hex(remoterev))
2804 2799 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2805 2800 else:
2806 2801 if rev:
2807 2802 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2808 2803 ctx = scmutil.revsingle(repo, rev, None)
2809 2804
2810 2805 if ctx.rev() is None:
2811 2806 ctx = repo[None]
2812 2807 parents = ctx.parents()
2813 2808 taglist = []
2814 2809 for p in parents:
2815 2810 taglist.extend(p.tags())
2816 2811
2817 2812 dirty = ""
2818 2813 if ctx.dirty(missing=True, merge=False, branch=False):
2819 2814 dirty = '+'
2820 2815 fm.data(dirty=dirty)
2821 2816
2822 2817 hexoutput = [hexfunc(p.node()) for p in parents]
2823 2818 if default or id:
2824 2819 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2825 2820 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2826 2821
2827 2822 if num:
2828 2823 numoutput = ["%d" % p.rev() for p in parents]
2829 2824 output.append("%s%s" % ('+'.join(numoutput), dirty))
2830 2825
2831 2826 fn = fm.nested('parents')
2832 2827 for p in parents:
2833 2828 fn.startitem()
2834 2829 fn.data(rev=p.rev())
2835 2830 fn.data(node=p.hex())
2836 2831 fn.context(ctx=p)
2837 2832 fn.end()
2838 2833 else:
2839 2834 hexoutput = hexfunc(ctx.node())
2840 2835 if default or id:
2841 2836 output = [hexoutput]
2842 2837 fm.data(id=hexoutput)
2843 2838
2844 2839 if num:
2845 2840 output.append(pycompat.bytestr(ctx.rev()))
2846 2841 taglist = ctx.tags()
2847 2842
2848 2843 if default and not ui.quiet:
2849 2844 b = ctx.branch()
2850 2845 if b != 'default':
2851 2846 output.append("(%s)" % b)
2852 2847
2853 2848 # multiple tags for a single parent separated by '/'
2854 2849 t = '/'.join(taglist)
2855 2850 if t:
2856 2851 output.append(t)
2857 2852
2858 2853 # multiple bookmarks for a single parent separated by '/'
2859 2854 bm = '/'.join(ctx.bookmarks())
2860 2855 if bm:
2861 2856 output.append(bm)
2862 2857 else:
2863 2858 if branch:
2864 2859 output.append(ctx.branch())
2865 2860
2866 2861 if tags:
2867 2862 output.extend(taglist)
2868 2863
2869 2864 if bookmarks:
2870 2865 output.extend(ctx.bookmarks())
2871 2866
2872 2867 fm.data(node=ctx.hex())
2873 2868 fm.data(branch=ctx.branch())
2874 2869 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2875 2870 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2876 2871 fm.context(ctx=ctx)
2877 2872
2878 2873 fm.plain("%s\n" % ' '.join(output))
2879 2874 fm.end()
2880 2875
2881 2876 @command('import|patch',
2882 2877 [('p', 'strip', 1,
2883 2878 _('directory strip option for patch. This has the same '
2884 2879 'meaning as the corresponding patch option'), _('NUM')),
2885 2880 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2886 2881 ('e', 'edit', False, _('invoke editor on commit messages')),
2887 2882 ('f', 'force', None,
2888 2883 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2889 2884 ('', 'no-commit', None,
2890 2885 _("don't commit, just update the working directory")),
2891 2886 ('', 'bypass', None,
2892 2887 _("apply patch without touching the working directory")),
2893 2888 ('', 'partial', None,
2894 2889 _('commit even if some hunks fail')),
2895 2890 ('', 'exact', None,
2896 2891 _('abort if patch would apply lossily')),
2897 2892 ('', 'prefix', '',
2898 2893 _('apply patch to subdirectory'), _('DIR')),
2899 2894 ('', 'import-branch', None,
2900 2895 _('use any branch information in patch (implied by --exact)'))] +
2901 2896 commitopts + commitopts2 + similarityopts,
2902 2897 _('[OPTION]... PATCH...'))
2903 2898 def import_(ui, repo, patch1=None, *patches, **opts):
2904 2899 """import an ordered set of patches
2905 2900
2906 2901 Import a list of patches and commit them individually (unless
2907 2902 --no-commit is specified).
2908 2903
2909 2904 To read a patch from standard input (stdin), use "-" as the patch
2910 2905 name. If a URL is specified, the patch will be downloaded from
2911 2906 there.
2912 2907
2913 2908 Import first applies changes to the working directory (unless
2914 2909 --bypass is specified), import will abort if there are outstanding
2915 2910 changes.
2916 2911
2917 2912 Use --bypass to apply and commit patches directly to the
2918 2913 repository, without affecting the working directory. Without
2919 2914 --exact, patches will be applied on top of the working directory
2920 2915 parent revision.
2921 2916
2922 2917 You can import a patch straight from a mail message. Even patches
2923 2918 as attachments work (to use the body part, it must have type
2924 2919 text/plain or text/x-patch). From and Subject headers of email
2925 2920 message are used as default committer and commit message. All
2926 2921 text/plain body parts before first diff are added to the commit
2927 2922 message.
2928 2923
2929 2924 If the imported patch was generated by :hg:`export`, user and
2930 2925 description from patch override values from message headers and
2931 2926 body. Values given on command line with -m/--message and -u/--user
2932 2927 override these.
2933 2928
2934 2929 If --exact is specified, import will set the working directory to
2935 2930 the parent of each patch before applying it, and will abort if the
2936 2931 resulting changeset has a different ID than the one recorded in
2937 2932 the patch. This will guard against various ways that portable
2938 2933 patch formats and mail systems might fail to transfer Mercurial
2939 2934 data or metadata. See :hg:`bundle` for lossless transmission.
2940 2935
2941 2936 Use --partial to ensure a changeset will be created from the patch
2942 2937 even if some hunks fail to apply. Hunks that fail to apply will be
2943 2938 written to a <target-file>.rej file. Conflicts can then be resolved
2944 2939 by hand before :hg:`commit --amend` is run to update the created
2945 2940 changeset. This flag exists to let people import patches that
2946 2941 partially apply without losing the associated metadata (author,
2947 2942 date, description, ...).
2948 2943
2949 2944 .. note::
2950 2945
2951 2946 When no hunks apply cleanly, :hg:`import --partial` will create
2952 2947 an empty changeset, importing only the patch metadata.
2953 2948
2954 2949 With -s/--similarity, hg will attempt to discover renames and
2955 2950 copies in the patch in the same way as :hg:`addremove`.
2956 2951
2957 2952 It is possible to use external patch programs to perform the patch
2958 2953 by setting the ``ui.patch`` configuration option. For the default
2959 2954 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2960 2955 See :hg:`help config` for more information about configuration
2961 2956 files and how to use these options.
2962 2957
2963 2958 See :hg:`help dates` for a list of formats valid for -d/--date.
2964 2959
2965 2960 .. container:: verbose
2966 2961
2967 2962 Examples:
2968 2963
2969 2964 - import a traditional patch from a website and detect renames::
2970 2965
2971 2966 hg import -s 80 http://example.com/bugfix.patch
2972 2967
2973 2968 - import a changeset from an hgweb server::
2974 2969
2975 2970 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2976 2971
2977 2972 - import all the patches in an Unix-style mbox::
2978 2973
2979 2974 hg import incoming-patches.mbox
2980 2975
2981 2976 - import patches from stdin::
2982 2977
2983 2978 hg import -
2984 2979
2985 2980 - attempt to exactly restore an exported changeset (not always
2986 2981 possible)::
2987 2982
2988 2983 hg import --exact proposed-fix.patch
2989 2984
2990 2985 - use an external tool to apply a patch which is too fuzzy for
2991 2986 the default internal tool.
2992 2987
2993 2988 hg import --config ui.patch="patch --merge" fuzzy.patch
2994 2989
2995 2990 - change the default fuzzing from 2 to a less strict 7
2996 2991
2997 2992 hg import --config ui.fuzz=7 fuzz.patch
2998 2993
2999 2994 Returns 0 on success, 1 on partial success (see --partial).
3000 2995 """
3001 2996
3002 2997 opts = pycompat.byteskwargs(opts)
3003 2998 if not patch1:
3004 2999 raise error.Abort(_('need at least one patch to import'))
3005 3000
3006 3001 patches = (patch1,) + patches
3007 3002
3008 3003 date = opts.get('date')
3009 3004 if date:
3010 3005 opts['date'] = util.parsedate(date)
3011 3006
3012 3007 exact = opts.get('exact')
3013 3008 update = not opts.get('bypass')
3014 3009 if not update and opts.get('no_commit'):
3015 3010 raise error.Abort(_('cannot use --no-commit with --bypass'))
3016 3011 try:
3017 3012 sim = float(opts.get('similarity') or 0)
3018 3013 except ValueError:
3019 3014 raise error.Abort(_('similarity must be a number'))
3020 3015 if sim < 0 or sim > 100:
3021 3016 raise error.Abort(_('similarity must be between 0 and 100'))
3022 3017 if sim and not update:
3023 3018 raise error.Abort(_('cannot use --similarity with --bypass'))
3024 3019 if exact:
3025 3020 if opts.get('edit'):
3026 3021 raise error.Abort(_('cannot use --exact with --edit'))
3027 3022 if opts.get('prefix'):
3028 3023 raise error.Abort(_('cannot use --exact with --prefix'))
3029 3024
3030 3025 base = opts["base"]
3031 3026 wlock = dsguard = lock = tr = None
3032 3027 msgs = []
3033 3028 ret = 0
3034 3029
3035 3030
3036 3031 try:
3037 3032 wlock = repo.wlock()
3038 3033
3039 3034 if update:
3040 3035 cmdutil.checkunfinished(repo)
3041 3036 if (exact or not opts.get('force')):
3042 3037 cmdutil.bailifchanged(repo)
3043 3038
3044 3039 if not opts.get('no_commit'):
3045 3040 lock = repo.lock()
3046 3041 tr = repo.transaction('import')
3047 3042 else:
3048 3043 dsguard = dirstateguard.dirstateguard(repo, 'import')
3049 3044 parents = repo[None].parents()
3050 3045 for patchurl in patches:
3051 3046 if patchurl == '-':
3052 3047 ui.status(_('applying patch from stdin\n'))
3053 3048 patchfile = ui.fin
3054 3049 patchurl = 'stdin' # for error message
3055 3050 else:
3056 3051 patchurl = os.path.join(base, patchurl)
3057 3052 ui.status(_('applying %s\n') % patchurl)
3058 3053 patchfile = hg.openpath(ui, patchurl)
3059 3054
3060 3055 haspatch = False
3061 3056 for hunk in patch.split(patchfile):
3062 3057 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3063 3058 parents, opts,
3064 3059 msgs, hg.clean)
3065 3060 if msg:
3066 3061 haspatch = True
3067 3062 ui.note(msg + '\n')
3068 3063 if update or exact:
3069 3064 parents = repo[None].parents()
3070 3065 else:
3071 3066 parents = [repo[node]]
3072 3067 if rej:
3073 3068 ui.write_err(_("patch applied partially\n"))
3074 3069 ui.write_err(_("(fix the .rej files and run "
3075 3070 "`hg commit --amend`)\n"))
3076 3071 ret = 1
3077 3072 break
3078 3073
3079 3074 if not haspatch:
3080 3075 raise error.Abort(_('%s: no diffs found') % patchurl)
3081 3076
3082 3077 if tr:
3083 3078 tr.close()
3084 3079 if msgs:
3085 3080 repo.savecommitmessage('\n* * *\n'.join(msgs))
3086 3081 if dsguard:
3087 3082 dsguard.close()
3088 3083 return ret
3089 3084 finally:
3090 3085 if tr:
3091 3086 tr.release()
3092 3087 release(lock, dsguard, wlock)
3093 3088
3094 3089 @command('incoming|in',
3095 3090 [('f', 'force', None,
3096 3091 _('run even if remote repository is unrelated')),
3097 3092 ('n', 'newest-first', None, _('show newest record first')),
3098 3093 ('', 'bundle', '',
3099 3094 _('file to store the bundles into'), _('FILE')),
3100 3095 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3101 3096 ('B', 'bookmarks', False, _("compare bookmarks")),
3102 3097 ('b', 'branch', [],
3103 3098 _('a specific branch you would like to pull'), _('BRANCH')),
3104 3099 ] + logopts + remoteopts + subrepoopts,
3105 3100 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3106 3101 def incoming(ui, repo, source="default", **opts):
3107 3102 """show new changesets found in source
3108 3103
3109 3104 Show new changesets found in the specified path/URL or the default
3110 3105 pull location. These are the changesets that would have been pulled
3111 3106 by :hg:`pull` at the time you issued this command.
3112 3107
3113 3108 See pull for valid source format details.
3114 3109
3115 3110 .. container:: verbose
3116 3111
3117 3112 With -B/--bookmarks, the result of bookmark comparison between
3118 3113 local and remote repositories is displayed. With -v/--verbose,
3119 3114 status is also displayed for each bookmark like below::
3120 3115
3121 3116 BM1 01234567890a added
3122 3117 BM2 1234567890ab advanced
3123 3118 BM3 234567890abc diverged
3124 3119 BM4 34567890abcd changed
3125 3120
3126 3121 The action taken locally when pulling depends on the
3127 3122 status of each bookmark:
3128 3123
3129 3124 :``added``: pull will create it
3130 3125 :``advanced``: pull will update it
3131 3126 :``diverged``: pull will create a divergent bookmark
3132 3127 :``changed``: result depends on remote changesets
3133 3128
3134 3129 From the point of view of pulling behavior, bookmark
3135 3130 existing only in the remote repository are treated as ``added``,
3136 3131 even if it is in fact locally deleted.
3137 3132
3138 3133 .. container:: verbose
3139 3134
3140 3135 For remote repository, using --bundle avoids downloading the
3141 3136 changesets twice if the incoming is followed by a pull.
3142 3137
3143 3138 Examples:
3144 3139
3145 3140 - show incoming changes with patches and full description::
3146 3141
3147 3142 hg incoming -vp
3148 3143
3149 3144 - show incoming changes excluding merges, store a bundle::
3150 3145
3151 3146 hg in -vpM --bundle incoming.hg
3152 3147 hg pull incoming.hg
3153 3148
3154 3149 - briefly list changes inside a bundle::
3155 3150
3156 3151 hg in changes.hg -T "{desc|firstline}\\n"
3157 3152
3158 3153 Returns 0 if there are incoming changes, 1 otherwise.
3159 3154 """
3160 3155 opts = pycompat.byteskwargs(opts)
3161 3156 if opts.get('graph'):
3162 3157 cmdutil.checkunsupportedgraphflags([], opts)
3163 3158 def display(other, chlist, displayer):
3164 3159 revdag = cmdutil.graphrevs(other, chlist, opts)
3165 3160 cmdutil.displaygraph(ui, repo, revdag, displayer,
3166 3161 graphmod.asciiedges)
3167 3162
3168 3163 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3169 3164 return 0
3170 3165
3171 3166 if opts.get('bundle') and opts.get('subrepos'):
3172 3167 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3173 3168
3174 3169 if opts.get('bookmarks'):
3175 3170 source, branches = hg.parseurl(ui.expandpath(source),
3176 3171 opts.get('branch'))
3177 3172 other = hg.peer(repo, opts, source)
3178 3173 if 'bookmarks' not in other.listkeys('namespaces'):
3179 3174 ui.warn(_("remote doesn't support bookmarks\n"))
3180 3175 return 0
3181 3176 ui.pager('incoming')
3182 3177 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3183 3178 return bookmarks.incoming(ui, repo, other)
3184 3179
3185 3180 repo._subtoppath = ui.expandpath(source)
3186 3181 try:
3187 3182 return hg.incoming(ui, repo, source, opts)
3188 3183 finally:
3189 3184 del repo._subtoppath
3190 3185
3191 3186
3192 3187 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3193 3188 norepo=True)
3194 3189 def init(ui, dest=".", **opts):
3195 3190 """create a new repository in the given directory
3196 3191
3197 3192 Initialize a new repository in the given directory. If the given
3198 3193 directory does not exist, it will be created.
3199 3194
3200 3195 If no directory is given, the current directory is used.
3201 3196
3202 3197 It is possible to specify an ``ssh://`` URL as the destination.
3203 3198 See :hg:`help urls` for more information.
3204 3199
3205 3200 Returns 0 on success.
3206 3201 """
3207 3202 opts = pycompat.byteskwargs(opts)
3208 3203 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3209 3204
3210 3205 @command('locate',
3211 3206 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3212 3207 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3213 3208 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3214 3209 ] + walkopts,
3215 3210 _('[OPTION]... [PATTERN]...'))
3216 3211 def locate(ui, repo, *pats, **opts):
3217 3212 """locate files matching specific patterns (DEPRECATED)
3218 3213
3219 3214 Print files under Mercurial control in the working directory whose
3220 3215 names match the given patterns.
3221 3216
3222 3217 By default, this command searches all directories in the working
3223 3218 directory. To search just the current directory and its
3224 3219 subdirectories, use "--include .".
3225 3220
3226 3221 If no patterns are given to match, this command prints the names
3227 3222 of all files under Mercurial control in the working directory.
3228 3223
3229 3224 If you want to feed the output of this command into the "xargs"
3230 3225 command, use the -0 option to both this command and "xargs". This
3231 3226 will avoid the problem of "xargs" treating single filenames that
3232 3227 contain whitespace as multiple filenames.
3233 3228
3234 3229 See :hg:`help files` for a more versatile command.
3235 3230
3236 3231 Returns 0 if a match is found, 1 otherwise.
3237 3232 """
3238 3233 opts = pycompat.byteskwargs(opts)
3239 3234 if opts.get('print0'):
3240 3235 end = '\0'
3241 3236 else:
3242 3237 end = '\n'
3243 3238 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3244 3239
3245 3240 ret = 1
3246 3241 ctx = repo[rev]
3247 3242 m = scmutil.match(ctx, pats, opts, default='relglob',
3248 3243 badfn=lambda x, y: False)
3249 3244
3250 3245 ui.pager('locate')
3251 3246 for abs in ctx.matches(m):
3252 3247 if opts.get('fullpath'):
3253 3248 ui.write(repo.wjoin(abs), end)
3254 3249 else:
3255 3250 ui.write(((pats and m.rel(abs)) or abs), end)
3256 3251 ret = 0
3257 3252
3258 3253 return ret
3259 3254
3260 3255 @command('^log|history',
3261 3256 [('f', 'follow', None,
3262 3257 _('follow changeset history, or file history across copies and renames')),
3263 3258 ('', 'follow-first', None,
3264 3259 _('only follow the first parent of merge changesets (DEPRECATED)')),
3265 3260 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3266 3261 ('C', 'copies', None, _('show copied files')),
3267 3262 ('k', 'keyword', [],
3268 3263 _('do case-insensitive search for a given text'), _('TEXT')),
3269 3264 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3270 3265 ('L', 'line-range', [],
3271 3266 _('follow line range of specified file (EXPERIMENTAL)'),
3272 3267 _('FILE,RANGE')),
3273 3268 ('', 'removed', None, _('include revisions where files were removed')),
3274 3269 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3275 3270 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3276 3271 ('', 'only-branch', [],
3277 3272 _('show only changesets within the given named branch (DEPRECATED)'),
3278 3273 _('BRANCH')),
3279 3274 ('b', 'branch', [],
3280 3275 _('show changesets within the given named branch'), _('BRANCH')),
3281 3276 ('P', 'prune', [],
3282 3277 _('do not display revision or any of its ancestors'), _('REV')),
3283 3278 ] + logopts + walkopts,
3284 3279 _('[OPTION]... [FILE]'),
3285 3280 inferrepo=True, cmdtype=readonly)
3286 3281 def log(ui, repo, *pats, **opts):
3287 3282 """show revision history of entire repository or files
3288 3283
3289 3284 Print the revision history of the specified files or the entire
3290 3285 project.
3291 3286
3292 3287 If no revision range is specified, the default is ``tip:0`` unless
3293 3288 --follow is set, in which case the working directory parent is
3294 3289 used as the starting revision.
3295 3290
3296 3291 File history is shown without following rename or copy history of
3297 3292 files. Use -f/--follow with a filename to follow history across
3298 3293 renames and copies. --follow without a filename will only show
3299 3294 ancestors of the starting revision.
3300 3295
3301 3296 By default this command prints revision number and changeset id,
3302 3297 tags, non-trivial parents, user, date and time, and a summary for
3303 3298 each commit. When the -v/--verbose switch is used, the list of
3304 3299 changed files and full commit message are shown.
3305 3300
3306 3301 With --graph the revisions are shown as an ASCII art DAG with the most
3307 3302 recent changeset at the top.
3308 3303 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3309 3304 and '+' represents a fork where the changeset from the lines below is a
3310 3305 parent of the 'o' merge on the same line.
3311 3306 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3312 3307 of a '|' indicates one or more revisions in a path are omitted.
3313 3308
3314 3309 .. container:: verbose
3315 3310
3316 3311 Use -L/--line-range FILE,M:N options to follow the history of lines
3317 3312 from M to N in FILE. With -p/--patch only diff hunks affecting
3318 3313 specified line range will be shown. This option requires --follow;
3319 3314 it can be specified multiple times. Currently, this option is not
3320 3315 compatible with --graph. This option is experimental.
3321 3316
3322 3317 .. note::
3323 3318
3324 3319 :hg:`log --patch` may generate unexpected diff output for merge
3325 3320 changesets, as it will only compare the merge changeset against
3326 3321 its first parent. Also, only files different from BOTH parents
3327 3322 will appear in files:.
3328 3323
3329 3324 .. note::
3330 3325
3331 3326 For performance reasons, :hg:`log FILE` may omit duplicate changes
3332 3327 made on branches and will not show removals or mode changes. To
3333 3328 see all such changes, use the --removed switch.
3334 3329
3335 3330 .. container:: verbose
3336 3331
3337 3332 .. note::
3338 3333
3339 3334 The history resulting from -L/--line-range options depends on diff
3340 3335 options; for instance if white-spaces are ignored, respective changes
3341 3336 with only white-spaces in specified line range will not be listed.
3342 3337
3343 3338 .. container:: verbose
3344 3339
3345 3340 Some examples:
3346 3341
3347 3342 - changesets with full descriptions and file lists::
3348 3343
3349 3344 hg log -v
3350 3345
3351 3346 - changesets ancestral to the working directory::
3352 3347
3353 3348 hg log -f
3354 3349
3355 3350 - last 10 commits on the current branch::
3356 3351
3357 3352 hg log -l 10 -b .
3358 3353
3359 3354 - changesets showing all modifications of a file, including removals::
3360 3355
3361 3356 hg log --removed file.c
3362 3357
3363 3358 - all changesets that touch a directory, with diffs, excluding merges::
3364 3359
3365 3360 hg log -Mp lib/
3366 3361
3367 3362 - all revision numbers that match a keyword::
3368 3363
3369 3364 hg log -k bug --template "{rev}\\n"
3370 3365
3371 3366 - the full hash identifier of the working directory parent::
3372 3367
3373 3368 hg log -r . --template "{node}\\n"
3374 3369
3375 3370 - list available log templates::
3376 3371
3377 3372 hg log -T list
3378 3373
3379 3374 - check if a given changeset is included in a tagged release::
3380 3375
3381 3376 hg log -r "a21ccf and ancestor(1.9)"
3382 3377
3383 3378 - find all changesets by some user in a date range::
3384 3379
3385 3380 hg log -k alice -d "may 2008 to jul 2008"
3386 3381
3387 3382 - summary of all changesets after the last tag::
3388 3383
3389 3384 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3390 3385
3391 3386 - changesets touching lines 13 to 23 for file.c::
3392 3387
3393 3388 hg log -L file.c,13:23
3394 3389
3395 3390 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3396 3391 main.c with patch::
3397 3392
3398 3393 hg log -L file.c,13:23 -L main.c,2:6 -p
3399 3394
3400 3395 See :hg:`help dates` for a list of formats valid for -d/--date.
3401 3396
3402 3397 See :hg:`help revisions` for more about specifying and ordering
3403 3398 revisions.
3404 3399
3405 3400 See :hg:`help templates` for more about pre-packaged styles and
3406 3401 specifying custom templates. The default template used by the log
3407 3402 command can be customized via the ``ui.logtemplate`` configuration
3408 3403 setting.
3409 3404
3410 3405 Returns 0 on success.
3411 3406
3412 3407 """
3413 3408 opts = pycompat.byteskwargs(opts)
3414 3409 linerange = opts.get('line_range')
3415 3410
3416 3411 if linerange and not opts.get('follow'):
3417 3412 raise error.Abort(_('--line-range requires --follow'))
3418 3413
3419 3414 if linerange and pats:
3420 3415 raise error.Abort(
3421 3416 _('FILE arguments are not compatible with --line-range option')
3422 3417 )
3423 3418
3424 3419 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3425 3420 revs, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3426 3421 hunksfilter = None
3427 3422
3428 3423 if opts.get('graph'):
3429 3424 if linerange:
3430 3425 raise error.Abort(_('graph not supported with line range patterns'))
3431 3426 return cmdutil.graphlog(ui, repo, revs, filematcher, opts)
3432 3427
3433 3428 if linerange:
3434 3429 revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
3435 3430 repo, revs, opts)
3436 3431
3437 3432 if filematcher is not None and lrfilematcher is not None:
3438 3433 basefilematcher = filematcher
3439 3434
3440 3435 def filematcher(rev):
3441 3436 files = (basefilematcher(rev).files()
3442 3437 + lrfilematcher(rev).files())
3443 3438 return scmutil.matchfiles(repo, files)
3444 3439
3445 3440 elif filematcher is None:
3446 3441 filematcher = lrfilematcher
3447 3442
3448 3443 getrenamed = None
3449 3444 if opts.get('copies'):
3450 3445 endrev = None
3451 3446 if opts.get('rev'):
3452 3447 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3453 3448 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3454 3449
3455 3450 ui.pager('log')
3456 3451 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3457 3452 for rev in revs:
3458 3453 ctx = repo[rev]
3459 3454 copies = None
3460 3455 if getrenamed is not None and rev:
3461 3456 copies = []
3462 3457 for fn in ctx.files():
3463 3458 rename = getrenamed(fn, rev)
3464 3459 if rename:
3465 3460 copies.append((fn, rename[0]))
3466 3461 if filematcher:
3467 3462 revmatchfn = filematcher(ctx.rev())
3468 3463 else:
3469 3464 revmatchfn = None
3470 3465 if hunksfilter:
3471 3466 revhunksfilter = hunksfilter(rev)
3472 3467 else:
3473 3468 revhunksfilter = None
3474 3469 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
3475 3470 hunksfilterfn=revhunksfilter)
3476 3471 displayer.flush(ctx)
3477 3472
3478 3473 displayer.close()
3479 3474
3480 3475 @command('manifest',
3481 3476 [('r', 'rev', '', _('revision to display'), _('REV')),
3482 3477 ('', 'all', False, _("list files from all revisions"))]
3483 3478 + formatteropts,
3484 3479 _('[-r REV]'), cmdtype=readonly)
3485 3480 def manifest(ui, repo, node=None, rev=None, **opts):
3486 3481 """output the current or given revision of the project manifest
3487 3482
3488 3483 Print a list of version controlled files for the given revision.
3489 3484 If no revision is given, the first parent of the working directory
3490 3485 is used, or the null revision if no revision is checked out.
3491 3486
3492 3487 With -v, print file permissions, symlink and executable bits.
3493 3488 With --debug, print file revision hashes.
3494 3489
3495 3490 If option --all is specified, the list of all files from all revisions
3496 3491 is printed. This includes deleted and renamed files.
3497 3492
3498 3493 Returns 0 on success.
3499 3494 """
3500 3495 opts = pycompat.byteskwargs(opts)
3501 3496 fm = ui.formatter('manifest', opts)
3502 3497
3503 3498 if opts.get('all'):
3504 3499 if rev or node:
3505 3500 raise error.Abort(_("can't specify a revision with --all"))
3506 3501
3507 3502 res = []
3508 3503 prefix = "data/"
3509 3504 suffix = ".i"
3510 3505 plen = len(prefix)
3511 3506 slen = len(suffix)
3512 3507 with repo.lock():
3513 3508 for fn, b, size in repo.store.datafiles():
3514 3509 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3515 3510 res.append(fn[plen:-slen])
3516 3511 ui.pager('manifest')
3517 3512 for f in res:
3518 3513 fm.startitem()
3519 3514 fm.write("path", '%s\n', f)
3520 3515 fm.end()
3521 3516 return
3522 3517
3523 3518 if rev and node:
3524 3519 raise error.Abort(_("please specify just one revision"))
3525 3520
3526 3521 if not node:
3527 3522 node = rev
3528 3523
3529 3524 char = {'l': '@', 'x': '*', '': ''}
3530 3525 mode = {'l': '644', 'x': '755', '': '644'}
3531 3526 if node:
3532 3527 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3533 3528 ctx = scmutil.revsingle(repo, node)
3534 3529 mf = ctx.manifest()
3535 3530 ui.pager('manifest')
3536 3531 for f in ctx:
3537 3532 fm.startitem()
3538 3533 fl = ctx[f].flags()
3539 3534 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3540 3535 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3541 3536 fm.write('path', '%s\n', f)
3542 3537 fm.end()
3543 3538
3544 3539 @command('^merge',
3545 3540 [('f', 'force', None,
3546 3541 _('force a merge including outstanding changes (DEPRECATED)')),
3547 3542 ('r', 'rev', '', _('revision to merge'), _('REV')),
3548 3543 ('P', 'preview', None,
3549 3544 _('review revisions to merge (no merge is performed)')),
3550 3545 ('', 'abort', None, _('abort the ongoing merge')),
3551 3546 ] + mergetoolopts,
3552 3547 _('[-P] [[-r] REV]'))
3553 3548 def merge(ui, repo, node=None, **opts):
3554 3549 """merge another revision into working directory
3555 3550
3556 3551 The current working directory is updated with all changes made in
3557 3552 the requested revision since the last common predecessor revision.
3558 3553
3559 3554 Files that changed between either parent are marked as changed for
3560 3555 the next commit and a commit must be performed before any further
3561 3556 updates to the repository are allowed. The next commit will have
3562 3557 two parents.
3563 3558
3564 3559 ``--tool`` can be used to specify the merge tool used for file
3565 3560 merges. It overrides the HGMERGE environment variable and your
3566 3561 configuration files. See :hg:`help merge-tools` for options.
3567 3562
3568 3563 If no revision is specified, the working directory's parent is a
3569 3564 head revision, and the current branch contains exactly one other
3570 3565 head, the other head is merged with by default. Otherwise, an
3571 3566 explicit revision with which to merge with must be provided.
3572 3567
3573 3568 See :hg:`help resolve` for information on handling file conflicts.
3574 3569
3575 3570 To undo an uncommitted merge, use :hg:`merge --abort` which
3576 3571 will check out a clean copy of the original merge parent, losing
3577 3572 all changes.
3578 3573
3579 3574 Returns 0 on success, 1 if there are unresolved files.
3580 3575 """
3581 3576
3582 3577 opts = pycompat.byteskwargs(opts)
3583 3578 abort = opts.get('abort')
3584 3579 if abort and repo.dirstate.p2() == nullid:
3585 3580 cmdutil.wrongtooltocontinue(repo, _('merge'))
3586 3581 if abort:
3587 3582 if node:
3588 3583 raise error.Abort(_("cannot specify a node with --abort"))
3589 3584 if opts.get('rev'):
3590 3585 raise error.Abort(_("cannot specify both --rev and --abort"))
3591 3586 if opts.get('preview'):
3592 3587 raise error.Abort(_("cannot specify --preview with --abort"))
3593 3588 if opts.get('rev') and node:
3594 3589 raise error.Abort(_("please specify just one revision"))
3595 3590 if not node:
3596 3591 node = opts.get('rev')
3597 3592
3598 3593 if node:
3599 3594 node = scmutil.revsingle(repo, node).node()
3600 3595
3601 3596 if not node and not abort:
3602 3597 node = repo[destutil.destmerge(repo)].node()
3603 3598
3604 3599 if opts.get('preview'):
3605 3600 # find nodes that are ancestors of p2 but not of p1
3606 3601 p1 = repo.lookup('.')
3607 3602 p2 = repo.lookup(node)
3608 3603 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3609 3604
3610 3605 displayer = cmdutil.show_changeset(ui, repo, opts)
3611 3606 for node in nodes:
3612 3607 displayer.show(repo[node])
3613 3608 displayer.close()
3614 3609 return 0
3615 3610
3616 3611 try:
3617 3612 # ui.forcemerge is an internal variable, do not document
3618 3613 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3619 3614 force = opts.get('force')
3620 3615 labels = ['working copy', 'merge rev']
3621 3616 return hg.merge(repo, node, force=force, mergeforce=force,
3622 3617 labels=labels, abort=abort)
3623 3618 finally:
3624 3619 ui.setconfig('ui', 'forcemerge', '', 'merge')
3625 3620
3626 3621 @command('outgoing|out',
3627 3622 [('f', 'force', None, _('run even when the destination is unrelated')),
3628 3623 ('r', 'rev', [],
3629 3624 _('a changeset intended to be included in the destination'), _('REV')),
3630 3625 ('n', 'newest-first', None, _('show newest record first')),
3631 3626 ('B', 'bookmarks', False, _('compare bookmarks')),
3632 3627 ('b', 'branch', [], _('a specific branch you would like to push'),
3633 3628 _('BRANCH')),
3634 3629 ] + logopts + remoteopts + subrepoopts,
3635 3630 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3636 3631 def outgoing(ui, repo, dest=None, **opts):
3637 3632 """show changesets not found in the destination
3638 3633
3639 3634 Show changesets not found in the specified destination repository
3640 3635 or the default push location. These are the changesets that would
3641 3636 be pushed if a push was requested.
3642 3637
3643 3638 See pull for details of valid destination formats.
3644 3639
3645 3640 .. container:: verbose
3646 3641
3647 3642 With -B/--bookmarks, the result of bookmark comparison between
3648 3643 local and remote repositories is displayed. With -v/--verbose,
3649 3644 status is also displayed for each bookmark like below::
3650 3645
3651 3646 BM1 01234567890a added
3652 3647 BM2 deleted
3653 3648 BM3 234567890abc advanced
3654 3649 BM4 34567890abcd diverged
3655 3650 BM5 4567890abcde changed
3656 3651
3657 3652 The action taken when pushing depends on the
3658 3653 status of each bookmark:
3659 3654
3660 3655 :``added``: push with ``-B`` will create it
3661 3656 :``deleted``: push with ``-B`` will delete it
3662 3657 :``advanced``: push will update it
3663 3658 :``diverged``: push with ``-B`` will update it
3664 3659 :``changed``: push with ``-B`` will update it
3665 3660
3666 3661 From the point of view of pushing behavior, bookmarks
3667 3662 existing only in the remote repository are treated as
3668 3663 ``deleted``, even if it is in fact added remotely.
3669 3664
3670 3665 Returns 0 if there are outgoing changes, 1 otherwise.
3671 3666 """
3672 3667 opts = pycompat.byteskwargs(opts)
3673 3668 if opts.get('graph'):
3674 3669 cmdutil.checkunsupportedgraphflags([], opts)
3675 3670 o, other = hg._outgoing(ui, repo, dest, opts)
3676 3671 if not o:
3677 3672 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3678 3673 return
3679 3674
3680 3675 revdag = cmdutil.graphrevs(repo, o, opts)
3681 3676 ui.pager('outgoing')
3682 3677 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3683 3678 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3684 3679 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3685 3680 return 0
3686 3681
3687 3682 if opts.get('bookmarks'):
3688 3683 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3689 3684 dest, branches = hg.parseurl(dest, opts.get('branch'))
3690 3685 other = hg.peer(repo, opts, dest)
3691 3686 if 'bookmarks' not in other.listkeys('namespaces'):
3692 3687 ui.warn(_("remote doesn't support bookmarks\n"))
3693 3688 return 0
3694 3689 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3695 3690 ui.pager('outgoing')
3696 3691 return bookmarks.outgoing(ui, repo, other)
3697 3692
3698 3693 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3699 3694 try:
3700 3695 return hg.outgoing(ui, repo, dest, opts)
3701 3696 finally:
3702 3697 del repo._subtoppath
3703 3698
3704 3699 @command('parents',
3705 3700 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3706 3701 ] + templateopts,
3707 3702 _('[-r REV] [FILE]'),
3708 3703 inferrepo=True)
3709 3704 def parents(ui, repo, file_=None, **opts):
3710 3705 """show the parents of the working directory or revision (DEPRECATED)
3711 3706
3712 3707 Print the working directory's parent revisions. If a revision is
3713 3708 given via -r/--rev, the parent of that revision will be printed.
3714 3709 If a file argument is given, the revision in which the file was
3715 3710 last changed (before the working directory revision or the
3716 3711 argument to --rev if given) is printed.
3717 3712
3718 3713 This command is equivalent to::
3719 3714
3720 3715 hg log -r "p1()+p2()" or
3721 3716 hg log -r "p1(REV)+p2(REV)" or
3722 3717 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3723 3718 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3724 3719
3725 3720 See :hg:`summary` and :hg:`help revsets` for related information.
3726 3721
3727 3722 Returns 0 on success.
3728 3723 """
3729 3724
3730 3725 opts = pycompat.byteskwargs(opts)
3731 3726 rev = opts.get('rev')
3732 3727 if rev:
3733 3728 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3734 3729 ctx = scmutil.revsingle(repo, rev, None)
3735 3730
3736 3731 if file_:
3737 3732 m = scmutil.match(ctx, (file_,), opts)
3738 3733 if m.anypats() or len(m.files()) != 1:
3739 3734 raise error.Abort(_('can only specify an explicit filename'))
3740 3735 file_ = m.files()[0]
3741 3736 filenodes = []
3742 3737 for cp in ctx.parents():
3743 3738 if not cp:
3744 3739 continue
3745 3740 try:
3746 3741 filenodes.append(cp.filenode(file_))
3747 3742 except error.LookupError:
3748 3743 pass
3749 3744 if not filenodes:
3750 3745 raise error.Abort(_("'%s' not found in manifest!") % file_)
3751 3746 p = []
3752 3747 for fn in filenodes:
3753 3748 fctx = repo.filectx(file_, fileid=fn)
3754 3749 p.append(fctx.node())
3755 3750 else:
3756 3751 p = [cp.node() for cp in ctx.parents()]
3757 3752
3758 3753 displayer = cmdutil.show_changeset(ui, repo, opts)
3759 3754 for n in p:
3760 3755 if n != nullid:
3761 3756 displayer.show(repo[n])
3762 3757 displayer.close()
3763 3758
3764 3759 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3765 3760 cmdtype=readonly)
3766 3761 def paths(ui, repo, search=None, **opts):
3767 3762 """show aliases for remote repositories
3768 3763
3769 3764 Show definition of symbolic path name NAME. If no name is given,
3770 3765 show definition of all available names.
3771 3766
3772 3767 Option -q/--quiet suppresses all output when searching for NAME
3773 3768 and shows only the path names when listing all definitions.
3774 3769
3775 3770 Path names are defined in the [paths] section of your
3776 3771 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3777 3772 repository, ``.hg/hgrc`` is used, too.
3778 3773
3779 3774 The path names ``default`` and ``default-push`` have a special
3780 3775 meaning. When performing a push or pull operation, they are used
3781 3776 as fallbacks if no location is specified on the command-line.
3782 3777 When ``default-push`` is set, it will be used for push and
3783 3778 ``default`` will be used for pull; otherwise ``default`` is used
3784 3779 as the fallback for both. When cloning a repository, the clone
3785 3780 source is written as ``default`` in ``.hg/hgrc``.
3786 3781
3787 3782 .. note::
3788 3783
3789 3784 ``default`` and ``default-push`` apply to all inbound (e.g.
3790 3785 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3791 3786 and :hg:`bundle`) operations.
3792 3787
3793 3788 See :hg:`help urls` for more information.
3794 3789
3795 3790 Returns 0 on success.
3796 3791 """
3797 3792
3798 3793 opts = pycompat.byteskwargs(opts)
3799 3794 ui.pager('paths')
3800 3795 if search:
3801 3796 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3802 3797 if name == search]
3803 3798 else:
3804 3799 pathitems = sorted(ui.paths.iteritems())
3805 3800
3806 3801 fm = ui.formatter('paths', opts)
3807 3802 if fm.isplain():
3808 3803 hidepassword = util.hidepassword
3809 3804 else:
3810 3805 hidepassword = str
3811 3806 if ui.quiet:
3812 3807 namefmt = '%s\n'
3813 3808 else:
3814 3809 namefmt = '%s = '
3815 3810 showsubopts = not search and not ui.quiet
3816 3811
3817 3812 for name, path in pathitems:
3818 3813 fm.startitem()
3819 3814 fm.condwrite(not search, 'name', namefmt, name)
3820 3815 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3821 3816 for subopt, value in sorted(path.suboptions.items()):
3822 3817 assert subopt not in ('name', 'url')
3823 3818 if showsubopts:
3824 3819 fm.plain('%s:%s = ' % (name, subopt))
3825 3820 fm.condwrite(showsubopts, subopt, '%s\n', value)
3826 3821
3827 3822 fm.end()
3828 3823
3829 3824 if search and not pathitems:
3830 3825 if not ui.quiet:
3831 3826 ui.warn(_("not found!\n"))
3832 3827 return 1
3833 3828 else:
3834 3829 return 0
3835 3830
3836 3831 @command('phase',
3837 3832 [('p', 'public', False, _('set changeset phase to public')),
3838 3833 ('d', 'draft', False, _('set changeset phase to draft')),
3839 3834 ('s', 'secret', False, _('set changeset phase to secret')),
3840 3835 ('f', 'force', False, _('allow to move boundary backward')),
3841 3836 ('r', 'rev', [], _('target revision'), _('REV')),
3842 3837 ],
3843 3838 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3844 3839 def phase(ui, repo, *revs, **opts):
3845 3840 """set or show the current phase name
3846 3841
3847 3842 With no argument, show the phase name of the current revision(s).
3848 3843
3849 3844 With one of -p/--public, -d/--draft or -s/--secret, change the
3850 3845 phase value of the specified revisions.
3851 3846
3852 3847 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3853 3848 lower phase to a higher phase. Phases are ordered as follows::
3854 3849
3855 3850 public < draft < secret
3856 3851
3857 3852 Returns 0 on success, 1 if some phases could not be changed.
3858 3853
3859 3854 (For more information about the phases concept, see :hg:`help phases`.)
3860 3855 """
3861 3856 opts = pycompat.byteskwargs(opts)
3862 3857 # search for a unique phase argument
3863 3858 targetphase = None
3864 3859 for idx, name in enumerate(phases.phasenames):
3865 3860 if opts[name]:
3866 3861 if targetphase is not None:
3867 3862 raise error.Abort(_('only one phase can be specified'))
3868 3863 targetphase = idx
3869 3864
3870 3865 # look for specified revision
3871 3866 revs = list(revs)
3872 3867 revs.extend(opts['rev'])
3873 3868 if not revs:
3874 3869 # display both parents as the second parent phase can influence
3875 3870 # the phase of a merge commit
3876 3871 revs = [c.rev() for c in repo[None].parents()]
3877 3872
3878 3873 revs = scmutil.revrange(repo, revs)
3879 3874
3880 3875 ret = 0
3881 3876 if targetphase is None:
3882 3877 # display
3883 3878 for r in revs:
3884 3879 ctx = repo[r]
3885 3880 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3886 3881 else:
3887 3882 with repo.lock(), repo.transaction("phase") as tr:
3888 3883 # set phase
3889 3884 if not revs:
3890 3885 raise error.Abort(_('empty revision set'))
3891 3886 nodes = [repo[r].node() for r in revs]
3892 3887 # moving revision from public to draft may hide them
3893 3888 # We have to check result on an unfiltered repository
3894 3889 unfi = repo.unfiltered()
3895 3890 getphase = unfi._phasecache.phase
3896 3891 olddata = [getphase(unfi, r) for r in unfi]
3897 3892 phases.advanceboundary(repo, tr, targetphase, nodes)
3898 3893 if opts['force']:
3899 3894 phases.retractboundary(repo, tr, targetphase, nodes)
3900 3895 getphase = unfi._phasecache.phase
3901 3896 newdata = [getphase(unfi, r) for r in unfi]
3902 3897 changes = sum(newdata[r] != olddata[r] for r in unfi)
3903 3898 cl = unfi.changelog
3904 3899 rejected = [n for n in nodes
3905 3900 if newdata[cl.rev(n)] < targetphase]
3906 3901 if rejected:
3907 3902 ui.warn(_('cannot move %i changesets to a higher '
3908 3903 'phase, use --force\n') % len(rejected))
3909 3904 ret = 1
3910 3905 if changes:
3911 3906 msg = _('phase changed for %i changesets\n') % changes
3912 3907 if ret:
3913 3908 ui.status(msg)
3914 3909 else:
3915 3910 ui.note(msg)
3916 3911 else:
3917 3912 ui.warn(_('no phases changed\n'))
3918 3913 return ret
3919 3914
3920 3915 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3921 3916 """Run after a changegroup has been added via pull/unbundle
3922 3917
3923 3918 This takes arguments below:
3924 3919
3925 3920 :modheads: change of heads by pull/unbundle
3926 3921 :optupdate: updating working directory is needed or not
3927 3922 :checkout: update destination revision (or None to default destination)
3928 3923 :brev: a name, which might be a bookmark to be activated after updating
3929 3924 """
3930 3925 if modheads == 0:
3931 3926 return
3932 3927 if optupdate:
3933 3928 try:
3934 3929 return hg.updatetotally(ui, repo, checkout, brev)
3935 3930 except error.UpdateAbort as inst:
3936 3931 msg = _("not updating: %s") % str(inst)
3937 3932 hint = inst.hint
3938 3933 raise error.UpdateAbort(msg, hint=hint)
3939 3934 if modheads > 1:
3940 3935 currentbranchheads = len(repo.branchheads())
3941 3936 if currentbranchheads == modheads:
3942 3937 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3943 3938 elif currentbranchheads > 1:
3944 3939 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3945 3940 "merge)\n"))
3946 3941 else:
3947 3942 ui.status(_("(run 'hg heads' to see heads)\n"))
3948 3943 elif not ui.configbool('commands', 'update.requiredest'):
3949 3944 ui.status(_("(run 'hg update' to get a working copy)\n"))
3950 3945
3951 3946 @command('^pull',
3952 3947 [('u', 'update', None,
3953 3948 _('update to new branch head if new descendants were pulled')),
3954 3949 ('f', 'force', None, _('run even when remote repository is unrelated')),
3955 3950 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3956 3951 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3957 3952 ('b', 'branch', [], _('a specific branch you would like to pull'),
3958 3953 _('BRANCH')),
3959 3954 ] + remoteopts,
3960 3955 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3961 3956 def pull(ui, repo, source="default", **opts):
3962 3957 """pull changes from the specified source
3963 3958
3964 3959 Pull changes from a remote repository to a local one.
3965 3960
3966 3961 This finds all changes from the repository at the specified path
3967 3962 or URL and adds them to a local repository (the current one unless
3968 3963 -R is specified). By default, this does not update the copy of the
3969 3964 project in the working directory.
3970 3965
3971 3966 Use :hg:`incoming` if you want to see what would have been added
3972 3967 by a pull at the time you issued this command. If you then decide
3973 3968 to add those changes to the repository, you should use :hg:`pull
3974 3969 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3975 3970
3976 3971 If SOURCE is omitted, the 'default' path will be used.
3977 3972 See :hg:`help urls` for more information.
3978 3973
3979 3974 Specifying bookmark as ``.`` is equivalent to specifying the active
3980 3975 bookmark's name.
3981 3976
3982 3977 Returns 0 on success, 1 if an update had unresolved files.
3983 3978 """
3984 3979
3985 3980 opts = pycompat.byteskwargs(opts)
3986 3981 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3987 3982 msg = _('update destination required by configuration')
3988 3983 hint = _('use hg pull followed by hg update DEST')
3989 3984 raise error.Abort(msg, hint=hint)
3990 3985
3991 3986 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3992 3987 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3993 3988 other = hg.peer(repo, opts, source)
3994 3989 try:
3995 3990 revs, checkout = hg.addbranchrevs(repo, other, branches,
3996 3991 opts.get('rev'))
3997 3992
3998 3993
3999 3994 pullopargs = {}
4000 3995 if opts.get('bookmark'):
4001 3996 if not revs:
4002 3997 revs = []
4003 3998 # The list of bookmark used here is not the one used to actually
4004 3999 # update the bookmark name. This can result in the revision pulled
4005 4000 # not ending up with the name of the bookmark because of a race
4006 4001 # condition on the server. (See issue 4689 for details)
4007 4002 remotebookmarks = other.listkeys('bookmarks')
4008 4003 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4009 4004 pullopargs['remotebookmarks'] = remotebookmarks
4010 4005 for b in opts['bookmark']:
4011 4006 b = repo._bookmarks.expandname(b)
4012 4007 if b not in remotebookmarks:
4013 4008 raise error.Abort(_('remote bookmark %s not found!') % b)
4014 4009 revs.append(hex(remotebookmarks[b]))
4015 4010
4016 4011 if revs:
4017 4012 try:
4018 4013 # When 'rev' is a bookmark name, we cannot guarantee that it
4019 4014 # will be updated with that name because of a race condition
4020 4015 # server side. (See issue 4689 for details)
4021 4016 oldrevs = revs
4022 4017 revs = [] # actually, nodes
4023 4018 for r in oldrevs:
4024 4019 node = other.lookup(r)
4025 4020 revs.append(node)
4026 4021 if r == checkout:
4027 4022 checkout = node
4028 4023 except error.CapabilityError:
4029 4024 err = _("other repository doesn't support revision lookup, "
4030 4025 "so a rev cannot be specified.")
4031 4026 raise error.Abort(err)
4032 4027
4033 4028 wlock = util.nullcontextmanager()
4034 4029 if opts.get('update'):
4035 4030 wlock = repo.wlock()
4036 4031 with wlock:
4037 4032 pullopargs.update(opts.get('opargs', {}))
4038 4033 modheads = exchange.pull(repo, other, heads=revs,
4039 4034 force=opts.get('force'),
4040 4035 bookmarks=opts.get('bookmark', ()),
4041 4036 opargs=pullopargs).cgresult
4042 4037
4043 4038 # brev is a name, which might be a bookmark to be activated at
4044 4039 # the end of the update. In other words, it is an explicit
4045 4040 # destination of the update
4046 4041 brev = None
4047 4042
4048 4043 if checkout:
4049 4044 checkout = str(repo.changelog.rev(checkout))
4050 4045
4051 4046 # order below depends on implementation of
4052 4047 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4053 4048 # because 'checkout' is determined without it.
4054 4049 if opts.get('rev'):
4055 4050 brev = opts['rev'][0]
4056 4051 elif opts.get('branch'):
4057 4052 brev = opts['branch'][0]
4058 4053 else:
4059 4054 brev = branches[0]
4060 4055 repo._subtoppath = source
4061 4056 try:
4062 4057 ret = postincoming(ui, repo, modheads, opts.get('update'),
4063 4058 checkout, brev)
4064 4059
4065 4060 finally:
4066 4061 del repo._subtoppath
4067 4062
4068 4063 finally:
4069 4064 other.close()
4070 4065 return ret
4071 4066
4072 4067 @command('^push',
4073 4068 [('f', 'force', None, _('force push')),
4074 4069 ('r', 'rev', [],
4075 4070 _('a changeset intended to be included in the destination'),
4076 4071 _('REV')),
4077 4072 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4078 4073 ('b', 'branch', [],
4079 4074 _('a specific branch you would like to push'), _('BRANCH')),
4080 4075 ('', 'new-branch', False, _('allow pushing a new branch')),
4081 4076 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4082 4077 ] + remoteopts,
4083 4078 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4084 4079 def push(ui, repo, dest=None, **opts):
4085 4080 """push changes to the specified destination
4086 4081
4087 4082 Push changesets from the local repository to the specified
4088 4083 destination.
4089 4084
4090 4085 This operation is symmetrical to pull: it is identical to a pull
4091 4086 in the destination repository from the current one.
4092 4087
4093 4088 By default, push will not allow creation of new heads at the
4094 4089 destination, since multiple heads would make it unclear which head
4095 4090 to use. In this situation, it is recommended to pull and merge
4096 4091 before pushing.
4097 4092
4098 4093 Use --new-branch if you want to allow push to create a new named
4099 4094 branch that is not present at the destination. This allows you to
4100 4095 only create a new branch without forcing other changes.
4101 4096
4102 4097 .. note::
4103 4098
4104 4099 Extra care should be taken with the -f/--force option,
4105 4100 which will push all new heads on all branches, an action which will
4106 4101 almost always cause confusion for collaborators.
4107 4102
4108 4103 If -r/--rev is used, the specified revision and all its ancestors
4109 4104 will be pushed to the remote repository.
4110 4105
4111 4106 If -B/--bookmark is used, the specified bookmarked revision, its
4112 4107 ancestors, and the bookmark will be pushed to the remote
4113 4108 repository. Specifying ``.`` is equivalent to specifying the active
4114 4109 bookmark's name.
4115 4110
4116 4111 Please see :hg:`help urls` for important details about ``ssh://``
4117 4112 URLs. If DESTINATION is omitted, a default path will be used.
4118 4113
4119 4114 .. container:: verbose
4120 4115
4121 4116 The --pushvars option sends strings to the server that become
4122 4117 environment variables prepended with ``HG_USERVAR_``. For example,
4123 4118 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4124 4119 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4125 4120
4126 4121 pushvars can provide for user-overridable hooks as well as set debug
4127 4122 levels. One example is having a hook that blocks commits containing
4128 4123 conflict markers, but enables the user to override the hook if the file
4129 4124 is using conflict markers for testing purposes or the file format has
4130 4125 strings that look like conflict markers.
4131 4126
4132 4127 By default, servers will ignore `--pushvars`. To enable it add the
4133 4128 following to your configuration file::
4134 4129
4135 4130 [push]
4136 4131 pushvars.server = true
4137 4132
4138 4133 Returns 0 if push was successful, 1 if nothing to push.
4139 4134 """
4140 4135
4141 4136 opts = pycompat.byteskwargs(opts)
4142 4137 if opts.get('bookmark'):
4143 4138 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4144 4139 for b in opts['bookmark']:
4145 4140 # translate -B options to -r so changesets get pushed
4146 4141 b = repo._bookmarks.expandname(b)
4147 4142 if b in repo._bookmarks:
4148 4143 opts.setdefault('rev', []).append(b)
4149 4144 else:
4150 4145 # if we try to push a deleted bookmark, translate it to null
4151 4146 # this lets simultaneous -r, -b options continue working
4152 4147 opts.setdefault('rev', []).append("null")
4153 4148
4154 4149 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4155 4150 if not path:
4156 4151 raise error.Abort(_('default repository not configured!'),
4157 4152 hint=_("see 'hg help config.paths'"))
4158 4153 dest = path.pushloc or path.loc
4159 4154 branches = (path.branch, opts.get('branch') or [])
4160 4155 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4161 4156 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4162 4157 other = hg.peer(repo, opts, dest)
4163 4158
4164 4159 if revs:
4165 4160 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4166 4161 if not revs:
4167 4162 raise error.Abort(_("specified revisions evaluate to an empty set"),
4168 4163 hint=_("use different revision arguments"))
4169 4164 elif path.pushrev:
4170 4165 # It doesn't make any sense to specify ancestor revisions. So limit
4171 4166 # to DAG heads to make discovery simpler.
4172 4167 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4173 4168 revs = scmutil.revrange(repo, [expr])
4174 4169 revs = [repo[rev].node() for rev in revs]
4175 4170 if not revs:
4176 4171 raise error.Abort(_('default push revset for path evaluates to an '
4177 4172 'empty set'))
4178 4173
4179 4174 repo._subtoppath = dest
4180 4175 try:
4181 4176 # push subrepos depth-first for coherent ordering
4182 4177 c = repo['']
4183 4178 subs = c.substate # only repos that are committed
4184 4179 for s in sorted(subs):
4185 4180 result = c.sub(s).push(opts)
4186 4181 if result == 0:
4187 4182 return not result
4188 4183 finally:
4189 4184 del repo._subtoppath
4190 4185
4191 4186 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4192 4187 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4193 4188
4194 4189 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4195 4190 newbranch=opts.get('new_branch'),
4196 4191 bookmarks=opts.get('bookmark', ()),
4197 4192 opargs=opargs)
4198 4193
4199 4194 result = not pushop.cgresult
4200 4195
4201 4196 if pushop.bkresult is not None:
4202 4197 if pushop.bkresult == 2:
4203 4198 result = 2
4204 4199 elif not result and pushop.bkresult:
4205 4200 result = 2
4206 4201
4207 4202 return result
4208 4203
4209 4204 @command('recover', [])
4210 4205 def recover(ui, repo):
4211 4206 """roll back an interrupted transaction
4212 4207
4213 4208 Recover from an interrupted commit or pull.
4214 4209
4215 4210 This command tries to fix the repository status after an
4216 4211 interrupted operation. It should only be necessary when Mercurial
4217 4212 suggests it.
4218 4213
4219 4214 Returns 0 if successful, 1 if nothing to recover or verify fails.
4220 4215 """
4221 4216 if repo.recover():
4222 4217 return hg.verify(repo)
4223 4218 return 1
4224 4219
4225 4220 @command('^remove|rm',
4226 4221 [('A', 'after', None, _('record delete for missing files')),
4227 4222 ('f', 'force', None,
4228 4223 _('forget added files, delete modified files')),
4229 4224 ] + subrepoopts + walkopts,
4230 4225 _('[OPTION]... FILE...'),
4231 4226 inferrepo=True)
4232 4227 def remove(ui, repo, *pats, **opts):
4233 4228 """remove the specified files on the next commit
4234 4229
4235 4230 Schedule the indicated files for removal from the current branch.
4236 4231
4237 4232 This command schedules the files to be removed at the next commit.
4238 4233 To undo a remove before that, see :hg:`revert`. To undo added
4239 4234 files, see :hg:`forget`.
4240 4235
4241 4236 .. container:: verbose
4242 4237
4243 4238 -A/--after can be used to remove only files that have already
4244 4239 been deleted, -f/--force can be used to force deletion, and -Af
4245 4240 can be used to remove files from the next revision without
4246 4241 deleting them from the working directory.
4247 4242
4248 4243 The following table details the behavior of remove for different
4249 4244 file states (columns) and option combinations (rows). The file
4250 4245 states are Added [A], Clean [C], Modified [M] and Missing [!]
4251 4246 (as reported by :hg:`status`). The actions are Warn, Remove
4252 4247 (from branch) and Delete (from disk):
4253 4248
4254 4249 ========= == == == ==
4255 4250 opt/state A C M !
4256 4251 ========= == == == ==
4257 4252 none W RD W R
4258 4253 -f R RD RD R
4259 4254 -A W W W R
4260 4255 -Af R R R R
4261 4256 ========= == == == ==
4262 4257
4263 4258 .. note::
4264 4259
4265 4260 :hg:`remove` never deletes files in Added [A] state from the
4266 4261 working directory, not even if ``--force`` is specified.
4267 4262
4268 4263 Returns 0 on success, 1 if any warnings encountered.
4269 4264 """
4270 4265
4271 4266 opts = pycompat.byteskwargs(opts)
4272 4267 after, force = opts.get('after'), opts.get('force')
4273 4268 if not pats and not after:
4274 4269 raise error.Abort(_('no files specified'))
4275 4270
4276 4271 m = scmutil.match(repo[None], pats, opts)
4277 4272 subrepos = opts.get('subrepos')
4278 4273 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4279 4274
4280 4275 @command('rename|move|mv',
4281 4276 [('A', 'after', None, _('record a rename that has already occurred')),
4282 4277 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4283 4278 ] + walkopts + dryrunopts,
4284 4279 _('[OPTION]... SOURCE... DEST'))
4285 4280 def rename(ui, repo, *pats, **opts):
4286 4281 """rename files; equivalent of copy + remove
4287 4282
4288 4283 Mark dest as copies of sources; mark sources for deletion. If dest
4289 4284 is a directory, copies are put in that directory. If dest is a
4290 4285 file, there can only be one source.
4291 4286
4292 4287 By default, this command copies the contents of files as they
4293 4288 exist in the working directory. If invoked with -A/--after, the
4294 4289 operation is recorded, but no copying is performed.
4295 4290
4296 4291 This command takes effect at the next commit. To undo a rename
4297 4292 before that, see :hg:`revert`.
4298 4293
4299 4294 Returns 0 on success, 1 if errors are encountered.
4300 4295 """
4301 4296 opts = pycompat.byteskwargs(opts)
4302 4297 with repo.wlock(False):
4303 4298 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4304 4299
4305 4300 @command('resolve',
4306 4301 [('a', 'all', None, _('select all unresolved files')),
4307 4302 ('l', 'list', None, _('list state of files needing merge')),
4308 4303 ('m', 'mark', None, _('mark files as resolved')),
4309 4304 ('u', 'unmark', None, _('mark files as unresolved')),
4310 4305 ('n', 'no-status', None, _('hide status prefix'))]
4311 4306 + mergetoolopts + walkopts + formatteropts,
4312 4307 _('[OPTION]... [FILE]...'),
4313 4308 inferrepo=True)
4314 4309 def resolve(ui, repo, *pats, **opts):
4315 4310 """redo merges or set/view the merge status of files
4316 4311
4317 4312 Merges with unresolved conflicts are often the result of
4318 4313 non-interactive merging using the ``internal:merge`` configuration
4319 4314 setting, or a command-line merge tool like ``diff3``. The resolve
4320 4315 command is used to manage the files involved in a merge, after
4321 4316 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4322 4317 working directory must have two parents). See :hg:`help
4323 4318 merge-tools` for information on configuring merge tools.
4324 4319
4325 4320 The resolve command can be used in the following ways:
4326 4321
4327 4322 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4328 4323 files, discarding any previous merge attempts. Re-merging is not
4329 4324 performed for files already marked as resolved. Use ``--all/-a``
4330 4325 to select all unresolved files. ``--tool`` can be used to specify
4331 4326 the merge tool used for the given files. It overrides the HGMERGE
4332 4327 environment variable and your configuration files. Previous file
4333 4328 contents are saved with a ``.orig`` suffix.
4334 4329
4335 4330 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4336 4331 (e.g. after having manually fixed-up the files). The default is
4337 4332 to mark all unresolved files.
4338 4333
4339 4334 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4340 4335 default is to mark all resolved files.
4341 4336
4342 4337 - :hg:`resolve -l`: list files which had or still have conflicts.
4343 4338 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4344 4339 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4345 4340 the list. See :hg:`help filesets` for details.
4346 4341
4347 4342 .. note::
4348 4343
4349 4344 Mercurial will not let you commit files with unresolved merge
4350 4345 conflicts. You must use :hg:`resolve -m ...` before you can
4351 4346 commit after a conflicting merge.
4352 4347
4353 4348 Returns 0 on success, 1 if any files fail a resolve attempt.
4354 4349 """
4355 4350
4356 4351 opts = pycompat.byteskwargs(opts)
4357 4352 flaglist = 'all mark unmark list no_status'.split()
4358 4353 all, mark, unmark, show, nostatus = \
4359 4354 [opts.get(o) for o in flaglist]
4360 4355
4361 4356 if (show and (mark or unmark)) or (mark and unmark):
4362 4357 raise error.Abort(_("too many options specified"))
4363 4358 if pats and all:
4364 4359 raise error.Abort(_("can't specify --all and patterns"))
4365 4360 if not (all or pats or show or mark or unmark):
4366 4361 raise error.Abort(_('no files or directories specified'),
4367 4362 hint=('use --all to re-merge all unresolved files'))
4368 4363
4369 4364 if show:
4370 4365 ui.pager('resolve')
4371 4366 fm = ui.formatter('resolve', opts)
4372 4367 ms = mergemod.mergestate.read(repo)
4373 4368 m = scmutil.match(repo[None], pats, opts)
4374 4369
4375 4370 # Labels and keys based on merge state. Unresolved path conflicts show
4376 4371 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4377 4372 # resolved conflicts.
4378 4373 mergestateinfo = {
4379 4374 'u': ('resolve.unresolved', 'U'),
4380 4375 'r': ('resolve.resolved', 'R'),
4381 4376 'pu': ('resolve.unresolved', 'P'),
4382 4377 'pr': ('resolve.resolved', 'R'),
4383 4378 'd': ('resolve.driverresolved', 'D'),
4384 4379 }
4385 4380
4386 4381 for f in ms:
4387 4382 if not m(f):
4388 4383 continue
4389 4384
4390 4385 label, key = mergestateinfo[ms[f]]
4391 4386 fm.startitem()
4392 4387 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4393 4388 fm.write('path', '%s\n', f, label=label)
4394 4389 fm.end()
4395 4390 return 0
4396 4391
4397 4392 with repo.wlock():
4398 4393 ms = mergemod.mergestate.read(repo)
4399 4394
4400 4395 if not (ms.active() or repo.dirstate.p2() != nullid):
4401 4396 raise error.Abort(
4402 4397 _('resolve command not applicable when not merging'))
4403 4398
4404 4399 wctx = repo[None]
4405 4400
4406 4401 if ms.mergedriver and ms.mdstate() == 'u':
4407 4402 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4408 4403 ms.commit()
4409 4404 # allow mark and unmark to go through
4410 4405 if not mark and not unmark and not proceed:
4411 4406 return 1
4412 4407
4413 4408 m = scmutil.match(wctx, pats, opts)
4414 4409 ret = 0
4415 4410 didwork = False
4416 4411 runconclude = False
4417 4412
4418 4413 tocomplete = []
4419 4414 for f in ms:
4420 4415 if not m(f):
4421 4416 continue
4422 4417
4423 4418 didwork = True
4424 4419
4425 4420 # don't let driver-resolved files be marked, and run the conclude
4426 4421 # step if asked to resolve
4427 4422 if ms[f] == "d":
4428 4423 exact = m.exact(f)
4429 4424 if mark:
4430 4425 if exact:
4431 4426 ui.warn(_('not marking %s as it is driver-resolved\n')
4432 4427 % f)
4433 4428 elif unmark:
4434 4429 if exact:
4435 4430 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4436 4431 % f)
4437 4432 else:
4438 4433 runconclude = True
4439 4434 continue
4440 4435
4441 4436 # path conflicts must be resolved manually
4442 4437 if ms[f] in ("pu", "pr"):
4443 4438 if mark:
4444 4439 ms.mark(f, "pr")
4445 4440 elif unmark:
4446 4441 ms.mark(f, "pu")
4447 4442 elif ms[f] == "pu":
4448 4443 ui.warn(_('%s: path conflict must be resolved manually\n')
4449 4444 % f)
4450 4445 continue
4451 4446
4452 4447 if mark:
4453 4448 ms.mark(f, "r")
4454 4449 elif unmark:
4455 4450 ms.mark(f, "u")
4456 4451 else:
4457 4452 # backup pre-resolve (merge uses .orig for its own purposes)
4458 4453 a = repo.wjoin(f)
4459 4454 try:
4460 4455 util.copyfile(a, a + ".resolve")
4461 4456 except (IOError, OSError) as inst:
4462 4457 if inst.errno != errno.ENOENT:
4463 4458 raise
4464 4459
4465 4460 try:
4466 4461 # preresolve file
4467 4462 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4468 4463 'resolve')
4469 4464 complete, r = ms.preresolve(f, wctx)
4470 4465 if not complete:
4471 4466 tocomplete.append(f)
4472 4467 elif r:
4473 4468 ret = 1
4474 4469 finally:
4475 4470 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4476 4471 ms.commit()
4477 4472
4478 4473 # replace filemerge's .orig file with our resolve file, but only
4479 4474 # for merges that are complete
4480 4475 if complete:
4481 4476 try:
4482 4477 util.rename(a + ".resolve",
4483 4478 scmutil.origpath(ui, repo, a))
4484 4479 except OSError as inst:
4485 4480 if inst.errno != errno.ENOENT:
4486 4481 raise
4487 4482
4488 4483 for f in tocomplete:
4489 4484 try:
4490 4485 # resolve file
4491 4486 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4492 4487 'resolve')
4493 4488 r = ms.resolve(f, wctx)
4494 4489 if r:
4495 4490 ret = 1
4496 4491 finally:
4497 4492 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4498 4493 ms.commit()
4499 4494
4500 4495 # replace filemerge's .orig file with our resolve file
4501 4496 a = repo.wjoin(f)
4502 4497 try:
4503 4498 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4504 4499 except OSError as inst:
4505 4500 if inst.errno != errno.ENOENT:
4506 4501 raise
4507 4502
4508 4503 ms.commit()
4509 4504 ms.recordactions()
4510 4505
4511 4506 if not didwork and pats:
4512 4507 hint = None
4513 4508 if not any([p for p in pats if p.find(':') >= 0]):
4514 4509 pats = ['path:%s' % p for p in pats]
4515 4510 m = scmutil.match(wctx, pats, opts)
4516 4511 for f in ms:
4517 4512 if not m(f):
4518 4513 continue
4519 4514 flags = ''.join(['-%s ' % o[0] for o in flaglist
4520 4515 if opts.get(o)])
4521 4516 hint = _("(try: hg resolve %s%s)\n") % (
4522 4517 flags,
4523 4518 ' '.join(pats))
4524 4519 break
4525 4520 ui.warn(_("arguments do not match paths that need resolving\n"))
4526 4521 if hint:
4527 4522 ui.warn(hint)
4528 4523 elif ms.mergedriver and ms.mdstate() != 's':
4529 4524 # run conclude step when either a driver-resolved file is requested
4530 4525 # or there are no driver-resolved files
4531 4526 # we can't use 'ret' to determine whether any files are unresolved
4532 4527 # because we might not have tried to resolve some
4533 4528 if ((runconclude or not list(ms.driverresolved()))
4534 4529 and not list(ms.unresolved())):
4535 4530 proceed = mergemod.driverconclude(repo, ms, wctx)
4536 4531 ms.commit()
4537 4532 if not proceed:
4538 4533 return 1
4539 4534
4540 4535 # Nudge users into finishing an unfinished operation
4541 4536 unresolvedf = list(ms.unresolved())
4542 4537 driverresolvedf = list(ms.driverresolved())
4543 4538 if not unresolvedf and not driverresolvedf:
4544 4539 ui.status(_('(no more unresolved files)\n'))
4545 4540 cmdutil.checkafterresolved(repo)
4546 4541 elif not unresolvedf:
4547 4542 ui.status(_('(no more unresolved files -- '
4548 4543 'run "hg resolve --all" to conclude)\n'))
4549 4544
4550 4545 return ret
4551 4546
4552 4547 @command('revert',
4553 4548 [('a', 'all', None, _('revert all changes when no arguments given')),
4554 4549 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4555 4550 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4556 4551 ('C', 'no-backup', None, _('do not save backup copies of files')),
4557 4552 ('i', 'interactive', None, _('interactively select the changes')),
4558 4553 ] + walkopts + dryrunopts,
4559 4554 _('[OPTION]... [-r REV] [NAME]...'))
4560 4555 def revert(ui, repo, *pats, **opts):
4561 4556 """restore files to their checkout state
4562 4557
4563 4558 .. note::
4564 4559
4565 4560 To check out earlier revisions, you should use :hg:`update REV`.
4566 4561 To cancel an uncommitted merge (and lose your changes),
4567 4562 use :hg:`update --clean .`.
4568 4563
4569 4564 With no revision specified, revert the specified files or directories
4570 4565 to the contents they had in the parent of the working directory.
4571 4566 This restores the contents of files to an unmodified
4572 4567 state and unschedules adds, removes, copies, and renames. If the
4573 4568 working directory has two parents, you must explicitly specify a
4574 4569 revision.
4575 4570
4576 4571 Using the -r/--rev or -d/--date options, revert the given files or
4577 4572 directories to their states as of a specific revision. Because
4578 4573 revert does not change the working directory parents, this will
4579 4574 cause these files to appear modified. This can be helpful to "back
4580 4575 out" some or all of an earlier change. See :hg:`backout` for a
4581 4576 related method.
4582 4577
4583 4578 Modified files are saved with a .orig suffix before reverting.
4584 4579 To disable these backups, use --no-backup. It is possible to store
4585 4580 the backup files in a custom directory relative to the root of the
4586 4581 repository by setting the ``ui.origbackuppath`` configuration
4587 4582 option.
4588 4583
4589 4584 See :hg:`help dates` for a list of formats valid for -d/--date.
4590 4585
4591 4586 See :hg:`help backout` for a way to reverse the effect of an
4592 4587 earlier changeset.
4593 4588
4594 4589 Returns 0 on success.
4595 4590 """
4596 4591
4597 4592 opts = pycompat.byteskwargs(opts)
4598 4593 if opts.get("date"):
4599 4594 if opts.get("rev"):
4600 4595 raise error.Abort(_("you can't specify a revision and a date"))
4601 4596 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4602 4597
4603 4598 parent, p2 = repo.dirstate.parents()
4604 4599 if not opts.get('rev') and p2 != nullid:
4605 4600 # revert after merge is a trap for new users (issue2915)
4606 4601 raise error.Abort(_('uncommitted merge with no revision specified'),
4607 4602 hint=_("use 'hg update' or see 'hg help revert'"))
4608 4603
4609 4604 rev = opts.get('rev')
4610 4605 if rev:
4611 4606 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4612 4607 ctx = scmutil.revsingle(repo, rev)
4613 4608
4614 4609 if (not (pats or opts.get('include') or opts.get('exclude') or
4615 4610 opts.get('all') or opts.get('interactive'))):
4616 4611 msg = _("no files or directories specified")
4617 4612 if p2 != nullid:
4618 4613 hint = _("uncommitted merge, use --all to discard all changes,"
4619 4614 " or 'hg update -C .' to abort the merge")
4620 4615 raise error.Abort(msg, hint=hint)
4621 4616 dirty = any(repo.status())
4622 4617 node = ctx.node()
4623 4618 if node != parent:
4624 4619 if dirty:
4625 4620 hint = _("uncommitted changes, use --all to discard all"
4626 4621 " changes, or 'hg update %s' to update") % ctx.rev()
4627 4622 else:
4628 4623 hint = _("use --all to revert all files,"
4629 4624 " or 'hg update %s' to update") % ctx.rev()
4630 4625 elif dirty:
4631 4626 hint = _("uncommitted changes, use --all to discard all changes")
4632 4627 else:
4633 4628 hint = _("use --all to revert all files")
4634 4629 raise error.Abort(msg, hint=hint)
4635 4630
4636 4631 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4637 4632 **pycompat.strkwargs(opts))
4638 4633
4639 4634 @command('rollback', dryrunopts +
4640 4635 [('f', 'force', False, _('ignore safety measures'))])
4641 4636 def rollback(ui, repo, **opts):
4642 4637 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4643 4638
4644 4639 Please use :hg:`commit --amend` instead of rollback to correct
4645 4640 mistakes in the last commit.
4646 4641
4647 4642 This command should be used with care. There is only one level of
4648 4643 rollback, and there is no way to undo a rollback. It will also
4649 4644 restore the dirstate at the time of the last transaction, losing
4650 4645 any dirstate changes since that time. This command does not alter
4651 4646 the working directory.
4652 4647
4653 4648 Transactions are used to encapsulate the effects of all commands
4654 4649 that create new changesets or propagate existing changesets into a
4655 4650 repository.
4656 4651
4657 4652 .. container:: verbose
4658 4653
4659 4654 For example, the following commands are transactional, and their
4660 4655 effects can be rolled back:
4661 4656
4662 4657 - commit
4663 4658 - import
4664 4659 - pull
4665 4660 - push (with this repository as the destination)
4666 4661 - unbundle
4667 4662
4668 4663 To avoid permanent data loss, rollback will refuse to rollback a
4669 4664 commit transaction if it isn't checked out. Use --force to
4670 4665 override this protection.
4671 4666
4672 4667 The rollback command can be entirely disabled by setting the
4673 4668 ``ui.rollback`` configuration setting to false. If you're here
4674 4669 because you want to use rollback and it's disabled, you can
4675 4670 re-enable the command by setting ``ui.rollback`` to true.
4676 4671
4677 4672 This command is not intended for use on public repositories. Once
4678 4673 changes are visible for pull by other users, rolling a transaction
4679 4674 back locally is ineffective (someone else may already have pulled
4680 4675 the changes). Furthermore, a race is possible with readers of the
4681 4676 repository; for example an in-progress pull from the repository
4682 4677 may fail if a rollback is performed.
4683 4678
4684 4679 Returns 0 on success, 1 if no rollback data is available.
4685 4680 """
4686 4681 if not ui.configbool('ui', 'rollback'):
4687 4682 raise error.Abort(_('rollback is disabled because it is unsafe'),
4688 4683 hint=('see `hg help -v rollback` for information'))
4689 4684 return repo.rollback(dryrun=opts.get(r'dry_run'),
4690 4685 force=opts.get(r'force'))
4691 4686
4692 4687 @command('root', [], cmdtype=readonly)
4693 4688 def root(ui, repo):
4694 4689 """print the root (top) of the current working directory
4695 4690
4696 4691 Print the root directory of the current repository.
4697 4692
4698 4693 Returns 0 on success.
4699 4694 """
4700 4695 ui.write(repo.root + "\n")
4701 4696
4702 4697 @command('^serve',
4703 4698 [('A', 'accesslog', '', _('name of access log file to write to'),
4704 4699 _('FILE')),
4705 4700 ('d', 'daemon', None, _('run server in background')),
4706 4701 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4707 4702 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4708 4703 # use string type, then we can check if something was passed
4709 4704 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4710 4705 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4711 4706 _('ADDR')),
4712 4707 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4713 4708 _('PREFIX')),
4714 4709 ('n', 'name', '',
4715 4710 _('name to show in web pages (default: working directory)'), _('NAME')),
4716 4711 ('', 'web-conf', '',
4717 4712 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4718 4713 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4719 4714 _('FILE')),
4720 4715 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4721 4716 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4722 4717 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4723 4718 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4724 4719 ('', 'style', '', _('template style to use'), _('STYLE')),
4725 4720 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4726 4721 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4727 4722 + subrepoopts,
4728 4723 _('[OPTION]...'),
4729 4724 optionalrepo=True)
4730 4725 def serve(ui, repo, **opts):
4731 4726 """start stand-alone webserver
4732 4727
4733 4728 Start a local HTTP repository browser and pull server. You can use
4734 4729 this for ad-hoc sharing and browsing of repositories. It is
4735 4730 recommended to use a real web server to serve a repository for
4736 4731 longer periods of time.
4737 4732
4738 4733 Please note that the server does not implement access control.
4739 4734 This means that, by default, anybody can read from the server and
4740 4735 nobody can write to it by default. Set the ``web.allow-push``
4741 4736 option to ``*`` to allow everybody to push to the server. You
4742 4737 should use a real web server if you need to authenticate users.
4743 4738
4744 4739 By default, the server logs accesses to stdout and errors to
4745 4740 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4746 4741 files.
4747 4742
4748 4743 To have the server choose a free port number to listen on, specify
4749 4744 a port number of 0; in this case, the server will print the port
4750 4745 number it uses.
4751 4746
4752 4747 Returns 0 on success.
4753 4748 """
4754 4749
4755 4750 opts = pycompat.byteskwargs(opts)
4756 4751 if opts["stdio"] and opts["cmdserver"]:
4757 4752 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4758 4753
4759 4754 if opts["stdio"]:
4760 4755 if repo is None:
4761 4756 raise error.RepoError(_("there is no Mercurial repository here"
4762 4757 " (.hg not found)"))
4763 4758 s = sshserver.sshserver(ui, repo)
4764 4759 s.serve_forever()
4765 4760
4766 4761 service = server.createservice(ui, repo, opts)
4767 4762 return server.runservice(opts, initfn=service.init, runfn=service.run)
4768 4763
4769 4764 @command('^status|st',
4770 4765 [('A', 'all', None, _('show status of all files')),
4771 4766 ('m', 'modified', None, _('show only modified files')),
4772 4767 ('a', 'added', None, _('show only added files')),
4773 4768 ('r', 'removed', None, _('show only removed files')),
4774 4769 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4775 4770 ('c', 'clean', None, _('show only files without changes')),
4776 4771 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4777 4772 ('i', 'ignored', None, _('show only ignored files')),
4778 4773 ('n', 'no-status', None, _('hide status prefix')),
4779 4774 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4780 4775 ('C', 'copies', None, _('show source of copied files')),
4781 4776 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4782 4777 ('', 'rev', [], _('show difference from revision'), _('REV')),
4783 4778 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4784 4779 ] + walkopts + subrepoopts + formatteropts,
4785 4780 _('[OPTION]... [FILE]...'),
4786 4781 inferrepo=True, cmdtype=readonly)
4787 4782 def status(ui, repo, *pats, **opts):
4788 4783 """show changed files in the working directory
4789 4784
4790 4785 Show status of files in the repository. If names are given, only
4791 4786 files that match are shown. Files that are clean or ignored or
4792 4787 the source of a copy/move operation, are not listed unless
4793 4788 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4794 4789 Unless options described with "show only ..." are given, the
4795 4790 options -mardu are used.
4796 4791
4797 4792 Option -q/--quiet hides untracked (unknown and ignored) files
4798 4793 unless explicitly requested with -u/--unknown or -i/--ignored.
4799 4794
4800 4795 .. note::
4801 4796
4802 4797 :hg:`status` may appear to disagree with diff if permissions have
4803 4798 changed or a merge has occurred. The standard diff format does
4804 4799 not report permission changes and diff only reports changes
4805 4800 relative to one merge parent.
4806 4801
4807 4802 If one revision is given, it is used as the base revision.
4808 4803 If two revisions are given, the differences between them are
4809 4804 shown. The --change option can also be used as a shortcut to list
4810 4805 the changed files of a revision from its first parent.
4811 4806
4812 4807 The codes used to show the status of files are::
4813 4808
4814 4809 M = modified
4815 4810 A = added
4816 4811 R = removed
4817 4812 C = clean
4818 4813 ! = missing (deleted by non-hg command, but still tracked)
4819 4814 ? = not tracked
4820 4815 I = ignored
4821 4816 = origin of the previous file (with --copies)
4822 4817
4823 4818 .. container:: verbose
4824 4819
4825 4820 The -t/--terse option abbreviates the output by showing only the directory
4826 4821 name if all the files in it share the same status. The option takes an
4827 4822 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4828 4823 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4829 4824 for 'ignored' and 'c' for clean.
4830 4825
4831 4826 It abbreviates only those statuses which are passed. Note that clean and
4832 4827 ignored files are not displayed with '--terse ic' unless the -c/--clean
4833 4828 and -i/--ignored options are also used.
4834 4829
4835 4830 The -v/--verbose option shows information when the repository is in an
4836 4831 unfinished merge, shelve, rebase state etc. You can have this behavior
4837 4832 turned on by default by enabling the ``commands.status.verbose`` option.
4838 4833
4839 4834 You can skip displaying some of these states by setting
4840 4835 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4841 4836 'histedit', 'merge', 'rebase', or 'unshelve'.
4842 4837
4843 4838 Examples:
4844 4839
4845 4840 - show changes in the working directory relative to a
4846 4841 changeset::
4847 4842
4848 4843 hg status --rev 9353
4849 4844
4850 4845 - show changes in the working directory relative to the
4851 4846 current directory (see :hg:`help patterns` for more information)::
4852 4847
4853 4848 hg status re:
4854 4849
4855 4850 - show all changes including copies in an existing changeset::
4856 4851
4857 4852 hg status --copies --change 9353
4858 4853
4859 4854 - get a NUL separated list of added files, suitable for xargs::
4860 4855
4861 4856 hg status -an0
4862 4857
4863 4858 - show more information about the repository status, abbreviating
4864 4859 added, removed, modified, deleted, and untracked paths::
4865 4860
4866 4861 hg status -v -t mardu
4867 4862
4868 4863 Returns 0 on success.
4869 4864
4870 4865 """
4871 4866
4872 4867 opts = pycompat.byteskwargs(opts)
4873 4868 revs = opts.get('rev')
4874 4869 change = opts.get('change')
4875 4870 terse = opts.get('terse')
4876 4871
4877 4872 if revs and change:
4878 4873 msg = _('cannot specify --rev and --change at the same time')
4879 4874 raise error.Abort(msg)
4880 4875 elif revs and terse:
4881 4876 msg = _('cannot use --terse with --rev')
4882 4877 raise error.Abort(msg)
4883 4878 elif change:
4884 4879 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4885 4880 node2 = scmutil.revsingle(repo, change, None).node()
4886 4881 node1 = repo[node2].p1().node()
4887 4882 else:
4888 4883 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4889 4884 node1, node2 = scmutil.revpair(repo, revs)
4890 4885
4891 4886 if pats or ui.configbool('commands', 'status.relative'):
4892 4887 cwd = repo.getcwd()
4893 4888 else:
4894 4889 cwd = ''
4895 4890
4896 4891 if opts.get('print0'):
4897 4892 end = '\0'
4898 4893 else:
4899 4894 end = '\n'
4900 4895 copy = {}
4901 4896 states = 'modified added removed deleted unknown ignored clean'.split()
4902 4897 show = [k for k in states if opts.get(k)]
4903 4898 if opts.get('all'):
4904 4899 show += ui.quiet and (states[:4] + ['clean']) or states
4905 4900
4906 4901 if not show:
4907 4902 if ui.quiet:
4908 4903 show = states[:4]
4909 4904 else:
4910 4905 show = states[:5]
4911 4906
4912 4907 m = scmutil.match(repo[node2], pats, opts)
4913 4908 if terse:
4914 4909 # we need to compute clean and unknown to terse
4915 4910 stat = repo.status(node1, node2, m,
4916 4911 'ignored' in show or 'i' in terse,
4917 4912 True, True, opts.get('subrepos'))
4918 4913
4919 4914 stat = cmdutil.tersedir(stat, terse)
4920 4915 else:
4921 4916 stat = repo.status(node1, node2, m,
4922 4917 'ignored' in show, 'clean' in show,
4923 4918 'unknown' in show, opts.get('subrepos'))
4924 4919
4925 4920 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4926 4921
4927 4922 if (opts.get('all') or opts.get('copies')
4928 4923 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4929 4924 copy = copies.pathcopies(repo[node1], repo[node2], m)
4930 4925
4931 4926 ui.pager('status')
4932 4927 fm = ui.formatter('status', opts)
4933 4928 fmt = '%s' + end
4934 4929 showchar = not opts.get('no_status')
4935 4930
4936 4931 for state, char, files in changestates:
4937 4932 if state in show:
4938 4933 label = 'status.' + state
4939 4934 for f in files:
4940 4935 fm.startitem()
4941 4936 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4942 4937 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4943 4938 if f in copy:
4944 4939 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4945 4940 label='status.copied')
4946 4941
4947 4942 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4948 4943 and not ui.plain()):
4949 4944 cmdutil.morestatus(repo, fm)
4950 4945 fm.end()
4951 4946
4952 4947 @command('^summary|sum',
4953 4948 [('', 'remote', None, _('check for push and pull'))],
4954 4949 '[--remote]', cmdtype=readonly)
4955 4950 def summary(ui, repo, **opts):
4956 4951 """summarize working directory state
4957 4952
4958 4953 This generates a brief summary of the working directory state,
4959 4954 including parents, branch, commit status, phase and available updates.
4960 4955
4961 4956 With the --remote option, this will check the default paths for
4962 4957 incoming and outgoing changes. This can be time-consuming.
4963 4958
4964 4959 Returns 0 on success.
4965 4960 """
4966 4961
4967 4962 opts = pycompat.byteskwargs(opts)
4968 4963 ui.pager('summary')
4969 4964 ctx = repo[None]
4970 4965 parents = ctx.parents()
4971 4966 pnode = parents[0].node()
4972 4967 marks = []
4973 4968
4974 4969 ms = None
4975 4970 try:
4976 4971 ms = mergemod.mergestate.read(repo)
4977 4972 except error.UnsupportedMergeRecords as e:
4978 4973 s = ' '.join(e.recordtypes)
4979 4974 ui.warn(
4980 4975 _('warning: merge state has unsupported record types: %s\n') % s)
4981 4976 unresolved = []
4982 4977 else:
4983 4978 unresolved = list(ms.unresolved())
4984 4979
4985 4980 for p in parents:
4986 4981 # label with log.changeset (instead of log.parent) since this
4987 4982 # shows a working directory parent *changeset*:
4988 4983 # i18n: column positioning for "hg summary"
4989 4984 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4990 4985 label=cmdutil._changesetlabels(p))
4991 4986 ui.write(' '.join(p.tags()), label='log.tag')
4992 4987 if p.bookmarks():
4993 4988 marks.extend(p.bookmarks())
4994 4989 if p.rev() == -1:
4995 4990 if not len(repo):
4996 4991 ui.write(_(' (empty repository)'))
4997 4992 else:
4998 4993 ui.write(_(' (no revision checked out)'))
4999 4994 if p.obsolete():
5000 4995 ui.write(_(' (obsolete)'))
5001 4996 if p.isunstable():
5002 4997 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5003 4998 for instability in p.instabilities())
5004 4999 ui.write(' ('
5005 5000 + ', '.join(instabilities)
5006 5001 + ')')
5007 5002 ui.write('\n')
5008 5003 if p.description():
5009 5004 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5010 5005 label='log.summary')
5011 5006
5012 5007 branch = ctx.branch()
5013 5008 bheads = repo.branchheads(branch)
5014 5009 # i18n: column positioning for "hg summary"
5015 5010 m = _('branch: %s\n') % branch
5016 5011 if branch != 'default':
5017 5012 ui.write(m, label='log.branch')
5018 5013 else:
5019 5014 ui.status(m, label='log.branch')
5020 5015
5021 5016 if marks:
5022 5017 active = repo._activebookmark
5023 5018 # i18n: column positioning for "hg summary"
5024 5019 ui.write(_('bookmarks:'), label='log.bookmark')
5025 5020 if active is not None:
5026 5021 if active in marks:
5027 5022 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5028 5023 marks.remove(active)
5029 5024 else:
5030 5025 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5031 5026 for m in marks:
5032 5027 ui.write(' ' + m, label='log.bookmark')
5033 5028 ui.write('\n', label='log.bookmark')
5034 5029
5035 5030 status = repo.status(unknown=True)
5036 5031
5037 5032 c = repo.dirstate.copies()
5038 5033 copied, renamed = [], []
5039 5034 for d, s in c.iteritems():
5040 5035 if s in status.removed:
5041 5036 status.removed.remove(s)
5042 5037 renamed.append(d)
5043 5038 else:
5044 5039 copied.append(d)
5045 5040 if d in status.added:
5046 5041 status.added.remove(d)
5047 5042
5048 5043 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5049 5044
5050 5045 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5051 5046 (ui.label(_('%d added'), 'status.added'), status.added),
5052 5047 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5053 5048 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5054 5049 (ui.label(_('%d copied'), 'status.copied'), copied),
5055 5050 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5056 5051 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5057 5052 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5058 5053 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5059 5054 t = []
5060 5055 for l, s in labels:
5061 5056 if s:
5062 5057 t.append(l % len(s))
5063 5058
5064 5059 t = ', '.join(t)
5065 5060 cleanworkdir = False
5066 5061
5067 5062 if repo.vfs.exists('graftstate'):
5068 5063 t += _(' (graft in progress)')
5069 5064 if repo.vfs.exists('updatestate'):
5070 5065 t += _(' (interrupted update)')
5071 5066 elif len(parents) > 1:
5072 5067 t += _(' (merge)')
5073 5068 elif branch != parents[0].branch():
5074 5069 t += _(' (new branch)')
5075 5070 elif (parents[0].closesbranch() and
5076 5071 pnode in repo.branchheads(branch, closed=True)):
5077 5072 t += _(' (head closed)')
5078 5073 elif not (status.modified or status.added or status.removed or renamed or
5079 5074 copied or subs):
5080 5075 t += _(' (clean)')
5081 5076 cleanworkdir = True
5082 5077 elif pnode not in bheads:
5083 5078 t += _(' (new branch head)')
5084 5079
5085 5080 if parents:
5086 5081 pendingphase = max(p.phase() for p in parents)
5087 5082 else:
5088 5083 pendingphase = phases.public
5089 5084
5090 5085 if pendingphase > phases.newcommitphase(ui):
5091 5086 t += ' (%s)' % phases.phasenames[pendingphase]
5092 5087
5093 5088 if cleanworkdir:
5094 5089 # i18n: column positioning for "hg summary"
5095 5090 ui.status(_('commit: %s\n') % t.strip())
5096 5091 else:
5097 5092 # i18n: column positioning for "hg summary"
5098 5093 ui.write(_('commit: %s\n') % t.strip())
5099 5094
5100 5095 # all ancestors of branch heads - all ancestors of parent = new csets
5101 5096 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5102 5097 bheads))
5103 5098
5104 5099 if new == 0:
5105 5100 # i18n: column positioning for "hg summary"
5106 5101 ui.status(_('update: (current)\n'))
5107 5102 elif pnode not in bheads:
5108 5103 # i18n: column positioning for "hg summary"
5109 5104 ui.write(_('update: %d new changesets (update)\n') % new)
5110 5105 else:
5111 5106 # i18n: column positioning for "hg summary"
5112 5107 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5113 5108 (new, len(bheads)))
5114 5109
5115 5110 t = []
5116 5111 draft = len(repo.revs('draft()'))
5117 5112 if draft:
5118 5113 t.append(_('%d draft') % draft)
5119 5114 secret = len(repo.revs('secret()'))
5120 5115 if secret:
5121 5116 t.append(_('%d secret') % secret)
5122 5117
5123 5118 if draft or secret:
5124 5119 ui.status(_('phases: %s\n') % ', '.join(t))
5125 5120
5126 5121 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5127 5122 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5128 5123 numtrouble = len(repo.revs(trouble + "()"))
5129 5124 # We write all the possibilities to ease translation
5130 5125 troublemsg = {
5131 5126 "orphan": _("orphan: %d changesets"),
5132 5127 "contentdivergent": _("content-divergent: %d changesets"),
5133 5128 "phasedivergent": _("phase-divergent: %d changesets"),
5134 5129 }
5135 5130 if numtrouble > 0:
5136 5131 ui.status(troublemsg[trouble] % numtrouble + "\n")
5137 5132
5138 5133 cmdutil.summaryhooks(ui, repo)
5139 5134
5140 5135 if opts.get('remote'):
5141 5136 needsincoming, needsoutgoing = True, True
5142 5137 else:
5143 5138 needsincoming, needsoutgoing = False, False
5144 5139 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5145 5140 if i:
5146 5141 needsincoming = True
5147 5142 if o:
5148 5143 needsoutgoing = True
5149 5144 if not needsincoming and not needsoutgoing:
5150 5145 return
5151 5146
5152 5147 def getincoming():
5153 5148 source, branches = hg.parseurl(ui.expandpath('default'))
5154 5149 sbranch = branches[0]
5155 5150 try:
5156 5151 other = hg.peer(repo, {}, source)
5157 5152 except error.RepoError:
5158 5153 if opts.get('remote'):
5159 5154 raise
5160 5155 return source, sbranch, None, None, None
5161 5156 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5162 5157 if revs:
5163 5158 revs = [other.lookup(rev) for rev in revs]
5164 5159 ui.debug('comparing with %s\n' % util.hidepassword(source))
5165 5160 repo.ui.pushbuffer()
5166 5161 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5167 5162 repo.ui.popbuffer()
5168 5163 return source, sbranch, other, commoninc, commoninc[1]
5169 5164
5170 5165 if needsincoming:
5171 5166 source, sbranch, sother, commoninc, incoming = getincoming()
5172 5167 else:
5173 5168 source = sbranch = sother = commoninc = incoming = None
5174 5169
5175 5170 def getoutgoing():
5176 5171 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5177 5172 dbranch = branches[0]
5178 5173 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5179 5174 if source != dest:
5180 5175 try:
5181 5176 dother = hg.peer(repo, {}, dest)
5182 5177 except error.RepoError:
5183 5178 if opts.get('remote'):
5184 5179 raise
5185 5180 return dest, dbranch, None, None
5186 5181 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5187 5182 elif sother is None:
5188 5183 # there is no explicit destination peer, but source one is invalid
5189 5184 return dest, dbranch, None, None
5190 5185 else:
5191 5186 dother = sother
5192 5187 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5193 5188 common = None
5194 5189 else:
5195 5190 common = commoninc
5196 5191 if revs:
5197 5192 revs = [repo.lookup(rev) for rev in revs]
5198 5193 repo.ui.pushbuffer()
5199 5194 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5200 5195 commoninc=common)
5201 5196 repo.ui.popbuffer()
5202 5197 return dest, dbranch, dother, outgoing
5203 5198
5204 5199 if needsoutgoing:
5205 5200 dest, dbranch, dother, outgoing = getoutgoing()
5206 5201 else:
5207 5202 dest = dbranch = dother = outgoing = None
5208 5203
5209 5204 if opts.get('remote'):
5210 5205 t = []
5211 5206 if incoming:
5212 5207 t.append(_('1 or more incoming'))
5213 5208 o = outgoing.missing
5214 5209 if o:
5215 5210 t.append(_('%d outgoing') % len(o))
5216 5211 other = dother or sother
5217 5212 if 'bookmarks' in other.listkeys('namespaces'):
5218 5213 counts = bookmarks.summary(repo, other)
5219 5214 if counts[0] > 0:
5220 5215 t.append(_('%d incoming bookmarks') % counts[0])
5221 5216 if counts[1] > 0:
5222 5217 t.append(_('%d outgoing bookmarks') % counts[1])
5223 5218
5224 5219 if t:
5225 5220 # i18n: column positioning for "hg summary"
5226 5221 ui.write(_('remote: %s\n') % (', '.join(t)))
5227 5222 else:
5228 5223 # i18n: column positioning for "hg summary"
5229 5224 ui.status(_('remote: (synced)\n'))
5230 5225
5231 5226 cmdutil.summaryremotehooks(ui, repo, opts,
5232 5227 ((source, sbranch, sother, commoninc),
5233 5228 (dest, dbranch, dother, outgoing)))
5234 5229
5235 5230 @command('tag',
5236 5231 [('f', 'force', None, _('force tag')),
5237 5232 ('l', 'local', None, _('make the tag local')),
5238 5233 ('r', 'rev', '', _('revision to tag'), _('REV')),
5239 5234 ('', 'remove', None, _('remove a tag')),
5240 5235 # -l/--local is already there, commitopts cannot be used
5241 5236 ('e', 'edit', None, _('invoke editor on commit messages')),
5242 5237 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5243 5238 ] + commitopts2,
5244 5239 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5245 5240 def tag(ui, repo, name1, *names, **opts):
5246 5241 """add one or more tags for the current or given revision
5247 5242
5248 5243 Name a particular revision using <name>.
5249 5244
5250 5245 Tags are used to name particular revisions of the repository and are
5251 5246 very useful to compare different revisions, to go back to significant
5252 5247 earlier versions or to mark branch points as releases, etc. Changing
5253 5248 an existing tag is normally disallowed; use -f/--force to override.
5254 5249
5255 5250 If no revision is given, the parent of the working directory is
5256 5251 used.
5257 5252
5258 5253 To facilitate version control, distribution, and merging of tags,
5259 5254 they are stored as a file named ".hgtags" which is managed similarly
5260 5255 to other project files and can be hand-edited if necessary. This
5261 5256 also means that tagging creates a new commit. The file
5262 5257 ".hg/localtags" is used for local tags (not shared among
5263 5258 repositories).
5264 5259
5265 5260 Tag commits are usually made at the head of a branch. If the parent
5266 5261 of the working directory is not a branch head, :hg:`tag` aborts; use
5267 5262 -f/--force to force the tag commit to be based on a non-head
5268 5263 changeset.
5269 5264
5270 5265 See :hg:`help dates` for a list of formats valid for -d/--date.
5271 5266
5272 5267 Since tag names have priority over branch names during revision
5273 5268 lookup, using an existing branch name as a tag name is discouraged.
5274 5269
5275 5270 Returns 0 on success.
5276 5271 """
5277 5272 opts = pycompat.byteskwargs(opts)
5278 5273 wlock = lock = None
5279 5274 try:
5280 5275 wlock = repo.wlock()
5281 5276 lock = repo.lock()
5282 5277 rev_ = "."
5283 5278 names = [t.strip() for t in (name1,) + names]
5284 5279 if len(names) != len(set(names)):
5285 5280 raise error.Abort(_('tag names must be unique'))
5286 5281 for n in names:
5287 5282 scmutil.checknewlabel(repo, n, 'tag')
5288 5283 if not n:
5289 5284 raise error.Abort(_('tag names cannot consist entirely of '
5290 5285 'whitespace'))
5291 5286 if opts.get('rev') and opts.get('remove'):
5292 5287 raise error.Abort(_("--rev and --remove are incompatible"))
5293 5288 if opts.get('rev'):
5294 5289 rev_ = opts['rev']
5295 5290 message = opts.get('message')
5296 5291 if opts.get('remove'):
5297 5292 if opts.get('local'):
5298 5293 expectedtype = 'local'
5299 5294 else:
5300 5295 expectedtype = 'global'
5301 5296
5302 5297 for n in names:
5303 5298 if not repo.tagtype(n):
5304 5299 raise error.Abort(_("tag '%s' does not exist") % n)
5305 5300 if repo.tagtype(n) != expectedtype:
5306 5301 if expectedtype == 'global':
5307 5302 raise error.Abort(_("tag '%s' is not a global tag") % n)
5308 5303 else:
5309 5304 raise error.Abort(_("tag '%s' is not a local tag") % n)
5310 5305 rev_ = 'null'
5311 5306 if not message:
5312 5307 # we don't translate commit messages
5313 5308 message = 'Removed tag %s' % ', '.join(names)
5314 5309 elif not opts.get('force'):
5315 5310 for n in names:
5316 5311 if n in repo.tags():
5317 5312 raise error.Abort(_("tag '%s' already exists "
5318 5313 "(use -f to force)") % n)
5319 5314 if not opts.get('local'):
5320 5315 p1, p2 = repo.dirstate.parents()
5321 5316 if p2 != nullid:
5322 5317 raise error.Abort(_('uncommitted merge'))
5323 5318 bheads = repo.branchheads()
5324 5319 if not opts.get('force') and bheads and p1 not in bheads:
5325 5320 raise error.Abort(_('working directory is not at a branch head '
5326 5321 '(use -f to force)'))
5327 5322 r = scmutil.revsingle(repo, rev_).node()
5328 5323
5329 5324 if not message:
5330 5325 # we don't translate commit messages
5331 5326 message = ('Added tag %s for changeset %s' %
5332 5327 (', '.join(names), short(r)))
5333 5328
5334 5329 date = opts.get('date')
5335 5330 if date:
5336 5331 date = util.parsedate(date)
5337 5332
5338 5333 if opts.get('remove'):
5339 5334 editform = 'tag.remove'
5340 5335 else:
5341 5336 editform = 'tag.add'
5342 5337 editor = cmdutil.getcommiteditor(editform=editform,
5343 5338 **pycompat.strkwargs(opts))
5344 5339
5345 5340 # don't allow tagging the null rev
5346 5341 if (not opts.get('remove') and
5347 5342 scmutil.revsingle(repo, rev_).rev() == nullrev):
5348 5343 raise error.Abort(_("cannot tag null revision"))
5349 5344
5350 5345 tagsmod.tag(repo, names, r, message, opts.get('local'),
5351 5346 opts.get('user'), date, editor=editor)
5352 5347 finally:
5353 5348 release(lock, wlock)
5354 5349
5355 5350 @command('tags', formatteropts, '', cmdtype=readonly)
5356 5351 def tags(ui, repo, **opts):
5357 5352 """list repository tags
5358 5353
5359 5354 This lists both regular and local tags. When the -v/--verbose
5360 5355 switch is used, a third column "local" is printed for local tags.
5361 5356 When the -q/--quiet switch is used, only the tag name is printed.
5362 5357
5363 5358 Returns 0 on success.
5364 5359 """
5365 5360
5366 5361 opts = pycompat.byteskwargs(opts)
5367 5362 ui.pager('tags')
5368 5363 fm = ui.formatter('tags', opts)
5369 5364 hexfunc = fm.hexfunc
5370 5365 tagtype = ""
5371 5366
5372 5367 for t, n in reversed(repo.tagslist()):
5373 5368 hn = hexfunc(n)
5374 5369 label = 'tags.normal'
5375 5370 tagtype = ''
5376 5371 if repo.tagtype(t) == 'local':
5377 5372 label = 'tags.local'
5378 5373 tagtype = 'local'
5379 5374
5380 5375 fm.startitem()
5381 5376 fm.write('tag', '%s', t, label=label)
5382 5377 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5383 5378 fm.condwrite(not ui.quiet, 'rev node', fmt,
5384 5379 repo.changelog.rev(n), hn, label=label)
5385 5380 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5386 5381 tagtype, label=label)
5387 5382 fm.plain('\n')
5388 5383 fm.end()
5389 5384
5390 5385 @command('tip',
5391 5386 [('p', 'patch', None, _('show patch')),
5392 5387 ('g', 'git', None, _('use git extended diff format')),
5393 5388 ] + templateopts,
5394 5389 _('[-p] [-g]'))
5395 5390 def tip(ui, repo, **opts):
5396 5391 """show the tip revision (DEPRECATED)
5397 5392
5398 5393 The tip revision (usually just called the tip) is the changeset
5399 5394 most recently added to the repository (and therefore the most
5400 5395 recently changed head).
5401 5396
5402 5397 If you have just made a commit, that commit will be the tip. If
5403 5398 you have just pulled changes from another repository, the tip of
5404 5399 that repository becomes the current tip. The "tip" tag is special
5405 5400 and cannot be renamed or assigned to a different changeset.
5406 5401
5407 5402 This command is deprecated, please use :hg:`heads` instead.
5408 5403
5409 5404 Returns 0 on success.
5410 5405 """
5411 5406 opts = pycompat.byteskwargs(opts)
5412 5407 displayer = cmdutil.show_changeset(ui, repo, opts)
5413 5408 displayer.show(repo['tip'])
5414 5409 displayer.close()
5415 5410
5416 5411 @command('unbundle',
5417 5412 [('u', 'update', None,
5418 5413 _('update to new branch head if changesets were unbundled'))],
5419 5414 _('[-u] FILE...'))
5420 5415 def unbundle(ui, repo, fname1, *fnames, **opts):
5421 5416 """apply one or more bundle files
5422 5417
5423 5418 Apply one or more bundle files generated by :hg:`bundle`.
5424 5419
5425 5420 Returns 0 on success, 1 if an update has unresolved files.
5426 5421 """
5427 5422 fnames = (fname1,) + fnames
5428 5423
5429 5424 with repo.lock():
5430 5425 for fname in fnames:
5431 5426 f = hg.openpath(ui, fname)
5432 5427 gen = exchange.readbundle(ui, f, fname)
5433 5428 if isinstance(gen, streamclone.streamcloneapplier):
5434 5429 raise error.Abort(
5435 5430 _('packed bundles cannot be applied with '
5436 5431 '"hg unbundle"'),
5437 5432 hint=_('use "hg debugapplystreamclonebundle"'))
5438 5433 url = 'bundle:' + fname
5439 5434 try:
5440 5435 txnname = 'unbundle'
5441 5436 if not isinstance(gen, bundle2.unbundle20):
5442 5437 txnname = 'unbundle\n%s' % util.hidepassword(url)
5443 5438 with repo.transaction(txnname) as tr:
5444 5439 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5445 5440 url=url)
5446 5441 except error.BundleUnknownFeatureError as exc:
5447 5442 raise error.Abort(
5448 5443 _('%s: unknown bundle feature, %s') % (fname, exc),
5449 5444 hint=_("see https://mercurial-scm.org/"
5450 5445 "wiki/BundleFeature for more "
5451 5446 "information"))
5452 5447 modheads = bundle2.combinechangegroupresults(op)
5453 5448
5454 5449 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5455 5450
5456 5451 @command('^update|up|checkout|co',
5457 5452 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5458 5453 ('c', 'check', None, _('require clean working directory')),
5459 5454 ('m', 'merge', None, _('merge uncommitted changes')),
5460 5455 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5461 5456 ('r', 'rev', '', _('revision'), _('REV'))
5462 5457 ] + mergetoolopts,
5463 5458 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5464 5459 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5465 5460 merge=None, tool=None):
5466 5461 """update working directory (or switch revisions)
5467 5462
5468 5463 Update the repository's working directory to the specified
5469 5464 changeset. If no changeset is specified, update to the tip of the
5470 5465 current named branch and move the active bookmark (see :hg:`help
5471 5466 bookmarks`).
5472 5467
5473 5468 Update sets the working directory's parent revision to the specified
5474 5469 changeset (see :hg:`help parents`).
5475 5470
5476 5471 If the changeset is not a descendant or ancestor of the working
5477 5472 directory's parent and there are uncommitted changes, the update is
5478 5473 aborted. With the -c/--check option, the working directory is checked
5479 5474 for uncommitted changes; if none are found, the working directory is
5480 5475 updated to the specified changeset.
5481 5476
5482 5477 .. container:: verbose
5483 5478
5484 5479 The -C/--clean, -c/--check, and -m/--merge options control what
5485 5480 happens if the working directory contains uncommitted changes.
5486 5481 At most of one of them can be specified.
5487 5482
5488 5483 1. If no option is specified, and if
5489 5484 the requested changeset is an ancestor or descendant of
5490 5485 the working directory's parent, the uncommitted changes
5491 5486 are merged into the requested changeset and the merged
5492 5487 result is left uncommitted. If the requested changeset is
5493 5488 not an ancestor or descendant (that is, it is on another
5494 5489 branch), the update is aborted and the uncommitted changes
5495 5490 are preserved.
5496 5491
5497 5492 2. With the -m/--merge option, the update is allowed even if the
5498 5493 requested changeset is not an ancestor or descendant of
5499 5494 the working directory's parent.
5500 5495
5501 5496 3. With the -c/--check option, the update is aborted and the
5502 5497 uncommitted changes are preserved.
5503 5498
5504 5499 4. With the -C/--clean option, uncommitted changes are discarded and
5505 5500 the working directory is updated to the requested changeset.
5506 5501
5507 5502 To cancel an uncommitted merge (and lose your changes), use
5508 5503 :hg:`update --clean .`.
5509 5504
5510 5505 Use null as the changeset to remove the working directory (like
5511 5506 :hg:`clone -U`).
5512 5507
5513 5508 If you want to revert just one file to an older revision, use
5514 5509 :hg:`revert [-r REV] NAME`.
5515 5510
5516 5511 See :hg:`help dates` for a list of formats valid for -d/--date.
5517 5512
5518 5513 Returns 0 on success, 1 if there are unresolved files.
5519 5514 """
5520 5515 if rev and node:
5521 5516 raise error.Abort(_("please specify just one revision"))
5522 5517
5523 5518 if ui.configbool('commands', 'update.requiredest'):
5524 5519 if not node and not rev and not date:
5525 5520 raise error.Abort(_('you must specify a destination'),
5526 5521 hint=_('for example: hg update ".::"'))
5527 5522
5528 5523 if rev is None or rev == '':
5529 5524 rev = node
5530 5525
5531 5526 if date and rev is not None:
5532 5527 raise error.Abort(_("you can't specify a revision and a date"))
5533 5528
5534 5529 if len([x for x in (clean, check, merge) if x]) > 1:
5535 5530 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5536 5531 "or -m/--merge"))
5537 5532
5538 5533 updatecheck = None
5539 5534 if check:
5540 5535 updatecheck = 'abort'
5541 5536 elif merge:
5542 5537 updatecheck = 'none'
5543 5538
5544 5539 with repo.wlock():
5545 5540 cmdutil.clearunfinished(repo)
5546 5541
5547 5542 if date:
5548 5543 rev = cmdutil.finddate(ui, repo, date)
5549 5544
5550 5545 # if we defined a bookmark, we have to remember the original name
5551 5546 brev = rev
5552 5547 if rev:
5553 5548 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5554 5549 ctx = scmutil.revsingle(repo, rev, rev)
5555 5550 rev = ctx.rev()
5556 5551 if ctx.hidden():
5557 5552 ctxstr = ctx.hex()[:12]
5558 5553 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5559 5554
5560 5555 if ctx.obsolete():
5561 5556 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5562 5557 ui.warn("(%s)\n" % obsfatemsg)
5563 5558
5564 5559 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5565 5560
5566 5561 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5567 5562 updatecheck=updatecheck)
5568 5563
5569 5564 @command('verify', [])
5570 5565 def verify(ui, repo):
5571 5566 """verify the integrity of the repository
5572 5567
5573 5568 Verify the integrity of the current repository.
5574 5569
5575 5570 This will perform an extensive check of the repository's
5576 5571 integrity, validating the hashes and checksums of each entry in
5577 5572 the changelog, manifest, and tracked files, as well as the
5578 5573 integrity of their crosslinks and indices.
5579 5574
5580 5575 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5581 5576 for more information about recovery from corruption of the
5582 5577 repository.
5583 5578
5584 5579 Returns 0 on success, 1 if errors are encountered.
5585 5580 """
5586 5581 return hg.verify(repo)
5587 5582
5588 5583 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5589 5584 def version_(ui, **opts):
5590 5585 """output version and copyright information"""
5591 5586 opts = pycompat.byteskwargs(opts)
5592 5587 if ui.verbose:
5593 5588 ui.pager('version')
5594 5589 fm = ui.formatter("version", opts)
5595 5590 fm.startitem()
5596 5591 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5597 5592 util.version())
5598 5593 license = _(
5599 5594 "(see https://mercurial-scm.org for more information)\n"
5600 5595 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5601 5596 "This is free software; see the source for copying conditions. "
5602 5597 "There is NO\nwarranty; "
5603 5598 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5604 5599 )
5605 5600 if not ui.quiet:
5606 5601 fm.plain(license)
5607 5602
5608 5603 if ui.verbose:
5609 5604 fm.plain(_("\nEnabled extensions:\n\n"))
5610 5605 # format names and versions into columns
5611 5606 names = []
5612 5607 vers = []
5613 5608 isinternals = []
5614 5609 for name, module in extensions.extensions():
5615 5610 names.append(name)
5616 5611 vers.append(extensions.moduleversion(module) or None)
5617 5612 isinternals.append(extensions.ismoduleinternal(module))
5618 5613 fn = fm.nested("extensions")
5619 5614 if names:
5620 5615 namefmt = " %%-%ds " % max(len(n) for n in names)
5621 5616 places = [_("external"), _("internal")]
5622 5617 for n, v, p in zip(names, vers, isinternals):
5623 5618 fn.startitem()
5624 5619 fn.condwrite(ui.verbose, "name", namefmt, n)
5625 5620 if ui.verbose:
5626 5621 fn.plain("%s " % places[p])
5627 5622 fn.data(bundled=p)
5628 5623 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5629 5624 if ui.verbose:
5630 5625 fn.plain("\n")
5631 5626 fn.end()
5632 5627 fm.end()
5633 5628
5634 5629 def loadcmdtable(ui, name, cmdtable):
5635 5630 """Load command functions from specified cmdtable
5636 5631 """
5637 5632 overrides = [cmd for cmd in cmdtable if cmd in table]
5638 5633 if overrides:
5639 5634 ui.warn(_("extension '%s' overrides commands: %s\n")
5640 5635 % (name, " ".join(overrides)))
5641 5636 table.update(cmdtable)
@@ -1,296 +1,330
1 1 Testing changing branch on commits
2 2 ==================================
3 3
4 4 Setup
5 5
6 6 $ cat >> $HGRCPATH << EOF
7 7 > [alias]
8 8 > glog = log -G -T "{rev}:{node|short} {desc}\n{branch} ({bookmarks})"
9 9 > [experimental]
10 10 > evolution = createmarkers
11 11 > [extensions]
12 12 > rebase=
13 13 > EOF
14 14
15 15 $ hg init repo
16 16 $ cd repo
17 17 $ for ch in a b c d e; do echo foo >> $ch; hg ci -Aqm "Added "$ch; done
18 18 $ hg glog
19 19 @ 4:aa98ab95a928 Added e
20 20 | default ()
21 21 o 3:62615734edd5 Added d
22 22 | default ()
23 23 o 2:28ad74487de9 Added c
24 24 | default ()
25 25 o 1:29becc82797a Added b
26 26 | default ()
27 27 o 0:18d04c59bb5d Added a
28 28 default ()
29 29
30 30 $ hg branches
31 31 default 4:aa98ab95a928
32 32
33 33 Try without passing a new branch name
34 34
35 35 $ hg branch -r .
36 36 abort: no branch name specified for the revisions
37 37 [255]
38 38
39 39 Setting an invalid branch name
40 40
41 41 $ hg branch -r . a:b
42 42 abort: ':' cannot be used in a name
43 43 [255]
44 44 $ hg branch -r . tip
45 45 abort: the name 'tip' is reserved
46 46 [255]
47 47 $ hg branch -r . 1234
48 48 abort: cannot use an integer as a name
49 49 [255]
50 50
51 51 Change on non-linear set of commits
52 52
53 53 $ hg branch -r 2 -r 4 foo
54 54 abort: cannot change branch of non-linear revisions
55 55 [255]
56 56
57 57 Change in middle of the stack (linear commits)
58 58
59 59 $ hg branch -r 1::3 foo
60 60 abort: cannot change branch of changeset with children
61 61 [255]
62 62
63 63 Change with dirty working directory
64 64
65 65 $ echo bar > a
66 66 $ hg branch -r . foo
67 67 abort: uncommitted changes
68 68 [255]
69 69
70 70 $ hg revert --all
71 71 reverting a
72 72
73 73 Change on empty revision set
74 74
75 75 $ hg branch -r 'draft() - all()' foo
76 76 abort: empty revision set
77 77 [255]
78 78
79 79 Changing branch on linear set of commits from head
80 80
81 81 Without obsmarkers
82 82
83 83 $ hg branch -r 3:4 foo --config experimental.evolution=!
84 84 changed branch on 2 changesets
85 85 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/62615734edd5-e86bd13a-branch-change.hg (glob)
86 86 $ hg glog
87 87 @ 4:3938acfb5c0f Added e
88 88 | foo ()
89 89 o 3:9435da006bdc Added d
90 90 | foo ()
91 91 o 2:28ad74487de9 Added c
92 92 | default ()
93 93 o 1:29becc82797a Added b
94 94 | default ()
95 95 o 0:18d04c59bb5d Added a
96 96 default ()
97 97
98 98 $ hg branches
99 99 foo 4:3938acfb5c0f
100 100 default 2:28ad74487de9 (inactive)
101 101
102 102 With obsmarkers
103 103
104 104 $ hg branch -r 3::4 bar
105 105 changed branch on 2 changesets
106 106 $ hg glog
107 107 @ 6:7c1991464886 Added e
108 108 | bar ()
109 109 o 5:1ea05e93925f Added d
110 110 | bar ()
111 111 o 2:28ad74487de9 Added c
112 112 | default ()
113 113 o 1:29becc82797a Added b
114 114 | default ()
115 115 o 0:18d04c59bb5d Added a
116 116 default ()
117 117
118 118 $ hg branches
119 119 bar 6:7c1991464886
120 120 default 2:28ad74487de9 (inactive)
121 121
122 122 Change branch name to an existing branch
123 123
124 124 $ hg branch -r . default
125 125 abort: a branch of the same name already exists
126 126 [255]
127 127
128 128 Changing on a branch head which is not topological head
129 129
130 130 $ hg branch -r 2 stable
131 131 abort: cannot change branch of changeset with children
132 132 [255]
133 133
134 134 Enabling the allowunstable config and trying to change branch on a branch head
135 135 which is not a topological head
136 136
137 137 $ echo "[experimental]" >> .hg/hgrc
138 138 $ echo "evolution.allowunstable=yes" >> .hg/hgrc
139 139 $ hg branch -r 2 foo
140 140 changed branch on 1 changesets
141 141 2 new orphan changesets
142 142
143 143 Changing branch of an obsoleted changeset
144 144
145 145 $ hg branch -r 4 foobar
146 146 abort: hidden revision '4' was rewritten as: 7c1991464886!
147 147 (use --hidden to access hidden revisions)
148 148 [255]
149 149
150 150 $ hg branch -r 4 --hidden foobar
151 151 abort: cannot change branch of a obsolete changeset
152 152 [255]
153 153
154 154 Make sure bookmark movement is correct
155 155
156 156 $ hg bookmark b1
157 157 $ hg glog -r '.^::'
158 158 @ 6:7c1991464886 Added e
159 159 | bar (b1)
160 160 * 5:1ea05e93925f Added d
161 161 | bar ()
162 162 ~
163 163
164 164 $ hg branch -r '(.^)::' wat --debug
165 165 changing branch of '1ea05e93925f806d875a2163f9b76764be644636' from 'bar' to 'wat'
166 166 committing files:
167 167 d
168 168 committing manifest
169 169 committing changelog
170 170 new node id is 343660ccab7400da637bd6a211d07f413536d718
171 171 changing branch of '7c19914648869f5b02fc7fed31ddee9783fdd680' from 'bar' to 'wat'
172 172 committing files:
173 173 e
174 174 committing manifest
175 175 committing changelog
176 176 new node id is de1404b45a69f8cc6437d7679033ee33e9efb4ba
177 177 moving bookmarks ['b1'] from 7c19914648869f5b02fc7fed31ddee9783fdd680 to de1404b45a69f8cc6437d7679033ee33e9efb4ba
178 178 resolving manifests
179 179 branchmerge: False, force: False, partial: False
180 180 ancestor: 7c1991464886, local: 7c1991464886+, remote: de1404b45a69
181 181 changed branch on 2 changesets
182 182 updating the branch cache
183 183 invalid branchheads cache (served): tip differs
184 184
185 185 $ hg glog -r '(.^)::'
186 186 @ 9:de1404b45a69 Added e
187 187 | wat (b1)
188 188 * 8:343660ccab74 Added d
189 189 | wat ()
190 190 ~
191 191
192 192 Make sure phase handling is correct
193 193
194 194 $ echo foo >> bar
195 195 $ hg ci -Aqm "added bar" --secret
196 196 1 new orphan changesets
197 197 $ hg glog -r .
198 198 @ 10:8ad1294c1660 added bar
199 199 | wat (b1)
200 200 ~
201 201 $ hg branch -r . secret
202 202 changed branch on 1 changesets
203 203 $ hg phase -r .
204 204 11: secret
205 205
206 206 $ hg branches
207 207 secret 11:38a9b2d53f98
208 208 foo 7:8a4729a5e2b8
209 209 wat 9:de1404b45a69 (inactive)
210 210 default 2:28ad74487de9 (inactive)
211 211 $ hg branch
212 212 secret
213 213
214 214 Changing branch of another head, different from one on which we are
215 215
216 216 $ hg glog
217 217 @ 11:38a9b2d53f98 added bar
218 218 | secret (b1)
219 219 * 9:de1404b45a69 Added e
220 220 | wat ()
221 221 * 8:343660ccab74 Added d
222 222 | wat ()
223 223 | o 7:8a4729a5e2b8 Added c
224 224 | | foo ()
225 225 x | 2:28ad74487de9 Added c
226 226 |/ default ()
227 227 o 1:29becc82797a Added b
228 228 | default ()
229 229 o 0:18d04c59bb5d Added a
230 230 default ()
231 231
232 232 $ hg branch
233 233 secret
234 234
235 235 $ hg branch -r 7 foobar
236 236 changed branch on 1 changesets
237 237
238 238 The current branch must be preserved
239 239 $ hg branch
240 240 secret
241 241
242 242 Changing branch on multiple heads at once
243 243
244 244 $ hg rebase -s 8 -d 12 --keepbranches -q
245 245
246 246 $ hg rebase -s 14 -d 1 --keepbranches -q
247 247
248 248 $ hg branch -r 0: stable
249 249 changed branch on 6 changesets
250 250 $ hg glog
251 251 @ 23:6a5ddbcfb870 added bar
252 252 | stable (b1)
253 253 o 22:baedc6e98a67 Added e
254 254 | stable ()
255 255 | o 21:99ac7bf8aad1 Added d
256 256 | | stable ()
257 257 | o 20:0ecb4d39c4bd Added c
258 258 |/ stable ()
259 259 o 19:fd45b986b109 Added b
260 260 | stable ()
261 261 o 18:204d2769eca2 Added a
262 262 stable ()
263 263
264 264 $ hg branches
265 265 stable 23:6a5ddbcfb870
266 266
267 267 $ hg branch
268 268 stable
269 269
270 Changing to same branch name does not work
270 Changing to same branch is no-op
271 271
272 272 $ hg branch -r 19::21 stable
273 abort: a branch of the same name already exists
274 [255]
273 changed branch on 0 changesets
274
275 Changing branch name to existing branch name if the branch of parent of root of
276 revs is same as the new branch name
277
278 $ hg branch -r 20::21 bugfix
279 changed branch on 2 changesets
280 $ hg glog
281 o 25:714defe1cf34 Added d
282 | bugfix ()
283 o 24:98394def28fc Added c
284 | bugfix ()
285 | @ 23:6a5ddbcfb870 added bar
286 | | stable (b1)
287 | o 22:baedc6e98a67 Added e
288 |/ stable ()
289 o 19:fd45b986b109 Added b
290 | stable ()
291 o 18:204d2769eca2 Added a
292 stable ()
293
294 $ hg branch -r 24:25 stable
295 changed branch on 2 changesets
296 $ hg glog
297 o 27:4ec342341562 Added d
298 | stable ()
299 o 26:83f48859c2de Added c
300 | stable ()
301 | @ 23:6a5ddbcfb870 added bar
302 | | stable (b1)
303 | o 22:baedc6e98a67 Added e
304 |/ stable ()
305 o 19:fd45b986b109 Added b
306 | stable ()
307 o 18:204d2769eca2 Added a
308 stable ()
275 309
276 310 Testing on merge
277 311
278 $ hg merge -r 20
312 $ hg merge -r 26
279 313 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 314 (branch merge, don't forget to commit)
281 315
282 316 $ hg branch -r . abcd
283 317 abort: outstanding uncommitted merge
284 318 [255]
285 319 $ hg ci -m "Merge commit"
286 320 $ hg branch -r '(.^)::' def
287 321 abort: cannot change branch of a merge commit
288 322 [255]
289 323
290 324 Changing branch on public changeset
291 325
292 $ hg phase -r 21 -p
293 $ hg branch -r 21 def
326 $ hg phase -r 27 -p
327 $ hg branch -r 27 def
294 328 abort: cannot change branch of public changesets
295 329 (see 'hg help phases' for details)
296 330 [255]
General Comments 0
You need to be logged in to leave comments. Login now