##// END OF EJS Templates
py3: handle opts correctly for `hg add`...
Pulkit Goyal -
r32147:a77e61b4 default
parent child Browse files
Show More
@@ -1,3486 +1,3486 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import 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 bin,
19 19 hex,
20 20 nullid,
21 21 nullrev,
22 22 short,
23 23 )
24 24
25 25 from . import (
26 26 bookmarks,
27 27 changelog,
28 28 copies,
29 29 crecord as crecordmod,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 graphmod,
34 34 lock as lockmod,
35 35 match as matchmod,
36 36 obsolete,
37 37 patch,
38 38 pathutil,
39 39 phases,
40 40 pycompat,
41 41 repair,
42 42 revlog,
43 43 revset,
44 44 scmutil,
45 45 smartset,
46 46 templatekw,
47 47 templater,
48 48 util,
49 49 vfs as vfsmod,
50 50 )
51 51 stringio = util.stringio
52 52
53 53 # special string such that everything below this line will be ingored in the
54 54 # editor text
55 55 _linebelow = "^HG: ------------------------ >8 ------------------------$"
56 56
57 57 def ishunk(x):
58 58 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
59 59 return isinstance(x, hunkclasses)
60 60
61 61 def newandmodified(chunks, originalchunks):
62 62 newlyaddedandmodifiedfiles = set()
63 63 for chunk in chunks:
64 64 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
65 65 originalchunks:
66 66 newlyaddedandmodifiedfiles.add(chunk.header.filename())
67 67 return newlyaddedandmodifiedfiles
68 68
69 69 def parsealiases(cmd):
70 70 return cmd.lstrip("^").split("|")
71 71
72 72 def setupwrapcolorwrite(ui):
73 73 # wrap ui.write so diff output can be labeled/colorized
74 74 def wrapwrite(orig, *args, **kw):
75 75 label = kw.pop('label', '')
76 76 for chunk, l in patch.difflabel(lambda: args):
77 77 orig(chunk, label=label + l)
78 78
79 79 oldwrite = ui.write
80 80 def wrap(*args, **kwargs):
81 81 return wrapwrite(oldwrite, *args, **kwargs)
82 82 setattr(ui, 'write', wrap)
83 83 return oldwrite
84 84
85 85 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
86 86 if usecurses:
87 87 if testfile:
88 88 recordfn = crecordmod.testdecorator(testfile,
89 89 crecordmod.testchunkselector)
90 90 else:
91 91 recordfn = crecordmod.chunkselector
92 92
93 93 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
94 94
95 95 else:
96 96 return patch.filterpatch(ui, originalhunks, operation)
97 97
98 98 def recordfilter(ui, originalhunks, operation=None):
99 99 """ Prompts the user to filter the originalhunks and return a list of
100 100 selected hunks.
101 101 *operation* is used for to build ui messages to indicate the user what
102 102 kind of filtering they are doing: reverting, committing, shelving, etc.
103 103 (see patch.filterpatch).
104 104 """
105 105 usecurses = crecordmod.checkcurses(ui)
106 106 testfile = ui.config('experimental', 'crecordtest', None)
107 107 oldwrite = setupwrapcolorwrite(ui)
108 108 try:
109 109 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
110 110 testfile, operation)
111 111 finally:
112 112 ui.write = oldwrite
113 113 return newchunks, newopts
114 114
115 115 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
116 116 filterfn, *pats, **opts):
117 117 from . import merge as mergemod
118 118 opts = pycompat.byteskwargs(opts)
119 119 if not ui.interactive():
120 120 if cmdsuggest:
121 121 msg = _('running non-interactively, use %s instead') % cmdsuggest
122 122 else:
123 123 msg = _('running non-interactively')
124 124 raise error.Abort(msg)
125 125
126 126 # make sure username is set before going interactive
127 127 if not opts.get('user'):
128 128 ui.username() # raise exception, username not provided
129 129
130 130 def recordfunc(ui, repo, message, match, opts):
131 131 """This is generic record driver.
132 132
133 133 Its job is to interactively filter local changes, and
134 134 accordingly prepare working directory into a state in which the
135 135 job can be delegated to a non-interactive commit command such as
136 136 'commit' or 'qrefresh'.
137 137
138 138 After the actual job is done by non-interactive command, the
139 139 working directory is restored to its original state.
140 140
141 141 In the end we'll record interesting changes, and everything else
142 142 will be left in place, so the user can continue working.
143 143 """
144 144
145 145 checkunfinished(repo, commit=True)
146 146 wctx = repo[None]
147 147 merge = len(wctx.parents()) > 1
148 148 if merge:
149 149 raise error.Abort(_('cannot partially commit a merge '
150 150 '(use "hg commit" instead)'))
151 151
152 152 def fail(f, msg):
153 153 raise error.Abort('%s: %s' % (f, msg))
154 154
155 155 force = opts.get('force')
156 156 if not force:
157 157 vdirs = []
158 158 match.explicitdir = vdirs.append
159 159 match.bad = fail
160 160
161 161 status = repo.status(match=match)
162 162 if not force:
163 163 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
164 164 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
165 165 diffopts.nodates = True
166 166 diffopts.git = True
167 167 diffopts.showfunc = True
168 168 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
169 169 originalchunks = patch.parsepatch(originaldiff)
170 170
171 171 # 1. filter patch, since we are intending to apply subset of it
172 172 try:
173 173 chunks, newopts = filterfn(ui, originalchunks)
174 174 except patch.PatchError as err:
175 175 raise error.Abort(_('error parsing patch: %s') % err)
176 176 opts.update(newopts)
177 177
178 178 # We need to keep a backup of files that have been newly added and
179 179 # modified during the recording process because there is a previous
180 180 # version without the edit in the workdir
181 181 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
182 182 contenders = set()
183 183 for h in chunks:
184 184 try:
185 185 contenders.update(set(h.files()))
186 186 except AttributeError:
187 187 pass
188 188
189 189 changed = status.modified + status.added + status.removed
190 190 newfiles = [f for f in changed if f in contenders]
191 191 if not newfiles:
192 192 ui.status(_('no changes to record\n'))
193 193 return 0
194 194
195 195 modified = set(status.modified)
196 196
197 197 # 2. backup changed files, so we can restore them in the end
198 198
199 199 if backupall:
200 200 tobackup = changed
201 201 else:
202 202 tobackup = [f for f in newfiles if f in modified or f in \
203 203 newlyaddedandmodifiedfiles]
204 204 backups = {}
205 205 if tobackup:
206 206 backupdir = repo.vfs.join('record-backups')
207 207 try:
208 208 os.mkdir(backupdir)
209 209 except OSError as err:
210 210 if err.errno != errno.EEXIST:
211 211 raise
212 212 try:
213 213 # backup continues
214 214 for f in tobackup:
215 215 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
216 216 dir=backupdir)
217 217 os.close(fd)
218 218 ui.debug('backup %r as %r\n' % (f, tmpname))
219 219 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
220 220 backups[f] = tmpname
221 221
222 222 fp = stringio()
223 223 for c in chunks:
224 224 fname = c.filename()
225 225 if fname in backups:
226 226 c.write(fp)
227 227 dopatch = fp.tell()
228 228 fp.seek(0)
229 229
230 230 # 2.5 optionally review / modify patch in text editor
231 231 if opts.get('review', False):
232 232 patchtext = (crecordmod.diffhelptext
233 233 + crecordmod.patchhelptext
234 234 + fp.read())
235 235 reviewedpatch = ui.edit(patchtext, "",
236 236 extra={"suffix": ".diff"},
237 237 repopath=repo.path)
238 238 fp.truncate(0)
239 239 fp.write(reviewedpatch)
240 240 fp.seek(0)
241 241
242 242 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
243 243 # 3a. apply filtered patch to clean repo (clean)
244 244 if backups:
245 245 # Equivalent to hg.revert
246 246 m = scmutil.matchfiles(repo, backups.keys())
247 247 mergemod.update(repo, repo.dirstate.p1(),
248 248 False, True, matcher=m)
249 249
250 250 # 3b. (apply)
251 251 if dopatch:
252 252 try:
253 253 ui.debug('applying patch\n')
254 254 ui.debug(fp.getvalue())
255 255 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
256 256 except patch.PatchError as err:
257 257 raise error.Abort(str(err))
258 258 del fp
259 259
260 260 # 4. We prepared working directory according to filtered
261 261 # patch. Now is the time to delegate the job to
262 262 # commit/qrefresh or the like!
263 263
264 264 # Make all of the pathnames absolute.
265 265 newfiles = [repo.wjoin(nf) for nf in newfiles]
266 266 return commitfunc(ui, repo, *newfiles, **opts)
267 267 finally:
268 268 # 5. finally restore backed-up files
269 269 try:
270 270 dirstate = repo.dirstate
271 271 for realname, tmpname in backups.iteritems():
272 272 ui.debug('restoring %r to %r\n' % (tmpname, realname))
273 273
274 274 if dirstate[realname] == 'n':
275 275 # without normallookup, restoring timestamp
276 276 # may cause partially committed files
277 277 # to be treated as unmodified
278 278 dirstate.normallookup(realname)
279 279
280 280 # copystat=True here and above are a hack to trick any
281 281 # editors that have f open that we haven't modified them.
282 282 #
283 283 # Also note that this racy as an editor could notice the
284 284 # file's mtime before we've finished writing it.
285 285 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
286 286 os.unlink(tmpname)
287 287 if tobackup:
288 288 os.rmdir(backupdir)
289 289 except OSError:
290 290 pass
291 291
292 292 def recordinwlock(ui, repo, message, match, opts):
293 293 with repo.wlock():
294 294 return recordfunc(ui, repo, message, match, opts)
295 295
296 296 return commit(ui, repo, recordinwlock, pats, opts)
297 297
298 298 def findpossible(cmd, table, strict=False):
299 299 """
300 300 Return cmd -> (aliases, command table entry)
301 301 for each matching command.
302 302 Return debug commands (or their aliases) only if no normal command matches.
303 303 """
304 304 choice = {}
305 305 debugchoice = {}
306 306
307 307 if cmd in table:
308 308 # short-circuit exact matches, "log" alias beats "^log|history"
309 309 keys = [cmd]
310 310 else:
311 311 keys = table.keys()
312 312
313 313 allcmds = []
314 314 for e in keys:
315 315 aliases = parsealiases(e)
316 316 allcmds.extend(aliases)
317 317 found = None
318 318 if cmd in aliases:
319 319 found = cmd
320 320 elif not strict:
321 321 for a in aliases:
322 322 if a.startswith(cmd):
323 323 found = a
324 324 break
325 325 if found is not None:
326 326 if aliases[0].startswith("debug") or found.startswith("debug"):
327 327 debugchoice[found] = (aliases, table[e])
328 328 else:
329 329 choice[found] = (aliases, table[e])
330 330
331 331 if not choice and debugchoice:
332 332 choice = debugchoice
333 333
334 334 return choice, allcmds
335 335
336 336 def findcmd(cmd, table, strict=True):
337 337 """Return (aliases, command table entry) for command string."""
338 338 choice, allcmds = findpossible(cmd, table, strict)
339 339
340 340 if cmd in choice:
341 341 return choice[cmd]
342 342
343 343 if len(choice) > 1:
344 344 clist = choice.keys()
345 345 clist.sort()
346 346 raise error.AmbiguousCommand(cmd, clist)
347 347
348 348 if choice:
349 349 return choice.values()[0]
350 350
351 351 raise error.UnknownCommand(cmd, allcmds)
352 352
353 353 def findrepo(p):
354 354 while not os.path.isdir(os.path.join(p, ".hg")):
355 355 oldp, p = p, os.path.dirname(p)
356 356 if p == oldp:
357 357 return None
358 358
359 359 return p
360 360
361 361 def bailifchanged(repo, merge=True, hint=None):
362 362 """ enforce the precondition that working directory must be clean.
363 363
364 364 'merge' can be set to false if a pending uncommitted merge should be
365 365 ignored (such as when 'update --check' runs).
366 366
367 367 'hint' is the usual hint given to Abort exception.
368 368 """
369 369
370 370 if merge and repo.dirstate.p2() != nullid:
371 371 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
372 372 modified, added, removed, deleted = repo.status()[:4]
373 373 if modified or added or removed or deleted:
374 374 raise error.Abort(_('uncommitted changes'), hint=hint)
375 375 ctx = repo[None]
376 376 for s in sorted(ctx.substate):
377 377 ctx.sub(s).bailifchanged(hint=hint)
378 378
379 379 def logmessage(ui, opts):
380 380 """ get the log message according to -m and -l option """
381 381 message = opts.get('message')
382 382 logfile = opts.get('logfile')
383 383
384 384 if message and logfile:
385 385 raise error.Abort(_('options --message and --logfile are mutually '
386 386 'exclusive'))
387 387 if not message and logfile:
388 388 try:
389 389 if logfile == '-':
390 390 message = ui.fin.read()
391 391 else:
392 392 message = '\n'.join(util.readfile(logfile).splitlines())
393 393 except IOError as inst:
394 394 raise error.Abort(_("can't read commit message '%s': %s") %
395 395 (logfile, inst.strerror))
396 396 return message
397 397
398 398 def mergeeditform(ctxorbool, baseformname):
399 399 """return appropriate editform name (referencing a committemplate)
400 400
401 401 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
402 402 merging is committed.
403 403
404 404 This returns baseformname with '.merge' appended if it is a merge,
405 405 otherwise '.normal' is appended.
406 406 """
407 407 if isinstance(ctxorbool, bool):
408 408 if ctxorbool:
409 409 return baseformname + ".merge"
410 410 elif 1 < len(ctxorbool.parents()):
411 411 return baseformname + ".merge"
412 412
413 413 return baseformname + ".normal"
414 414
415 415 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
416 416 editform='', **opts):
417 417 """get appropriate commit message editor according to '--edit' option
418 418
419 419 'finishdesc' is a function to be called with edited commit message
420 420 (= 'description' of the new changeset) just after editing, but
421 421 before checking empty-ness. It should return actual text to be
422 422 stored into history. This allows to change description before
423 423 storing.
424 424
425 425 'extramsg' is a extra message to be shown in the editor instead of
426 426 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
427 427 is automatically added.
428 428
429 429 'editform' is a dot-separated list of names, to distinguish
430 430 the purpose of commit text editing.
431 431
432 432 'getcommiteditor' returns 'commitforceeditor' regardless of
433 433 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
434 434 they are specific for usage in MQ.
435 435 """
436 436 if edit or finishdesc or extramsg:
437 437 return lambda r, c, s: commitforceeditor(r, c, s,
438 438 finishdesc=finishdesc,
439 439 extramsg=extramsg,
440 440 editform=editform)
441 441 elif editform:
442 442 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
443 443 else:
444 444 return commiteditor
445 445
446 446 def loglimit(opts):
447 447 """get the log limit according to option -l/--limit"""
448 448 limit = opts.get('limit')
449 449 if limit:
450 450 try:
451 451 limit = int(limit)
452 452 except ValueError:
453 453 raise error.Abort(_('limit must be a positive integer'))
454 454 if limit <= 0:
455 455 raise error.Abort(_('limit must be positive'))
456 456 else:
457 457 limit = None
458 458 return limit
459 459
460 460 def makefilename(repo, pat, node, desc=None,
461 461 total=None, seqno=None, revwidth=None, pathname=None):
462 462 node_expander = {
463 463 'H': lambda: hex(node),
464 464 'R': lambda: str(repo.changelog.rev(node)),
465 465 'h': lambda: short(node),
466 466 'm': lambda: re.sub('[^\w]', '_', str(desc))
467 467 }
468 468 expander = {
469 469 '%': lambda: '%',
470 470 'b': lambda: os.path.basename(repo.root),
471 471 }
472 472
473 473 try:
474 474 if node:
475 475 expander.update(node_expander)
476 476 if node:
477 477 expander['r'] = (lambda:
478 478 str(repo.changelog.rev(node)).zfill(revwidth or 0))
479 479 if total is not None:
480 480 expander['N'] = lambda: str(total)
481 481 if seqno is not None:
482 482 expander['n'] = lambda: str(seqno)
483 483 if total is not None and seqno is not None:
484 484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
485 485 if pathname is not None:
486 486 expander['s'] = lambda: os.path.basename(pathname)
487 487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
488 488 expander['p'] = lambda: pathname
489 489
490 490 newname = []
491 491 patlen = len(pat)
492 492 i = 0
493 493 while i < patlen:
494 494 c = pat[i]
495 495 if c == '%':
496 496 i += 1
497 497 c = pat[i]
498 498 c = expander[c]()
499 499 newname.append(c)
500 500 i += 1
501 501 return ''.join(newname)
502 502 except KeyError as inst:
503 503 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
504 504 inst.args[0])
505 505
506 506 class _unclosablefile(object):
507 507 def __init__(self, fp):
508 508 self._fp = fp
509 509
510 510 def close(self):
511 511 pass
512 512
513 513 def __iter__(self):
514 514 return iter(self._fp)
515 515
516 516 def __getattr__(self, attr):
517 517 return getattr(self._fp, attr)
518 518
519 519 def __enter__(self):
520 520 return self
521 521
522 522 def __exit__(self, exc_type, exc_value, exc_tb):
523 523 pass
524 524
525 525 def makefileobj(repo, pat, node=None, desc=None, total=None,
526 526 seqno=None, revwidth=None, mode='wb', modemap=None,
527 527 pathname=None):
528 528
529 529 writable = mode not in ('r', 'rb')
530 530
531 531 if not pat or pat == '-':
532 532 if writable:
533 533 fp = repo.ui.fout
534 534 else:
535 535 fp = repo.ui.fin
536 536 return _unclosablefile(fp)
537 537 if util.safehasattr(pat, 'write') and writable:
538 538 return pat
539 539 if util.safehasattr(pat, 'read') and 'r' in mode:
540 540 return pat
541 541 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
542 542 if modemap is not None:
543 543 mode = modemap.get(fn, mode)
544 544 if mode == 'wb':
545 545 modemap[fn] = 'ab'
546 546 return open(fn, mode)
547 547
548 548 def openrevlog(repo, cmd, file_, opts):
549 549 """opens the changelog, manifest, a filelog or a given revlog"""
550 550 cl = opts['changelog']
551 551 mf = opts['manifest']
552 552 dir = opts['dir']
553 553 msg = None
554 554 if cl and mf:
555 555 msg = _('cannot specify --changelog and --manifest at the same time')
556 556 elif cl and dir:
557 557 msg = _('cannot specify --changelog and --dir at the same time')
558 558 elif cl or mf or dir:
559 559 if file_:
560 560 msg = _('cannot specify filename with --changelog or --manifest')
561 561 elif not repo:
562 562 msg = _('cannot specify --changelog or --manifest or --dir '
563 563 'without a repository')
564 564 if msg:
565 565 raise error.Abort(msg)
566 566
567 567 r = None
568 568 if repo:
569 569 if cl:
570 570 r = repo.unfiltered().changelog
571 571 elif dir:
572 572 if 'treemanifest' not in repo.requirements:
573 573 raise error.Abort(_("--dir can only be used on repos with "
574 574 "treemanifest enabled"))
575 575 dirlog = repo.manifestlog._revlog.dirlog(dir)
576 576 if len(dirlog):
577 577 r = dirlog
578 578 elif mf:
579 579 r = repo.manifestlog._revlog
580 580 elif file_:
581 581 filelog = repo.file(file_)
582 582 if len(filelog):
583 583 r = filelog
584 584 if not r:
585 585 if not file_:
586 586 raise error.CommandError(cmd, _('invalid arguments'))
587 587 if not os.path.isfile(file_):
588 588 raise error.Abort(_("revlog '%s' not found") % file_)
589 589 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
590 590 file_[:-2] + ".i")
591 591 return r
592 592
593 593 def copy(ui, repo, pats, opts, rename=False):
594 594 # called with the repo lock held
595 595 #
596 596 # hgsep => pathname that uses "/" to separate directories
597 597 # ossep => pathname that uses os.sep to separate directories
598 598 cwd = repo.getcwd()
599 599 targets = {}
600 600 after = opts.get("after")
601 601 dryrun = opts.get("dry_run")
602 602 wctx = repo[None]
603 603
604 604 def walkpat(pat):
605 605 srcs = []
606 606 if after:
607 607 badstates = '?'
608 608 else:
609 609 badstates = '?r'
610 610 m = scmutil.match(repo[None], [pat], opts, globbed=True)
611 611 for abs in repo.walk(m):
612 612 state = repo.dirstate[abs]
613 613 rel = m.rel(abs)
614 614 exact = m.exact(abs)
615 615 if state in badstates:
616 616 if exact and state == '?':
617 617 ui.warn(_('%s: not copying - file is not managed\n') % rel)
618 618 if exact and state == 'r':
619 619 ui.warn(_('%s: not copying - file has been marked for'
620 620 ' remove\n') % rel)
621 621 continue
622 622 # abs: hgsep
623 623 # rel: ossep
624 624 srcs.append((abs, rel, exact))
625 625 return srcs
626 626
627 627 # abssrc: hgsep
628 628 # relsrc: ossep
629 629 # otarget: ossep
630 630 def copyfile(abssrc, relsrc, otarget, exact):
631 631 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
632 632 if '/' in abstarget:
633 633 # We cannot normalize abstarget itself, this would prevent
634 634 # case only renames, like a => A.
635 635 abspath, absname = abstarget.rsplit('/', 1)
636 636 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
637 637 reltarget = repo.pathto(abstarget, cwd)
638 638 target = repo.wjoin(abstarget)
639 639 src = repo.wjoin(abssrc)
640 640 state = repo.dirstate[abstarget]
641 641
642 642 scmutil.checkportable(ui, abstarget)
643 643
644 644 # check for collisions
645 645 prevsrc = targets.get(abstarget)
646 646 if prevsrc is not None:
647 647 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
648 648 (reltarget, repo.pathto(abssrc, cwd),
649 649 repo.pathto(prevsrc, cwd)))
650 650 return
651 651
652 652 # check for overwrites
653 653 exists = os.path.lexists(target)
654 654 samefile = False
655 655 if exists and abssrc != abstarget:
656 656 if (repo.dirstate.normalize(abssrc) ==
657 657 repo.dirstate.normalize(abstarget)):
658 658 if not rename:
659 659 ui.warn(_("%s: can't copy - same file\n") % reltarget)
660 660 return
661 661 exists = False
662 662 samefile = True
663 663
664 664 if not after and exists or after and state in 'mn':
665 665 if not opts['force']:
666 666 if state in 'mn':
667 667 msg = _('%s: not overwriting - file already committed\n')
668 668 if after:
669 669 flags = '--after --force'
670 670 else:
671 671 flags = '--force'
672 672 if rename:
673 673 hint = _('(hg rename %s to replace the file by '
674 674 'recording a rename)\n') % flags
675 675 else:
676 676 hint = _('(hg copy %s to replace the file by '
677 677 'recording a copy)\n') % flags
678 678 else:
679 679 msg = _('%s: not overwriting - file exists\n')
680 680 if rename:
681 681 hint = _('(hg rename --after to record the rename)\n')
682 682 else:
683 683 hint = _('(hg copy --after to record the copy)\n')
684 684 ui.warn(msg % reltarget)
685 685 ui.warn(hint)
686 686 return
687 687
688 688 if after:
689 689 if not exists:
690 690 if rename:
691 691 ui.warn(_('%s: not recording move - %s does not exist\n') %
692 692 (relsrc, reltarget))
693 693 else:
694 694 ui.warn(_('%s: not recording copy - %s does not exist\n') %
695 695 (relsrc, reltarget))
696 696 return
697 697 elif not dryrun:
698 698 try:
699 699 if exists:
700 700 os.unlink(target)
701 701 targetdir = os.path.dirname(target) or '.'
702 702 if not os.path.isdir(targetdir):
703 703 os.makedirs(targetdir)
704 704 if samefile:
705 705 tmp = target + "~hgrename"
706 706 os.rename(src, tmp)
707 707 os.rename(tmp, target)
708 708 else:
709 709 util.copyfile(src, target)
710 710 srcexists = True
711 711 except IOError as inst:
712 712 if inst.errno == errno.ENOENT:
713 713 ui.warn(_('%s: deleted in working directory\n') % relsrc)
714 714 srcexists = False
715 715 else:
716 716 ui.warn(_('%s: cannot copy - %s\n') %
717 717 (relsrc, inst.strerror))
718 718 return True # report a failure
719 719
720 720 if ui.verbose or not exact:
721 721 if rename:
722 722 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
723 723 else:
724 724 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
725 725
726 726 targets[abstarget] = abssrc
727 727
728 728 # fix up dirstate
729 729 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
730 730 dryrun=dryrun, cwd=cwd)
731 731 if rename and not dryrun:
732 732 if not after and srcexists and not samefile:
733 733 repo.wvfs.unlinkpath(abssrc)
734 734 wctx.forget([abssrc])
735 735
736 736 # pat: ossep
737 737 # dest ossep
738 738 # srcs: list of (hgsep, hgsep, ossep, bool)
739 739 # return: function that takes hgsep and returns ossep
740 740 def targetpathfn(pat, dest, srcs):
741 741 if os.path.isdir(pat):
742 742 abspfx = pathutil.canonpath(repo.root, cwd, pat)
743 743 abspfx = util.localpath(abspfx)
744 744 if destdirexists:
745 745 striplen = len(os.path.split(abspfx)[0])
746 746 else:
747 747 striplen = len(abspfx)
748 748 if striplen:
749 749 striplen += len(pycompat.ossep)
750 750 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
751 751 elif destdirexists:
752 752 res = lambda p: os.path.join(dest,
753 753 os.path.basename(util.localpath(p)))
754 754 else:
755 755 res = lambda p: dest
756 756 return res
757 757
758 758 # pat: ossep
759 759 # dest ossep
760 760 # srcs: list of (hgsep, hgsep, ossep, bool)
761 761 # return: function that takes hgsep and returns ossep
762 762 def targetpathafterfn(pat, dest, srcs):
763 763 if matchmod.patkind(pat):
764 764 # a mercurial pattern
765 765 res = lambda p: os.path.join(dest,
766 766 os.path.basename(util.localpath(p)))
767 767 else:
768 768 abspfx = pathutil.canonpath(repo.root, cwd, pat)
769 769 if len(abspfx) < len(srcs[0][0]):
770 770 # A directory. Either the target path contains the last
771 771 # component of the source path or it does not.
772 772 def evalpath(striplen):
773 773 score = 0
774 774 for s in srcs:
775 775 t = os.path.join(dest, util.localpath(s[0])[striplen:])
776 776 if os.path.lexists(t):
777 777 score += 1
778 778 return score
779 779
780 780 abspfx = util.localpath(abspfx)
781 781 striplen = len(abspfx)
782 782 if striplen:
783 783 striplen += len(pycompat.ossep)
784 784 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
785 785 score = evalpath(striplen)
786 786 striplen1 = len(os.path.split(abspfx)[0])
787 787 if striplen1:
788 788 striplen1 += len(pycompat.ossep)
789 789 if evalpath(striplen1) > score:
790 790 striplen = striplen1
791 791 res = lambda p: os.path.join(dest,
792 792 util.localpath(p)[striplen:])
793 793 else:
794 794 # a file
795 795 if destdirexists:
796 796 res = lambda p: os.path.join(dest,
797 797 os.path.basename(util.localpath(p)))
798 798 else:
799 799 res = lambda p: dest
800 800 return res
801 801
802 802 pats = scmutil.expandpats(pats)
803 803 if not pats:
804 804 raise error.Abort(_('no source or destination specified'))
805 805 if len(pats) == 1:
806 806 raise error.Abort(_('no destination specified'))
807 807 dest = pats.pop()
808 808 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
809 809 if not destdirexists:
810 810 if len(pats) > 1 or matchmod.patkind(pats[0]):
811 811 raise error.Abort(_('with multiple sources, destination must be an '
812 812 'existing directory'))
813 813 if util.endswithsep(dest):
814 814 raise error.Abort(_('destination %s is not a directory') % dest)
815 815
816 816 tfn = targetpathfn
817 817 if after:
818 818 tfn = targetpathafterfn
819 819 copylist = []
820 820 for pat in pats:
821 821 srcs = walkpat(pat)
822 822 if not srcs:
823 823 continue
824 824 copylist.append((tfn(pat, dest, srcs), srcs))
825 825 if not copylist:
826 826 raise error.Abort(_('no files to copy'))
827 827
828 828 errors = 0
829 829 for targetpath, srcs in copylist:
830 830 for abssrc, relsrc, exact in srcs:
831 831 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
832 832 errors += 1
833 833
834 834 if errors:
835 835 ui.warn(_('(consider using --after)\n'))
836 836
837 837 return errors != 0
838 838
839 839 ## facility to let extension process additional data into an import patch
840 840 # list of identifier to be executed in order
841 841 extrapreimport = [] # run before commit
842 842 extrapostimport = [] # run after commit
843 843 # mapping from identifier to actual import function
844 844 #
845 845 # 'preimport' are run before the commit is made and are provided the following
846 846 # arguments:
847 847 # - repo: the localrepository instance,
848 848 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
849 849 # - extra: the future extra dictionary of the changeset, please mutate it,
850 850 # - opts: the import options.
851 851 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
852 852 # mutation of in memory commit and more. Feel free to rework the code to get
853 853 # there.
854 854 extrapreimportmap = {}
855 855 # 'postimport' are run after the commit is made and are provided the following
856 856 # argument:
857 857 # - ctx: the changectx created by import.
858 858 extrapostimportmap = {}
859 859
860 860 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
861 861 """Utility function used by commands.import to import a single patch
862 862
863 863 This function is explicitly defined here to help the evolve extension to
864 864 wrap this part of the import logic.
865 865
866 866 The API is currently a bit ugly because it a simple code translation from
867 867 the import command. Feel free to make it better.
868 868
869 869 :hunk: a patch (as a binary string)
870 870 :parents: nodes that will be parent of the created commit
871 871 :opts: the full dict of option passed to the import command
872 872 :msgs: list to save commit message to.
873 873 (used in case we need to save it when failing)
874 874 :updatefunc: a function that update a repo to a given node
875 875 updatefunc(<repo>, <node>)
876 876 """
877 877 # avoid cycle context -> subrepo -> cmdutil
878 878 from . import context
879 879 extractdata = patch.extract(ui, hunk)
880 880 tmpname = extractdata.get('filename')
881 881 message = extractdata.get('message')
882 882 user = opts.get('user') or extractdata.get('user')
883 883 date = opts.get('date') or extractdata.get('date')
884 884 branch = extractdata.get('branch')
885 885 nodeid = extractdata.get('nodeid')
886 886 p1 = extractdata.get('p1')
887 887 p2 = extractdata.get('p2')
888 888
889 889 nocommit = opts.get('no_commit')
890 890 importbranch = opts.get('import_branch')
891 891 update = not opts.get('bypass')
892 892 strip = opts["strip"]
893 893 prefix = opts["prefix"]
894 894 sim = float(opts.get('similarity') or 0)
895 895 if not tmpname:
896 896 return (None, None, False)
897 897
898 898 rejects = False
899 899
900 900 try:
901 901 cmdline_message = logmessage(ui, opts)
902 902 if cmdline_message:
903 903 # pickup the cmdline msg
904 904 message = cmdline_message
905 905 elif message:
906 906 # pickup the patch msg
907 907 message = message.strip()
908 908 else:
909 909 # launch the editor
910 910 message = None
911 911 ui.debug('message:\n%s\n' % message)
912 912
913 913 if len(parents) == 1:
914 914 parents.append(repo[nullid])
915 915 if opts.get('exact'):
916 916 if not nodeid or not p1:
917 917 raise error.Abort(_('not a Mercurial patch'))
918 918 p1 = repo[p1]
919 919 p2 = repo[p2 or nullid]
920 920 elif p2:
921 921 try:
922 922 p1 = repo[p1]
923 923 p2 = repo[p2]
924 924 # Without any options, consider p2 only if the
925 925 # patch is being applied on top of the recorded
926 926 # first parent.
927 927 if p1 != parents[0]:
928 928 p1 = parents[0]
929 929 p2 = repo[nullid]
930 930 except error.RepoError:
931 931 p1, p2 = parents
932 932 if p2.node() == nullid:
933 933 ui.warn(_("warning: import the patch as a normal revision\n"
934 934 "(use --exact to import the patch as a merge)\n"))
935 935 else:
936 936 p1, p2 = parents
937 937
938 938 n = None
939 939 if update:
940 940 if p1 != parents[0]:
941 941 updatefunc(repo, p1.node())
942 942 if p2 != parents[1]:
943 943 repo.setparents(p1.node(), p2.node())
944 944
945 945 if opts.get('exact') or importbranch:
946 946 repo.dirstate.setbranch(branch or 'default')
947 947
948 948 partial = opts.get('partial', False)
949 949 files = set()
950 950 try:
951 951 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
952 952 files=files, eolmode=None, similarity=sim / 100.0)
953 953 except patch.PatchError as e:
954 954 if not partial:
955 955 raise error.Abort(str(e))
956 956 if partial:
957 957 rejects = True
958 958
959 959 files = list(files)
960 960 if nocommit:
961 961 if message:
962 962 msgs.append(message)
963 963 else:
964 964 if opts.get('exact') or p2:
965 965 # If you got here, you either use --force and know what
966 966 # you are doing or used --exact or a merge patch while
967 967 # being updated to its first parent.
968 968 m = None
969 969 else:
970 970 m = scmutil.matchfiles(repo, files or [])
971 971 editform = mergeeditform(repo[None], 'import.normal')
972 972 if opts.get('exact'):
973 973 editor = None
974 974 else:
975 975 editor = getcommiteditor(editform=editform, **opts)
976 976 extra = {}
977 977 for idfunc in extrapreimport:
978 978 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
979 979 overrides = {}
980 980 if partial:
981 981 overrides[('ui', 'allowemptycommit')] = True
982 982 with repo.ui.configoverride(overrides, 'import'):
983 983 n = repo.commit(message, user,
984 984 date, match=m,
985 985 editor=editor, extra=extra)
986 986 for idfunc in extrapostimport:
987 987 extrapostimportmap[idfunc](repo[n])
988 988 else:
989 989 if opts.get('exact') or importbranch:
990 990 branch = branch or 'default'
991 991 else:
992 992 branch = p1.branch()
993 993 store = patch.filestore()
994 994 try:
995 995 files = set()
996 996 try:
997 997 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
998 998 files, eolmode=None)
999 999 except patch.PatchError as e:
1000 1000 raise error.Abort(str(e))
1001 1001 if opts.get('exact'):
1002 1002 editor = None
1003 1003 else:
1004 1004 editor = getcommiteditor(editform='import.bypass')
1005 1005 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1006 1006 message,
1007 1007 user,
1008 1008 date,
1009 1009 branch, files, store,
1010 1010 editor=editor)
1011 1011 n = memctx.commit()
1012 1012 finally:
1013 1013 store.close()
1014 1014 if opts.get('exact') and nocommit:
1015 1015 # --exact with --no-commit is still useful in that it does merge
1016 1016 # and branch bits
1017 1017 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1018 1018 elif opts.get('exact') and hex(n) != nodeid:
1019 1019 raise error.Abort(_('patch is damaged or loses information'))
1020 1020 msg = _('applied to working directory')
1021 1021 if n:
1022 1022 # i18n: refers to a short changeset id
1023 1023 msg = _('created %s') % short(n)
1024 1024 return (msg, n, rejects)
1025 1025 finally:
1026 1026 os.unlink(tmpname)
1027 1027
1028 1028 # facility to let extensions include additional data in an exported patch
1029 1029 # list of identifiers to be executed in order
1030 1030 extraexport = []
1031 1031 # mapping from identifier to actual export function
1032 1032 # function as to return a string to be added to the header or None
1033 1033 # it is given two arguments (sequencenumber, changectx)
1034 1034 extraexportmap = {}
1035 1035
1036 1036 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1037 1037 opts=None, match=None):
1038 1038 '''export changesets as hg patches.'''
1039 1039
1040 1040 total = len(revs)
1041 1041 revwidth = max([len(str(rev)) for rev in revs])
1042 1042 filemode = {}
1043 1043
1044 1044 def single(rev, seqno, fp):
1045 1045 ctx = repo[rev]
1046 1046 node = ctx.node()
1047 1047 parents = [p.node() for p in ctx.parents() if p]
1048 1048 branch = ctx.branch()
1049 1049 if switch_parent:
1050 1050 parents.reverse()
1051 1051
1052 1052 if parents:
1053 1053 prev = parents[0]
1054 1054 else:
1055 1055 prev = nullid
1056 1056
1057 1057 shouldclose = False
1058 1058 if not fp and len(template) > 0:
1059 1059 desc_lines = ctx.description().rstrip().split('\n')
1060 1060 desc = desc_lines[0] #Commit always has a first line.
1061 1061 fp = makefileobj(repo, template, node, desc=desc, total=total,
1062 1062 seqno=seqno, revwidth=revwidth, mode='wb',
1063 1063 modemap=filemode)
1064 1064 shouldclose = True
1065 1065 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1066 1066 repo.ui.note("%s\n" % fp.name)
1067 1067
1068 1068 if not fp:
1069 1069 write = repo.ui.write
1070 1070 else:
1071 1071 def write(s, **kw):
1072 1072 fp.write(s)
1073 1073
1074 1074 write("# HG changeset patch\n")
1075 1075 write("# User %s\n" % ctx.user())
1076 1076 write("# Date %d %d\n" % ctx.date())
1077 1077 write("# %s\n" % util.datestr(ctx.date()))
1078 1078 if branch and branch != 'default':
1079 1079 write("# Branch %s\n" % branch)
1080 1080 write("# Node ID %s\n" % hex(node))
1081 1081 write("# Parent %s\n" % hex(prev))
1082 1082 if len(parents) > 1:
1083 1083 write("# Parent %s\n" % hex(parents[1]))
1084 1084
1085 1085 for headerid in extraexport:
1086 1086 header = extraexportmap[headerid](seqno, ctx)
1087 1087 if header is not None:
1088 1088 write('# %s\n' % header)
1089 1089 write(ctx.description().rstrip())
1090 1090 write("\n\n")
1091 1091
1092 1092 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1093 1093 write(chunk, label=label)
1094 1094
1095 1095 if shouldclose:
1096 1096 fp.close()
1097 1097
1098 1098 for seqno, rev in enumerate(revs):
1099 1099 single(rev, seqno + 1, fp)
1100 1100
1101 1101 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1102 1102 changes=None, stat=False, fp=None, prefix='',
1103 1103 root='', listsubrepos=False):
1104 1104 '''show diff or diffstat.'''
1105 1105 if fp is None:
1106 1106 write = ui.write
1107 1107 else:
1108 1108 def write(s, **kw):
1109 1109 fp.write(s)
1110 1110
1111 1111 if root:
1112 1112 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1113 1113 else:
1114 1114 relroot = ''
1115 1115 if relroot != '':
1116 1116 # XXX relative roots currently don't work if the root is within a
1117 1117 # subrepo
1118 1118 uirelroot = match.uipath(relroot)
1119 1119 relroot += '/'
1120 1120 for matchroot in match.files():
1121 1121 if not matchroot.startswith(relroot):
1122 1122 ui.warn(_('warning: %s not inside relative root %s\n') % (
1123 1123 match.uipath(matchroot), uirelroot))
1124 1124
1125 1125 if stat:
1126 1126 diffopts = diffopts.copy(context=0)
1127 1127 width = 80
1128 1128 if not ui.plain():
1129 1129 width = ui.termwidth()
1130 1130 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1131 1131 prefix=prefix, relroot=relroot)
1132 1132 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1133 1133 width=width):
1134 1134 write(chunk, label=label)
1135 1135 else:
1136 1136 for chunk, label in patch.diffui(repo, node1, node2, match,
1137 1137 changes, diffopts, prefix=prefix,
1138 1138 relroot=relroot):
1139 1139 write(chunk, label=label)
1140 1140
1141 1141 if listsubrepos:
1142 1142 ctx1 = repo[node1]
1143 1143 ctx2 = repo[node2]
1144 1144 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1145 1145 tempnode2 = node2
1146 1146 try:
1147 1147 if node2 is not None:
1148 1148 tempnode2 = ctx2.substate[subpath][1]
1149 1149 except KeyError:
1150 1150 # A subrepo that existed in node1 was deleted between node1 and
1151 1151 # node2 (inclusive). Thus, ctx2's substate won't contain that
1152 1152 # subpath. The best we can do is to ignore it.
1153 1153 tempnode2 = None
1154 1154 submatch = matchmod.subdirmatcher(subpath, match)
1155 1155 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1156 1156 stat=stat, fp=fp, prefix=prefix)
1157 1157
1158 1158 def _changesetlabels(ctx):
1159 1159 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1160 1160 if ctx.obsolete():
1161 1161 labels.append('changeset.obsolete')
1162 1162 if ctx.troubled():
1163 1163 labels.append('changeset.troubled')
1164 1164 for trouble in ctx.troubles():
1165 1165 labels.append('trouble.%s' % trouble)
1166 1166 return ' '.join(labels)
1167 1167
1168 1168 class changeset_printer(object):
1169 1169 '''show changeset information when templating not requested.'''
1170 1170
1171 1171 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1172 1172 self.ui = ui
1173 1173 self.repo = repo
1174 1174 self.buffered = buffered
1175 1175 self.matchfn = matchfn
1176 1176 self.diffopts = diffopts
1177 1177 self.header = {}
1178 1178 self.hunk = {}
1179 1179 self.lastheader = None
1180 1180 self.footer = None
1181 1181
1182 1182 def flush(self, ctx):
1183 1183 rev = ctx.rev()
1184 1184 if rev in self.header:
1185 1185 h = self.header[rev]
1186 1186 if h != self.lastheader:
1187 1187 self.lastheader = h
1188 1188 self.ui.write(h)
1189 1189 del self.header[rev]
1190 1190 if rev in self.hunk:
1191 1191 self.ui.write(self.hunk[rev])
1192 1192 del self.hunk[rev]
1193 1193 return 1
1194 1194 return 0
1195 1195
1196 1196 def close(self):
1197 1197 if self.footer:
1198 1198 self.ui.write(self.footer)
1199 1199
1200 1200 def show(self, ctx, copies=None, matchfn=None, **props):
1201 1201 if self.buffered:
1202 1202 self.ui.pushbuffer(labeled=True)
1203 1203 self._show(ctx, copies, matchfn, props)
1204 1204 self.hunk[ctx.rev()] = self.ui.popbuffer()
1205 1205 else:
1206 1206 self._show(ctx, copies, matchfn, props)
1207 1207
1208 1208 def _show(self, ctx, copies, matchfn, props):
1209 1209 '''show a single changeset or file revision'''
1210 1210 changenode = ctx.node()
1211 1211 rev = ctx.rev()
1212 1212 if self.ui.debugflag:
1213 1213 hexfunc = hex
1214 1214 else:
1215 1215 hexfunc = short
1216 1216 # as of now, wctx.node() and wctx.rev() return None, but we want to
1217 1217 # show the same values as {node} and {rev} templatekw
1218 1218 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1219 1219
1220 1220 if self.ui.quiet:
1221 1221 self.ui.write("%d:%s\n" % revnode, label='log.node')
1222 1222 return
1223 1223
1224 1224 date = util.datestr(ctx.date())
1225 1225
1226 1226 # i18n: column positioning for "hg log"
1227 1227 self.ui.write(_("changeset: %d:%s\n") % revnode,
1228 1228 label=_changesetlabels(ctx))
1229 1229
1230 1230 # branches are shown first before any other names due to backwards
1231 1231 # compatibility
1232 1232 branch = ctx.branch()
1233 1233 # don't show the default branch name
1234 1234 if branch != 'default':
1235 1235 # i18n: column positioning for "hg log"
1236 1236 self.ui.write(_("branch: %s\n") % branch,
1237 1237 label='log.branch')
1238 1238
1239 1239 for nsname, ns in self.repo.names.iteritems():
1240 1240 # branches has special logic already handled above, so here we just
1241 1241 # skip it
1242 1242 if nsname == 'branches':
1243 1243 continue
1244 1244 # we will use the templatename as the color name since those two
1245 1245 # should be the same
1246 1246 for name in ns.names(self.repo, changenode):
1247 1247 self.ui.write(ns.logfmt % name,
1248 1248 label='log.%s' % ns.colorname)
1249 1249 if self.ui.debugflag:
1250 1250 # i18n: column positioning for "hg log"
1251 1251 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1252 1252 label='log.phase')
1253 1253 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1254 1254 label = 'log.parent changeset.%s' % pctx.phasestr()
1255 1255 # i18n: column positioning for "hg log"
1256 1256 self.ui.write(_("parent: %d:%s\n")
1257 1257 % (pctx.rev(), hexfunc(pctx.node())),
1258 1258 label=label)
1259 1259
1260 1260 if self.ui.debugflag and rev is not None:
1261 1261 mnode = ctx.manifestnode()
1262 1262 # i18n: column positioning for "hg log"
1263 1263 self.ui.write(_("manifest: %d:%s\n") %
1264 1264 (self.repo.manifestlog._revlog.rev(mnode),
1265 1265 hex(mnode)),
1266 1266 label='ui.debug log.manifest')
1267 1267 # i18n: column positioning for "hg log"
1268 1268 self.ui.write(_("user: %s\n") % ctx.user(),
1269 1269 label='log.user')
1270 1270 # i18n: column positioning for "hg log"
1271 1271 self.ui.write(_("date: %s\n") % date,
1272 1272 label='log.date')
1273 1273
1274 1274 if ctx.troubled():
1275 1275 # i18n: column positioning for "hg log"
1276 1276 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1277 1277 label='log.trouble')
1278 1278
1279 1279 if self.ui.debugflag:
1280 1280 files = ctx.p1().status(ctx)[:3]
1281 1281 for key, value in zip([# i18n: column positioning for "hg log"
1282 1282 _("files:"),
1283 1283 # i18n: column positioning for "hg log"
1284 1284 _("files+:"),
1285 1285 # i18n: column positioning for "hg log"
1286 1286 _("files-:")], files):
1287 1287 if value:
1288 1288 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1289 1289 label='ui.debug log.files')
1290 1290 elif ctx.files() and self.ui.verbose:
1291 1291 # i18n: column positioning for "hg log"
1292 1292 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1293 1293 label='ui.note log.files')
1294 1294 if copies and self.ui.verbose:
1295 1295 copies = ['%s (%s)' % c for c in copies]
1296 1296 # i18n: column positioning for "hg log"
1297 1297 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1298 1298 label='ui.note log.copies')
1299 1299
1300 1300 extra = ctx.extra()
1301 1301 if extra and self.ui.debugflag:
1302 1302 for key, value in sorted(extra.items()):
1303 1303 # i18n: column positioning for "hg log"
1304 1304 self.ui.write(_("extra: %s=%s\n")
1305 1305 % (key, util.escapestr(value)),
1306 1306 label='ui.debug log.extra')
1307 1307
1308 1308 description = ctx.description().strip()
1309 1309 if description:
1310 1310 if self.ui.verbose:
1311 1311 self.ui.write(_("description:\n"),
1312 1312 label='ui.note log.description')
1313 1313 self.ui.write(description,
1314 1314 label='ui.note log.description')
1315 1315 self.ui.write("\n\n")
1316 1316 else:
1317 1317 # i18n: column positioning for "hg log"
1318 1318 self.ui.write(_("summary: %s\n") %
1319 1319 description.splitlines()[0],
1320 1320 label='log.summary')
1321 1321 self.ui.write("\n")
1322 1322
1323 1323 self.showpatch(ctx, matchfn)
1324 1324
1325 1325 def showpatch(self, ctx, matchfn):
1326 1326 if not matchfn:
1327 1327 matchfn = self.matchfn
1328 1328 if matchfn:
1329 1329 stat = self.diffopts.get('stat')
1330 1330 diff = self.diffopts.get('patch')
1331 1331 diffopts = patch.diffallopts(self.ui, self.diffopts)
1332 1332 node = ctx.node()
1333 1333 prev = ctx.p1().node()
1334 1334 if stat:
1335 1335 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1336 1336 match=matchfn, stat=True)
1337 1337 if diff:
1338 1338 if stat:
1339 1339 self.ui.write("\n")
1340 1340 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1341 1341 match=matchfn, stat=False)
1342 1342 self.ui.write("\n")
1343 1343
1344 1344 class jsonchangeset(changeset_printer):
1345 1345 '''format changeset information.'''
1346 1346
1347 1347 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1348 1348 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1349 1349 self.cache = {}
1350 1350 self._first = True
1351 1351
1352 1352 def close(self):
1353 1353 if not self._first:
1354 1354 self.ui.write("\n]\n")
1355 1355 else:
1356 1356 self.ui.write("[]\n")
1357 1357
1358 1358 def _show(self, ctx, copies, matchfn, props):
1359 1359 '''show a single changeset or file revision'''
1360 1360 rev = ctx.rev()
1361 1361 if rev is None:
1362 1362 jrev = jnode = 'null'
1363 1363 else:
1364 1364 jrev = str(rev)
1365 1365 jnode = '"%s"' % hex(ctx.node())
1366 1366 j = encoding.jsonescape
1367 1367
1368 1368 if self._first:
1369 1369 self.ui.write("[\n {")
1370 1370 self._first = False
1371 1371 else:
1372 1372 self.ui.write(",\n {")
1373 1373
1374 1374 if self.ui.quiet:
1375 1375 self.ui.write(('\n "rev": %s') % jrev)
1376 1376 self.ui.write((',\n "node": %s') % jnode)
1377 1377 self.ui.write('\n }')
1378 1378 return
1379 1379
1380 1380 self.ui.write(('\n "rev": %s') % jrev)
1381 1381 self.ui.write((',\n "node": %s') % jnode)
1382 1382 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1383 1383 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1384 1384 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1385 1385 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1386 1386 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1387 1387
1388 1388 self.ui.write((',\n "bookmarks": [%s]') %
1389 1389 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1390 1390 self.ui.write((',\n "tags": [%s]') %
1391 1391 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1392 1392 self.ui.write((',\n "parents": [%s]') %
1393 1393 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1394 1394
1395 1395 if self.ui.debugflag:
1396 1396 if rev is None:
1397 1397 jmanifestnode = 'null'
1398 1398 else:
1399 1399 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1400 1400 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1401 1401
1402 1402 self.ui.write((',\n "extra": {%s}') %
1403 1403 ", ".join('"%s": "%s"' % (j(k), j(v))
1404 1404 for k, v in ctx.extra().items()))
1405 1405
1406 1406 files = ctx.p1().status(ctx)
1407 1407 self.ui.write((',\n "modified": [%s]') %
1408 1408 ", ".join('"%s"' % j(f) for f in files[0]))
1409 1409 self.ui.write((',\n "added": [%s]') %
1410 1410 ", ".join('"%s"' % j(f) for f in files[1]))
1411 1411 self.ui.write((',\n "removed": [%s]') %
1412 1412 ", ".join('"%s"' % j(f) for f in files[2]))
1413 1413
1414 1414 elif self.ui.verbose:
1415 1415 self.ui.write((',\n "files": [%s]') %
1416 1416 ", ".join('"%s"' % j(f) for f in ctx.files()))
1417 1417
1418 1418 if copies:
1419 1419 self.ui.write((',\n "copies": {%s}') %
1420 1420 ", ".join('"%s": "%s"' % (j(k), j(v))
1421 1421 for k, v in copies))
1422 1422
1423 1423 matchfn = self.matchfn
1424 1424 if matchfn:
1425 1425 stat = self.diffopts.get('stat')
1426 1426 diff = self.diffopts.get('patch')
1427 1427 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1428 1428 node, prev = ctx.node(), ctx.p1().node()
1429 1429 if stat:
1430 1430 self.ui.pushbuffer()
1431 1431 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1432 1432 match=matchfn, stat=True)
1433 1433 self.ui.write((',\n "diffstat": "%s"')
1434 1434 % j(self.ui.popbuffer()))
1435 1435 if diff:
1436 1436 self.ui.pushbuffer()
1437 1437 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1438 1438 match=matchfn, stat=False)
1439 1439 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1440 1440
1441 1441 self.ui.write("\n }")
1442 1442
1443 1443 class changeset_templater(changeset_printer):
1444 1444 '''format changeset information.'''
1445 1445
1446 1446 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1447 1447 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1448 1448 assert not (tmpl and mapfile)
1449 1449 defaulttempl = templatekw.defaulttempl
1450 1450 if mapfile:
1451 1451 self.t = templater.templater.frommapfile(mapfile,
1452 1452 cache=defaulttempl)
1453 1453 else:
1454 1454 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1455 1455 cache=defaulttempl)
1456 1456
1457 1457 self._counter = itertools.count()
1458 1458 self.cache = {}
1459 1459
1460 1460 # find correct templates for current mode
1461 1461 tmplmodes = [
1462 1462 (True, None),
1463 1463 (self.ui.verbose, 'verbose'),
1464 1464 (self.ui.quiet, 'quiet'),
1465 1465 (self.ui.debugflag, 'debug'),
1466 1466 ]
1467 1467
1468 1468 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1469 1469 'docheader': '', 'docfooter': ''}
1470 1470 for mode, postfix in tmplmodes:
1471 1471 for t in self._parts:
1472 1472 cur = t
1473 1473 if postfix:
1474 1474 cur += "_" + postfix
1475 1475 if mode and cur in self.t:
1476 1476 self._parts[t] = cur
1477 1477
1478 1478 if self._parts['docheader']:
1479 1479 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1480 1480
1481 1481 def close(self):
1482 1482 if self._parts['docfooter']:
1483 1483 if not self.footer:
1484 1484 self.footer = ""
1485 1485 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1486 1486 return super(changeset_templater, self).close()
1487 1487
1488 1488 def _show(self, ctx, copies, matchfn, props):
1489 1489 '''show a single changeset or file revision'''
1490 1490 props = props.copy()
1491 1491 props.update(templatekw.keywords)
1492 1492 props['templ'] = self.t
1493 1493 props['ctx'] = ctx
1494 1494 props['repo'] = self.repo
1495 1495 props['ui'] = self.repo.ui
1496 1496 props['index'] = next(self._counter)
1497 1497 props['revcache'] = {'copies': copies}
1498 1498 props['cache'] = self.cache
1499 1499
1500 1500 # write header
1501 1501 if self._parts['header']:
1502 1502 h = templater.stringify(self.t(self._parts['header'], **props))
1503 1503 if self.buffered:
1504 1504 self.header[ctx.rev()] = h
1505 1505 else:
1506 1506 if self.lastheader != h:
1507 1507 self.lastheader = h
1508 1508 self.ui.write(h)
1509 1509
1510 1510 # write changeset metadata, then patch if requested
1511 1511 key = self._parts['changeset']
1512 1512 self.ui.write(templater.stringify(self.t(key, **props)))
1513 1513 self.showpatch(ctx, matchfn)
1514 1514
1515 1515 if self._parts['footer']:
1516 1516 if not self.footer:
1517 1517 self.footer = templater.stringify(
1518 1518 self.t(self._parts['footer'], **props))
1519 1519
1520 1520 def gettemplate(ui, tmpl, style):
1521 1521 """
1522 1522 Find the template matching the given template spec or style.
1523 1523 """
1524 1524
1525 1525 # ui settings
1526 1526 if not tmpl and not style: # template are stronger than style
1527 1527 tmpl = ui.config('ui', 'logtemplate')
1528 1528 if tmpl:
1529 1529 return templater.unquotestring(tmpl), None
1530 1530 else:
1531 1531 style = util.expandpath(ui.config('ui', 'style', ''))
1532 1532
1533 1533 if not tmpl and style:
1534 1534 mapfile = style
1535 1535 if not os.path.split(mapfile)[0]:
1536 1536 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1537 1537 or templater.templatepath(mapfile))
1538 1538 if mapname:
1539 1539 mapfile = mapname
1540 1540 return None, mapfile
1541 1541
1542 1542 if not tmpl:
1543 1543 return None, None
1544 1544
1545 1545 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1546 1546
1547 1547 def show_changeset(ui, repo, opts, buffered=False):
1548 1548 """show one changeset using template or regular display.
1549 1549
1550 1550 Display format will be the first non-empty hit of:
1551 1551 1. option 'template'
1552 1552 2. option 'style'
1553 1553 3. [ui] setting 'logtemplate'
1554 1554 4. [ui] setting 'style'
1555 1555 If all of these values are either the unset or the empty string,
1556 1556 regular display via changeset_printer() is done.
1557 1557 """
1558 1558 # options
1559 1559 matchfn = None
1560 1560 if opts.get('patch') or opts.get('stat'):
1561 1561 matchfn = scmutil.matchall(repo)
1562 1562
1563 1563 if opts.get('template') == 'json':
1564 1564 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1565 1565
1566 1566 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1567 1567
1568 1568 if not tmpl and not mapfile:
1569 1569 return changeset_printer(ui, repo, matchfn, opts, buffered)
1570 1570
1571 1571 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1572 1572
1573 1573 def showmarker(fm, marker, index=None):
1574 1574 """utility function to display obsolescence marker in a readable way
1575 1575
1576 1576 To be used by debug function."""
1577 1577 if index is not None:
1578 1578 fm.write('index', '%i ', index)
1579 1579 fm.write('precnode', '%s ', hex(marker.precnode()))
1580 1580 succs = marker.succnodes()
1581 1581 fm.condwrite(succs, 'succnodes', '%s ',
1582 1582 fm.formatlist(map(hex, succs), name='node'))
1583 1583 fm.write('flag', '%X ', marker.flags())
1584 1584 parents = marker.parentnodes()
1585 1585 if parents is not None:
1586 1586 fm.write('parentnodes', '{%s} ',
1587 1587 fm.formatlist(map(hex, parents), name='node', sep=', '))
1588 1588 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1589 1589 meta = marker.metadata().copy()
1590 1590 meta.pop('date', None)
1591 1591 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1592 1592 fm.plain('\n')
1593 1593
1594 1594 def finddate(ui, repo, date):
1595 1595 """Find the tipmost changeset that matches the given date spec"""
1596 1596
1597 1597 df = util.matchdate(date)
1598 1598 m = scmutil.matchall(repo)
1599 1599 results = {}
1600 1600
1601 1601 def prep(ctx, fns):
1602 1602 d = ctx.date()
1603 1603 if df(d[0]):
1604 1604 results[ctx.rev()] = d
1605 1605
1606 1606 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1607 1607 rev = ctx.rev()
1608 1608 if rev in results:
1609 1609 ui.status(_("found revision %s from %s\n") %
1610 1610 (rev, util.datestr(results[rev])))
1611 1611 return str(rev)
1612 1612
1613 1613 raise error.Abort(_("revision matching date not found"))
1614 1614
1615 1615 def increasingwindows(windowsize=8, sizelimit=512):
1616 1616 while True:
1617 1617 yield windowsize
1618 1618 if windowsize < sizelimit:
1619 1619 windowsize *= 2
1620 1620
1621 1621 class FileWalkError(Exception):
1622 1622 pass
1623 1623
1624 1624 def walkfilerevs(repo, match, follow, revs, fncache):
1625 1625 '''Walks the file history for the matched files.
1626 1626
1627 1627 Returns the changeset revs that are involved in the file history.
1628 1628
1629 1629 Throws FileWalkError if the file history can't be walked using
1630 1630 filelogs alone.
1631 1631 '''
1632 1632 wanted = set()
1633 1633 copies = []
1634 1634 minrev, maxrev = min(revs), max(revs)
1635 1635 def filerevgen(filelog, last):
1636 1636 """
1637 1637 Only files, no patterns. Check the history of each file.
1638 1638
1639 1639 Examines filelog entries within minrev, maxrev linkrev range
1640 1640 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1641 1641 tuples in backwards order
1642 1642 """
1643 1643 cl_count = len(repo)
1644 1644 revs = []
1645 1645 for j in xrange(0, last + 1):
1646 1646 linkrev = filelog.linkrev(j)
1647 1647 if linkrev < minrev:
1648 1648 continue
1649 1649 # only yield rev for which we have the changelog, it can
1650 1650 # happen while doing "hg log" during a pull or commit
1651 1651 if linkrev >= cl_count:
1652 1652 break
1653 1653
1654 1654 parentlinkrevs = []
1655 1655 for p in filelog.parentrevs(j):
1656 1656 if p != nullrev:
1657 1657 parentlinkrevs.append(filelog.linkrev(p))
1658 1658 n = filelog.node(j)
1659 1659 revs.append((linkrev, parentlinkrevs,
1660 1660 follow and filelog.renamed(n)))
1661 1661
1662 1662 return reversed(revs)
1663 1663 def iterfiles():
1664 1664 pctx = repo['.']
1665 1665 for filename in match.files():
1666 1666 if follow:
1667 1667 if filename not in pctx:
1668 1668 raise error.Abort(_('cannot follow file not in parent '
1669 1669 'revision: "%s"') % filename)
1670 1670 yield filename, pctx[filename].filenode()
1671 1671 else:
1672 1672 yield filename, None
1673 1673 for filename_node in copies:
1674 1674 yield filename_node
1675 1675
1676 1676 for file_, node in iterfiles():
1677 1677 filelog = repo.file(file_)
1678 1678 if not len(filelog):
1679 1679 if node is None:
1680 1680 # A zero count may be a directory or deleted file, so
1681 1681 # try to find matching entries on the slow path.
1682 1682 if follow:
1683 1683 raise error.Abort(
1684 1684 _('cannot follow nonexistent file: "%s"') % file_)
1685 1685 raise FileWalkError("Cannot walk via filelog")
1686 1686 else:
1687 1687 continue
1688 1688
1689 1689 if node is None:
1690 1690 last = len(filelog) - 1
1691 1691 else:
1692 1692 last = filelog.rev(node)
1693 1693
1694 1694 # keep track of all ancestors of the file
1695 1695 ancestors = set([filelog.linkrev(last)])
1696 1696
1697 1697 # iterate from latest to oldest revision
1698 1698 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1699 1699 if not follow:
1700 1700 if rev > maxrev:
1701 1701 continue
1702 1702 else:
1703 1703 # Note that last might not be the first interesting
1704 1704 # rev to us:
1705 1705 # if the file has been changed after maxrev, we'll
1706 1706 # have linkrev(last) > maxrev, and we still need
1707 1707 # to explore the file graph
1708 1708 if rev not in ancestors:
1709 1709 continue
1710 1710 # XXX insert 1327 fix here
1711 1711 if flparentlinkrevs:
1712 1712 ancestors.update(flparentlinkrevs)
1713 1713
1714 1714 fncache.setdefault(rev, []).append(file_)
1715 1715 wanted.add(rev)
1716 1716 if copied:
1717 1717 copies.append(copied)
1718 1718
1719 1719 return wanted
1720 1720
1721 1721 class _followfilter(object):
1722 1722 def __init__(self, repo, onlyfirst=False):
1723 1723 self.repo = repo
1724 1724 self.startrev = nullrev
1725 1725 self.roots = set()
1726 1726 self.onlyfirst = onlyfirst
1727 1727
1728 1728 def match(self, rev):
1729 1729 def realparents(rev):
1730 1730 if self.onlyfirst:
1731 1731 return self.repo.changelog.parentrevs(rev)[0:1]
1732 1732 else:
1733 1733 return filter(lambda x: x != nullrev,
1734 1734 self.repo.changelog.parentrevs(rev))
1735 1735
1736 1736 if self.startrev == nullrev:
1737 1737 self.startrev = rev
1738 1738 return True
1739 1739
1740 1740 if rev > self.startrev:
1741 1741 # forward: all descendants
1742 1742 if not self.roots:
1743 1743 self.roots.add(self.startrev)
1744 1744 for parent in realparents(rev):
1745 1745 if parent in self.roots:
1746 1746 self.roots.add(rev)
1747 1747 return True
1748 1748 else:
1749 1749 # backwards: all parents
1750 1750 if not self.roots:
1751 1751 self.roots.update(realparents(self.startrev))
1752 1752 if rev in self.roots:
1753 1753 self.roots.remove(rev)
1754 1754 self.roots.update(realparents(rev))
1755 1755 return True
1756 1756
1757 1757 return False
1758 1758
1759 1759 def walkchangerevs(repo, match, opts, prepare):
1760 1760 '''Iterate over files and the revs in which they changed.
1761 1761
1762 1762 Callers most commonly need to iterate backwards over the history
1763 1763 in which they are interested. Doing so has awful (quadratic-looking)
1764 1764 performance, so we use iterators in a "windowed" way.
1765 1765
1766 1766 We walk a window of revisions in the desired order. Within the
1767 1767 window, we first walk forwards to gather data, then in the desired
1768 1768 order (usually backwards) to display it.
1769 1769
1770 1770 This function returns an iterator yielding contexts. Before
1771 1771 yielding each context, the iterator will first call the prepare
1772 1772 function on each context in the window in forward order.'''
1773 1773
1774 1774 follow = opts.get('follow') or opts.get('follow_first')
1775 1775 revs = _logrevs(repo, opts)
1776 1776 if not revs:
1777 1777 return []
1778 1778 wanted = set()
1779 1779 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1780 1780 opts.get('removed'))
1781 1781 fncache = {}
1782 1782 change = repo.changectx
1783 1783
1784 1784 # First step is to fill wanted, the set of revisions that we want to yield.
1785 1785 # When it does not induce extra cost, we also fill fncache for revisions in
1786 1786 # wanted: a cache of filenames that were changed (ctx.files()) and that
1787 1787 # match the file filtering conditions.
1788 1788
1789 1789 if match.always():
1790 1790 # No files, no patterns. Display all revs.
1791 1791 wanted = revs
1792 1792 elif not slowpath:
1793 1793 # We only have to read through the filelog to find wanted revisions
1794 1794
1795 1795 try:
1796 1796 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1797 1797 except FileWalkError:
1798 1798 slowpath = True
1799 1799
1800 1800 # We decided to fall back to the slowpath because at least one
1801 1801 # of the paths was not a file. Check to see if at least one of them
1802 1802 # existed in history, otherwise simply return
1803 1803 for path in match.files():
1804 1804 if path == '.' or path in repo.store:
1805 1805 break
1806 1806 else:
1807 1807 return []
1808 1808
1809 1809 if slowpath:
1810 1810 # We have to read the changelog to match filenames against
1811 1811 # changed files
1812 1812
1813 1813 if follow:
1814 1814 raise error.Abort(_('can only follow copies/renames for explicit '
1815 1815 'filenames'))
1816 1816
1817 1817 # The slow path checks files modified in every changeset.
1818 1818 # This is really slow on large repos, so compute the set lazily.
1819 1819 class lazywantedset(object):
1820 1820 def __init__(self):
1821 1821 self.set = set()
1822 1822 self.revs = set(revs)
1823 1823
1824 1824 # No need to worry about locality here because it will be accessed
1825 1825 # in the same order as the increasing window below.
1826 1826 def __contains__(self, value):
1827 1827 if value in self.set:
1828 1828 return True
1829 1829 elif not value in self.revs:
1830 1830 return False
1831 1831 else:
1832 1832 self.revs.discard(value)
1833 1833 ctx = change(value)
1834 1834 matches = filter(match, ctx.files())
1835 1835 if matches:
1836 1836 fncache[value] = matches
1837 1837 self.set.add(value)
1838 1838 return True
1839 1839 return False
1840 1840
1841 1841 def discard(self, value):
1842 1842 self.revs.discard(value)
1843 1843 self.set.discard(value)
1844 1844
1845 1845 wanted = lazywantedset()
1846 1846
1847 1847 # it might be worthwhile to do this in the iterator if the rev range
1848 1848 # is descending and the prune args are all within that range
1849 1849 for rev in opts.get('prune', ()):
1850 1850 rev = repo[rev].rev()
1851 1851 ff = _followfilter(repo)
1852 1852 stop = min(revs[0], revs[-1])
1853 1853 for x in xrange(rev, stop - 1, -1):
1854 1854 if ff.match(x):
1855 1855 wanted = wanted - [x]
1856 1856
1857 1857 # Now that wanted is correctly initialized, we can iterate over the
1858 1858 # revision range, yielding only revisions in wanted.
1859 1859 def iterate():
1860 1860 if follow and match.always():
1861 1861 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1862 1862 def want(rev):
1863 1863 return ff.match(rev) and rev in wanted
1864 1864 else:
1865 1865 def want(rev):
1866 1866 return rev in wanted
1867 1867
1868 1868 it = iter(revs)
1869 1869 stopiteration = False
1870 1870 for windowsize in increasingwindows():
1871 1871 nrevs = []
1872 1872 for i in xrange(windowsize):
1873 1873 rev = next(it, None)
1874 1874 if rev is None:
1875 1875 stopiteration = True
1876 1876 break
1877 1877 elif want(rev):
1878 1878 nrevs.append(rev)
1879 1879 for rev in sorted(nrevs):
1880 1880 fns = fncache.get(rev)
1881 1881 ctx = change(rev)
1882 1882 if not fns:
1883 1883 def fns_generator():
1884 1884 for f in ctx.files():
1885 1885 if match(f):
1886 1886 yield f
1887 1887 fns = fns_generator()
1888 1888 prepare(ctx, fns)
1889 1889 for rev in nrevs:
1890 1890 yield change(rev)
1891 1891
1892 1892 if stopiteration:
1893 1893 break
1894 1894
1895 1895 return iterate()
1896 1896
1897 1897 def _makefollowlogfilematcher(repo, files, followfirst):
1898 1898 # When displaying a revision with --patch --follow FILE, we have
1899 1899 # to know which file of the revision must be diffed. With
1900 1900 # --follow, we want the names of the ancestors of FILE in the
1901 1901 # revision, stored in "fcache". "fcache" is populated by
1902 1902 # reproducing the graph traversal already done by --follow revset
1903 1903 # and relating revs to file names (which is not "correct" but
1904 1904 # good enough).
1905 1905 fcache = {}
1906 1906 fcacheready = [False]
1907 1907 pctx = repo['.']
1908 1908
1909 1909 def populate():
1910 1910 for fn in files:
1911 1911 fctx = pctx[fn]
1912 1912 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1913 1913 for c in fctx.ancestors(followfirst=followfirst):
1914 1914 fcache.setdefault(c.rev(), set()).add(c.path())
1915 1915
1916 1916 def filematcher(rev):
1917 1917 if not fcacheready[0]:
1918 1918 # Lazy initialization
1919 1919 fcacheready[0] = True
1920 1920 populate()
1921 1921 return scmutil.matchfiles(repo, fcache.get(rev, []))
1922 1922
1923 1923 return filematcher
1924 1924
1925 1925 def _makenofollowlogfilematcher(repo, pats, opts):
1926 1926 '''hook for extensions to override the filematcher for non-follow cases'''
1927 1927 return None
1928 1928
1929 1929 def _makelogrevset(repo, pats, opts, revs):
1930 1930 """Return (expr, filematcher) where expr is a revset string built
1931 1931 from log options and file patterns or None. If --stat or --patch
1932 1932 are not passed filematcher is None. Otherwise it is a callable
1933 1933 taking a revision number and returning a match objects filtering
1934 1934 the files to be detailed when displaying the revision.
1935 1935 """
1936 1936 opt2revset = {
1937 1937 'no_merges': ('not merge()', None),
1938 1938 'only_merges': ('merge()', None),
1939 1939 '_ancestors': ('ancestors(%(val)s)', None),
1940 1940 '_fancestors': ('_firstancestors(%(val)s)', None),
1941 1941 '_descendants': ('descendants(%(val)s)', None),
1942 1942 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1943 1943 '_matchfiles': ('_matchfiles(%(val)s)', None),
1944 1944 'date': ('date(%(val)r)', None),
1945 1945 'branch': ('branch(%(val)r)', ' or '),
1946 1946 '_patslog': ('filelog(%(val)r)', ' or '),
1947 1947 '_patsfollow': ('follow(%(val)r)', ' or '),
1948 1948 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1949 1949 'keyword': ('keyword(%(val)r)', ' or '),
1950 1950 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1951 1951 'user': ('user(%(val)r)', ' or '),
1952 1952 }
1953 1953
1954 1954 opts = dict(opts)
1955 1955 # follow or not follow?
1956 1956 follow = opts.get('follow') or opts.get('follow_first')
1957 1957 if opts.get('follow_first'):
1958 1958 followfirst = 1
1959 1959 else:
1960 1960 followfirst = 0
1961 1961 # --follow with FILE behavior depends on revs...
1962 1962 it = iter(revs)
1963 1963 startrev = next(it)
1964 1964 followdescendants = startrev < next(it, startrev)
1965 1965
1966 1966 # branch and only_branch are really aliases and must be handled at
1967 1967 # the same time
1968 1968 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1969 1969 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1970 1970 # pats/include/exclude are passed to match.match() directly in
1971 1971 # _matchfiles() revset but walkchangerevs() builds its matcher with
1972 1972 # scmutil.match(). The difference is input pats are globbed on
1973 1973 # platforms without shell expansion (windows).
1974 1974 wctx = repo[None]
1975 1975 match, pats = scmutil.matchandpats(wctx, pats, opts)
1976 1976 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1977 1977 opts.get('removed'))
1978 1978 if not slowpath:
1979 1979 for f in match.files():
1980 1980 if follow and f not in wctx:
1981 1981 # If the file exists, it may be a directory, so let it
1982 1982 # take the slow path.
1983 1983 if os.path.exists(repo.wjoin(f)):
1984 1984 slowpath = True
1985 1985 continue
1986 1986 else:
1987 1987 raise error.Abort(_('cannot follow file not in parent '
1988 1988 'revision: "%s"') % f)
1989 1989 filelog = repo.file(f)
1990 1990 if not filelog:
1991 1991 # A zero count may be a directory or deleted file, so
1992 1992 # try to find matching entries on the slow path.
1993 1993 if follow:
1994 1994 raise error.Abort(
1995 1995 _('cannot follow nonexistent file: "%s"') % f)
1996 1996 slowpath = True
1997 1997
1998 1998 # We decided to fall back to the slowpath because at least one
1999 1999 # of the paths was not a file. Check to see if at least one of them
2000 2000 # existed in history - in that case, we'll continue down the
2001 2001 # slowpath; otherwise, we can turn off the slowpath
2002 2002 if slowpath:
2003 2003 for path in match.files():
2004 2004 if path == '.' or path in repo.store:
2005 2005 break
2006 2006 else:
2007 2007 slowpath = False
2008 2008
2009 2009 fpats = ('_patsfollow', '_patsfollowfirst')
2010 2010 fnopats = (('_ancestors', '_fancestors'),
2011 2011 ('_descendants', '_fdescendants'))
2012 2012 if slowpath:
2013 2013 # See walkchangerevs() slow path.
2014 2014 #
2015 2015 # pats/include/exclude cannot be represented as separate
2016 2016 # revset expressions as their filtering logic applies at file
2017 2017 # level. For instance "-I a -X a" matches a revision touching
2018 2018 # "a" and "b" while "file(a) and not file(b)" does
2019 2019 # not. Besides, filesets are evaluated against the working
2020 2020 # directory.
2021 2021 matchargs = ['r:', 'd:relpath']
2022 2022 for p in pats:
2023 2023 matchargs.append('p:' + p)
2024 2024 for p in opts.get('include', []):
2025 2025 matchargs.append('i:' + p)
2026 2026 for p in opts.get('exclude', []):
2027 2027 matchargs.append('x:' + p)
2028 2028 matchargs = ','.join(('%r' % p) for p in matchargs)
2029 2029 opts['_matchfiles'] = matchargs
2030 2030 if follow:
2031 2031 opts[fnopats[0][followfirst]] = '.'
2032 2032 else:
2033 2033 if follow:
2034 2034 if pats:
2035 2035 # follow() revset interprets its file argument as a
2036 2036 # manifest entry, so use match.files(), not pats.
2037 2037 opts[fpats[followfirst]] = list(match.files())
2038 2038 else:
2039 2039 op = fnopats[followdescendants][followfirst]
2040 2040 opts[op] = 'rev(%d)' % startrev
2041 2041 else:
2042 2042 opts['_patslog'] = list(pats)
2043 2043
2044 2044 filematcher = None
2045 2045 if opts.get('patch') or opts.get('stat'):
2046 2046 # When following files, track renames via a special matcher.
2047 2047 # If we're forced to take the slowpath it means we're following
2048 2048 # at least one pattern/directory, so don't bother with rename tracking.
2049 2049 if follow and not match.always() and not slowpath:
2050 2050 # _makefollowlogfilematcher expects its files argument to be
2051 2051 # relative to the repo root, so use match.files(), not pats.
2052 2052 filematcher = _makefollowlogfilematcher(repo, match.files(),
2053 2053 followfirst)
2054 2054 else:
2055 2055 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2056 2056 if filematcher is None:
2057 2057 filematcher = lambda rev: match
2058 2058
2059 2059 expr = []
2060 2060 for op, val in sorted(opts.iteritems()):
2061 2061 if not val:
2062 2062 continue
2063 2063 if op not in opt2revset:
2064 2064 continue
2065 2065 revop, andor = opt2revset[op]
2066 2066 if '%(val)' not in revop:
2067 2067 expr.append(revop)
2068 2068 else:
2069 2069 if not isinstance(val, list):
2070 2070 e = revop % {'val': val}
2071 2071 else:
2072 2072 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2073 2073 expr.append(e)
2074 2074
2075 2075 if expr:
2076 2076 expr = '(' + ' and '.join(expr) + ')'
2077 2077 else:
2078 2078 expr = None
2079 2079 return expr, filematcher
2080 2080
2081 2081 def _logrevs(repo, opts):
2082 2082 # Default --rev value depends on --follow but --follow behavior
2083 2083 # depends on revisions resolved from --rev...
2084 2084 follow = opts.get('follow') or opts.get('follow_first')
2085 2085 if opts.get('rev'):
2086 2086 revs = scmutil.revrange(repo, opts['rev'])
2087 2087 elif follow and repo.dirstate.p1() == nullid:
2088 2088 revs = smartset.baseset()
2089 2089 elif follow:
2090 2090 revs = repo.revs('reverse(:.)')
2091 2091 else:
2092 2092 revs = smartset.spanset(repo)
2093 2093 revs.reverse()
2094 2094 return revs
2095 2095
2096 2096 def getgraphlogrevs(repo, pats, opts):
2097 2097 """Return (revs, expr, filematcher) where revs is an iterable of
2098 2098 revision numbers, expr is a revset string built from log options
2099 2099 and file patterns or None, and used to filter 'revs'. If --stat or
2100 2100 --patch are not passed filematcher is None. Otherwise it is a
2101 2101 callable taking a revision number and returning a match objects
2102 2102 filtering the files to be detailed when displaying the revision.
2103 2103 """
2104 2104 limit = loglimit(opts)
2105 2105 revs = _logrevs(repo, opts)
2106 2106 if not revs:
2107 2107 return smartset.baseset(), None, None
2108 2108 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2109 2109 if opts.get('rev'):
2110 2110 # User-specified revs might be unsorted, but don't sort before
2111 2111 # _makelogrevset because it might depend on the order of revs
2112 2112 if not (revs.isdescending() or revs.istopo()):
2113 2113 revs.sort(reverse=True)
2114 2114 if expr:
2115 2115 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2116 2116 revs = matcher(repo, revs)
2117 2117 if limit is not None:
2118 2118 limitedrevs = []
2119 2119 for idx, rev in enumerate(revs):
2120 2120 if idx >= limit:
2121 2121 break
2122 2122 limitedrevs.append(rev)
2123 2123 revs = smartset.baseset(limitedrevs)
2124 2124
2125 2125 return revs, expr, filematcher
2126 2126
2127 2127 def getlogrevs(repo, pats, opts):
2128 2128 """Return (revs, expr, filematcher) where revs is an iterable of
2129 2129 revision numbers, expr is a revset string built from log options
2130 2130 and file patterns or None, and used to filter 'revs'. If --stat or
2131 2131 --patch are not passed filematcher is None. Otherwise it is a
2132 2132 callable taking a revision number and returning a match objects
2133 2133 filtering the files to be detailed when displaying the revision.
2134 2134 """
2135 2135 limit = loglimit(opts)
2136 2136 revs = _logrevs(repo, opts)
2137 2137 if not revs:
2138 2138 return smartset.baseset([]), None, None
2139 2139 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2140 2140 if expr:
2141 2141 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2142 2142 revs = matcher(repo, revs)
2143 2143 if limit is not None:
2144 2144 limitedrevs = []
2145 2145 for idx, r in enumerate(revs):
2146 2146 if limit <= idx:
2147 2147 break
2148 2148 limitedrevs.append(r)
2149 2149 revs = smartset.baseset(limitedrevs)
2150 2150
2151 2151 return revs, expr, filematcher
2152 2152
2153 2153 def _graphnodeformatter(ui, displayer):
2154 2154 spec = ui.config('ui', 'graphnodetemplate')
2155 2155 if not spec:
2156 2156 return templatekw.showgraphnode # fast path for "{graphnode}"
2157 2157
2158 2158 spec = templater.unquotestring(spec)
2159 2159 templ = formatter.gettemplater(ui, 'graphnode', spec)
2160 2160 cache = {}
2161 2161 if isinstance(displayer, changeset_templater):
2162 2162 cache = displayer.cache # reuse cache of slow templates
2163 2163 props = templatekw.keywords.copy()
2164 2164 props['templ'] = templ
2165 2165 props['cache'] = cache
2166 2166 def formatnode(repo, ctx):
2167 2167 props['ctx'] = ctx
2168 2168 props['repo'] = repo
2169 2169 props['ui'] = repo.ui
2170 2170 props['revcache'] = {}
2171 2171 return templater.stringify(templ('graphnode', **props))
2172 2172 return formatnode
2173 2173
2174 2174 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2175 2175 filematcher=None):
2176 2176 formatnode = _graphnodeformatter(ui, displayer)
2177 2177 state = graphmod.asciistate()
2178 2178 styles = state['styles']
2179 2179
2180 2180 # only set graph styling if HGPLAIN is not set.
2181 2181 if ui.plain('graph'):
2182 2182 # set all edge styles to |, the default pre-3.8 behaviour
2183 2183 styles.update(dict.fromkeys(styles, '|'))
2184 2184 else:
2185 2185 edgetypes = {
2186 2186 'parent': graphmod.PARENT,
2187 2187 'grandparent': graphmod.GRANDPARENT,
2188 2188 'missing': graphmod.MISSINGPARENT
2189 2189 }
2190 2190 for name, key in edgetypes.items():
2191 2191 # experimental config: experimental.graphstyle.*
2192 2192 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2193 2193 styles[key])
2194 2194 if not styles[key]:
2195 2195 styles[key] = None
2196 2196
2197 2197 # experimental config: experimental.graphshorten
2198 2198 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2199 2199
2200 2200 for rev, type, ctx, parents in dag:
2201 2201 char = formatnode(repo, ctx)
2202 2202 copies = None
2203 2203 if getrenamed and ctx.rev():
2204 2204 copies = []
2205 2205 for fn in ctx.files():
2206 2206 rename = getrenamed(fn, ctx.rev())
2207 2207 if rename:
2208 2208 copies.append((fn, rename[0]))
2209 2209 revmatchfn = None
2210 2210 if filematcher is not None:
2211 2211 revmatchfn = filematcher(ctx.rev())
2212 2212 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2213 2213 lines = displayer.hunk.pop(rev).split('\n')
2214 2214 if not lines[-1]:
2215 2215 del lines[-1]
2216 2216 displayer.flush(ctx)
2217 2217 edges = edgefn(type, char, lines, state, rev, parents)
2218 2218 for type, char, lines, coldata in edges:
2219 2219 graphmod.ascii(ui, state, type, char, lines, coldata)
2220 2220 displayer.close()
2221 2221
2222 2222 def graphlog(ui, repo, pats, opts):
2223 2223 # Parameters are identical to log command ones
2224 2224 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2225 2225 revdag = graphmod.dagwalker(repo, revs)
2226 2226
2227 2227 getrenamed = None
2228 2228 if opts.get('copies'):
2229 2229 endrev = None
2230 2230 if opts.get('rev'):
2231 2231 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2232 2232 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2233 2233
2234 2234 ui.pager('log')
2235 2235 displayer = show_changeset(ui, repo, opts, buffered=True)
2236 2236 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2237 2237 filematcher)
2238 2238
2239 2239 def checkunsupportedgraphflags(pats, opts):
2240 2240 for op in ["newest_first"]:
2241 2241 if op in opts and opts[op]:
2242 2242 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2243 2243 % op.replace("_", "-"))
2244 2244
2245 2245 def graphrevs(repo, nodes, opts):
2246 2246 limit = loglimit(opts)
2247 2247 nodes.reverse()
2248 2248 if limit is not None:
2249 2249 nodes = nodes[:limit]
2250 2250 return graphmod.nodes(repo, nodes)
2251 2251
2252 2252 def add(ui, repo, match, prefix, explicitonly, **opts):
2253 2253 join = lambda f: os.path.join(prefix, f)
2254 2254 bad = []
2255 2255
2256 2256 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2257 2257 names = []
2258 2258 wctx = repo[None]
2259 2259 cca = None
2260 2260 abort, warn = scmutil.checkportabilityalert(ui)
2261 2261 if abort or warn:
2262 2262 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2263 2263
2264 2264 badmatch = matchmod.badmatch(match, badfn)
2265 2265 dirstate = repo.dirstate
2266 2266 # We don't want to just call wctx.walk here, since it would return a lot of
2267 2267 # clean files, which we aren't interested in and takes time.
2268 2268 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2269 2269 True, False, full=False)):
2270 2270 exact = match.exact(f)
2271 2271 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2272 2272 if cca:
2273 2273 cca(f)
2274 2274 names.append(f)
2275 2275 if ui.verbose or not exact:
2276 2276 ui.status(_('adding %s\n') % match.rel(f))
2277 2277
2278 2278 for subpath in sorted(wctx.substate):
2279 2279 sub = wctx.sub(subpath)
2280 2280 try:
2281 2281 submatch = matchmod.subdirmatcher(subpath, match)
2282 if opts.get('subrepos'):
2282 if opts.get(r'subrepos'):
2283 2283 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2284 2284 else:
2285 2285 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2286 2286 except error.LookupError:
2287 2287 ui.status(_("skipping missing subrepository: %s\n")
2288 2288 % join(subpath))
2289 2289
2290 if not opts.get('dry_run'):
2290 if not opts.get(r'dry_run'):
2291 2291 rejected = wctx.add(names, prefix)
2292 2292 bad.extend(f for f in rejected if f in match.files())
2293 2293 return bad
2294 2294
2295 2295 def addwebdirpath(repo, serverpath, webconf):
2296 2296 webconf[serverpath] = repo.root
2297 2297 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2298 2298
2299 2299 for r in repo.revs('filelog("path:.hgsub")'):
2300 2300 ctx = repo[r]
2301 2301 for subpath in ctx.substate:
2302 2302 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2303 2303
2304 2304 def forget(ui, repo, match, prefix, explicitonly):
2305 2305 join = lambda f: os.path.join(prefix, f)
2306 2306 bad = []
2307 2307 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2308 2308 wctx = repo[None]
2309 2309 forgot = []
2310 2310
2311 2311 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2312 2312 forget = sorted(s[0] + s[1] + s[3] + s[6])
2313 2313 if explicitonly:
2314 2314 forget = [f for f in forget if match.exact(f)]
2315 2315
2316 2316 for subpath in sorted(wctx.substate):
2317 2317 sub = wctx.sub(subpath)
2318 2318 try:
2319 2319 submatch = matchmod.subdirmatcher(subpath, match)
2320 2320 subbad, subforgot = sub.forget(submatch, prefix)
2321 2321 bad.extend([subpath + '/' + f for f in subbad])
2322 2322 forgot.extend([subpath + '/' + f for f in subforgot])
2323 2323 except error.LookupError:
2324 2324 ui.status(_("skipping missing subrepository: %s\n")
2325 2325 % join(subpath))
2326 2326
2327 2327 if not explicitonly:
2328 2328 for f in match.files():
2329 2329 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2330 2330 if f not in forgot:
2331 2331 if repo.wvfs.exists(f):
2332 2332 # Don't complain if the exact case match wasn't given.
2333 2333 # But don't do this until after checking 'forgot', so
2334 2334 # that subrepo files aren't normalized, and this op is
2335 2335 # purely from data cached by the status walk above.
2336 2336 if repo.dirstate.normalize(f) in repo.dirstate:
2337 2337 continue
2338 2338 ui.warn(_('not removing %s: '
2339 2339 'file is already untracked\n')
2340 2340 % match.rel(f))
2341 2341 bad.append(f)
2342 2342
2343 2343 for f in forget:
2344 2344 if ui.verbose or not match.exact(f):
2345 2345 ui.status(_('removing %s\n') % match.rel(f))
2346 2346
2347 2347 rejected = wctx.forget(forget, prefix)
2348 2348 bad.extend(f for f in rejected if f in match.files())
2349 2349 forgot.extend(f for f in forget if f not in rejected)
2350 2350 return bad, forgot
2351 2351
2352 2352 def files(ui, ctx, m, fm, fmt, subrepos):
2353 2353 rev = ctx.rev()
2354 2354 ret = 1
2355 2355 ds = ctx.repo().dirstate
2356 2356
2357 2357 for f in ctx.matches(m):
2358 2358 if rev is None and ds[f] == 'r':
2359 2359 continue
2360 2360 fm.startitem()
2361 2361 if ui.verbose:
2362 2362 fc = ctx[f]
2363 2363 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2364 2364 fm.data(abspath=f)
2365 2365 fm.write('path', fmt, m.rel(f))
2366 2366 ret = 0
2367 2367
2368 2368 for subpath in sorted(ctx.substate):
2369 2369 submatch = matchmod.subdirmatcher(subpath, m)
2370 2370 if (subrepos or m.exact(subpath) or any(submatch.files())):
2371 2371 sub = ctx.sub(subpath)
2372 2372 try:
2373 2373 recurse = m.exact(subpath) or subrepos
2374 2374 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2375 2375 ret = 0
2376 2376 except error.LookupError:
2377 2377 ui.status(_("skipping missing subrepository: %s\n")
2378 2378 % m.abs(subpath))
2379 2379
2380 2380 return ret
2381 2381
2382 2382 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2383 2383 join = lambda f: os.path.join(prefix, f)
2384 2384 ret = 0
2385 2385 s = repo.status(match=m, clean=True)
2386 2386 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2387 2387
2388 2388 wctx = repo[None]
2389 2389
2390 2390 if warnings is None:
2391 2391 warnings = []
2392 2392 warn = True
2393 2393 else:
2394 2394 warn = False
2395 2395
2396 2396 subs = sorted(wctx.substate)
2397 2397 total = len(subs)
2398 2398 count = 0
2399 2399 for subpath in subs:
2400 2400 count += 1
2401 2401 submatch = matchmod.subdirmatcher(subpath, m)
2402 2402 if subrepos or m.exact(subpath) or any(submatch.files()):
2403 2403 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2404 2404 sub = wctx.sub(subpath)
2405 2405 try:
2406 2406 if sub.removefiles(submatch, prefix, after, force, subrepos,
2407 2407 warnings):
2408 2408 ret = 1
2409 2409 except error.LookupError:
2410 2410 warnings.append(_("skipping missing subrepository: %s\n")
2411 2411 % join(subpath))
2412 2412 ui.progress(_('searching'), None)
2413 2413
2414 2414 # warn about failure to delete explicit files/dirs
2415 2415 deleteddirs = util.dirs(deleted)
2416 2416 files = m.files()
2417 2417 total = len(files)
2418 2418 count = 0
2419 2419 for f in files:
2420 2420 def insubrepo():
2421 2421 for subpath in wctx.substate:
2422 2422 if f.startswith(subpath + '/'):
2423 2423 return True
2424 2424 return False
2425 2425
2426 2426 count += 1
2427 2427 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2428 2428 isdir = f in deleteddirs or wctx.hasdir(f)
2429 2429 if (f in repo.dirstate or isdir or f == '.'
2430 2430 or insubrepo() or f in subs):
2431 2431 continue
2432 2432
2433 2433 if repo.wvfs.exists(f):
2434 2434 if repo.wvfs.isdir(f):
2435 2435 warnings.append(_('not removing %s: no tracked files\n')
2436 2436 % m.rel(f))
2437 2437 else:
2438 2438 warnings.append(_('not removing %s: file is untracked\n')
2439 2439 % m.rel(f))
2440 2440 # missing files will generate a warning elsewhere
2441 2441 ret = 1
2442 2442 ui.progress(_('deleting'), None)
2443 2443
2444 2444 if force:
2445 2445 list = modified + deleted + clean + added
2446 2446 elif after:
2447 2447 list = deleted
2448 2448 remaining = modified + added + clean
2449 2449 total = len(remaining)
2450 2450 count = 0
2451 2451 for f in remaining:
2452 2452 count += 1
2453 2453 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2454 2454 warnings.append(_('not removing %s: file still exists\n')
2455 2455 % m.rel(f))
2456 2456 ret = 1
2457 2457 ui.progress(_('skipping'), None)
2458 2458 else:
2459 2459 list = deleted + clean
2460 2460 total = len(modified) + len(added)
2461 2461 count = 0
2462 2462 for f in modified:
2463 2463 count += 1
2464 2464 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2465 2465 warnings.append(_('not removing %s: file is modified (use -f'
2466 2466 ' to force removal)\n') % m.rel(f))
2467 2467 ret = 1
2468 2468 for f in added:
2469 2469 count += 1
2470 2470 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2471 2471 warnings.append(_("not removing %s: file has been marked for add"
2472 2472 " (use 'hg forget' to undo add)\n") % m.rel(f))
2473 2473 ret = 1
2474 2474 ui.progress(_('skipping'), None)
2475 2475
2476 2476 list = sorted(list)
2477 2477 total = len(list)
2478 2478 count = 0
2479 2479 for f in list:
2480 2480 count += 1
2481 2481 if ui.verbose or not m.exact(f):
2482 2482 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2483 2483 ui.status(_('removing %s\n') % m.rel(f))
2484 2484 ui.progress(_('deleting'), None)
2485 2485
2486 2486 with repo.wlock():
2487 2487 if not after:
2488 2488 for f in list:
2489 2489 if f in added:
2490 2490 continue # we never unlink added files on remove
2491 2491 repo.wvfs.unlinkpath(f, ignoremissing=True)
2492 2492 repo[None].forget(list)
2493 2493
2494 2494 if warn:
2495 2495 for warning in warnings:
2496 2496 ui.warn(warning)
2497 2497
2498 2498 return ret
2499 2499
2500 2500 def cat(ui, repo, ctx, matcher, prefix, **opts):
2501 2501 err = 1
2502 2502
2503 2503 def write(path):
2504 2504 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2505 2505 pathname=os.path.join(prefix, path))
2506 2506 data = ctx[path].data()
2507 2507 if opts.get('decode'):
2508 2508 data = repo.wwritedata(path, data)
2509 2509 fp.write(data)
2510 2510 fp.close()
2511 2511
2512 2512 # Automation often uses hg cat on single files, so special case it
2513 2513 # for performance to avoid the cost of parsing the manifest.
2514 2514 if len(matcher.files()) == 1 and not matcher.anypats():
2515 2515 file = matcher.files()[0]
2516 2516 mfl = repo.manifestlog
2517 2517 mfnode = ctx.manifestnode()
2518 2518 try:
2519 2519 if mfnode and mfl[mfnode].find(file)[0]:
2520 2520 write(file)
2521 2521 return 0
2522 2522 except KeyError:
2523 2523 pass
2524 2524
2525 2525 for abs in ctx.walk(matcher):
2526 2526 write(abs)
2527 2527 err = 0
2528 2528
2529 2529 for subpath in sorted(ctx.substate):
2530 2530 sub = ctx.sub(subpath)
2531 2531 try:
2532 2532 submatch = matchmod.subdirmatcher(subpath, matcher)
2533 2533
2534 2534 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2535 2535 **opts):
2536 2536 err = 0
2537 2537 except error.RepoLookupError:
2538 2538 ui.status(_("skipping missing subrepository: %s\n")
2539 2539 % os.path.join(prefix, subpath))
2540 2540
2541 2541 return err
2542 2542
2543 2543 def commit(ui, repo, commitfunc, pats, opts):
2544 2544 '''commit the specified files or all outstanding changes'''
2545 2545 date = opts.get('date')
2546 2546 if date:
2547 2547 opts['date'] = util.parsedate(date)
2548 2548 message = logmessage(ui, opts)
2549 2549 matcher = scmutil.match(repo[None], pats, opts)
2550 2550
2551 2551 # extract addremove carefully -- this function can be called from a command
2552 2552 # that doesn't support addremove
2553 2553 if opts.get('addremove'):
2554 2554 if scmutil.addremove(repo, matcher, "", opts) != 0:
2555 2555 raise error.Abort(
2556 2556 _("failed to mark all new/missing files as added/removed"))
2557 2557
2558 2558 return commitfunc(ui, repo, message, matcher, opts)
2559 2559
2560 2560 def samefile(f, ctx1, ctx2):
2561 2561 if f in ctx1.manifest():
2562 2562 a = ctx1.filectx(f)
2563 2563 if f in ctx2.manifest():
2564 2564 b = ctx2.filectx(f)
2565 2565 return (not a.cmp(b)
2566 2566 and a.flags() == b.flags())
2567 2567 else:
2568 2568 return False
2569 2569 else:
2570 2570 return f not in ctx2.manifest()
2571 2571
2572 2572 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2573 2573 # avoid cycle context -> subrepo -> cmdutil
2574 2574 from . import context
2575 2575
2576 2576 # amend will reuse the existing user if not specified, but the obsolete
2577 2577 # marker creation requires that the current user's name is specified.
2578 2578 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2579 2579 ui.username() # raise exception if username not set
2580 2580
2581 2581 ui.note(_('amending changeset %s\n') % old)
2582 2582 base = old.p1()
2583 2583 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2584 2584
2585 2585 wlock = lock = newid = None
2586 2586 try:
2587 2587 wlock = repo.wlock()
2588 2588 lock = repo.lock()
2589 2589 with repo.transaction('amend') as tr:
2590 2590 # See if we got a message from -m or -l, if not, open the editor
2591 2591 # with the message of the changeset to amend
2592 2592 message = logmessage(ui, opts)
2593 2593 # ensure logfile does not conflict with later enforcement of the
2594 2594 # message. potential logfile content has been processed by
2595 2595 # `logmessage` anyway.
2596 2596 opts.pop('logfile')
2597 2597 # First, do a regular commit to record all changes in the working
2598 2598 # directory (if there are any)
2599 2599 ui.callhooks = False
2600 2600 activebookmark = repo._bookmarks.active
2601 2601 try:
2602 2602 repo._bookmarks.active = None
2603 2603 opts['message'] = 'temporary amend commit for %s' % old
2604 2604 node = commit(ui, repo, commitfunc, pats, opts)
2605 2605 finally:
2606 2606 repo._bookmarks.active = activebookmark
2607 2607 repo._bookmarks.recordchange(tr)
2608 2608 ui.callhooks = True
2609 2609 ctx = repo[node]
2610 2610
2611 2611 # Participating changesets:
2612 2612 #
2613 2613 # node/ctx o - new (intermediate) commit that contains changes
2614 2614 # | from working dir to go into amending commit
2615 2615 # | (or a workingctx if there were no changes)
2616 2616 # |
2617 2617 # old o - changeset to amend
2618 2618 # |
2619 2619 # base o - parent of amending changeset
2620 2620
2621 2621 # Update extra dict from amended commit (e.g. to preserve graft
2622 2622 # source)
2623 2623 extra.update(old.extra())
2624 2624
2625 2625 # Also update it from the intermediate commit or from the wctx
2626 2626 extra.update(ctx.extra())
2627 2627
2628 2628 if len(old.parents()) > 1:
2629 2629 # ctx.files() isn't reliable for merges, so fall back to the
2630 2630 # slower repo.status() method
2631 2631 files = set([fn for st in repo.status(base, old)[:3]
2632 2632 for fn in st])
2633 2633 else:
2634 2634 files = set(old.files())
2635 2635
2636 2636 # Second, we use either the commit we just did, or if there were no
2637 2637 # changes the parent of the working directory as the version of the
2638 2638 # files in the final amend commit
2639 2639 if node:
2640 2640 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2641 2641
2642 2642 user = ctx.user()
2643 2643 date = ctx.date()
2644 2644 # Recompute copies (avoid recording a -> b -> a)
2645 2645 copied = copies.pathcopies(base, ctx)
2646 2646 if old.p2:
2647 2647 copied.update(copies.pathcopies(old.p2(), ctx))
2648 2648
2649 2649 # Prune files which were reverted by the updates: if old
2650 2650 # introduced file X and our intermediate commit, node,
2651 2651 # renamed that file, then those two files are the same and
2652 2652 # we can discard X from our list of files. Likewise if X
2653 2653 # was deleted, it's no longer relevant
2654 2654 files.update(ctx.files())
2655 2655 files = [f for f in files if not samefile(f, ctx, base)]
2656 2656
2657 2657 def filectxfn(repo, ctx_, path):
2658 2658 try:
2659 2659 fctx = ctx[path]
2660 2660 flags = fctx.flags()
2661 2661 mctx = context.memfilectx(repo,
2662 2662 fctx.path(), fctx.data(),
2663 2663 islink='l' in flags,
2664 2664 isexec='x' in flags,
2665 2665 copied=copied.get(path))
2666 2666 return mctx
2667 2667 except KeyError:
2668 2668 return None
2669 2669 else:
2670 2670 ui.note(_('copying changeset %s to %s\n') % (old, base))
2671 2671
2672 2672 # Use version of files as in the old cset
2673 2673 def filectxfn(repo, ctx_, path):
2674 2674 try:
2675 2675 return old.filectx(path)
2676 2676 except KeyError:
2677 2677 return None
2678 2678
2679 2679 user = opts.get('user') or old.user()
2680 2680 date = opts.get('date') or old.date()
2681 2681 editform = mergeeditform(old, 'commit.amend')
2682 2682 editor = getcommiteditor(editform=editform, **opts)
2683 2683 if not message:
2684 2684 editor = getcommiteditor(edit=True, editform=editform)
2685 2685 message = old.description()
2686 2686
2687 2687 pureextra = extra.copy()
2688 2688 extra['amend_source'] = old.hex()
2689 2689
2690 2690 new = context.memctx(repo,
2691 2691 parents=[base.node(), old.p2().node()],
2692 2692 text=message,
2693 2693 files=files,
2694 2694 filectxfn=filectxfn,
2695 2695 user=user,
2696 2696 date=date,
2697 2697 extra=extra,
2698 2698 editor=editor)
2699 2699
2700 2700 newdesc = changelog.stripdesc(new.description())
2701 2701 if ((not node)
2702 2702 and newdesc == old.description()
2703 2703 and user == old.user()
2704 2704 and date == old.date()
2705 2705 and pureextra == old.extra()):
2706 2706 # nothing changed. continuing here would create a new node
2707 2707 # anyway because of the amend_source noise.
2708 2708 #
2709 2709 # This not what we expect from amend.
2710 2710 return old.node()
2711 2711
2712 2712 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2713 2713 try:
2714 2714 if opts.get('secret'):
2715 2715 commitphase = 'secret'
2716 2716 else:
2717 2717 commitphase = old.phase()
2718 2718 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2719 2719 newid = repo.commitctx(new)
2720 2720 finally:
2721 2721 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2722 2722 if newid != old.node():
2723 2723 # Reroute the working copy parent to the new changeset
2724 2724 repo.setparents(newid, nullid)
2725 2725
2726 2726 # Move bookmarks from old parent to amend commit
2727 2727 bms = repo.nodebookmarks(old.node())
2728 2728 if bms:
2729 2729 marks = repo._bookmarks
2730 2730 for bm in bms:
2731 2731 ui.debug('moving bookmarks %r from %s to %s\n' %
2732 2732 (marks, old.hex(), hex(newid)))
2733 2733 marks[bm] = newid
2734 2734 marks.recordchange(tr)
2735 2735 #commit the whole amend process
2736 2736 if createmarkers:
2737 2737 # mark the new changeset as successor of the rewritten one
2738 2738 new = repo[newid]
2739 2739 obs = [(old, (new,))]
2740 2740 if node:
2741 2741 obs.append((ctx, ()))
2742 2742
2743 2743 obsolete.createmarkers(repo, obs)
2744 2744 if not createmarkers and newid != old.node():
2745 2745 # Strip the intermediate commit (if there was one) and the amended
2746 2746 # commit
2747 2747 if node:
2748 2748 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2749 2749 ui.note(_('stripping amended changeset %s\n') % old)
2750 2750 repair.strip(ui, repo, old.node(), topic='amend-backup')
2751 2751 finally:
2752 2752 lockmod.release(lock, wlock)
2753 2753 return newid
2754 2754
2755 2755 def commiteditor(repo, ctx, subs, editform=''):
2756 2756 if ctx.description():
2757 2757 return ctx.description()
2758 2758 return commitforceeditor(repo, ctx, subs, editform=editform,
2759 2759 unchangedmessagedetection=True)
2760 2760
2761 2761 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2762 2762 editform='', unchangedmessagedetection=False):
2763 2763 if not extramsg:
2764 2764 extramsg = _("Leave message empty to abort commit.")
2765 2765
2766 2766 forms = [e for e in editform.split('.') if e]
2767 2767 forms.insert(0, 'changeset')
2768 2768 templatetext = None
2769 2769 while forms:
2770 2770 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2771 2771 if tmpl:
2772 2772 tmpl = templater.unquotestring(tmpl)
2773 2773 templatetext = committext = buildcommittemplate(
2774 2774 repo, ctx, subs, extramsg, tmpl)
2775 2775 break
2776 2776 forms.pop()
2777 2777 else:
2778 2778 committext = buildcommittext(repo, ctx, subs, extramsg)
2779 2779
2780 2780 # run editor in the repository root
2781 2781 olddir = pycompat.getcwd()
2782 2782 os.chdir(repo.root)
2783 2783
2784 2784 # make in-memory changes visible to external process
2785 2785 tr = repo.currenttransaction()
2786 2786 repo.dirstate.write(tr)
2787 2787 pending = tr and tr.writepending() and repo.root
2788 2788
2789 2789 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2790 2790 editform=editform, pending=pending,
2791 2791 repopath=repo.path)
2792 2792 text = editortext
2793 2793
2794 2794 # strip away anything below this special string (used for editors that want
2795 2795 # to display the diff)
2796 2796 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2797 2797 if stripbelow:
2798 2798 text = text[:stripbelow.start()]
2799 2799
2800 2800 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2801 2801 os.chdir(olddir)
2802 2802
2803 2803 if finishdesc:
2804 2804 text = finishdesc(text)
2805 2805 if not text.strip():
2806 2806 raise error.Abort(_("empty commit message"))
2807 2807 if unchangedmessagedetection and editortext == templatetext:
2808 2808 raise error.Abort(_("commit message unchanged"))
2809 2809
2810 2810 return text
2811 2811
2812 2812 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2813 2813 ui = repo.ui
2814 2814 tmpl, mapfile = gettemplate(ui, tmpl, None)
2815 2815
2816 2816 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2817 2817
2818 2818 for k, v in repo.ui.configitems('committemplate'):
2819 2819 if k != 'changeset':
2820 2820 t.t.cache[k] = v
2821 2821
2822 2822 if not extramsg:
2823 2823 extramsg = '' # ensure that extramsg is string
2824 2824
2825 2825 ui.pushbuffer()
2826 2826 t.show(ctx, extramsg=extramsg)
2827 2827 return ui.popbuffer()
2828 2828
2829 2829 def hgprefix(msg):
2830 2830 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2831 2831
2832 2832 def buildcommittext(repo, ctx, subs, extramsg):
2833 2833 edittext = []
2834 2834 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2835 2835 if ctx.description():
2836 2836 edittext.append(ctx.description())
2837 2837 edittext.append("")
2838 2838 edittext.append("") # Empty line between message and comments.
2839 2839 edittext.append(hgprefix(_("Enter commit message."
2840 2840 " Lines beginning with 'HG:' are removed.")))
2841 2841 edittext.append(hgprefix(extramsg))
2842 2842 edittext.append("HG: --")
2843 2843 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2844 2844 if ctx.p2():
2845 2845 edittext.append(hgprefix(_("branch merge")))
2846 2846 if ctx.branch():
2847 2847 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2848 2848 if bookmarks.isactivewdirparent(repo):
2849 2849 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2850 2850 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2851 2851 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2852 2852 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2853 2853 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2854 2854 if not added and not modified and not removed:
2855 2855 edittext.append(hgprefix(_("no files changed")))
2856 2856 edittext.append("")
2857 2857
2858 2858 return "\n".join(edittext)
2859 2859
2860 2860 def commitstatus(repo, node, branch, bheads=None, opts=None):
2861 2861 if opts is None:
2862 2862 opts = {}
2863 2863 ctx = repo[node]
2864 2864 parents = ctx.parents()
2865 2865
2866 2866 if (not opts.get('amend') and bheads and node not in bheads and not
2867 2867 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2868 2868 repo.ui.status(_('created new head\n'))
2869 2869 # The message is not printed for initial roots. For the other
2870 2870 # changesets, it is printed in the following situations:
2871 2871 #
2872 2872 # Par column: for the 2 parents with ...
2873 2873 # N: null or no parent
2874 2874 # B: parent is on another named branch
2875 2875 # C: parent is a regular non head changeset
2876 2876 # H: parent was a branch head of the current branch
2877 2877 # Msg column: whether we print "created new head" message
2878 2878 # In the following, it is assumed that there already exists some
2879 2879 # initial branch heads of the current branch, otherwise nothing is
2880 2880 # printed anyway.
2881 2881 #
2882 2882 # Par Msg Comment
2883 2883 # N N y additional topo root
2884 2884 #
2885 2885 # B N y additional branch root
2886 2886 # C N y additional topo head
2887 2887 # H N n usual case
2888 2888 #
2889 2889 # B B y weird additional branch root
2890 2890 # C B y branch merge
2891 2891 # H B n merge with named branch
2892 2892 #
2893 2893 # C C y additional head from merge
2894 2894 # C H n merge with a head
2895 2895 #
2896 2896 # H H n head merge: head count decreases
2897 2897
2898 2898 if not opts.get('close_branch'):
2899 2899 for r in parents:
2900 2900 if r.closesbranch() and r.branch() == branch:
2901 2901 repo.ui.status(_('reopening closed branch head %d\n') % r)
2902 2902
2903 2903 if repo.ui.debugflag:
2904 2904 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2905 2905 elif repo.ui.verbose:
2906 2906 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2907 2907
2908 2908 def postcommitstatus(repo, pats, opts):
2909 2909 return repo.status(match=scmutil.match(repo[None], pats, opts))
2910 2910
2911 2911 def revert(ui, repo, ctx, parents, *pats, **opts):
2912 2912 parent, p2 = parents
2913 2913 node = ctx.node()
2914 2914
2915 2915 mf = ctx.manifest()
2916 2916 if node == p2:
2917 2917 parent = p2
2918 2918
2919 2919 # need all matching names in dirstate and manifest of target rev,
2920 2920 # so have to walk both. do not print errors if files exist in one
2921 2921 # but not other. in both cases, filesets should be evaluated against
2922 2922 # workingctx to get consistent result (issue4497). this means 'set:**'
2923 2923 # cannot be used to select missing files from target rev.
2924 2924
2925 2925 # `names` is a mapping for all elements in working copy and target revision
2926 2926 # The mapping is in the form:
2927 2927 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2928 2928 names = {}
2929 2929
2930 2930 with repo.wlock():
2931 2931 ## filling of the `names` mapping
2932 2932 # walk dirstate to fill `names`
2933 2933
2934 2934 interactive = opts.get('interactive', False)
2935 2935 wctx = repo[None]
2936 2936 m = scmutil.match(wctx, pats, opts)
2937 2937
2938 2938 # we'll need this later
2939 2939 targetsubs = sorted(s for s in wctx.substate if m(s))
2940 2940
2941 2941 if not m.always():
2942 2942 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2943 2943 names[abs] = m.rel(abs), m.exact(abs)
2944 2944
2945 2945 # walk target manifest to fill `names`
2946 2946
2947 2947 def badfn(path, msg):
2948 2948 if path in names:
2949 2949 return
2950 2950 if path in ctx.substate:
2951 2951 return
2952 2952 path_ = path + '/'
2953 2953 for f in names:
2954 2954 if f.startswith(path_):
2955 2955 return
2956 2956 ui.warn("%s: %s\n" % (m.rel(path), msg))
2957 2957
2958 2958 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2959 2959 if abs not in names:
2960 2960 names[abs] = m.rel(abs), m.exact(abs)
2961 2961
2962 2962 # Find status of all file in `names`.
2963 2963 m = scmutil.matchfiles(repo, names)
2964 2964
2965 2965 changes = repo.status(node1=node, match=m,
2966 2966 unknown=True, ignored=True, clean=True)
2967 2967 else:
2968 2968 changes = repo.status(node1=node, match=m)
2969 2969 for kind in changes:
2970 2970 for abs in kind:
2971 2971 names[abs] = m.rel(abs), m.exact(abs)
2972 2972
2973 2973 m = scmutil.matchfiles(repo, names)
2974 2974
2975 2975 modified = set(changes.modified)
2976 2976 added = set(changes.added)
2977 2977 removed = set(changes.removed)
2978 2978 _deleted = set(changes.deleted)
2979 2979 unknown = set(changes.unknown)
2980 2980 unknown.update(changes.ignored)
2981 2981 clean = set(changes.clean)
2982 2982 modadded = set()
2983 2983
2984 2984 # We need to account for the state of the file in the dirstate,
2985 2985 # even when we revert against something else than parent. This will
2986 2986 # slightly alter the behavior of revert (doing back up or not, delete
2987 2987 # or just forget etc).
2988 2988 if parent == node:
2989 2989 dsmodified = modified
2990 2990 dsadded = added
2991 2991 dsremoved = removed
2992 2992 # store all local modifications, useful later for rename detection
2993 2993 localchanges = dsmodified | dsadded
2994 2994 modified, added, removed = set(), set(), set()
2995 2995 else:
2996 2996 changes = repo.status(node1=parent, match=m)
2997 2997 dsmodified = set(changes.modified)
2998 2998 dsadded = set(changes.added)
2999 2999 dsremoved = set(changes.removed)
3000 3000 # store all local modifications, useful later for rename detection
3001 3001 localchanges = dsmodified | dsadded
3002 3002
3003 3003 # only take into account for removes between wc and target
3004 3004 clean |= dsremoved - removed
3005 3005 dsremoved &= removed
3006 3006 # distinct between dirstate remove and other
3007 3007 removed -= dsremoved
3008 3008
3009 3009 modadded = added & dsmodified
3010 3010 added -= modadded
3011 3011
3012 3012 # tell newly modified apart.
3013 3013 dsmodified &= modified
3014 3014 dsmodified |= modified & dsadded # dirstate added may need backup
3015 3015 modified -= dsmodified
3016 3016
3017 3017 # We need to wait for some post-processing to update this set
3018 3018 # before making the distinction. The dirstate will be used for
3019 3019 # that purpose.
3020 3020 dsadded = added
3021 3021
3022 3022 # in case of merge, files that are actually added can be reported as
3023 3023 # modified, we need to post process the result
3024 3024 if p2 != nullid:
3025 3025 mergeadd = set(dsmodified)
3026 3026 for path in dsmodified:
3027 3027 if path in mf:
3028 3028 mergeadd.remove(path)
3029 3029 dsadded |= mergeadd
3030 3030 dsmodified -= mergeadd
3031 3031
3032 3032 # if f is a rename, update `names` to also revert the source
3033 3033 cwd = repo.getcwd()
3034 3034 for f in localchanges:
3035 3035 src = repo.dirstate.copied(f)
3036 3036 # XXX should we check for rename down to target node?
3037 3037 if src and src not in names and repo.dirstate[src] == 'r':
3038 3038 dsremoved.add(src)
3039 3039 names[src] = (repo.pathto(src, cwd), True)
3040 3040
3041 3041 # determine the exact nature of the deleted changesets
3042 3042 deladded = set(_deleted)
3043 3043 for path in _deleted:
3044 3044 if path in mf:
3045 3045 deladded.remove(path)
3046 3046 deleted = _deleted - deladded
3047 3047
3048 3048 # distinguish between file to forget and the other
3049 3049 added = set()
3050 3050 for abs in dsadded:
3051 3051 if repo.dirstate[abs] != 'a':
3052 3052 added.add(abs)
3053 3053 dsadded -= added
3054 3054
3055 3055 for abs in deladded:
3056 3056 if repo.dirstate[abs] == 'a':
3057 3057 dsadded.add(abs)
3058 3058 deladded -= dsadded
3059 3059
3060 3060 # For files marked as removed, we check if an unknown file is present at
3061 3061 # the same path. If a such file exists it may need to be backed up.
3062 3062 # Making the distinction at this stage helps have simpler backup
3063 3063 # logic.
3064 3064 removunk = set()
3065 3065 for abs in removed:
3066 3066 target = repo.wjoin(abs)
3067 3067 if os.path.lexists(target):
3068 3068 removunk.add(abs)
3069 3069 removed -= removunk
3070 3070
3071 3071 dsremovunk = set()
3072 3072 for abs in dsremoved:
3073 3073 target = repo.wjoin(abs)
3074 3074 if os.path.lexists(target):
3075 3075 dsremovunk.add(abs)
3076 3076 dsremoved -= dsremovunk
3077 3077
3078 3078 # action to be actually performed by revert
3079 3079 # (<list of file>, message>) tuple
3080 3080 actions = {'revert': ([], _('reverting %s\n')),
3081 3081 'add': ([], _('adding %s\n')),
3082 3082 'remove': ([], _('removing %s\n')),
3083 3083 'drop': ([], _('removing %s\n')),
3084 3084 'forget': ([], _('forgetting %s\n')),
3085 3085 'undelete': ([], _('undeleting %s\n')),
3086 3086 'noop': (None, _('no changes needed to %s\n')),
3087 3087 'unknown': (None, _('file not managed: %s\n')),
3088 3088 }
3089 3089
3090 3090 # "constant" that convey the backup strategy.
3091 3091 # All set to `discard` if `no-backup` is set do avoid checking
3092 3092 # no_backup lower in the code.
3093 3093 # These values are ordered for comparison purposes
3094 3094 backupinteractive = 3 # do backup if interactively modified
3095 3095 backup = 2 # unconditionally do backup
3096 3096 check = 1 # check if the existing file differs from target
3097 3097 discard = 0 # never do backup
3098 3098 if opts.get('no_backup'):
3099 3099 backupinteractive = backup = check = discard
3100 3100 if interactive:
3101 3101 dsmodifiedbackup = backupinteractive
3102 3102 else:
3103 3103 dsmodifiedbackup = backup
3104 3104 tobackup = set()
3105 3105
3106 3106 backupanddel = actions['remove']
3107 3107 if not opts.get('no_backup'):
3108 3108 backupanddel = actions['drop']
3109 3109
3110 3110 disptable = (
3111 3111 # dispatch table:
3112 3112 # file state
3113 3113 # action
3114 3114 # make backup
3115 3115
3116 3116 ## Sets that results that will change file on disk
3117 3117 # Modified compared to target, no local change
3118 3118 (modified, actions['revert'], discard),
3119 3119 # Modified compared to target, but local file is deleted
3120 3120 (deleted, actions['revert'], discard),
3121 3121 # Modified compared to target, local change
3122 3122 (dsmodified, actions['revert'], dsmodifiedbackup),
3123 3123 # Added since target
3124 3124 (added, actions['remove'], discard),
3125 3125 # Added in working directory
3126 3126 (dsadded, actions['forget'], discard),
3127 3127 # Added since target, have local modification
3128 3128 (modadded, backupanddel, backup),
3129 3129 # Added since target but file is missing in working directory
3130 3130 (deladded, actions['drop'], discard),
3131 3131 # Removed since target, before working copy parent
3132 3132 (removed, actions['add'], discard),
3133 3133 # Same as `removed` but an unknown file exists at the same path
3134 3134 (removunk, actions['add'], check),
3135 3135 # Removed since targe, marked as such in working copy parent
3136 3136 (dsremoved, actions['undelete'], discard),
3137 3137 # Same as `dsremoved` but an unknown file exists at the same path
3138 3138 (dsremovunk, actions['undelete'], check),
3139 3139 ## the following sets does not result in any file changes
3140 3140 # File with no modification
3141 3141 (clean, actions['noop'], discard),
3142 3142 # Existing file, not tracked anywhere
3143 3143 (unknown, actions['unknown'], discard),
3144 3144 )
3145 3145
3146 3146 for abs, (rel, exact) in sorted(names.items()):
3147 3147 # target file to be touch on disk (relative to cwd)
3148 3148 target = repo.wjoin(abs)
3149 3149 # search the entry in the dispatch table.
3150 3150 # if the file is in any of these sets, it was touched in the working
3151 3151 # directory parent and we are sure it needs to be reverted.
3152 3152 for table, (xlist, msg), dobackup in disptable:
3153 3153 if abs not in table:
3154 3154 continue
3155 3155 if xlist is not None:
3156 3156 xlist.append(abs)
3157 3157 if dobackup:
3158 3158 # If in interactive mode, don't automatically create
3159 3159 # .orig files (issue4793)
3160 3160 if dobackup == backupinteractive:
3161 3161 tobackup.add(abs)
3162 3162 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3163 3163 bakname = scmutil.origpath(ui, repo, rel)
3164 3164 ui.note(_('saving current version of %s as %s\n') %
3165 3165 (rel, bakname))
3166 3166 if not opts.get('dry_run'):
3167 3167 if interactive:
3168 3168 util.copyfile(target, bakname)
3169 3169 else:
3170 3170 util.rename(target, bakname)
3171 3171 if ui.verbose or not exact:
3172 3172 if not isinstance(msg, basestring):
3173 3173 msg = msg(abs)
3174 3174 ui.status(msg % rel)
3175 3175 elif exact:
3176 3176 ui.warn(msg % rel)
3177 3177 break
3178 3178
3179 3179 if not opts.get('dry_run'):
3180 3180 needdata = ('revert', 'add', 'undelete')
3181 3181 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3182 3182 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3183 3183
3184 3184 if targetsubs:
3185 3185 # Revert the subrepos on the revert list
3186 3186 for sub in targetsubs:
3187 3187 try:
3188 3188 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3189 3189 except KeyError:
3190 3190 raise error.Abort("subrepository '%s' does not exist in %s!"
3191 3191 % (sub, short(ctx.node())))
3192 3192
3193 3193 def _revertprefetch(repo, ctx, *files):
3194 3194 """Let extension changing the storage layer prefetch content"""
3195 3195 pass
3196 3196
3197 3197 def _performrevert(repo, parents, ctx, actions, interactive=False,
3198 3198 tobackup=None):
3199 3199 """function that actually perform all the actions computed for revert
3200 3200
3201 3201 This is an independent function to let extension to plug in and react to
3202 3202 the imminent revert.
3203 3203
3204 3204 Make sure you have the working directory locked when calling this function.
3205 3205 """
3206 3206 parent, p2 = parents
3207 3207 node = ctx.node()
3208 3208 excluded_files = []
3209 3209 matcher_opts = {"exclude": excluded_files}
3210 3210
3211 3211 def checkout(f):
3212 3212 fc = ctx[f]
3213 3213 repo.wwrite(f, fc.data(), fc.flags())
3214 3214
3215 3215 def doremove(f):
3216 3216 try:
3217 3217 repo.wvfs.unlinkpath(f)
3218 3218 except OSError:
3219 3219 pass
3220 3220 repo.dirstate.remove(f)
3221 3221
3222 3222 audit_path = pathutil.pathauditor(repo.root)
3223 3223 for f in actions['forget'][0]:
3224 3224 if interactive:
3225 3225 choice = repo.ui.promptchoice(
3226 3226 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3227 3227 if choice == 0:
3228 3228 repo.dirstate.drop(f)
3229 3229 else:
3230 3230 excluded_files.append(repo.wjoin(f))
3231 3231 else:
3232 3232 repo.dirstate.drop(f)
3233 3233 for f in actions['remove'][0]:
3234 3234 audit_path(f)
3235 3235 if interactive:
3236 3236 choice = repo.ui.promptchoice(
3237 3237 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3238 3238 if choice == 0:
3239 3239 doremove(f)
3240 3240 else:
3241 3241 excluded_files.append(repo.wjoin(f))
3242 3242 else:
3243 3243 doremove(f)
3244 3244 for f in actions['drop'][0]:
3245 3245 audit_path(f)
3246 3246 repo.dirstate.remove(f)
3247 3247
3248 3248 normal = None
3249 3249 if node == parent:
3250 3250 # We're reverting to our parent. If possible, we'd like status
3251 3251 # to report the file as clean. We have to use normallookup for
3252 3252 # merges to avoid losing information about merged/dirty files.
3253 3253 if p2 != nullid:
3254 3254 normal = repo.dirstate.normallookup
3255 3255 else:
3256 3256 normal = repo.dirstate.normal
3257 3257
3258 3258 newlyaddedandmodifiedfiles = set()
3259 3259 if interactive:
3260 3260 # Prompt the user for changes to revert
3261 3261 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3262 3262 m = scmutil.match(ctx, torevert, matcher_opts)
3263 3263 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3264 3264 diffopts.nodates = True
3265 3265 diffopts.git = True
3266 3266 operation = 'discard'
3267 3267 reversehunks = True
3268 3268 if node != parent:
3269 3269 operation = 'revert'
3270 3270 reversehunks = repo.ui.configbool('experimental',
3271 3271 'revertalternateinteractivemode',
3272 3272 True)
3273 3273 if reversehunks:
3274 3274 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3275 3275 else:
3276 3276 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3277 3277 originalchunks = patch.parsepatch(diff)
3278 3278
3279 3279 try:
3280 3280
3281 3281 chunks, opts = recordfilter(repo.ui, originalchunks,
3282 3282 operation=operation)
3283 3283 if reversehunks:
3284 3284 chunks = patch.reversehunks(chunks)
3285 3285
3286 3286 except patch.PatchError as err:
3287 3287 raise error.Abort(_('error parsing patch: %s') % err)
3288 3288
3289 3289 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3290 3290 if tobackup is None:
3291 3291 tobackup = set()
3292 3292 # Apply changes
3293 3293 fp = stringio()
3294 3294 for c in chunks:
3295 3295 # Create a backup file only if this hunk should be backed up
3296 3296 if ishunk(c) and c.header.filename() in tobackup:
3297 3297 abs = c.header.filename()
3298 3298 target = repo.wjoin(abs)
3299 3299 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3300 3300 util.copyfile(target, bakname)
3301 3301 tobackup.remove(abs)
3302 3302 c.write(fp)
3303 3303 dopatch = fp.tell()
3304 3304 fp.seek(0)
3305 3305 if dopatch:
3306 3306 try:
3307 3307 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3308 3308 except patch.PatchError as err:
3309 3309 raise error.Abort(str(err))
3310 3310 del fp
3311 3311 else:
3312 3312 for f in actions['revert'][0]:
3313 3313 checkout(f)
3314 3314 if normal:
3315 3315 normal(f)
3316 3316
3317 3317 for f in actions['add'][0]:
3318 3318 # Don't checkout modified files, they are already created by the diff
3319 3319 if f not in newlyaddedandmodifiedfiles:
3320 3320 checkout(f)
3321 3321 repo.dirstate.add(f)
3322 3322
3323 3323 normal = repo.dirstate.normallookup
3324 3324 if node == parent and p2 == nullid:
3325 3325 normal = repo.dirstate.normal
3326 3326 for f in actions['undelete'][0]:
3327 3327 checkout(f)
3328 3328 normal(f)
3329 3329
3330 3330 copied = copies.pathcopies(repo[parent], ctx)
3331 3331
3332 3332 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3333 3333 if f in copied:
3334 3334 repo.dirstate.copy(copied[f], f)
3335 3335
3336 3336 def command(table):
3337 3337 """Returns a function object to be used as a decorator for making commands.
3338 3338
3339 3339 This function receives a command table as its argument. The table should
3340 3340 be a dict.
3341 3341
3342 3342 The returned function can be used as a decorator for adding commands
3343 3343 to that command table. This function accepts multiple arguments to define
3344 3344 a command.
3345 3345
3346 3346 The first argument is the command name.
3347 3347
3348 3348 The options argument is an iterable of tuples defining command arguments.
3349 3349 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3350 3350
3351 3351 The synopsis argument defines a short, one line summary of how to use the
3352 3352 command. This shows up in the help output.
3353 3353
3354 3354 The norepo argument defines whether the command does not require a
3355 3355 local repository. Most commands operate against a repository, thus the
3356 3356 default is False.
3357 3357
3358 3358 The optionalrepo argument defines whether the command optionally requires
3359 3359 a local repository.
3360 3360
3361 3361 The inferrepo argument defines whether to try to find a repository from the
3362 3362 command line arguments. If True, arguments will be examined for potential
3363 3363 repository locations. See ``findrepo()``. If a repository is found, it
3364 3364 will be used.
3365 3365 """
3366 3366 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3367 3367 inferrepo=False):
3368 3368 def decorator(func):
3369 3369 func.norepo = norepo
3370 3370 func.optionalrepo = optionalrepo
3371 3371 func.inferrepo = inferrepo
3372 3372 if synopsis:
3373 3373 table[name] = func, list(options), synopsis
3374 3374 else:
3375 3375 table[name] = func, list(options)
3376 3376 return func
3377 3377 return decorator
3378 3378
3379 3379 return cmd
3380 3380
3381 3381 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3382 3382 # commands.outgoing. "missing" is "missing" of the result of
3383 3383 # "findcommonoutgoing()"
3384 3384 outgoinghooks = util.hooks()
3385 3385
3386 3386 # a list of (ui, repo) functions called by commands.summary
3387 3387 summaryhooks = util.hooks()
3388 3388
3389 3389 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3390 3390 #
3391 3391 # functions should return tuple of booleans below, if 'changes' is None:
3392 3392 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3393 3393 #
3394 3394 # otherwise, 'changes' is a tuple of tuples below:
3395 3395 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3396 3396 # - (desturl, destbranch, destpeer, outgoing)
3397 3397 summaryremotehooks = util.hooks()
3398 3398
3399 3399 # A list of state files kept by multistep operations like graft.
3400 3400 # Since graft cannot be aborted, it is considered 'clearable' by update.
3401 3401 # note: bisect is intentionally excluded
3402 3402 # (state file, clearable, allowcommit, error, hint)
3403 3403 unfinishedstates = [
3404 3404 ('graftstate', True, False, _('graft in progress'),
3405 3405 _("use 'hg graft --continue' or 'hg update' to abort")),
3406 3406 ('updatestate', True, False, _('last update was interrupted'),
3407 3407 _("use 'hg update' to get a consistent checkout"))
3408 3408 ]
3409 3409
3410 3410 def checkunfinished(repo, commit=False):
3411 3411 '''Look for an unfinished multistep operation, like graft, and abort
3412 3412 if found. It's probably good to check this right before
3413 3413 bailifchanged().
3414 3414 '''
3415 3415 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3416 3416 if commit and allowcommit:
3417 3417 continue
3418 3418 if repo.vfs.exists(f):
3419 3419 raise error.Abort(msg, hint=hint)
3420 3420
3421 3421 def clearunfinished(repo):
3422 3422 '''Check for unfinished operations (as above), and clear the ones
3423 3423 that are clearable.
3424 3424 '''
3425 3425 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3426 3426 if not clearable and repo.vfs.exists(f):
3427 3427 raise error.Abort(msg, hint=hint)
3428 3428 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3429 3429 if clearable and repo.vfs.exists(f):
3430 3430 util.unlink(repo.vfs.join(f))
3431 3431
3432 3432 afterresolvedstates = [
3433 3433 ('graftstate',
3434 3434 _('hg graft --continue')),
3435 3435 ]
3436 3436
3437 3437 def howtocontinue(repo):
3438 3438 '''Check for an unfinished operation and return the command to finish
3439 3439 it.
3440 3440
3441 3441 afterresolvedstates tuples define a .hg/{file} and the corresponding
3442 3442 command needed to finish it.
3443 3443
3444 3444 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3445 3445 a boolean.
3446 3446 '''
3447 3447 contmsg = _("continue: %s")
3448 3448 for f, msg in afterresolvedstates:
3449 3449 if repo.vfs.exists(f):
3450 3450 return contmsg % msg, True
3451 3451 workingctx = repo[None]
3452 3452 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3453 3453 for s in workingctx.substate)
3454 3454 if dirty:
3455 3455 return contmsg % _("hg commit"), False
3456 3456 return None, None
3457 3457
3458 3458 def checkafterresolved(repo):
3459 3459 '''Inform the user about the next action after completing hg resolve
3460 3460
3461 3461 If there's a matching afterresolvedstates, howtocontinue will yield
3462 3462 repo.ui.warn as the reporter.
3463 3463
3464 3464 Otherwise, it will yield repo.ui.note.
3465 3465 '''
3466 3466 msg, warning = howtocontinue(repo)
3467 3467 if msg is not None:
3468 3468 if warning:
3469 3469 repo.ui.warn("%s\n" % msg)
3470 3470 else:
3471 3471 repo.ui.note("%s\n" % msg)
3472 3472
3473 3473 def wrongtooltocontinue(repo, task):
3474 3474 '''Raise an abort suggesting how to properly continue if there is an
3475 3475 active task.
3476 3476
3477 3477 Uses howtocontinue() to find the active task.
3478 3478
3479 3479 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3480 3480 a hint.
3481 3481 '''
3482 3482 after = howtocontinue(repo)
3483 3483 hint = None
3484 3484 if after[1]:
3485 3485 hint = after[0]
3486 3486 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5520 +1,5519 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 )
22 22 from . import (
23 23 archival,
24 24 bookmarks,
25 25 bundle2,
26 26 changegroup,
27 27 cmdutil,
28 28 copies,
29 29 destutil,
30 30 dirstateguard,
31 31 discovery,
32 32 encoding,
33 33 error,
34 34 exchange,
35 35 extensions,
36 36 graphmod,
37 37 hbisect,
38 38 help,
39 39 hg,
40 40 lock as lockmod,
41 41 merge as mergemod,
42 42 obsolete,
43 43 patch,
44 44 phases,
45 45 pycompat,
46 46 rcutil,
47 47 revsetlang,
48 48 scmutil,
49 49 server,
50 50 sshserver,
51 51 streamclone,
52 52 tags as tagsmod,
53 53 templatekw,
54 54 ui as uimod,
55 55 util,
56 56 )
57 57
58 58 release = lockmod.release
59 59
60 60 table = {}
61 61
62 62 command = cmdutil.command(table)
63 63
64 64 # label constants
65 65 # until 3.5, bookmarks.current was the advertised name, not
66 66 # bookmarks.active, so we must use both to avoid breaking old
67 67 # custom styles
68 68 activebookmarklabel = 'bookmarks.active bookmarks.current'
69 69
70 70 # common command options
71 71
72 72 globalopts = [
73 73 ('R', 'repository', '',
74 74 _('repository root directory or name of overlay bundle file'),
75 75 _('REPO')),
76 76 ('', 'cwd', '',
77 77 _('change working directory'), _('DIR')),
78 78 ('y', 'noninteractive', None,
79 79 _('do not prompt, automatically pick the first choice for all prompts')),
80 80 ('q', 'quiet', None, _('suppress output')),
81 81 ('v', 'verbose', None, _('enable additional output')),
82 82 ('', 'color', '',
83 83 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
84 84 # and should not be translated
85 85 _("when to colorize (boolean, always, auto, never, or debug)"),
86 86 _('TYPE')),
87 87 ('', 'config', [],
88 88 _('set/override config option (use \'section.name=value\')'),
89 89 _('CONFIG')),
90 90 ('', 'debug', None, _('enable debugging output')),
91 91 ('', 'debugger', None, _('start debugger')),
92 92 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
93 93 _('ENCODE')),
94 94 ('', 'encodingmode', encoding.encodingmode,
95 95 _('set the charset encoding mode'), _('MODE')),
96 96 ('', 'traceback', None, _('always print a traceback on exception')),
97 97 ('', 'time', None, _('time how long the command takes')),
98 98 ('', 'profile', None, _('print command execution profile')),
99 99 ('', 'version', None, _('output version information and exit')),
100 100 ('h', 'help', None, _('display help and exit')),
101 101 ('', 'hidden', False, _('consider hidden changesets')),
102 102 ('', 'pager', 'auto',
103 103 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
104 104 ]
105 105
106 106 dryrunopts = [('n', 'dry-run', None,
107 107 _('do not perform actions, just print output'))]
108 108
109 109 remoteopts = [
110 110 ('e', 'ssh', '',
111 111 _('specify ssh command to use'), _('CMD')),
112 112 ('', 'remotecmd', '',
113 113 _('specify hg command to run on the remote side'), _('CMD')),
114 114 ('', 'insecure', None,
115 115 _('do not verify server certificate (ignoring web.cacerts config)')),
116 116 ]
117 117
118 118 walkopts = [
119 119 ('I', 'include', [],
120 120 _('include names matching the given patterns'), _('PATTERN')),
121 121 ('X', 'exclude', [],
122 122 _('exclude names matching the given patterns'), _('PATTERN')),
123 123 ]
124 124
125 125 commitopts = [
126 126 ('m', 'message', '',
127 127 _('use text as commit message'), _('TEXT')),
128 128 ('l', 'logfile', '',
129 129 _('read commit message from file'), _('FILE')),
130 130 ]
131 131
132 132 commitopts2 = [
133 133 ('d', 'date', '',
134 134 _('record the specified date as commit date'), _('DATE')),
135 135 ('u', 'user', '',
136 136 _('record the specified user as committer'), _('USER')),
137 137 ]
138 138
139 139 # hidden for now
140 140 formatteropts = [
141 141 ('T', 'template', '',
142 142 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
143 143 ]
144 144
145 145 templateopts = [
146 146 ('', 'style', '',
147 147 _('display using template map file (DEPRECATED)'), _('STYLE')),
148 148 ('T', 'template', '',
149 149 _('display with template'), _('TEMPLATE')),
150 150 ]
151 151
152 152 logopts = [
153 153 ('p', 'patch', None, _('show patch')),
154 154 ('g', 'git', None, _('use git extended diff format')),
155 155 ('l', 'limit', '',
156 156 _('limit number of changes displayed'), _('NUM')),
157 157 ('M', 'no-merges', None, _('do not show merges')),
158 158 ('', 'stat', None, _('output diffstat-style summary of changes')),
159 159 ('G', 'graph', None, _("show the revision DAG")),
160 160 ] + templateopts
161 161
162 162 diffopts = [
163 163 ('a', 'text', None, _('treat all files as text')),
164 164 ('g', 'git', None, _('use git extended diff format')),
165 165 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
166 166 ('', 'nodates', None, _('omit dates from diff headers'))
167 167 ]
168 168
169 169 diffwsopts = [
170 170 ('w', 'ignore-all-space', None,
171 171 _('ignore white space when comparing lines')),
172 172 ('b', 'ignore-space-change', None,
173 173 _('ignore changes in the amount of white space')),
174 174 ('B', 'ignore-blank-lines', None,
175 175 _('ignore changes whose lines are all blank')),
176 176 ]
177 177
178 178 diffopts2 = [
179 179 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
180 180 ('p', 'show-function', None, _('show which function each change is in')),
181 181 ('', 'reverse', None, _('produce a diff that undoes the changes')),
182 182 ] + diffwsopts + [
183 183 ('U', 'unified', '',
184 184 _('number of lines of context to show'), _('NUM')),
185 185 ('', 'stat', None, _('output diffstat-style summary of changes')),
186 186 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
187 187 ]
188 188
189 189 mergetoolopts = [
190 190 ('t', 'tool', '', _('specify merge tool')),
191 191 ]
192 192
193 193 similarityopts = [
194 194 ('s', 'similarity', '',
195 195 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
196 196 ]
197 197
198 198 subrepoopts = [
199 199 ('S', 'subrepos', None,
200 200 _('recurse into subrepositories'))
201 201 ]
202 202
203 203 debugrevlogopts = [
204 204 ('c', 'changelog', False, _('open changelog')),
205 205 ('m', 'manifest', False, _('open manifest')),
206 206 ('', 'dir', '', _('open directory manifest')),
207 207 ]
208 208
209 209 # Commands start here, listed alphabetically
210 210
211 211 @command('^add',
212 212 walkopts + subrepoopts + dryrunopts,
213 213 _('[OPTION]... [FILE]...'),
214 214 inferrepo=True)
215 215 def add(ui, repo, *pats, **opts):
216 216 """add the specified files on the next commit
217 217
218 218 Schedule files to be version controlled and added to the
219 219 repository.
220 220
221 221 The files will be added to the repository at the next commit. To
222 222 undo an add before that, see :hg:`forget`.
223 223
224 224 If no names are given, add all files to the repository (except
225 225 files matching ``.hgignore``).
226 226
227 227 .. container:: verbose
228 228
229 229 Examples:
230 230
231 231 - New (unknown) files are added
232 232 automatically by :hg:`add`::
233 233
234 234 $ ls
235 235 foo.c
236 236 $ hg status
237 237 ? foo.c
238 238 $ hg add
239 239 adding foo.c
240 240 $ hg status
241 241 A foo.c
242 242
243 243 - Specific files to be added can be specified::
244 244
245 245 $ ls
246 246 bar.c foo.c
247 247 $ hg status
248 248 ? bar.c
249 249 ? foo.c
250 250 $ hg add bar.c
251 251 $ hg status
252 252 A bar.c
253 253 ? foo.c
254 254
255 255 Returns 0 if all files are successfully added.
256 256 """
257 257
258 opts = pycompat.byteskwargs(opts)
259 m = scmutil.match(repo[None], pats, opts)
258 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
260 259 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
261 260 return rejected and 1 or 0
262 261
263 262 @command('addremove',
264 263 similarityopts + subrepoopts + walkopts + dryrunopts,
265 264 _('[OPTION]... [FILE]...'),
266 265 inferrepo=True)
267 266 def addremove(ui, repo, *pats, **opts):
268 267 """add all new files, delete all missing files
269 268
270 269 Add all new files and remove all missing files from the
271 270 repository.
272 271
273 272 Unless names are given, new files are ignored if they match any of
274 273 the patterns in ``.hgignore``. As with add, these changes take
275 274 effect at the next commit.
276 275
277 276 Use the -s/--similarity option to detect renamed files. This
278 277 option takes a percentage between 0 (disabled) and 100 (files must
279 278 be identical) as its parameter. With a parameter greater than 0,
280 279 this compares every removed file with every added file and records
281 280 those similar enough as renames. Detecting renamed files this way
282 281 can be expensive. After using this option, :hg:`status -C` can be
283 282 used to check which files were identified as moved or renamed. If
284 283 not specified, -s/--similarity defaults to 100 and only renames of
285 284 identical files are detected.
286 285
287 286 .. container:: verbose
288 287
289 288 Examples:
290 289
291 290 - A number of files (bar.c and foo.c) are new,
292 291 while foobar.c has been removed (without using :hg:`remove`)
293 292 from the repository::
294 293
295 294 $ ls
296 295 bar.c foo.c
297 296 $ hg status
298 297 ! foobar.c
299 298 ? bar.c
300 299 ? foo.c
301 300 $ hg addremove
302 301 adding bar.c
303 302 adding foo.c
304 303 removing foobar.c
305 304 $ hg status
306 305 A bar.c
307 306 A foo.c
308 307 R foobar.c
309 308
310 309 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 310 Afterwards, it was edited slightly::
312 311
313 312 $ ls
314 313 foo.c
315 314 $ hg status
316 315 ! foobar.c
317 316 ? foo.c
318 317 $ hg addremove --similarity 90
319 318 removing foobar.c
320 319 adding foo.c
321 320 recording removal of foobar.c as rename to foo.c (94% similar)
322 321 $ hg status -C
323 322 A foo.c
324 323 foobar.c
325 324 R foobar.c
326 325
327 326 Returns 0 if all files are successfully added.
328 327 """
329 328 opts = pycompat.byteskwargs(opts)
330 329 try:
331 330 sim = float(opts.get('similarity') or 100)
332 331 except ValueError:
333 332 raise error.Abort(_('similarity must be a number'))
334 333 if sim < 0 or sim > 100:
335 334 raise error.Abort(_('similarity must be between 0 and 100'))
336 335 matcher = scmutil.match(repo[None], pats, opts)
337 336 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
338 337
339 338 @command('^annotate|blame',
340 339 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
341 340 ('', 'follow', None,
342 341 _('follow copies/renames and list the filename (DEPRECATED)')),
343 342 ('', 'no-follow', None, _("don't follow copies and renames")),
344 343 ('a', 'text', None, _('treat all files as text')),
345 344 ('u', 'user', None, _('list the author (long with -v)')),
346 345 ('f', 'file', None, _('list the filename')),
347 346 ('d', 'date', None, _('list the date (short with -q)')),
348 347 ('n', 'number', None, _('list the revision number (default)')),
349 348 ('c', 'changeset', None, _('list the changeset')),
350 349 ('l', 'line-number', None, _('show line number at the first appearance'))
351 350 ] + diffwsopts + walkopts + formatteropts,
352 351 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
353 352 inferrepo=True)
354 353 def annotate(ui, repo, *pats, **opts):
355 354 """show changeset information by line for each file
356 355
357 356 List changes in files, showing the revision id responsible for
358 357 each line.
359 358
360 359 This command is useful for discovering when a change was made and
361 360 by whom.
362 361
363 362 If you include --file, --user, or --date, the revision number is
364 363 suppressed unless you also include --number.
365 364
366 365 Without the -a/--text option, annotate will avoid processing files
367 366 it detects as binary. With -a, annotate will annotate the file
368 367 anyway, although the results will probably be neither useful
369 368 nor desirable.
370 369
371 370 Returns 0 on success.
372 371 """
373 372 opts = pycompat.byteskwargs(opts)
374 373 if not pats:
375 374 raise error.Abort(_('at least one filename or pattern is required'))
376 375
377 376 if opts.get('follow'):
378 377 # --follow is deprecated and now just an alias for -f/--file
379 378 # to mimic the behavior of Mercurial before version 1.5
380 379 opts['file'] = True
381 380
382 381 ctx = scmutil.revsingle(repo, opts.get('rev'))
383 382
384 383 fm = ui.formatter('annotate', opts)
385 384 if ui.quiet:
386 385 datefunc = util.shortdate
387 386 else:
388 387 datefunc = util.datestr
389 388 if ctx.rev() is None:
390 389 def hexfn(node):
391 390 if node is None:
392 391 return None
393 392 else:
394 393 return fm.hexfunc(node)
395 394 if opts.get('changeset'):
396 395 # omit "+" suffix which is appended to node hex
397 396 def formatrev(rev):
398 397 if rev is None:
399 398 return '%d' % ctx.p1().rev()
400 399 else:
401 400 return '%d' % rev
402 401 else:
403 402 def formatrev(rev):
404 403 if rev is None:
405 404 return '%d+' % ctx.p1().rev()
406 405 else:
407 406 return '%d ' % rev
408 407 def formathex(hex):
409 408 if hex is None:
410 409 return '%s+' % fm.hexfunc(ctx.p1().node())
411 410 else:
412 411 return '%s ' % hex
413 412 else:
414 413 hexfn = fm.hexfunc
415 414 formatrev = formathex = str
416 415
417 416 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
418 417 ('number', ' ', lambda x: x[0].rev(), formatrev),
419 418 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
420 419 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
421 420 ('file', ' ', lambda x: x[0].path(), str),
422 421 ('line_number', ':', lambda x: x[1], str),
423 422 ]
424 423 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
425 424
426 425 if (not opts.get('user') and not opts.get('changeset')
427 426 and not opts.get('date') and not opts.get('file')):
428 427 opts['number'] = True
429 428
430 429 linenumber = opts.get('line_number') is not None
431 430 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
432 431 raise error.Abort(_('at least one of -n/-c is required for -l'))
433 432
434 433 ui.pager('annotate')
435 434
436 435 if fm.isplain():
437 436 def makefunc(get, fmt):
438 437 return lambda x: fmt(get(x))
439 438 else:
440 439 def makefunc(get, fmt):
441 440 return get
442 441 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
443 442 if opts.get(op)]
444 443 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
445 444 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
446 445 if opts.get(op))
447 446
448 447 def bad(x, y):
449 448 raise error.Abort("%s: %s" % (x, y))
450 449
451 450 m = scmutil.match(ctx, pats, opts, badfn=bad)
452 451
453 452 follow = not opts.get('no_follow')
454 453 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
455 454 whitespace=True)
456 455 for abs in ctx.walk(m):
457 456 fctx = ctx[abs]
458 457 if not opts.get('text') and fctx.isbinary():
459 458 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
460 459 continue
461 460
462 461 lines = fctx.annotate(follow=follow, linenumber=linenumber,
463 462 diffopts=diffopts)
464 463 if not lines:
465 464 continue
466 465 formats = []
467 466 pieces = []
468 467
469 468 for f, sep in funcmap:
470 469 l = [f(n) for n, dummy in lines]
471 470 if fm.isplain():
472 471 sizes = [encoding.colwidth(x) for x in l]
473 472 ml = max(sizes)
474 473 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
475 474 else:
476 475 formats.append(['%s' for x in l])
477 476 pieces.append(l)
478 477
479 478 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
480 479 fm.startitem()
481 480 fm.write(fields, "".join(f), *p)
482 481 fm.write('line', ": %s", l[1])
483 482
484 483 if not lines[-1][1].endswith('\n'):
485 484 fm.plain('\n')
486 485
487 486 fm.end()
488 487
489 488 @command('archive',
490 489 [('', 'no-decode', None, _('do not pass files through decoders')),
491 490 ('p', 'prefix', '', _('directory prefix for files in archive'),
492 491 _('PREFIX')),
493 492 ('r', 'rev', '', _('revision to distribute'), _('REV')),
494 493 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
495 494 ] + subrepoopts + walkopts,
496 495 _('[OPTION]... DEST'))
497 496 def archive(ui, repo, dest, **opts):
498 497 '''create an unversioned archive of a repository revision
499 498
500 499 By default, the revision used is the parent of the working
501 500 directory; use -r/--rev to specify a different revision.
502 501
503 502 The archive type is automatically detected based on file
504 503 extension (to override, use -t/--type).
505 504
506 505 .. container:: verbose
507 506
508 507 Examples:
509 508
510 509 - create a zip file containing the 1.0 release::
511 510
512 511 hg archive -r 1.0 project-1.0.zip
513 512
514 513 - create a tarball excluding .hg files::
515 514
516 515 hg archive project.tar.gz -X ".hg*"
517 516
518 517 Valid types are:
519 518
520 519 :``files``: a directory full of files (default)
521 520 :``tar``: tar archive, uncompressed
522 521 :``tbz2``: tar archive, compressed using bzip2
523 522 :``tgz``: tar archive, compressed using gzip
524 523 :``uzip``: zip archive, uncompressed
525 524 :``zip``: zip archive, compressed using deflate
526 525
527 526 The exact name of the destination archive or directory is given
528 527 using a format string; see :hg:`help export` for details.
529 528
530 529 Each member added to an archive file has a directory prefix
531 530 prepended. Use -p/--prefix to specify a format string for the
532 531 prefix. The default is the basename of the archive, with suffixes
533 532 removed.
534 533
535 534 Returns 0 on success.
536 535 '''
537 536
538 537 opts = pycompat.byteskwargs(opts)
539 538 ctx = scmutil.revsingle(repo, opts.get('rev'))
540 539 if not ctx:
541 540 raise error.Abort(_('no working directory: please specify a revision'))
542 541 node = ctx.node()
543 542 dest = cmdutil.makefilename(repo, dest, node)
544 543 if os.path.realpath(dest) == repo.root:
545 544 raise error.Abort(_('repository root cannot be destination'))
546 545
547 546 kind = opts.get('type') or archival.guesskind(dest) or 'files'
548 547 prefix = opts.get('prefix')
549 548
550 549 if dest == '-':
551 550 if kind == 'files':
552 551 raise error.Abort(_('cannot archive plain files to stdout'))
553 552 dest = cmdutil.makefileobj(repo, dest)
554 553 if not prefix:
555 554 prefix = os.path.basename(repo.root) + '-%h'
556 555
557 556 prefix = cmdutil.makefilename(repo, prefix, node)
558 557 matchfn = scmutil.match(ctx, [], opts)
559 558 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
560 559 matchfn, prefix, subrepos=opts.get('subrepos'))
561 560
562 561 @command('backout',
563 562 [('', 'merge', None, _('merge with old dirstate parent after backout')),
564 563 ('', 'commit', None,
565 564 _('commit if no conflicts were encountered (DEPRECATED)')),
566 565 ('', 'no-commit', None, _('do not commit')),
567 566 ('', 'parent', '',
568 567 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
569 568 ('r', 'rev', '', _('revision to backout'), _('REV')),
570 569 ('e', 'edit', False, _('invoke editor on commit messages')),
571 570 ] + mergetoolopts + walkopts + commitopts + commitopts2,
572 571 _('[OPTION]... [-r] REV'))
573 572 def backout(ui, repo, node=None, rev=None, **opts):
574 573 '''reverse effect of earlier changeset
575 574
576 575 Prepare a new changeset with the effect of REV undone in the
577 576 current working directory. If no conflicts were encountered,
578 577 it will be committed immediately.
579 578
580 579 If REV is the parent of the working directory, then this new changeset
581 580 is committed automatically (unless --no-commit is specified).
582 581
583 582 .. note::
584 583
585 584 :hg:`backout` cannot be used to fix either an unwanted or
586 585 incorrect merge.
587 586
588 587 .. container:: verbose
589 588
590 589 Examples:
591 590
592 591 - Reverse the effect of the parent of the working directory.
593 592 This backout will be committed immediately::
594 593
595 594 hg backout -r .
596 595
597 596 - Reverse the effect of previous bad revision 23::
598 597
599 598 hg backout -r 23
600 599
601 600 - Reverse the effect of previous bad revision 23 and
602 601 leave changes uncommitted::
603 602
604 603 hg backout -r 23 --no-commit
605 604 hg commit -m "Backout revision 23"
606 605
607 606 By default, the pending changeset will have one parent,
608 607 maintaining a linear history. With --merge, the pending
609 608 changeset will instead have two parents: the old parent of the
610 609 working directory and a new child of REV that simply undoes REV.
611 610
612 611 Before version 1.7, the behavior without --merge was equivalent
613 612 to specifying --merge followed by :hg:`update --clean .` to
614 613 cancel the merge and leave the child of REV as a head to be
615 614 merged separately.
616 615
617 616 See :hg:`help dates` for a list of formats valid for -d/--date.
618 617
619 618 See :hg:`help revert` for a way to restore files to the state
620 619 of another revision.
621 620
622 621 Returns 0 on success, 1 if nothing to backout or there are unresolved
623 622 files.
624 623 '''
625 624 wlock = lock = None
626 625 try:
627 626 wlock = repo.wlock()
628 627 lock = repo.lock()
629 628 return _dobackout(ui, repo, node, rev, **opts)
630 629 finally:
631 630 release(lock, wlock)
632 631
633 632 def _dobackout(ui, repo, node=None, rev=None, **opts):
634 633 opts = pycompat.byteskwargs(opts)
635 634 if opts.get('commit') and opts.get('no_commit'):
636 635 raise error.Abort(_("cannot use --commit with --no-commit"))
637 636 if opts.get('merge') and opts.get('no_commit'):
638 637 raise error.Abort(_("cannot use --merge with --no-commit"))
639 638
640 639 if rev and node:
641 640 raise error.Abort(_("please specify just one revision"))
642 641
643 642 if not rev:
644 643 rev = node
645 644
646 645 if not rev:
647 646 raise error.Abort(_("please specify a revision to backout"))
648 647
649 648 date = opts.get('date')
650 649 if date:
651 650 opts['date'] = util.parsedate(date)
652 651
653 652 cmdutil.checkunfinished(repo)
654 653 cmdutil.bailifchanged(repo)
655 654 node = scmutil.revsingle(repo, rev).node()
656 655
657 656 op1, op2 = repo.dirstate.parents()
658 657 if not repo.changelog.isancestor(node, op1):
659 658 raise error.Abort(_('cannot backout change that is not an ancestor'))
660 659
661 660 p1, p2 = repo.changelog.parents(node)
662 661 if p1 == nullid:
663 662 raise error.Abort(_('cannot backout a change with no parents'))
664 663 if p2 != nullid:
665 664 if not opts.get('parent'):
666 665 raise error.Abort(_('cannot backout a merge changeset'))
667 666 p = repo.lookup(opts['parent'])
668 667 if p not in (p1, p2):
669 668 raise error.Abort(_('%s is not a parent of %s') %
670 669 (short(p), short(node)))
671 670 parent = p
672 671 else:
673 672 if opts.get('parent'):
674 673 raise error.Abort(_('cannot use --parent on non-merge changeset'))
675 674 parent = p1
676 675
677 676 # the backout should appear on the same branch
678 677 branch = repo.dirstate.branch()
679 678 bheads = repo.branchheads(branch)
680 679 rctx = scmutil.revsingle(repo, hex(parent))
681 680 if not opts.get('merge') and op1 != node:
682 681 dsguard = dirstateguard.dirstateguard(repo, 'backout')
683 682 try:
684 683 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
685 684 'backout')
686 685 stats = mergemod.update(repo, parent, True, True, node, False)
687 686 repo.setparents(op1, op2)
688 687 dsguard.close()
689 688 hg._showstats(repo, stats)
690 689 if stats[3]:
691 690 repo.ui.status(_("use 'hg resolve' to retry unresolved "
692 691 "file merges\n"))
693 692 return 1
694 693 finally:
695 694 ui.setconfig('ui', 'forcemerge', '', '')
696 695 lockmod.release(dsguard)
697 696 else:
698 697 hg.clean(repo, node, show_stats=False)
699 698 repo.dirstate.setbranch(branch)
700 699 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
701 700
702 701 if opts.get('no_commit'):
703 702 msg = _("changeset %s backed out, "
704 703 "don't forget to commit.\n")
705 704 ui.status(msg % short(node))
706 705 return 0
707 706
708 707 def commitfunc(ui, repo, message, match, opts):
709 708 editform = 'backout'
710 709 e = cmdutil.getcommiteditor(editform=editform, **opts)
711 710 if not message:
712 711 # we don't translate commit messages
713 712 message = "Backed out changeset %s" % short(node)
714 713 e = cmdutil.getcommiteditor(edit=True, editform=editform)
715 714 return repo.commit(message, opts.get('user'), opts.get('date'),
716 715 match, editor=e)
717 716 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
718 717 if not newnode:
719 718 ui.status(_("nothing changed\n"))
720 719 return 1
721 720 cmdutil.commitstatus(repo, newnode, branch, bheads)
722 721
723 722 def nice(node):
724 723 return '%d:%s' % (repo.changelog.rev(node), short(node))
725 724 ui.status(_('changeset %s backs out changeset %s\n') %
726 725 (nice(repo.changelog.tip()), nice(node)))
727 726 if opts.get('merge') and op1 != node:
728 727 hg.clean(repo, op1, show_stats=False)
729 728 ui.status(_('merging with changeset %s\n')
730 729 % nice(repo.changelog.tip()))
731 730 try:
732 731 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
733 732 'backout')
734 733 return hg.merge(repo, hex(repo.changelog.tip()))
735 734 finally:
736 735 ui.setconfig('ui', 'forcemerge', '', '')
737 736 return 0
738 737
739 738 @command('bisect',
740 739 [('r', 'reset', False, _('reset bisect state')),
741 740 ('g', 'good', False, _('mark changeset good')),
742 741 ('b', 'bad', False, _('mark changeset bad')),
743 742 ('s', 'skip', False, _('skip testing changeset')),
744 743 ('e', 'extend', False, _('extend the bisect range')),
745 744 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
746 745 ('U', 'noupdate', False, _('do not update to target'))],
747 746 _("[-gbsr] [-U] [-c CMD] [REV]"))
748 747 def bisect(ui, repo, rev=None, extra=None, command=None,
749 748 reset=None, good=None, bad=None, skip=None, extend=None,
750 749 noupdate=None):
751 750 """subdivision search of changesets
752 751
753 752 This command helps to find changesets which introduce problems. To
754 753 use, mark the earliest changeset you know exhibits the problem as
755 754 bad, then mark the latest changeset which is free from the problem
756 755 as good. Bisect will update your working directory to a revision
757 756 for testing (unless the -U/--noupdate option is specified). Once
758 757 you have performed tests, mark the working directory as good or
759 758 bad, and bisect will either update to another candidate changeset
760 759 or announce that it has found the bad revision.
761 760
762 761 As a shortcut, you can also use the revision argument to mark a
763 762 revision as good or bad without checking it out first.
764 763
765 764 If you supply a command, it will be used for automatic bisection.
766 765 The environment variable HG_NODE will contain the ID of the
767 766 changeset being tested. The exit status of the command will be
768 767 used to mark revisions as good or bad: status 0 means good, 125
769 768 means to skip the revision, 127 (command not found) will abort the
770 769 bisection, and any other non-zero exit status means the revision
771 770 is bad.
772 771
773 772 .. container:: verbose
774 773
775 774 Some examples:
776 775
777 776 - start a bisection with known bad revision 34, and good revision 12::
778 777
779 778 hg bisect --bad 34
780 779 hg bisect --good 12
781 780
782 781 - advance the current bisection by marking current revision as good or
783 782 bad::
784 783
785 784 hg bisect --good
786 785 hg bisect --bad
787 786
788 787 - mark the current revision, or a known revision, to be skipped (e.g. if
789 788 that revision is not usable because of another issue)::
790 789
791 790 hg bisect --skip
792 791 hg bisect --skip 23
793 792
794 793 - skip all revisions that do not touch directories ``foo`` or ``bar``::
795 794
796 795 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
797 796
798 797 - forget the current bisection::
799 798
800 799 hg bisect --reset
801 800
802 801 - use 'make && make tests' to automatically find the first broken
803 802 revision::
804 803
805 804 hg bisect --reset
806 805 hg bisect --bad 34
807 806 hg bisect --good 12
808 807 hg bisect --command "make && make tests"
809 808
810 809 - see all changesets whose states are already known in the current
811 810 bisection::
812 811
813 812 hg log -r "bisect(pruned)"
814 813
815 814 - see the changeset currently being bisected (especially useful
816 815 if running with -U/--noupdate)::
817 816
818 817 hg log -r "bisect(current)"
819 818
820 819 - see all changesets that took part in the current bisection::
821 820
822 821 hg log -r "bisect(range)"
823 822
824 823 - you can even get a nice graph::
825 824
826 825 hg log --graph -r "bisect(range)"
827 826
828 827 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
829 828
830 829 Returns 0 on success.
831 830 """
832 831 # backward compatibility
833 832 if rev in "good bad reset init".split():
834 833 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
835 834 cmd, rev, extra = rev, extra, None
836 835 if cmd == "good":
837 836 good = True
838 837 elif cmd == "bad":
839 838 bad = True
840 839 else:
841 840 reset = True
842 841 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
843 842 raise error.Abort(_('incompatible arguments'))
844 843
845 844 if reset:
846 845 hbisect.resetstate(repo)
847 846 return
848 847
849 848 state = hbisect.load_state(repo)
850 849
851 850 # update state
852 851 if good or bad or skip:
853 852 if rev:
854 853 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
855 854 else:
856 855 nodes = [repo.lookup('.')]
857 856 if good:
858 857 state['good'] += nodes
859 858 elif bad:
860 859 state['bad'] += nodes
861 860 elif skip:
862 861 state['skip'] += nodes
863 862 hbisect.save_state(repo, state)
864 863 if not (state['good'] and state['bad']):
865 864 return
866 865
867 866 def mayupdate(repo, node, show_stats=True):
868 867 """common used update sequence"""
869 868 if noupdate:
870 869 return
871 870 cmdutil.checkunfinished(repo)
872 871 cmdutil.bailifchanged(repo)
873 872 return hg.clean(repo, node, show_stats=show_stats)
874 873
875 874 displayer = cmdutil.show_changeset(ui, repo, {})
876 875
877 876 if command:
878 877 changesets = 1
879 878 if noupdate:
880 879 try:
881 880 node = state['current'][0]
882 881 except LookupError:
883 882 raise error.Abort(_('current bisect revision is unknown - '
884 883 'start a new bisect to fix'))
885 884 else:
886 885 node, p2 = repo.dirstate.parents()
887 886 if p2 != nullid:
888 887 raise error.Abort(_('current bisect revision is a merge'))
889 888 if rev:
890 889 node = repo[scmutil.revsingle(repo, rev, node)].node()
891 890 try:
892 891 while changesets:
893 892 # update state
894 893 state['current'] = [node]
895 894 hbisect.save_state(repo, state)
896 895 status = ui.system(command, environ={'HG_NODE': hex(node)},
897 896 blockedtag='bisect_check')
898 897 if status == 125:
899 898 transition = "skip"
900 899 elif status == 0:
901 900 transition = "good"
902 901 # status < 0 means process was killed
903 902 elif status == 127:
904 903 raise error.Abort(_("failed to execute %s") % command)
905 904 elif status < 0:
906 905 raise error.Abort(_("%s killed") % command)
907 906 else:
908 907 transition = "bad"
909 908 state[transition].append(node)
910 909 ctx = repo[node]
911 910 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
912 911 hbisect.checkstate(state)
913 912 # bisect
914 913 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
915 914 # update to next check
916 915 node = nodes[0]
917 916 mayupdate(repo, node, show_stats=False)
918 917 finally:
919 918 state['current'] = [node]
920 919 hbisect.save_state(repo, state)
921 920 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
922 921 return
923 922
924 923 hbisect.checkstate(state)
925 924
926 925 # actually bisect
927 926 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
928 927 if extend:
929 928 if not changesets:
930 929 extendnode = hbisect.extendrange(repo, state, nodes, good)
931 930 if extendnode is not None:
932 931 ui.write(_("Extending search to changeset %d:%s\n")
933 932 % (extendnode.rev(), extendnode))
934 933 state['current'] = [extendnode.node()]
935 934 hbisect.save_state(repo, state)
936 935 return mayupdate(repo, extendnode.node())
937 936 raise error.Abort(_("nothing to extend"))
938 937
939 938 if changesets == 0:
940 939 hbisect.printresult(ui, repo, state, displayer, nodes, good)
941 940 else:
942 941 assert len(nodes) == 1 # only a single node can be tested next
943 942 node = nodes[0]
944 943 # compute the approximate number of remaining tests
945 944 tests, size = 0, 2
946 945 while size <= changesets:
947 946 tests, size = tests + 1, size * 2
948 947 rev = repo.changelog.rev(node)
949 948 ui.write(_("Testing changeset %d:%s "
950 949 "(%d changesets remaining, ~%d tests)\n")
951 950 % (rev, short(node), changesets, tests))
952 951 state['current'] = [node]
953 952 hbisect.save_state(repo, state)
954 953 return mayupdate(repo, node)
955 954
956 955 @command('bookmarks|bookmark',
957 956 [('f', 'force', False, _('force')),
958 957 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
959 958 ('d', 'delete', False, _('delete a given bookmark')),
960 959 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
961 960 ('i', 'inactive', False, _('mark a bookmark inactive')),
962 961 ] + formatteropts,
963 962 _('hg bookmarks [OPTIONS]... [NAME]...'))
964 963 def bookmark(ui, repo, *names, **opts):
965 964 '''create a new bookmark or list existing bookmarks
966 965
967 966 Bookmarks are labels on changesets to help track lines of development.
968 967 Bookmarks are unversioned and can be moved, renamed and deleted.
969 968 Deleting or moving a bookmark has no effect on the associated changesets.
970 969
971 970 Creating or updating to a bookmark causes it to be marked as 'active'.
972 971 The active bookmark is indicated with a '*'.
973 972 When a commit is made, the active bookmark will advance to the new commit.
974 973 A plain :hg:`update` will also advance an active bookmark, if possible.
975 974 Updating away from a bookmark will cause it to be deactivated.
976 975
977 976 Bookmarks can be pushed and pulled between repositories (see
978 977 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
979 978 diverged, a new 'divergent bookmark' of the form 'name@path' will
980 979 be created. Using :hg:`merge` will resolve the divergence.
981 980
982 981 A bookmark named '@' has the special property that :hg:`clone` will
983 982 check it out by default if it exists.
984 983
985 984 .. container:: verbose
986 985
987 986 Examples:
988 987
989 988 - create an active bookmark for a new line of development::
990 989
991 990 hg book new-feature
992 991
993 992 - create an inactive bookmark as a place marker::
994 993
995 994 hg book -i reviewed
996 995
997 996 - create an inactive bookmark on another changeset::
998 997
999 998 hg book -r .^ tested
1000 999
1001 1000 - rename bookmark turkey to dinner::
1002 1001
1003 1002 hg book -m turkey dinner
1004 1003
1005 1004 - move the '@' bookmark from another branch::
1006 1005
1007 1006 hg book -f @
1008 1007 '''
1009 1008 opts = pycompat.byteskwargs(opts)
1010 1009 force = opts.get('force')
1011 1010 rev = opts.get('rev')
1012 1011 delete = opts.get('delete')
1013 1012 rename = opts.get('rename')
1014 1013 inactive = opts.get('inactive')
1015 1014
1016 1015 def checkformat(mark):
1017 1016 mark = mark.strip()
1018 1017 if not mark:
1019 1018 raise error.Abort(_("bookmark names cannot consist entirely of "
1020 1019 "whitespace"))
1021 1020 scmutil.checknewlabel(repo, mark, 'bookmark')
1022 1021 return mark
1023 1022
1024 1023 def checkconflict(repo, mark, cur, force=False, target=None):
1025 1024 if mark in marks and not force:
1026 1025 if target:
1027 1026 if marks[mark] == target and target == cur:
1028 1027 # re-activating a bookmark
1029 1028 return
1030 1029 anc = repo.changelog.ancestors([repo[target].rev()])
1031 1030 bmctx = repo[marks[mark]]
1032 1031 divs = [repo[b].node() for b in marks
1033 1032 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1034 1033
1035 1034 # allow resolving a single divergent bookmark even if moving
1036 1035 # the bookmark across branches when a revision is specified
1037 1036 # that contains a divergent bookmark
1038 1037 if bmctx.rev() not in anc and target in divs:
1039 1038 bookmarks.deletedivergent(repo, [target], mark)
1040 1039 return
1041 1040
1042 1041 deletefrom = [b for b in divs
1043 1042 if repo[b].rev() in anc or b == target]
1044 1043 bookmarks.deletedivergent(repo, deletefrom, mark)
1045 1044 if bookmarks.validdest(repo, bmctx, repo[target]):
1046 1045 ui.status(_("moving bookmark '%s' forward from %s\n") %
1047 1046 (mark, short(bmctx.node())))
1048 1047 return
1049 1048 raise error.Abort(_("bookmark '%s' already exists "
1050 1049 "(use -f to force)") % mark)
1051 1050 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1052 1051 and not force):
1053 1052 raise error.Abort(
1054 1053 _("a bookmark cannot have the name of an existing branch"))
1055 1054
1056 1055 if delete and rename:
1057 1056 raise error.Abort(_("--delete and --rename are incompatible"))
1058 1057 if delete and rev:
1059 1058 raise error.Abort(_("--rev is incompatible with --delete"))
1060 1059 if rename and rev:
1061 1060 raise error.Abort(_("--rev is incompatible with --rename"))
1062 1061 if not names and (delete or rev):
1063 1062 raise error.Abort(_("bookmark name required"))
1064 1063
1065 1064 if delete or rename or names or inactive:
1066 1065 wlock = lock = tr = None
1067 1066 try:
1068 1067 wlock = repo.wlock()
1069 1068 lock = repo.lock()
1070 1069 cur = repo.changectx('.').node()
1071 1070 marks = repo._bookmarks
1072 1071 if delete:
1073 1072 tr = repo.transaction('bookmark')
1074 1073 for mark in names:
1075 1074 if mark not in marks:
1076 1075 raise error.Abort(_("bookmark '%s' does not exist") %
1077 1076 mark)
1078 1077 if mark == repo._activebookmark:
1079 1078 bookmarks.deactivate(repo)
1080 1079 del marks[mark]
1081 1080
1082 1081 elif rename:
1083 1082 tr = repo.transaction('bookmark')
1084 1083 if not names:
1085 1084 raise error.Abort(_("new bookmark name required"))
1086 1085 elif len(names) > 1:
1087 1086 raise error.Abort(_("only one new bookmark name allowed"))
1088 1087 mark = checkformat(names[0])
1089 1088 if rename not in marks:
1090 1089 raise error.Abort(_("bookmark '%s' does not exist")
1091 1090 % rename)
1092 1091 checkconflict(repo, mark, cur, force)
1093 1092 marks[mark] = marks[rename]
1094 1093 if repo._activebookmark == rename and not inactive:
1095 1094 bookmarks.activate(repo, mark)
1096 1095 del marks[rename]
1097 1096 elif names:
1098 1097 tr = repo.transaction('bookmark')
1099 1098 newact = None
1100 1099 for mark in names:
1101 1100 mark = checkformat(mark)
1102 1101 if newact is None:
1103 1102 newact = mark
1104 1103 if inactive and mark == repo._activebookmark:
1105 1104 bookmarks.deactivate(repo)
1106 1105 return
1107 1106 tgt = cur
1108 1107 if rev:
1109 1108 tgt = scmutil.revsingle(repo, rev).node()
1110 1109 checkconflict(repo, mark, cur, force, tgt)
1111 1110 marks[mark] = tgt
1112 1111 if not inactive and cur == marks[newact] and not rev:
1113 1112 bookmarks.activate(repo, newact)
1114 1113 elif cur != tgt and newact == repo._activebookmark:
1115 1114 bookmarks.deactivate(repo)
1116 1115 elif inactive:
1117 1116 if len(marks) == 0:
1118 1117 ui.status(_("no bookmarks set\n"))
1119 1118 elif not repo._activebookmark:
1120 1119 ui.status(_("no active bookmark\n"))
1121 1120 else:
1122 1121 bookmarks.deactivate(repo)
1123 1122 if tr is not None:
1124 1123 marks.recordchange(tr)
1125 1124 tr.close()
1126 1125 finally:
1127 1126 lockmod.release(tr, lock, wlock)
1128 1127 else: # show bookmarks
1129 1128 fm = ui.formatter('bookmarks', opts)
1130 1129 hexfn = fm.hexfunc
1131 1130 marks = repo._bookmarks
1132 1131 if len(marks) == 0 and fm.isplain():
1133 1132 ui.status(_("no bookmarks set\n"))
1134 1133 for bmark, n in sorted(marks.iteritems()):
1135 1134 active = repo._activebookmark
1136 1135 if bmark == active:
1137 1136 prefix, label = '*', activebookmarklabel
1138 1137 else:
1139 1138 prefix, label = ' ', ''
1140 1139
1141 1140 fm.startitem()
1142 1141 if not ui.quiet:
1143 1142 fm.plain(' %s ' % prefix, label=label)
1144 1143 fm.write('bookmark', '%s', bmark, label=label)
1145 1144 pad = " " * (25 - encoding.colwidth(bmark))
1146 1145 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1147 1146 repo.changelog.rev(n), hexfn(n), label=label)
1148 1147 fm.data(active=(bmark == active))
1149 1148 fm.plain('\n')
1150 1149 fm.end()
1151 1150
1152 1151 @command('branch',
1153 1152 [('f', 'force', None,
1154 1153 _('set branch name even if it shadows an existing branch')),
1155 1154 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1156 1155 _('[-fC] [NAME]'))
1157 1156 def branch(ui, repo, label=None, **opts):
1158 1157 """set or show the current branch name
1159 1158
1160 1159 .. note::
1161 1160
1162 1161 Branch names are permanent and global. Use :hg:`bookmark` to create a
1163 1162 light-weight bookmark instead. See :hg:`help glossary` for more
1164 1163 information about named branches and bookmarks.
1165 1164
1166 1165 With no argument, show the current branch name. With one argument,
1167 1166 set the working directory branch name (the branch will not exist
1168 1167 in the repository until the next commit). Standard practice
1169 1168 recommends that primary development take place on the 'default'
1170 1169 branch.
1171 1170
1172 1171 Unless -f/--force is specified, branch will not let you set a
1173 1172 branch name that already exists.
1174 1173
1175 1174 Use -C/--clean to reset the working directory branch to that of
1176 1175 the parent of the working directory, negating a previous branch
1177 1176 change.
1178 1177
1179 1178 Use the command :hg:`update` to switch to an existing branch. Use
1180 1179 :hg:`commit --close-branch` to mark this branch head as closed.
1181 1180 When all heads of a branch are closed, the branch will be
1182 1181 considered closed.
1183 1182
1184 1183 Returns 0 on success.
1185 1184 """
1186 1185 opts = pycompat.byteskwargs(opts)
1187 1186 if label:
1188 1187 label = label.strip()
1189 1188
1190 1189 if not opts.get('clean') and not label:
1191 1190 ui.write("%s\n" % repo.dirstate.branch())
1192 1191 return
1193 1192
1194 1193 with repo.wlock():
1195 1194 if opts.get('clean'):
1196 1195 label = repo[None].p1().branch()
1197 1196 repo.dirstate.setbranch(label)
1198 1197 ui.status(_('reset working directory to branch %s\n') % label)
1199 1198 elif label:
1200 1199 if not opts.get('force') and label in repo.branchmap():
1201 1200 if label not in [p.branch() for p in repo[None].parents()]:
1202 1201 raise error.Abort(_('a branch of the same name already'
1203 1202 ' exists'),
1204 1203 # i18n: "it" refers to an existing branch
1205 1204 hint=_("use 'hg update' to switch to it"))
1206 1205 scmutil.checknewlabel(repo, label, 'branch')
1207 1206 repo.dirstate.setbranch(label)
1208 1207 ui.status(_('marked working directory as branch %s\n') % label)
1209 1208
1210 1209 # find any open named branches aside from default
1211 1210 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1212 1211 if n != "default" and not c]
1213 1212 if not others:
1214 1213 ui.status(_('(branches are permanent and global, '
1215 1214 'did you want a bookmark?)\n'))
1216 1215
1217 1216 @command('branches',
1218 1217 [('a', 'active', False,
1219 1218 _('show only branches that have unmerged heads (DEPRECATED)')),
1220 1219 ('c', 'closed', False, _('show normal and closed branches')),
1221 1220 ] + formatteropts,
1222 1221 _('[-c]'))
1223 1222 def branches(ui, repo, active=False, closed=False, **opts):
1224 1223 """list repository named branches
1225 1224
1226 1225 List the repository's named branches, indicating which ones are
1227 1226 inactive. If -c/--closed is specified, also list branches which have
1228 1227 been marked closed (see :hg:`commit --close-branch`).
1229 1228
1230 1229 Use the command :hg:`update` to switch to an existing branch.
1231 1230
1232 1231 Returns 0.
1233 1232 """
1234 1233
1235 1234 opts = pycompat.byteskwargs(opts)
1236 1235 ui.pager('branches')
1237 1236 fm = ui.formatter('branches', opts)
1238 1237 hexfunc = fm.hexfunc
1239 1238
1240 1239 allheads = set(repo.heads())
1241 1240 branches = []
1242 1241 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1243 1242 isactive = not isclosed and bool(set(heads) & allheads)
1244 1243 branches.append((tag, repo[tip], isactive, not isclosed))
1245 1244 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1246 1245 reverse=True)
1247 1246
1248 1247 for tag, ctx, isactive, isopen in branches:
1249 1248 if active and not isactive:
1250 1249 continue
1251 1250 if isactive:
1252 1251 label = 'branches.active'
1253 1252 notice = ''
1254 1253 elif not isopen:
1255 1254 if not closed:
1256 1255 continue
1257 1256 label = 'branches.closed'
1258 1257 notice = _(' (closed)')
1259 1258 else:
1260 1259 label = 'branches.inactive'
1261 1260 notice = _(' (inactive)')
1262 1261 current = (tag == repo.dirstate.branch())
1263 1262 if current:
1264 1263 label = 'branches.current'
1265 1264
1266 1265 fm.startitem()
1267 1266 fm.write('branch', '%s', tag, label=label)
1268 1267 rev = ctx.rev()
1269 1268 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1270 1269 fmt = ' ' * padsize + ' %d:%s'
1271 1270 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1272 1271 label='log.changeset changeset.%s' % ctx.phasestr())
1273 1272 fm.context(ctx=ctx)
1274 1273 fm.data(active=isactive, closed=not isopen, current=current)
1275 1274 if not ui.quiet:
1276 1275 fm.plain(notice)
1277 1276 fm.plain('\n')
1278 1277 fm.end()
1279 1278
1280 1279 @command('bundle',
1281 1280 [('f', 'force', None, _('run even when the destination is unrelated')),
1282 1281 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1283 1282 _('REV')),
1284 1283 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1285 1284 _('BRANCH')),
1286 1285 ('', 'base', [],
1287 1286 _('a base changeset assumed to be available at the destination'),
1288 1287 _('REV')),
1289 1288 ('a', 'all', None, _('bundle all changesets in the repository')),
1290 1289 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1291 1290 ] + remoteopts,
1292 1291 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1293 1292 def bundle(ui, repo, fname, dest=None, **opts):
1294 1293 """create a bundle file
1295 1294
1296 1295 Generate a bundle file containing data to be added to a repository.
1297 1296
1298 1297 To create a bundle containing all changesets, use -a/--all
1299 1298 (or --base null). Otherwise, hg assumes the destination will have
1300 1299 all the nodes you specify with --base parameters. Otherwise, hg
1301 1300 will assume the repository has all the nodes in destination, or
1302 1301 default-push/default if no destination is specified.
1303 1302
1304 1303 You can change bundle format with the -t/--type option. See
1305 1304 :hg:`help bundlespec` for documentation on this format. By default,
1306 1305 the most appropriate format is used and compression defaults to
1307 1306 bzip2.
1308 1307
1309 1308 The bundle file can then be transferred using conventional means
1310 1309 and applied to another repository with the unbundle or pull
1311 1310 command. This is useful when direct push and pull are not
1312 1311 available or when exporting an entire repository is undesirable.
1313 1312
1314 1313 Applying bundles preserves all changeset contents including
1315 1314 permissions, copy/rename information, and revision history.
1316 1315
1317 1316 Returns 0 on success, 1 if no changes found.
1318 1317 """
1319 1318 opts = pycompat.byteskwargs(opts)
1320 1319 revs = None
1321 1320 if 'rev' in opts:
1322 1321 revstrings = opts['rev']
1323 1322 revs = scmutil.revrange(repo, revstrings)
1324 1323 if revstrings and not revs:
1325 1324 raise error.Abort(_('no commits to bundle'))
1326 1325
1327 1326 bundletype = opts.get('type', 'bzip2').lower()
1328 1327 try:
1329 1328 bcompression, cgversion, params = exchange.parsebundlespec(
1330 1329 repo, bundletype, strict=False)
1331 1330 except error.UnsupportedBundleSpecification as e:
1332 1331 raise error.Abort(str(e),
1333 1332 hint=_("see 'hg help bundlespec' for supported "
1334 1333 "values for --type"))
1335 1334
1336 1335 # Packed bundles are a pseudo bundle format for now.
1337 1336 if cgversion == 's1':
1338 1337 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1339 1338 hint=_("use 'hg debugcreatestreamclonebundle'"))
1340 1339
1341 1340 if opts.get('all'):
1342 1341 if dest:
1343 1342 raise error.Abort(_("--all is incompatible with specifying "
1344 1343 "a destination"))
1345 1344 if opts.get('base'):
1346 1345 ui.warn(_("ignoring --base because --all was specified\n"))
1347 1346 base = ['null']
1348 1347 else:
1349 1348 base = scmutil.revrange(repo, opts.get('base'))
1350 1349 # TODO: get desired bundlecaps from command line.
1351 1350 bundlecaps = None
1352 1351 if cgversion not in changegroup.supportedoutgoingversions(repo):
1353 1352 raise error.Abort(_("repository does not support bundle version %s") %
1354 1353 cgversion)
1355 1354
1356 1355 if base:
1357 1356 if dest:
1358 1357 raise error.Abort(_("--base is incompatible with specifying "
1359 1358 "a destination"))
1360 1359 common = [repo.lookup(rev) for rev in base]
1361 1360 heads = revs and map(repo.lookup, revs) or None
1362 1361 outgoing = discovery.outgoing(repo, common, heads)
1363 1362 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1364 1363 bundlecaps=bundlecaps,
1365 1364 version=cgversion)
1366 1365 outgoing = None
1367 1366 else:
1368 1367 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1369 1368 dest, branches = hg.parseurl(dest, opts.get('branch'))
1370 1369 other = hg.peer(repo, opts, dest)
1371 1370 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1372 1371 heads = revs and map(repo.lookup, revs) or revs
1373 1372 outgoing = discovery.findcommonoutgoing(repo, other,
1374 1373 onlyheads=heads,
1375 1374 force=opts.get('force'),
1376 1375 portable=True)
1377 1376 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1378 1377 bundlecaps, version=cgversion)
1379 1378 if not cg:
1380 1379 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1381 1380 return 1
1382 1381
1383 1382 if cgversion == '01': #bundle1
1384 1383 if bcompression is None:
1385 1384 bcompression = 'UN'
1386 1385 bversion = 'HG10' + bcompression
1387 1386 bcompression = None
1388 1387 elif cgversion in ('02', '03'):
1389 1388 bversion = 'HG20'
1390 1389 else:
1391 1390 raise error.ProgrammingError(
1392 1391 'bundle: unexpected changegroup version %s' % cgversion)
1393 1392
1394 1393 # TODO compression options should be derived from bundlespec parsing.
1395 1394 # This is a temporary hack to allow adjusting bundle compression
1396 1395 # level without a) formalizing the bundlespec changes to declare it
1397 1396 # b) introducing a command flag.
1398 1397 compopts = {}
1399 1398 complevel = ui.configint('experimental', 'bundlecomplevel')
1400 1399 if complevel is not None:
1401 1400 compopts['level'] = complevel
1402 1401
1403 1402 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression,
1404 1403 compopts=compopts)
1405 1404
1406 1405 @command('cat',
1407 1406 [('o', 'output', '',
1408 1407 _('print output to file with formatted name'), _('FORMAT')),
1409 1408 ('r', 'rev', '', _('print the given revision'), _('REV')),
1410 1409 ('', 'decode', None, _('apply any matching decode filter')),
1411 1410 ] + walkopts,
1412 1411 _('[OPTION]... FILE...'),
1413 1412 inferrepo=True)
1414 1413 def cat(ui, repo, file1, *pats, **opts):
1415 1414 """output the current or given revision of files
1416 1415
1417 1416 Print the specified files as they were at the given revision. If
1418 1417 no revision is given, the parent of the working directory is used.
1419 1418
1420 1419 Output may be to a file, in which case the name of the file is
1421 1420 given using a format string. The formatting rules as follows:
1422 1421
1423 1422 :``%%``: literal "%" character
1424 1423 :``%s``: basename of file being printed
1425 1424 :``%d``: dirname of file being printed, or '.' if in repository root
1426 1425 :``%p``: root-relative path name of file being printed
1427 1426 :``%H``: changeset hash (40 hexadecimal digits)
1428 1427 :``%R``: changeset revision number
1429 1428 :``%h``: short-form changeset hash (12 hexadecimal digits)
1430 1429 :``%r``: zero-padded changeset revision number
1431 1430 :``%b``: basename of the exporting repository
1432 1431
1433 1432 Returns 0 on success.
1434 1433 """
1435 1434 ctx = scmutil.revsingle(repo, opts.get('rev'))
1436 1435 m = scmutil.match(ctx, (file1,) + pats, opts)
1437 1436
1438 1437 ui.pager('cat')
1439 1438 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1440 1439
1441 1440 @command('^clone',
1442 1441 [('U', 'noupdate', None, _('the clone will include an empty working '
1443 1442 'directory (only a repository)')),
1444 1443 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1445 1444 _('REV')),
1446 1445 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1447 1446 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1448 1447 ('', 'pull', None, _('use pull protocol to copy metadata')),
1449 1448 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1450 1449 ] + remoteopts,
1451 1450 _('[OPTION]... SOURCE [DEST]'),
1452 1451 norepo=True)
1453 1452 def clone(ui, source, dest=None, **opts):
1454 1453 """make a copy of an existing repository
1455 1454
1456 1455 Create a copy of an existing repository in a new directory.
1457 1456
1458 1457 If no destination directory name is specified, it defaults to the
1459 1458 basename of the source.
1460 1459
1461 1460 The location of the source is added to the new repository's
1462 1461 ``.hg/hgrc`` file, as the default to be used for future pulls.
1463 1462
1464 1463 Only local paths and ``ssh://`` URLs are supported as
1465 1464 destinations. For ``ssh://`` destinations, no working directory or
1466 1465 ``.hg/hgrc`` will be created on the remote side.
1467 1466
1468 1467 If the source repository has a bookmark called '@' set, that
1469 1468 revision will be checked out in the new repository by default.
1470 1469
1471 1470 To check out a particular version, use -u/--update, or
1472 1471 -U/--noupdate to create a clone with no working directory.
1473 1472
1474 1473 To pull only a subset of changesets, specify one or more revisions
1475 1474 identifiers with -r/--rev or branches with -b/--branch. The
1476 1475 resulting clone will contain only the specified changesets and
1477 1476 their ancestors. These options (or 'clone src#rev dest') imply
1478 1477 --pull, even for local source repositories.
1479 1478
1480 1479 .. note::
1481 1480
1482 1481 Specifying a tag will include the tagged changeset but not the
1483 1482 changeset containing the tag.
1484 1483
1485 1484 .. container:: verbose
1486 1485
1487 1486 For efficiency, hardlinks are used for cloning whenever the
1488 1487 source and destination are on the same filesystem (note this
1489 1488 applies only to the repository data, not to the working
1490 1489 directory). Some filesystems, such as AFS, implement hardlinking
1491 1490 incorrectly, but do not report errors. In these cases, use the
1492 1491 --pull option to avoid hardlinking.
1493 1492
1494 1493 In some cases, you can clone repositories and the working
1495 1494 directory using full hardlinks with ::
1496 1495
1497 1496 $ cp -al REPO REPOCLONE
1498 1497
1499 1498 This is the fastest way to clone, but it is not always safe. The
1500 1499 operation is not atomic (making sure REPO is not modified during
1501 1500 the operation is up to you) and you have to make sure your
1502 1501 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1503 1502 so). Also, this is not compatible with certain extensions that
1504 1503 place their metadata under the .hg directory, such as mq.
1505 1504
1506 1505 Mercurial will update the working directory to the first applicable
1507 1506 revision from this list:
1508 1507
1509 1508 a) null if -U or the source repository has no changesets
1510 1509 b) if -u . and the source repository is local, the first parent of
1511 1510 the source repository's working directory
1512 1511 c) the changeset specified with -u (if a branch name, this means the
1513 1512 latest head of that branch)
1514 1513 d) the changeset specified with -r
1515 1514 e) the tipmost head specified with -b
1516 1515 f) the tipmost head specified with the url#branch source syntax
1517 1516 g) the revision marked with the '@' bookmark, if present
1518 1517 h) the tipmost head of the default branch
1519 1518 i) tip
1520 1519
1521 1520 When cloning from servers that support it, Mercurial may fetch
1522 1521 pre-generated data from a server-advertised URL. When this is done,
1523 1522 hooks operating on incoming changesets and changegroups may fire twice,
1524 1523 once for the bundle fetched from the URL and another for any additional
1525 1524 data not fetched from this URL. In addition, if an error occurs, the
1526 1525 repository may be rolled back to a partial clone. This behavior may
1527 1526 change in future releases. See :hg:`help -e clonebundles` for more.
1528 1527
1529 1528 Examples:
1530 1529
1531 1530 - clone a remote repository to a new directory named hg/::
1532 1531
1533 1532 hg clone https://www.mercurial-scm.org/repo/hg/
1534 1533
1535 1534 - create a lightweight local clone::
1536 1535
1537 1536 hg clone project/ project-feature/
1538 1537
1539 1538 - clone from an absolute path on an ssh server (note double-slash)::
1540 1539
1541 1540 hg clone ssh://user@server//home/projects/alpha/
1542 1541
1543 1542 - do a high-speed clone over a LAN while checking out a
1544 1543 specified version::
1545 1544
1546 1545 hg clone --uncompressed http://server/repo -u 1.5
1547 1546
1548 1547 - create a repository without changesets after a particular revision::
1549 1548
1550 1549 hg clone -r 04e544 experimental/ good/
1551 1550
1552 1551 - clone (and track) a particular named branch::
1553 1552
1554 1553 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1555 1554
1556 1555 See :hg:`help urls` for details on specifying URLs.
1557 1556
1558 1557 Returns 0 on success.
1559 1558 """
1560 1559 opts = pycompat.byteskwargs(opts)
1561 1560 if opts.get('noupdate') and opts.get('updaterev'):
1562 1561 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1563 1562
1564 1563 r = hg.clone(ui, opts, source, dest,
1565 1564 pull=opts.get('pull'),
1566 1565 stream=opts.get('uncompressed'),
1567 1566 rev=opts.get('rev'),
1568 1567 update=opts.get('updaterev') or not opts.get('noupdate'),
1569 1568 branch=opts.get('branch'),
1570 1569 shareopts=opts.get('shareopts'))
1571 1570
1572 1571 return r is None
1573 1572
1574 1573 @command('^commit|ci',
1575 1574 [('A', 'addremove', None,
1576 1575 _('mark new/missing files as added/removed before committing')),
1577 1576 ('', 'close-branch', None,
1578 1577 _('mark a branch head as closed')),
1579 1578 ('', 'amend', None, _('amend the parent of the working directory')),
1580 1579 ('s', 'secret', None, _('use the secret phase for committing')),
1581 1580 ('e', 'edit', None, _('invoke editor on commit messages')),
1582 1581 ('i', 'interactive', None, _('use interactive mode')),
1583 1582 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1584 1583 _('[OPTION]... [FILE]...'),
1585 1584 inferrepo=True)
1586 1585 def commit(ui, repo, *pats, **opts):
1587 1586 """commit the specified files or all outstanding changes
1588 1587
1589 1588 Commit changes to the given files into the repository. Unlike a
1590 1589 centralized SCM, this operation is a local operation. See
1591 1590 :hg:`push` for a way to actively distribute your changes.
1592 1591
1593 1592 If a list of files is omitted, all changes reported by :hg:`status`
1594 1593 will be committed.
1595 1594
1596 1595 If you are committing the result of a merge, do not provide any
1597 1596 filenames or -I/-X filters.
1598 1597
1599 1598 If no commit message is specified, Mercurial starts your
1600 1599 configured editor where you can enter a message. In case your
1601 1600 commit fails, you will find a backup of your message in
1602 1601 ``.hg/last-message.txt``.
1603 1602
1604 1603 The --close-branch flag can be used to mark the current branch
1605 1604 head closed. When all heads of a branch are closed, the branch
1606 1605 will be considered closed and no longer listed.
1607 1606
1608 1607 The --amend flag can be used to amend the parent of the
1609 1608 working directory with a new commit that contains the changes
1610 1609 in the parent in addition to those currently reported by :hg:`status`,
1611 1610 if there are any. The old commit is stored in a backup bundle in
1612 1611 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1613 1612 on how to restore it).
1614 1613
1615 1614 Message, user and date are taken from the amended commit unless
1616 1615 specified. When a message isn't specified on the command line,
1617 1616 the editor will open with the message of the amended commit.
1618 1617
1619 1618 It is not possible to amend public changesets (see :hg:`help phases`)
1620 1619 or changesets that have children.
1621 1620
1622 1621 See :hg:`help dates` for a list of formats valid for -d/--date.
1623 1622
1624 1623 Returns 0 on success, 1 if nothing changed.
1625 1624
1626 1625 .. container:: verbose
1627 1626
1628 1627 Examples:
1629 1628
1630 1629 - commit all files ending in .py::
1631 1630
1632 1631 hg commit --include "set:**.py"
1633 1632
1634 1633 - commit all non-binary files::
1635 1634
1636 1635 hg commit --exclude "set:binary()"
1637 1636
1638 1637 - amend the current commit and set the date to now::
1639 1638
1640 1639 hg commit --amend --date now
1641 1640 """
1642 1641 wlock = lock = None
1643 1642 try:
1644 1643 wlock = repo.wlock()
1645 1644 lock = repo.lock()
1646 1645 return _docommit(ui, repo, *pats, **opts)
1647 1646 finally:
1648 1647 release(lock, wlock)
1649 1648
1650 1649 def _docommit(ui, repo, *pats, **opts):
1651 1650 if opts.get(r'interactive'):
1652 1651 opts.pop(r'interactive')
1653 1652 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 1653 cmdutil.recordfilter, *pats,
1655 1654 **opts)
1656 1655 # ret can be 0 (no changes to record) or the value returned by
1657 1656 # commit(), 1 if nothing changed or None on success.
1658 1657 return 1 if ret == 0 else ret
1659 1658
1660 1659 opts = pycompat.byteskwargs(opts)
1661 1660 if opts.get('subrepos'):
1662 1661 if opts.get('amend'):
1663 1662 raise error.Abort(_('cannot amend with --subrepos'))
1664 1663 # Let --subrepos on the command line override config setting.
1665 1664 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666 1665
1667 1666 cmdutil.checkunfinished(repo, commit=True)
1668 1667
1669 1668 branch = repo[None].branch()
1670 1669 bheads = repo.branchheads(branch)
1671 1670
1672 1671 extra = {}
1673 1672 if opts.get('close_branch'):
1674 1673 extra['close'] = 1
1675 1674
1676 1675 if not bheads:
1677 1676 raise error.Abort(_('can only close branch heads'))
1678 1677 elif opts.get('amend'):
1679 1678 if repo[None].parents()[0].p1().branch() != branch and \
1680 1679 repo[None].parents()[0].p2().branch() != branch:
1681 1680 raise error.Abort(_('can only close branch heads'))
1682 1681
1683 1682 if opts.get('amend'):
1684 1683 if ui.configbool('ui', 'commitsubrepos'):
1685 1684 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686 1685
1687 1686 old = repo['.']
1688 1687 if not old.mutable():
1689 1688 raise error.Abort(_('cannot amend public changesets'))
1690 1689 if len(repo[None].parents()) > 1:
1691 1690 raise error.Abort(_('cannot amend while merging'))
1692 1691 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1693 1692 if not allowunstable and old.children():
1694 1693 raise error.Abort(_('cannot amend changeset with children'))
1695 1694
1696 1695 # Currently histedit gets confused if an amend happens while histedit
1697 1696 # is in progress. Since we have a checkunfinished command, we are
1698 1697 # temporarily honoring it.
1699 1698 #
1700 1699 # Note: eventually this guard will be removed. Please do not expect
1701 1700 # this behavior to remain.
1702 1701 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1703 1702 cmdutil.checkunfinished(repo)
1704 1703
1705 1704 # commitfunc is used only for temporary amend commit by cmdutil.amend
1706 1705 def commitfunc(ui, repo, message, match, opts):
1707 1706 return repo.commit(message,
1708 1707 opts.get('user') or old.user(),
1709 1708 opts.get('date') or old.date(),
1710 1709 match,
1711 1710 extra=extra)
1712 1711
1713 1712 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1714 1713 if node == old.node():
1715 1714 ui.status(_("nothing changed\n"))
1716 1715 return 1
1717 1716 else:
1718 1717 def commitfunc(ui, repo, message, match, opts):
1719 1718 overrides = {}
1720 1719 if opts.get('secret'):
1721 1720 overrides[('phases', 'new-commit')] = 'secret'
1722 1721
1723 1722 baseui = repo.baseui
1724 1723 with baseui.configoverride(overrides, 'commit'):
1725 1724 with ui.configoverride(overrides, 'commit'):
1726 1725 editform = cmdutil.mergeeditform(repo[None],
1727 1726 'commit.normal')
1728 1727 editor = cmdutil.getcommiteditor(
1729 1728 editform=editform, **pycompat.strkwargs(opts))
1730 1729 return repo.commit(message,
1731 1730 opts.get('user'),
1732 1731 opts.get('date'),
1733 1732 match,
1734 1733 editor=editor,
1735 1734 extra=extra)
1736 1735
1737 1736 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1738 1737
1739 1738 if not node:
1740 1739 stat = cmdutil.postcommitstatus(repo, pats, opts)
1741 1740 if stat[3]:
1742 1741 ui.status(_("nothing changed (%d missing files, see "
1743 1742 "'hg status')\n") % len(stat[3]))
1744 1743 else:
1745 1744 ui.status(_("nothing changed\n"))
1746 1745 return 1
1747 1746
1748 1747 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1749 1748
1750 1749 @command('config|showconfig|debugconfig',
1751 1750 [('u', 'untrusted', None, _('show untrusted configuration options')),
1752 1751 ('e', 'edit', None, _('edit user config')),
1753 1752 ('l', 'local', None, _('edit repository config')),
1754 1753 ('g', 'global', None, _('edit global config'))] + formatteropts,
1755 1754 _('[-u] [NAME]...'),
1756 1755 optionalrepo=True)
1757 1756 def config(ui, repo, *values, **opts):
1758 1757 """show combined config settings from all hgrc files
1759 1758
1760 1759 With no arguments, print names and values of all config items.
1761 1760
1762 1761 With one argument of the form section.name, print just the value
1763 1762 of that config item.
1764 1763
1765 1764 With multiple arguments, print names and values of all config
1766 1765 items with matching section names.
1767 1766
1768 1767 With --edit, start an editor on the user-level config file. With
1769 1768 --global, edit the system-wide config file. With --local, edit the
1770 1769 repository-level config file.
1771 1770
1772 1771 With --debug, the source (filename and line number) is printed
1773 1772 for each config item.
1774 1773
1775 1774 See :hg:`help config` for more information about config files.
1776 1775
1777 1776 Returns 0 on success, 1 if NAME does not exist.
1778 1777
1779 1778 """
1780 1779
1781 1780 opts = pycompat.byteskwargs(opts)
1782 1781 if opts.get('edit') or opts.get('local') or opts.get('global'):
1783 1782 if opts.get('local') and opts.get('global'):
1784 1783 raise error.Abort(_("can't use --local and --global together"))
1785 1784
1786 1785 if opts.get('local'):
1787 1786 if not repo:
1788 1787 raise error.Abort(_("can't use --local outside a repository"))
1789 1788 paths = [repo.vfs.join('hgrc')]
1790 1789 elif opts.get('global'):
1791 1790 paths = rcutil.systemrcpath()
1792 1791 else:
1793 1792 paths = rcutil.userrcpath()
1794 1793
1795 1794 for f in paths:
1796 1795 if os.path.exists(f):
1797 1796 break
1798 1797 else:
1799 1798 if opts.get('global'):
1800 1799 samplehgrc = uimod.samplehgrcs['global']
1801 1800 elif opts.get('local'):
1802 1801 samplehgrc = uimod.samplehgrcs['local']
1803 1802 else:
1804 1803 samplehgrc = uimod.samplehgrcs['user']
1805 1804
1806 1805 f = paths[0]
1807 1806 fp = open(f, "w")
1808 1807 fp.write(samplehgrc)
1809 1808 fp.close()
1810 1809
1811 1810 editor = ui.geteditor()
1812 1811 ui.system("%s \"%s\"" % (editor, f),
1813 1812 onerr=error.Abort, errprefix=_("edit failed"),
1814 1813 blockedtag='config_edit')
1815 1814 return
1816 1815 ui.pager('config')
1817 1816 fm = ui.formatter('config', opts)
1818 1817 for t, f in rcutil.rccomponents():
1819 1818 if t == 'path':
1820 1819 ui.debug('read config from: %s\n' % f)
1821 1820 elif t == 'items':
1822 1821 for section, name, value, source in f:
1823 1822 ui.debug('set config by: %s\n' % source)
1824 1823 else:
1825 1824 raise error.ProgrammingError('unknown rctype: %s' % t)
1826 1825 untrusted = bool(opts.get('untrusted'))
1827 1826 if values:
1828 1827 sections = [v for v in values if '.' not in v]
1829 1828 items = [v for v in values if '.' in v]
1830 1829 if len(items) > 1 or items and sections:
1831 1830 raise error.Abort(_('only one config item permitted'))
1832 1831 matched = False
1833 1832 for section, name, value in ui.walkconfig(untrusted=untrusted):
1834 1833 source = ui.configsource(section, name, untrusted)
1835 1834 value = pycompat.bytestr(value)
1836 1835 if fm.isplain():
1837 1836 source = source or 'none'
1838 1837 value = value.replace('\n', '\\n')
1839 1838 entryname = section + '.' + name
1840 1839 if values:
1841 1840 for v in values:
1842 1841 if v == section:
1843 1842 fm.startitem()
1844 1843 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1845 1844 fm.write('name value', '%s=%s\n', entryname, value)
1846 1845 matched = True
1847 1846 elif v == entryname:
1848 1847 fm.startitem()
1849 1848 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1850 1849 fm.write('value', '%s\n', value)
1851 1850 fm.data(name=entryname)
1852 1851 matched = True
1853 1852 else:
1854 1853 fm.startitem()
1855 1854 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1856 1855 fm.write('name value', '%s=%s\n', entryname, value)
1857 1856 matched = True
1858 1857 fm.end()
1859 1858 if matched:
1860 1859 return 0
1861 1860 return 1
1862 1861
1863 1862 @command('copy|cp',
1864 1863 [('A', 'after', None, _('record a copy that has already occurred')),
1865 1864 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1866 1865 ] + walkopts + dryrunopts,
1867 1866 _('[OPTION]... [SOURCE]... DEST'))
1868 1867 def copy(ui, repo, *pats, **opts):
1869 1868 """mark files as copied for the next commit
1870 1869
1871 1870 Mark dest as having copies of source files. If dest is a
1872 1871 directory, copies are put in that directory. If dest is a file,
1873 1872 the source must be a single file.
1874 1873
1875 1874 By default, this command copies the contents of files as they
1876 1875 exist in the working directory. If invoked with -A/--after, the
1877 1876 operation is recorded, but no copying is performed.
1878 1877
1879 1878 This command takes effect with the next commit. To undo a copy
1880 1879 before that, see :hg:`revert`.
1881 1880
1882 1881 Returns 0 on success, 1 if errors are encountered.
1883 1882 """
1884 1883 opts = pycompat.byteskwargs(opts)
1885 1884 with repo.wlock(False):
1886 1885 return cmdutil.copy(ui, repo, pats, opts)
1887 1886
1888 1887 @command('^diff',
1889 1888 [('r', 'rev', [], _('revision'), _('REV')),
1890 1889 ('c', 'change', '', _('change made by revision'), _('REV'))
1891 1890 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1892 1891 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1893 1892 inferrepo=True)
1894 1893 def diff(ui, repo, *pats, **opts):
1895 1894 """diff repository (or selected files)
1896 1895
1897 1896 Show differences between revisions for the specified files.
1898 1897
1899 1898 Differences between files are shown using the unified diff format.
1900 1899
1901 1900 .. note::
1902 1901
1903 1902 :hg:`diff` may generate unexpected results for merges, as it will
1904 1903 default to comparing against the working directory's first
1905 1904 parent changeset if no revisions are specified.
1906 1905
1907 1906 When two revision arguments are given, then changes are shown
1908 1907 between those revisions. If only one revision is specified then
1909 1908 that revision is compared to the working directory, and, when no
1910 1909 revisions are specified, the working directory files are compared
1911 1910 to its first parent.
1912 1911
1913 1912 Alternatively you can specify -c/--change with a revision to see
1914 1913 the changes in that changeset relative to its first parent.
1915 1914
1916 1915 Without the -a/--text option, diff will avoid generating diffs of
1917 1916 files it detects as binary. With -a, diff will generate a diff
1918 1917 anyway, probably with undesirable results.
1919 1918
1920 1919 Use the -g/--git option to generate diffs in the git extended diff
1921 1920 format. For more information, read :hg:`help diffs`.
1922 1921
1923 1922 .. container:: verbose
1924 1923
1925 1924 Examples:
1926 1925
1927 1926 - compare a file in the current working directory to its parent::
1928 1927
1929 1928 hg diff foo.c
1930 1929
1931 1930 - compare two historical versions of a directory, with rename info::
1932 1931
1933 1932 hg diff --git -r 1.0:1.2 lib/
1934 1933
1935 1934 - get change stats relative to the last change on some date::
1936 1935
1937 1936 hg diff --stat -r "date('may 2')"
1938 1937
1939 1938 - diff all newly-added files that contain a keyword::
1940 1939
1941 1940 hg diff "set:added() and grep(GNU)"
1942 1941
1943 1942 - compare a revision and its parents::
1944 1943
1945 1944 hg diff -c 9353 # compare against first parent
1946 1945 hg diff -r 9353^:9353 # same using revset syntax
1947 1946 hg diff -r 9353^2:9353 # compare against the second parent
1948 1947
1949 1948 Returns 0 on success.
1950 1949 """
1951 1950
1952 1951 opts = pycompat.byteskwargs(opts)
1953 1952 revs = opts.get('rev')
1954 1953 change = opts.get('change')
1955 1954 stat = opts.get('stat')
1956 1955 reverse = opts.get('reverse')
1957 1956
1958 1957 if revs and change:
1959 1958 msg = _('cannot specify --rev and --change at the same time')
1960 1959 raise error.Abort(msg)
1961 1960 elif change:
1962 1961 node2 = scmutil.revsingle(repo, change, None).node()
1963 1962 node1 = repo[node2].p1().node()
1964 1963 else:
1965 1964 node1, node2 = scmutil.revpair(repo, revs)
1966 1965
1967 1966 if reverse:
1968 1967 node1, node2 = node2, node1
1969 1968
1970 1969 diffopts = patch.diffallopts(ui, opts)
1971 1970 m = scmutil.match(repo[node2], pats, opts)
1972 1971 ui.pager('diff')
1973 1972 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1974 1973 listsubrepos=opts.get('subrepos'),
1975 1974 root=opts.get('root'))
1976 1975
1977 1976 @command('^export',
1978 1977 [('o', 'output', '',
1979 1978 _('print output to file with formatted name'), _('FORMAT')),
1980 1979 ('', 'switch-parent', None, _('diff against the second parent')),
1981 1980 ('r', 'rev', [], _('revisions to export'), _('REV')),
1982 1981 ] + diffopts,
1983 1982 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1984 1983 def export(ui, repo, *changesets, **opts):
1985 1984 """dump the header and diffs for one or more changesets
1986 1985
1987 1986 Print the changeset header and diffs for one or more revisions.
1988 1987 If no revision is given, the parent of the working directory is used.
1989 1988
1990 1989 The information shown in the changeset header is: author, date,
1991 1990 branch name (if non-default), changeset hash, parent(s) and commit
1992 1991 comment.
1993 1992
1994 1993 .. note::
1995 1994
1996 1995 :hg:`export` may generate unexpected diff output for merge
1997 1996 changesets, as it will compare the merge changeset against its
1998 1997 first parent only.
1999 1998
2000 1999 Output may be to a file, in which case the name of the file is
2001 2000 given using a format string. The formatting rules are as follows:
2002 2001
2003 2002 :``%%``: literal "%" character
2004 2003 :``%H``: changeset hash (40 hexadecimal digits)
2005 2004 :``%N``: number of patches being generated
2006 2005 :``%R``: changeset revision number
2007 2006 :``%b``: basename of the exporting repository
2008 2007 :``%h``: short-form changeset hash (12 hexadecimal digits)
2009 2008 :``%m``: first line of the commit message (only alphanumeric characters)
2010 2009 :``%n``: zero-padded sequence number, starting at 1
2011 2010 :``%r``: zero-padded changeset revision number
2012 2011
2013 2012 Without the -a/--text option, export will avoid generating diffs
2014 2013 of files it detects as binary. With -a, export will generate a
2015 2014 diff anyway, probably with undesirable results.
2016 2015
2017 2016 Use the -g/--git option to generate diffs in the git extended diff
2018 2017 format. See :hg:`help diffs` for more information.
2019 2018
2020 2019 With the --switch-parent option, the diff will be against the
2021 2020 second parent. It can be useful to review a merge.
2022 2021
2023 2022 .. container:: verbose
2024 2023
2025 2024 Examples:
2026 2025
2027 2026 - use export and import to transplant a bugfix to the current
2028 2027 branch::
2029 2028
2030 2029 hg export -r 9353 | hg import -
2031 2030
2032 2031 - export all the changesets between two revisions to a file with
2033 2032 rename information::
2034 2033
2035 2034 hg export --git -r 123:150 > changes.txt
2036 2035
2037 2036 - split outgoing changes into a series of patches with
2038 2037 descriptive names::
2039 2038
2040 2039 hg export -r "outgoing()" -o "%n-%m.patch"
2041 2040
2042 2041 Returns 0 on success.
2043 2042 """
2044 2043 opts = pycompat.byteskwargs(opts)
2045 2044 changesets += tuple(opts.get('rev', []))
2046 2045 if not changesets:
2047 2046 changesets = ['.']
2048 2047 revs = scmutil.revrange(repo, changesets)
2049 2048 if not revs:
2050 2049 raise error.Abort(_("export requires at least one changeset"))
2051 2050 if len(revs) > 1:
2052 2051 ui.note(_('exporting patches:\n'))
2053 2052 else:
2054 2053 ui.note(_('exporting patch:\n'))
2055 2054 ui.pager('export')
2056 2055 cmdutil.export(repo, revs, template=opts.get('output'),
2057 2056 switch_parent=opts.get('switch_parent'),
2058 2057 opts=patch.diffallopts(ui, opts))
2059 2058
2060 2059 @command('files',
2061 2060 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2062 2061 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2063 2062 ] + walkopts + formatteropts + subrepoopts,
2064 2063 _('[OPTION]... [FILE]...'))
2065 2064 def files(ui, repo, *pats, **opts):
2066 2065 """list tracked files
2067 2066
2068 2067 Print files under Mercurial control in the working directory or
2069 2068 specified revision for given files (excluding removed files).
2070 2069 Files can be specified as filenames or filesets.
2071 2070
2072 2071 If no files are given to match, this command prints the names
2073 2072 of all files under Mercurial control.
2074 2073
2075 2074 .. container:: verbose
2076 2075
2077 2076 Examples:
2078 2077
2079 2078 - list all files under the current directory::
2080 2079
2081 2080 hg files .
2082 2081
2083 2082 - shows sizes and flags for current revision::
2084 2083
2085 2084 hg files -vr .
2086 2085
2087 2086 - list all files named README::
2088 2087
2089 2088 hg files -I "**/README"
2090 2089
2091 2090 - list all binary files::
2092 2091
2093 2092 hg files "set:binary()"
2094 2093
2095 2094 - find files containing a regular expression::
2096 2095
2097 2096 hg files "set:grep('bob')"
2098 2097
2099 2098 - search tracked file contents with xargs and grep::
2100 2099
2101 2100 hg files -0 | xargs -0 grep foo
2102 2101
2103 2102 See :hg:`help patterns` and :hg:`help filesets` for more information
2104 2103 on specifying file patterns.
2105 2104
2106 2105 Returns 0 if a match is found, 1 otherwise.
2107 2106
2108 2107 """
2109 2108
2110 2109 opts = pycompat.byteskwargs(opts)
2111 2110 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2112 2111
2113 2112 end = '\n'
2114 2113 if opts.get('print0'):
2115 2114 end = '\0'
2116 2115 fmt = '%s' + end
2117 2116
2118 2117 m = scmutil.match(ctx, pats, opts)
2119 2118 ui.pager('files')
2120 2119 with ui.formatter('files', opts) as fm:
2121 2120 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2122 2121
2123 2122 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2124 2123 def forget(ui, repo, *pats, **opts):
2125 2124 """forget the specified files on the next commit
2126 2125
2127 2126 Mark the specified files so they will no longer be tracked
2128 2127 after the next commit.
2129 2128
2130 2129 This only removes files from the current branch, not from the
2131 2130 entire project history, and it does not delete them from the
2132 2131 working directory.
2133 2132
2134 2133 To delete the file from the working directory, see :hg:`remove`.
2135 2134
2136 2135 To undo a forget before the next commit, see :hg:`add`.
2137 2136
2138 2137 .. container:: verbose
2139 2138
2140 2139 Examples:
2141 2140
2142 2141 - forget newly-added binary files::
2143 2142
2144 2143 hg forget "set:added() and binary()"
2145 2144
2146 2145 - forget files that would be excluded by .hgignore::
2147 2146
2148 2147 hg forget "set:hgignore()"
2149 2148
2150 2149 Returns 0 on success.
2151 2150 """
2152 2151
2153 2152 opts = pycompat.byteskwargs(opts)
2154 2153 if not pats:
2155 2154 raise error.Abort(_('no files specified'))
2156 2155
2157 2156 m = scmutil.match(repo[None], pats, opts)
2158 2157 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2159 2158 return rejected and 1 or 0
2160 2159
2161 2160 @command(
2162 2161 'graft',
2163 2162 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2164 2163 ('c', 'continue', False, _('resume interrupted graft')),
2165 2164 ('e', 'edit', False, _('invoke editor on commit messages')),
2166 2165 ('', 'log', None, _('append graft info to log message')),
2167 2166 ('f', 'force', False, _('force graft')),
2168 2167 ('D', 'currentdate', False,
2169 2168 _('record the current date as commit date')),
2170 2169 ('U', 'currentuser', False,
2171 2170 _('record the current user as committer'), _('DATE'))]
2172 2171 + commitopts2 + mergetoolopts + dryrunopts,
2173 2172 _('[OPTION]... [-r REV]... REV...'))
2174 2173 def graft(ui, repo, *revs, **opts):
2175 2174 '''copy changes from other branches onto the current branch
2176 2175
2177 2176 This command uses Mercurial's merge logic to copy individual
2178 2177 changes from other branches without merging branches in the
2179 2178 history graph. This is sometimes known as 'backporting' or
2180 2179 'cherry-picking'. By default, graft will copy user, date, and
2181 2180 description from the source changesets.
2182 2181
2183 2182 Changesets that are ancestors of the current revision, that have
2184 2183 already been grafted, or that are merges will be skipped.
2185 2184
2186 2185 If --log is specified, log messages will have a comment appended
2187 2186 of the form::
2188 2187
2189 2188 (grafted from CHANGESETHASH)
2190 2189
2191 2190 If --force is specified, revisions will be grafted even if they
2192 2191 are already ancestors of or have been grafted to the destination.
2193 2192 This is useful when the revisions have since been backed out.
2194 2193
2195 2194 If a graft merge results in conflicts, the graft process is
2196 2195 interrupted so that the current merge can be manually resolved.
2197 2196 Once all conflicts are addressed, the graft process can be
2198 2197 continued with the -c/--continue option.
2199 2198
2200 2199 .. note::
2201 2200
2202 2201 The -c/--continue option does not reapply earlier options, except
2203 2202 for --force.
2204 2203
2205 2204 .. container:: verbose
2206 2205
2207 2206 Examples:
2208 2207
2209 2208 - copy a single change to the stable branch and edit its description::
2210 2209
2211 2210 hg update stable
2212 2211 hg graft --edit 9393
2213 2212
2214 2213 - graft a range of changesets with one exception, updating dates::
2215 2214
2216 2215 hg graft -D "2085::2093 and not 2091"
2217 2216
2218 2217 - continue a graft after resolving conflicts::
2219 2218
2220 2219 hg graft -c
2221 2220
2222 2221 - show the source of a grafted changeset::
2223 2222
2224 2223 hg log --debug -r .
2225 2224
2226 2225 - show revisions sorted by date::
2227 2226
2228 2227 hg log -r "sort(all(), date)"
2229 2228
2230 2229 See :hg:`help revisions` for more about specifying revisions.
2231 2230
2232 2231 Returns 0 on successful completion.
2233 2232 '''
2234 2233 with repo.wlock():
2235 2234 return _dograft(ui, repo, *revs, **opts)
2236 2235
2237 2236 def _dograft(ui, repo, *revs, **opts):
2238 2237 opts = pycompat.byteskwargs(opts)
2239 2238 if revs and opts.get('rev'):
2240 2239 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2241 2240 'revision ordering!\n'))
2242 2241
2243 2242 revs = list(revs)
2244 2243 revs.extend(opts.get('rev'))
2245 2244
2246 2245 if not opts.get('user') and opts.get('currentuser'):
2247 2246 opts['user'] = ui.username()
2248 2247 if not opts.get('date') and opts.get('currentdate'):
2249 2248 opts['date'] = "%d %d" % util.makedate()
2250 2249
2251 2250 editor = cmdutil.getcommiteditor(editform='graft', **opts)
2252 2251
2253 2252 cont = False
2254 2253 if opts.get('continue'):
2255 2254 cont = True
2256 2255 if revs:
2257 2256 raise error.Abort(_("can't specify --continue and revisions"))
2258 2257 # read in unfinished revisions
2259 2258 try:
2260 2259 nodes = repo.vfs.read('graftstate').splitlines()
2261 2260 revs = [repo[node].rev() for node in nodes]
2262 2261 except IOError as inst:
2263 2262 if inst.errno != errno.ENOENT:
2264 2263 raise
2265 2264 cmdutil.wrongtooltocontinue(repo, _('graft'))
2266 2265 else:
2267 2266 cmdutil.checkunfinished(repo)
2268 2267 cmdutil.bailifchanged(repo)
2269 2268 if not revs:
2270 2269 raise error.Abort(_('no revisions specified'))
2271 2270 revs = scmutil.revrange(repo, revs)
2272 2271
2273 2272 skipped = set()
2274 2273 # check for merges
2275 2274 for rev in repo.revs('%ld and merge()', revs):
2276 2275 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2277 2276 skipped.add(rev)
2278 2277 revs = [r for r in revs if r not in skipped]
2279 2278 if not revs:
2280 2279 return -1
2281 2280
2282 2281 # Don't check in the --continue case, in effect retaining --force across
2283 2282 # --continues. That's because without --force, any revisions we decided to
2284 2283 # skip would have been filtered out here, so they wouldn't have made their
2285 2284 # way to the graftstate. With --force, any revisions we would have otherwise
2286 2285 # skipped would not have been filtered out, and if they hadn't been applied
2287 2286 # already, they'd have been in the graftstate.
2288 2287 if not (cont or opts.get('force')):
2289 2288 # check for ancestors of dest branch
2290 2289 crev = repo['.'].rev()
2291 2290 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2292 2291 # XXX make this lazy in the future
2293 2292 # don't mutate while iterating, create a copy
2294 2293 for rev in list(revs):
2295 2294 if rev in ancestors:
2296 2295 ui.warn(_('skipping ancestor revision %d:%s\n') %
2297 2296 (rev, repo[rev]))
2298 2297 # XXX remove on list is slow
2299 2298 revs.remove(rev)
2300 2299 if not revs:
2301 2300 return -1
2302 2301
2303 2302 # analyze revs for earlier grafts
2304 2303 ids = {}
2305 2304 for ctx in repo.set("%ld", revs):
2306 2305 ids[ctx.hex()] = ctx.rev()
2307 2306 n = ctx.extra().get('source')
2308 2307 if n:
2309 2308 ids[n] = ctx.rev()
2310 2309
2311 2310 # check ancestors for earlier grafts
2312 2311 ui.debug('scanning for duplicate grafts\n')
2313 2312
2314 2313 for rev in repo.changelog.findmissingrevs(revs, [crev]):
2315 2314 ctx = repo[rev]
2316 2315 n = ctx.extra().get('source')
2317 2316 if n in ids:
2318 2317 try:
2319 2318 r = repo[n].rev()
2320 2319 except error.RepoLookupError:
2321 2320 r = None
2322 2321 if r in revs:
2323 2322 ui.warn(_('skipping revision %d:%s '
2324 2323 '(already grafted to %d:%s)\n')
2325 2324 % (r, repo[r], rev, ctx))
2326 2325 revs.remove(r)
2327 2326 elif ids[n] in revs:
2328 2327 if r is None:
2329 2328 ui.warn(_('skipping already grafted revision %d:%s '
2330 2329 '(%d:%s also has unknown origin %s)\n')
2331 2330 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2332 2331 else:
2333 2332 ui.warn(_('skipping already grafted revision %d:%s '
2334 2333 '(%d:%s also has origin %d:%s)\n')
2335 2334 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2336 2335 revs.remove(ids[n])
2337 2336 elif ctx.hex() in ids:
2338 2337 r = ids[ctx.hex()]
2339 2338 ui.warn(_('skipping already grafted revision %d:%s '
2340 2339 '(was grafted from %d:%s)\n') %
2341 2340 (r, repo[r], rev, ctx))
2342 2341 revs.remove(r)
2343 2342 if not revs:
2344 2343 return -1
2345 2344
2346 2345 for pos, ctx in enumerate(repo.set("%ld", revs)):
2347 2346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2348 2347 ctx.description().split('\n', 1)[0])
2349 2348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2350 2349 if names:
2351 2350 desc += ' (%s)' % ' '.join(names)
2352 2351 ui.status(_('grafting %s\n') % desc)
2353 2352 if opts.get('dry_run'):
2354 2353 continue
2355 2354
2356 2355 source = ctx.extra().get('source')
2357 2356 extra = {}
2358 2357 if source:
2359 2358 extra['source'] = source
2360 2359 extra['intermediate-source'] = ctx.hex()
2361 2360 else:
2362 2361 extra['source'] = ctx.hex()
2363 2362 user = ctx.user()
2364 2363 if opts.get('user'):
2365 2364 user = opts['user']
2366 2365 date = ctx.date()
2367 2366 if opts.get('date'):
2368 2367 date = opts['date']
2369 2368 message = ctx.description()
2370 2369 if opts.get('log'):
2371 2370 message += '\n(grafted from %s)' % ctx.hex()
2372 2371
2373 2372 # we don't merge the first commit when continuing
2374 2373 if not cont:
2375 2374 # perform the graft merge with p1(rev) as 'ancestor'
2376 2375 try:
2377 2376 # ui.forcemerge is an internal variable, do not document
2378 2377 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2379 2378 'graft')
2380 2379 stats = mergemod.graft(repo, ctx, ctx.p1(),
2381 2380 ['local', 'graft'])
2382 2381 finally:
2383 2382 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2384 2383 # report any conflicts
2385 2384 if stats and stats[3] > 0:
2386 2385 # write out state for --continue
2387 2386 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2388 2387 repo.vfs.write('graftstate', ''.join(nodelines))
2389 2388 extra = ''
2390 2389 if opts.get('user'):
2391 2390 extra += ' --user %s' % util.shellquote(opts['user'])
2392 2391 if opts.get('date'):
2393 2392 extra += ' --date %s' % util.shellquote(opts['date'])
2394 2393 if opts.get('log'):
2395 2394 extra += ' --log'
2396 2395 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2397 2396 raise error.Abort(
2398 2397 _("unresolved conflicts, can't continue"),
2399 2398 hint=hint)
2400 2399 else:
2401 2400 cont = False
2402 2401
2403 2402 # commit
2404 2403 node = repo.commit(text=message, user=user,
2405 2404 date=date, extra=extra, editor=editor)
2406 2405 if node is None:
2407 2406 ui.warn(
2408 2407 _('note: graft of %d:%s created no changes to commit\n') %
2409 2408 (ctx.rev(), ctx))
2410 2409
2411 2410 # remove state when we complete successfully
2412 2411 if not opts.get('dry_run'):
2413 2412 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2414 2413
2415 2414 return 0
2416 2415
2417 2416 @command('grep',
2418 2417 [('0', 'print0', None, _('end fields with NUL')),
2419 2418 ('', 'all', None, _('print all revisions that match')),
2420 2419 ('a', 'text', None, _('treat all files as text')),
2421 2420 ('f', 'follow', None,
2422 2421 _('follow changeset history,'
2423 2422 ' or file history across copies and renames')),
2424 2423 ('i', 'ignore-case', None, _('ignore case when matching')),
2425 2424 ('l', 'files-with-matches', None,
2426 2425 _('print only filenames and revisions that match')),
2427 2426 ('n', 'line-number', None, _('print matching line numbers')),
2428 2427 ('r', 'rev', [],
2429 2428 _('only search files changed within revision range'), _('REV')),
2430 2429 ('u', 'user', None, _('list the author (long with -v)')),
2431 2430 ('d', 'date', None, _('list the date (short with -q)')),
2432 2431 ] + formatteropts + walkopts,
2433 2432 _('[OPTION]... PATTERN [FILE]...'),
2434 2433 inferrepo=True)
2435 2434 def grep(ui, repo, pattern, *pats, **opts):
2436 2435 """search revision history for a pattern in specified files
2437 2436
2438 2437 Search revision history for a regular expression in the specified
2439 2438 files or the entire project.
2440 2439
2441 2440 By default, grep prints the most recent revision number for each
2442 2441 file in which it finds a match. To get it to print every revision
2443 2442 that contains a change in match status ("-" for a match that becomes
2444 2443 a non-match, or "+" for a non-match that becomes a match), use the
2445 2444 --all flag.
2446 2445
2447 2446 PATTERN can be any Python (roughly Perl-compatible) regular
2448 2447 expression.
2449 2448
2450 2449 If no FILEs are specified (and -f/--follow isn't set), all files in
2451 2450 the repository are searched, including those that don't exist in the
2452 2451 current branch or have been deleted in a prior changeset.
2453 2452
2454 2453 Returns 0 if a match is found, 1 otherwise.
2455 2454 """
2456 2455 opts = pycompat.byteskwargs(opts)
2457 2456 reflags = re.M
2458 2457 if opts.get('ignore_case'):
2459 2458 reflags |= re.I
2460 2459 try:
2461 2460 regexp = util.re.compile(pattern, reflags)
2462 2461 except re.error as inst:
2463 2462 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2464 2463 return 1
2465 2464 sep, eol = ':', '\n'
2466 2465 if opts.get('print0'):
2467 2466 sep = eol = '\0'
2468 2467
2469 2468 getfile = util.lrucachefunc(repo.file)
2470 2469
2471 2470 def matchlines(body):
2472 2471 begin = 0
2473 2472 linenum = 0
2474 2473 while begin < len(body):
2475 2474 match = regexp.search(body, begin)
2476 2475 if not match:
2477 2476 break
2478 2477 mstart, mend = match.span()
2479 2478 linenum += body.count('\n', begin, mstart) + 1
2480 2479 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2481 2480 begin = body.find('\n', mend) + 1 or len(body) + 1
2482 2481 lend = begin - 1
2483 2482 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2484 2483
2485 2484 class linestate(object):
2486 2485 def __init__(self, line, linenum, colstart, colend):
2487 2486 self.line = line
2488 2487 self.linenum = linenum
2489 2488 self.colstart = colstart
2490 2489 self.colend = colend
2491 2490
2492 2491 def __hash__(self):
2493 2492 return hash((self.linenum, self.line))
2494 2493
2495 2494 def __eq__(self, other):
2496 2495 return self.line == other.line
2497 2496
2498 2497 def findpos(self):
2499 2498 """Iterate all (start, end) indices of matches"""
2500 2499 yield self.colstart, self.colend
2501 2500 p = self.colend
2502 2501 while p < len(self.line):
2503 2502 m = regexp.search(self.line, p)
2504 2503 if not m:
2505 2504 break
2506 2505 yield m.span()
2507 2506 p = m.end()
2508 2507
2509 2508 matches = {}
2510 2509 copies = {}
2511 2510 def grepbody(fn, rev, body):
2512 2511 matches[rev].setdefault(fn, [])
2513 2512 m = matches[rev][fn]
2514 2513 for lnum, cstart, cend, line in matchlines(body):
2515 2514 s = linestate(line, lnum, cstart, cend)
2516 2515 m.append(s)
2517 2516
2518 2517 def difflinestates(a, b):
2519 2518 sm = difflib.SequenceMatcher(None, a, b)
2520 2519 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2521 2520 if tag == 'insert':
2522 2521 for i in xrange(blo, bhi):
2523 2522 yield ('+', b[i])
2524 2523 elif tag == 'delete':
2525 2524 for i in xrange(alo, ahi):
2526 2525 yield ('-', a[i])
2527 2526 elif tag == 'replace':
2528 2527 for i in xrange(alo, ahi):
2529 2528 yield ('-', a[i])
2530 2529 for i in xrange(blo, bhi):
2531 2530 yield ('+', b[i])
2532 2531
2533 2532 def display(fm, fn, ctx, pstates, states):
2534 2533 rev = ctx.rev()
2535 2534 if fm.isplain():
2536 2535 formatuser = ui.shortuser
2537 2536 else:
2538 2537 formatuser = str
2539 2538 if ui.quiet:
2540 2539 datefmt = '%Y-%m-%d'
2541 2540 else:
2542 2541 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2543 2542 found = False
2544 2543 @util.cachefunc
2545 2544 def binary():
2546 2545 flog = getfile(fn)
2547 2546 return util.binary(flog.read(ctx.filenode(fn)))
2548 2547
2549 2548 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2550 2549 if opts.get('all'):
2551 2550 iter = difflinestates(pstates, states)
2552 2551 else:
2553 2552 iter = [('', l) for l in states]
2554 2553 for change, l in iter:
2555 2554 fm.startitem()
2556 2555 fm.data(node=fm.hexfunc(ctx.node()))
2557 2556 cols = [
2558 2557 ('filename', fn, True),
2559 2558 ('rev', rev, True),
2560 2559 ('linenumber', l.linenum, opts.get('line_number')),
2561 2560 ]
2562 2561 if opts.get('all'):
2563 2562 cols.append(('change', change, True))
2564 2563 cols.extend([
2565 2564 ('user', formatuser(ctx.user()), opts.get('user')),
2566 2565 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2567 2566 ])
2568 2567 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2569 2568 for name, data, cond in cols:
2570 2569 field = fieldnamemap.get(name, name)
2571 2570 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2572 2571 if cond and name != lastcol:
2573 2572 fm.plain(sep, label='grep.sep')
2574 2573 if not opts.get('files_with_matches'):
2575 2574 fm.plain(sep, label='grep.sep')
2576 2575 if not opts.get('text') and binary():
2577 2576 fm.plain(_(" Binary file matches"))
2578 2577 else:
2579 2578 displaymatches(fm.nested('texts'), l)
2580 2579 fm.plain(eol)
2581 2580 found = True
2582 2581 if opts.get('files_with_matches'):
2583 2582 break
2584 2583 return found
2585 2584
2586 2585 def displaymatches(fm, l):
2587 2586 p = 0
2588 2587 for s, e in l.findpos():
2589 2588 if p < s:
2590 2589 fm.startitem()
2591 2590 fm.write('text', '%s', l.line[p:s])
2592 2591 fm.data(matched=False)
2593 2592 fm.startitem()
2594 2593 fm.write('text', '%s', l.line[s:e], label='grep.match')
2595 2594 fm.data(matched=True)
2596 2595 p = e
2597 2596 if p < len(l.line):
2598 2597 fm.startitem()
2599 2598 fm.write('text', '%s', l.line[p:])
2600 2599 fm.data(matched=False)
2601 2600 fm.end()
2602 2601
2603 2602 skip = {}
2604 2603 revfiles = {}
2605 2604 matchfn = scmutil.match(repo[None], pats, opts)
2606 2605 found = False
2607 2606 follow = opts.get('follow')
2608 2607
2609 2608 def prep(ctx, fns):
2610 2609 rev = ctx.rev()
2611 2610 pctx = ctx.p1()
2612 2611 parent = pctx.rev()
2613 2612 matches.setdefault(rev, {})
2614 2613 matches.setdefault(parent, {})
2615 2614 files = revfiles.setdefault(rev, [])
2616 2615 for fn in fns:
2617 2616 flog = getfile(fn)
2618 2617 try:
2619 2618 fnode = ctx.filenode(fn)
2620 2619 except error.LookupError:
2621 2620 continue
2622 2621
2623 2622 copied = flog.renamed(fnode)
2624 2623 copy = follow and copied and copied[0]
2625 2624 if copy:
2626 2625 copies.setdefault(rev, {})[fn] = copy
2627 2626 if fn in skip:
2628 2627 if copy:
2629 2628 skip[copy] = True
2630 2629 continue
2631 2630 files.append(fn)
2632 2631
2633 2632 if fn not in matches[rev]:
2634 2633 grepbody(fn, rev, flog.read(fnode))
2635 2634
2636 2635 pfn = copy or fn
2637 2636 if pfn not in matches[parent]:
2638 2637 try:
2639 2638 fnode = pctx.filenode(pfn)
2640 2639 grepbody(pfn, parent, flog.read(fnode))
2641 2640 except error.LookupError:
2642 2641 pass
2643 2642
2644 2643 ui.pager('grep')
2645 2644 fm = ui.formatter('grep', opts)
2646 2645 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2647 2646 rev = ctx.rev()
2648 2647 parent = ctx.p1().rev()
2649 2648 for fn in sorted(revfiles.get(rev, [])):
2650 2649 states = matches[rev][fn]
2651 2650 copy = copies.get(rev, {}).get(fn)
2652 2651 if fn in skip:
2653 2652 if copy:
2654 2653 skip[copy] = True
2655 2654 continue
2656 2655 pstates = matches.get(parent, {}).get(copy or fn, [])
2657 2656 if pstates or states:
2658 2657 r = display(fm, fn, ctx, pstates, states)
2659 2658 found = found or r
2660 2659 if r and not opts.get('all'):
2661 2660 skip[fn] = True
2662 2661 if copy:
2663 2662 skip[copy] = True
2664 2663 del matches[rev]
2665 2664 del revfiles[rev]
2666 2665 fm.end()
2667 2666
2668 2667 return not found
2669 2668
2670 2669 @command('heads',
2671 2670 [('r', 'rev', '',
2672 2671 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2673 2672 ('t', 'topo', False, _('show topological heads only')),
2674 2673 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2675 2674 ('c', 'closed', False, _('show normal and closed branch heads')),
2676 2675 ] + templateopts,
2677 2676 _('[-ct] [-r STARTREV] [REV]...'))
2678 2677 def heads(ui, repo, *branchrevs, **opts):
2679 2678 """show branch heads
2680 2679
2681 2680 With no arguments, show all open branch heads in the repository.
2682 2681 Branch heads are changesets that have no descendants on the
2683 2682 same branch. They are where development generally takes place and
2684 2683 are the usual targets for update and merge operations.
2685 2684
2686 2685 If one or more REVs are given, only open branch heads on the
2687 2686 branches associated with the specified changesets are shown. This
2688 2687 means that you can use :hg:`heads .` to see the heads on the
2689 2688 currently checked-out branch.
2690 2689
2691 2690 If -c/--closed is specified, also show branch heads marked closed
2692 2691 (see :hg:`commit --close-branch`).
2693 2692
2694 2693 If STARTREV is specified, only those heads that are descendants of
2695 2694 STARTREV will be displayed.
2696 2695
2697 2696 If -t/--topo is specified, named branch mechanics will be ignored and only
2698 2697 topological heads (changesets with no children) will be shown.
2699 2698
2700 2699 Returns 0 if matching heads are found, 1 if not.
2701 2700 """
2702 2701
2703 2702 opts = pycompat.byteskwargs(opts)
2704 2703 start = None
2705 2704 if 'rev' in opts:
2706 2705 start = scmutil.revsingle(repo, opts['rev'], None).node()
2707 2706
2708 2707 if opts.get('topo'):
2709 2708 heads = [repo[h] for h in repo.heads(start)]
2710 2709 else:
2711 2710 heads = []
2712 2711 for branch in repo.branchmap():
2713 2712 heads += repo.branchheads(branch, start, opts.get('closed'))
2714 2713 heads = [repo[h] for h in heads]
2715 2714
2716 2715 if branchrevs:
2717 2716 branches = set(repo[br].branch() for br in branchrevs)
2718 2717 heads = [h for h in heads if h.branch() in branches]
2719 2718
2720 2719 if opts.get('active') and branchrevs:
2721 2720 dagheads = repo.heads(start)
2722 2721 heads = [h for h in heads if h.node() in dagheads]
2723 2722
2724 2723 if branchrevs:
2725 2724 haveheads = set(h.branch() for h in heads)
2726 2725 if branches - haveheads:
2727 2726 headless = ', '.join(b for b in branches - haveheads)
2728 2727 msg = _('no open branch heads found on branches %s')
2729 2728 if opts.get('rev'):
2730 2729 msg += _(' (started at %s)') % opts['rev']
2731 2730 ui.warn((msg + '\n') % headless)
2732 2731
2733 2732 if not heads:
2734 2733 return 1
2735 2734
2736 2735 ui.pager('heads')
2737 2736 heads = sorted(heads, key=lambda x: -x.rev())
2738 2737 displayer = cmdutil.show_changeset(ui, repo, opts)
2739 2738 for ctx in heads:
2740 2739 displayer.show(ctx)
2741 2740 displayer.close()
2742 2741
2743 2742 @command('help',
2744 2743 [('e', 'extension', None, _('show only help for extensions')),
2745 2744 ('c', 'command', None, _('show only help for commands')),
2746 2745 ('k', 'keyword', None, _('show topics matching keyword')),
2747 2746 ('s', 'system', [], _('show help for specific platform(s)')),
2748 2747 ],
2749 2748 _('[-ecks] [TOPIC]'),
2750 2749 norepo=True)
2751 2750 def help_(ui, name=None, **opts):
2752 2751 """show help for a given topic or a help overview
2753 2752
2754 2753 With no arguments, print a list of commands with short help messages.
2755 2754
2756 2755 Given a topic, extension, or command name, print help for that
2757 2756 topic.
2758 2757
2759 2758 Returns 0 if successful.
2760 2759 """
2761 2760
2762 2761 keep = opts.get(r'system') or []
2763 2762 if len(keep) == 0:
2764 2763 if pycompat.sysplatform.startswith('win'):
2765 2764 keep.append('windows')
2766 2765 elif pycompat.sysplatform == 'OpenVMS':
2767 2766 keep.append('vms')
2768 2767 elif pycompat.sysplatform == 'plan9':
2769 2768 keep.append('plan9')
2770 2769 else:
2771 2770 keep.append('unix')
2772 2771 keep.append(pycompat.sysplatform.lower())
2773 2772 if ui.verbose:
2774 2773 keep.append('verbose')
2775 2774
2776 2775 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2777 2776 ui.pager('help')
2778 2777 ui.write(formatted)
2779 2778
2780 2779
2781 2780 @command('identify|id',
2782 2781 [('r', 'rev', '',
2783 2782 _('identify the specified revision'), _('REV')),
2784 2783 ('n', 'num', None, _('show local revision number')),
2785 2784 ('i', 'id', None, _('show global revision id')),
2786 2785 ('b', 'branch', None, _('show branch')),
2787 2786 ('t', 'tags', None, _('show tags')),
2788 2787 ('B', 'bookmarks', None, _('show bookmarks')),
2789 2788 ] + remoteopts,
2790 2789 _('[-nibtB] [-r REV] [SOURCE]'),
2791 2790 optionalrepo=True)
2792 2791 def identify(ui, repo, source=None, rev=None,
2793 2792 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2794 2793 """identify the working directory or specified revision
2795 2794
2796 2795 Print a summary identifying the repository state at REV using one or
2797 2796 two parent hash identifiers, followed by a "+" if the working
2798 2797 directory has uncommitted changes, the branch name (if not default),
2799 2798 a list of tags, and a list of bookmarks.
2800 2799
2801 2800 When REV is not given, print a summary of the current state of the
2802 2801 repository.
2803 2802
2804 2803 Specifying a path to a repository root or Mercurial bundle will
2805 2804 cause lookup to operate on that repository/bundle.
2806 2805
2807 2806 .. container:: verbose
2808 2807
2809 2808 Examples:
2810 2809
2811 2810 - generate a build identifier for the working directory::
2812 2811
2813 2812 hg id --id > build-id.dat
2814 2813
2815 2814 - find the revision corresponding to a tag::
2816 2815
2817 2816 hg id -n -r 1.3
2818 2817
2819 2818 - check the most recent revision of a remote repository::
2820 2819
2821 2820 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2822 2821
2823 2822 See :hg:`log` for generating more information about specific revisions,
2824 2823 including full hash identifiers.
2825 2824
2826 2825 Returns 0 if successful.
2827 2826 """
2828 2827
2829 2828 opts = pycompat.byteskwargs(opts)
2830 2829 if not repo and not source:
2831 2830 raise error.Abort(_("there is no Mercurial repository here "
2832 2831 "(.hg not found)"))
2833 2832
2834 2833 if ui.debugflag:
2835 2834 hexfunc = hex
2836 2835 else:
2837 2836 hexfunc = short
2838 2837 default = not (num or id or branch or tags or bookmarks)
2839 2838 output = []
2840 2839 revs = []
2841 2840
2842 2841 if source:
2843 2842 source, branches = hg.parseurl(ui.expandpath(source))
2844 2843 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2845 2844 repo = peer.local()
2846 2845 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2847 2846
2848 2847 if not repo:
2849 2848 if num or branch or tags:
2850 2849 raise error.Abort(
2851 2850 _("can't query remote revision number, branch, or tags"))
2852 2851 if not rev and revs:
2853 2852 rev = revs[0]
2854 2853 if not rev:
2855 2854 rev = "tip"
2856 2855
2857 2856 remoterev = peer.lookup(rev)
2858 2857 if default or id:
2859 2858 output = [hexfunc(remoterev)]
2860 2859
2861 2860 def getbms():
2862 2861 bms = []
2863 2862
2864 2863 if 'bookmarks' in peer.listkeys('namespaces'):
2865 2864 hexremoterev = hex(remoterev)
2866 2865 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2867 2866 if bmr == hexremoterev]
2868 2867
2869 2868 return sorted(bms)
2870 2869
2871 2870 if bookmarks:
2872 2871 output.extend(getbms())
2873 2872 elif default and not ui.quiet:
2874 2873 # multiple bookmarks for a single parent separated by '/'
2875 2874 bm = '/'.join(getbms())
2876 2875 if bm:
2877 2876 output.append(bm)
2878 2877 else:
2879 2878 ctx = scmutil.revsingle(repo, rev, None)
2880 2879
2881 2880 if ctx.rev() is None:
2882 2881 ctx = repo[None]
2883 2882 parents = ctx.parents()
2884 2883 taglist = []
2885 2884 for p in parents:
2886 2885 taglist.extend(p.tags())
2887 2886
2888 2887 changed = ""
2889 2888 if default or id or num:
2890 2889 if (any(repo.status())
2891 2890 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2892 2891 changed = '+'
2893 2892 if default or id:
2894 2893 output = ["%s%s" %
2895 2894 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2896 2895 if num:
2897 2896 output.append("%s%s" %
2898 2897 ('+'.join([str(p.rev()) for p in parents]), changed))
2899 2898 else:
2900 2899 if default or id:
2901 2900 output = [hexfunc(ctx.node())]
2902 2901 if num:
2903 2902 output.append(str(ctx.rev()))
2904 2903 taglist = ctx.tags()
2905 2904
2906 2905 if default and not ui.quiet:
2907 2906 b = ctx.branch()
2908 2907 if b != 'default':
2909 2908 output.append("(%s)" % b)
2910 2909
2911 2910 # multiple tags for a single parent separated by '/'
2912 2911 t = '/'.join(taglist)
2913 2912 if t:
2914 2913 output.append(t)
2915 2914
2916 2915 # multiple bookmarks for a single parent separated by '/'
2917 2916 bm = '/'.join(ctx.bookmarks())
2918 2917 if bm:
2919 2918 output.append(bm)
2920 2919 else:
2921 2920 if branch:
2922 2921 output.append(ctx.branch())
2923 2922
2924 2923 if tags:
2925 2924 output.extend(taglist)
2926 2925
2927 2926 if bookmarks:
2928 2927 output.extend(ctx.bookmarks())
2929 2928
2930 2929 ui.write("%s\n" % ' '.join(output))
2931 2930
2932 2931 @command('import|patch',
2933 2932 [('p', 'strip', 1,
2934 2933 _('directory strip option for patch. This has the same '
2935 2934 'meaning as the corresponding patch option'), _('NUM')),
2936 2935 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2937 2936 ('e', 'edit', False, _('invoke editor on commit messages')),
2938 2937 ('f', 'force', None,
2939 2938 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2940 2939 ('', 'no-commit', None,
2941 2940 _("don't commit, just update the working directory")),
2942 2941 ('', 'bypass', None,
2943 2942 _("apply patch without touching the working directory")),
2944 2943 ('', 'partial', None,
2945 2944 _('commit even if some hunks fail')),
2946 2945 ('', 'exact', None,
2947 2946 _('abort if patch would apply lossily')),
2948 2947 ('', 'prefix', '',
2949 2948 _('apply patch to subdirectory'), _('DIR')),
2950 2949 ('', 'import-branch', None,
2951 2950 _('use any branch information in patch (implied by --exact)'))] +
2952 2951 commitopts + commitopts2 + similarityopts,
2953 2952 _('[OPTION]... PATCH...'))
2954 2953 def import_(ui, repo, patch1=None, *patches, **opts):
2955 2954 """import an ordered set of patches
2956 2955
2957 2956 Import a list of patches and commit them individually (unless
2958 2957 --no-commit is specified).
2959 2958
2960 2959 To read a patch from standard input (stdin), use "-" as the patch
2961 2960 name. If a URL is specified, the patch will be downloaded from
2962 2961 there.
2963 2962
2964 2963 Import first applies changes to the working directory (unless
2965 2964 --bypass is specified), import will abort if there are outstanding
2966 2965 changes.
2967 2966
2968 2967 Use --bypass to apply and commit patches directly to the
2969 2968 repository, without affecting the working directory. Without
2970 2969 --exact, patches will be applied on top of the working directory
2971 2970 parent revision.
2972 2971
2973 2972 You can import a patch straight from a mail message. Even patches
2974 2973 as attachments work (to use the body part, it must have type
2975 2974 text/plain or text/x-patch). From and Subject headers of email
2976 2975 message are used as default committer and commit message. All
2977 2976 text/plain body parts before first diff are added to the commit
2978 2977 message.
2979 2978
2980 2979 If the imported patch was generated by :hg:`export`, user and
2981 2980 description from patch override values from message headers and
2982 2981 body. Values given on command line with -m/--message and -u/--user
2983 2982 override these.
2984 2983
2985 2984 If --exact is specified, import will set the working directory to
2986 2985 the parent of each patch before applying it, and will abort if the
2987 2986 resulting changeset has a different ID than the one recorded in
2988 2987 the patch. This will guard against various ways that portable
2989 2988 patch formats and mail systems might fail to transfer Mercurial
2990 2989 data or metadata. See :hg:`bundle` for lossless transmission.
2991 2990
2992 2991 Use --partial to ensure a changeset will be created from the patch
2993 2992 even if some hunks fail to apply. Hunks that fail to apply will be
2994 2993 written to a <target-file>.rej file. Conflicts can then be resolved
2995 2994 by hand before :hg:`commit --amend` is run to update the created
2996 2995 changeset. This flag exists to let people import patches that
2997 2996 partially apply without losing the associated metadata (author,
2998 2997 date, description, ...).
2999 2998
3000 2999 .. note::
3001 3000
3002 3001 When no hunks apply cleanly, :hg:`import --partial` will create
3003 3002 an empty changeset, importing only the patch metadata.
3004 3003
3005 3004 With -s/--similarity, hg will attempt to discover renames and
3006 3005 copies in the patch in the same way as :hg:`addremove`.
3007 3006
3008 3007 It is possible to use external patch programs to perform the patch
3009 3008 by setting the ``ui.patch`` configuration option. For the default
3010 3009 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3011 3010 See :hg:`help config` for more information about configuration
3012 3011 files and how to use these options.
3013 3012
3014 3013 See :hg:`help dates` for a list of formats valid for -d/--date.
3015 3014
3016 3015 .. container:: verbose
3017 3016
3018 3017 Examples:
3019 3018
3020 3019 - import a traditional patch from a website and detect renames::
3021 3020
3022 3021 hg import -s 80 http://example.com/bugfix.patch
3023 3022
3024 3023 - import a changeset from an hgweb server::
3025 3024
3026 3025 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3027 3026
3028 3027 - import all the patches in an Unix-style mbox::
3029 3028
3030 3029 hg import incoming-patches.mbox
3031 3030
3032 3031 - import patches from stdin::
3033 3032
3034 3033 hg import -
3035 3034
3036 3035 - attempt to exactly restore an exported changeset (not always
3037 3036 possible)::
3038 3037
3039 3038 hg import --exact proposed-fix.patch
3040 3039
3041 3040 - use an external tool to apply a patch which is too fuzzy for
3042 3041 the default internal tool.
3043 3042
3044 3043 hg import --config ui.patch="patch --merge" fuzzy.patch
3045 3044
3046 3045 - change the default fuzzing from 2 to a less strict 7
3047 3046
3048 3047 hg import --config ui.fuzz=7 fuzz.patch
3049 3048
3050 3049 Returns 0 on success, 1 on partial success (see --partial).
3051 3050 """
3052 3051
3053 3052 opts = pycompat.byteskwargs(opts)
3054 3053 if not patch1:
3055 3054 raise error.Abort(_('need at least one patch to import'))
3056 3055
3057 3056 patches = (patch1,) + patches
3058 3057
3059 3058 date = opts.get('date')
3060 3059 if date:
3061 3060 opts['date'] = util.parsedate(date)
3062 3061
3063 3062 exact = opts.get('exact')
3064 3063 update = not opts.get('bypass')
3065 3064 if not update and opts.get('no_commit'):
3066 3065 raise error.Abort(_('cannot use --no-commit with --bypass'))
3067 3066 try:
3068 3067 sim = float(opts.get('similarity') or 0)
3069 3068 except ValueError:
3070 3069 raise error.Abort(_('similarity must be a number'))
3071 3070 if sim < 0 or sim > 100:
3072 3071 raise error.Abort(_('similarity must be between 0 and 100'))
3073 3072 if sim and not update:
3074 3073 raise error.Abort(_('cannot use --similarity with --bypass'))
3075 3074 if exact:
3076 3075 if opts.get('edit'):
3077 3076 raise error.Abort(_('cannot use --exact with --edit'))
3078 3077 if opts.get('prefix'):
3079 3078 raise error.Abort(_('cannot use --exact with --prefix'))
3080 3079
3081 3080 base = opts["base"]
3082 3081 wlock = dsguard = lock = tr = None
3083 3082 msgs = []
3084 3083 ret = 0
3085 3084
3086 3085
3087 3086 try:
3088 3087 wlock = repo.wlock()
3089 3088
3090 3089 if update:
3091 3090 cmdutil.checkunfinished(repo)
3092 3091 if (exact or not opts.get('force')):
3093 3092 cmdutil.bailifchanged(repo)
3094 3093
3095 3094 if not opts.get('no_commit'):
3096 3095 lock = repo.lock()
3097 3096 tr = repo.transaction('import')
3098 3097 else:
3099 3098 dsguard = dirstateguard.dirstateguard(repo, 'import')
3100 3099 parents = repo[None].parents()
3101 3100 for patchurl in patches:
3102 3101 if patchurl == '-':
3103 3102 ui.status(_('applying patch from stdin\n'))
3104 3103 patchfile = ui.fin
3105 3104 patchurl = 'stdin' # for error message
3106 3105 else:
3107 3106 patchurl = os.path.join(base, patchurl)
3108 3107 ui.status(_('applying %s\n') % patchurl)
3109 3108 patchfile = hg.openpath(ui, patchurl)
3110 3109
3111 3110 haspatch = False
3112 3111 for hunk in patch.split(patchfile):
3113 3112 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3114 3113 parents, opts,
3115 3114 msgs, hg.clean)
3116 3115 if msg:
3117 3116 haspatch = True
3118 3117 ui.note(msg + '\n')
3119 3118 if update or exact:
3120 3119 parents = repo[None].parents()
3121 3120 else:
3122 3121 parents = [repo[node]]
3123 3122 if rej:
3124 3123 ui.write_err(_("patch applied partially\n"))
3125 3124 ui.write_err(_("(fix the .rej files and run "
3126 3125 "`hg commit --amend`)\n"))
3127 3126 ret = 1
3128 3127 break
3129 3128
3130 3129 if not haspatch:
3131 3130 raise error.Abort(_('%s: no diffs found') % patchurl)
3132 3131
3133 3132 if tr:
3134 3133 tr.close()
3135 3134 if msgs:
3136 3135 repo.savecommitmessage('\n* * *\n'.join(msgs))
3137 3136 if dsguard:
3138 3137 dsguard.close()
3139 3138 return ret
3140 3139 finally:
3141 3140 if tr:
3142 3141 tr.release()
3143 3142 release(lock, dsguard, wlock)
3144 3143
3145 3144 @command('incoming|in',
3146 3145 [('f', 'force', None,
3147 3146 _('run even if remote repository is unrelated')),
3148 3147 ('n', 'newest-first', None, _('show newest record first')),
3149 3148 ('', 'bundle', '',
3150 3149 _('file to store the bundles into'), _('FILE')),
3151 3150 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3152 3151 ('B', 'bookmarks', False, _("compare bookmarks")),
3153 3152 ('b', 'branch', [],
3154 3153 _('a specific branch you would like to pull'), _('BRANCH')),
3155 3154 ] + logopts + remoteopts + subrepoopts,
3156 3155 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3157 3156 def incoming(ui, repo, source="default", **opts):
3158 3157 """show new changesets found in source
3159 3158
3160 3159 Show new changesets found in the specified path/URL or the default
3161 3160 pull location. These are the changesets that would have been pulled
3162 3161 if a pull at the time you issued this command.
3163 3162
3164 3163 See pull for valid source format details.
3165 3164
3166 3165 .. container:: verbose
3167 3166
3168 3167 With -B/--bookmarks, the result of bookmark comparison between
3169 3168 local and remote repositories is displayed. With -v/--verbose,
3170 3169 status is also displayed for each bookmark like below::
3171 3170
3172 3171 BM1 01234567890a added
3173 3172 BM2 1234567890ab advanced
3174 3173 BM3 234567890abc diverged
3175 3174 BM4 34567890abcd changed
3176 3175
3177 3176 The action taken locally when pulling depends on the
3178 3177 status of each bookmark:
3179 3178
3180 3179 :``added``: pull will create it
3181 3180 :``advanced``: pull will update it
3182 3181 :``diverged``: pull will create a divergent bookmark
3183 3182 :``changed``: result depends on remote changesets
3184 3183
3185 3184 From the point of view of pulling behavior, bookmark
3186 3185 existing only in the remote repository are treated as ``added``,
3187 3186 even if it is in fact locally deleted.
3188 3187
3189 3188 .. container:: verbose
3190 3189
3191 3190 For remote repository, using --bundle avoids downloading the
3192 3191 changesets twice if the incoming is followed by a pull.
3193 3192
3194 3193 Examples:
3195 3194
3196 3195 - show incoming changes with patches and full description::
3197 3196
3198 3197 hg incoming -vp
3199 3198
3200 3199 - show incoming changes excluding merges, store a bundle::
3201 3200
3202 3201 hg in -vpM --bundle incoming.hg
3203 3202 hg pull incoming.hg
3204 3203
3205 3204 - briefly list changes inside a bundle::
3206 3205
3207 3206 hg in changes.hg -T "{desc|firstline}\\n"
3208 3207
3209 3208 Returns 0 if there are incoming changes, 1 otherwise.
3210 3209 """
3211 3210 opts = pycompat.byteskwargs(opts)
3212 3211 if opts.get('graph'):
3213 3212 cmdutil.checkunsupportedgraphflags([], opts)
3214 3213 def display(other, chlist, displayer):
3215 3214 revdag = cmdutil.graphrevs(other, chlist, opts)
3216 3215 cmdutil.displaygraph(ui, repo, revdag, displayer,
3217 3216 graphmod.asciiedges)
3218 3217
3219 3218 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3220 3219 return 0
3221 3220
3222 3221 if opts.get('bundle') and opts.get('subrepos'):
3223 3222 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3224 3223
3225 3224 if opts.get('bookmarks'):
3226 3225 source, branches = hg.parseurl(ui.expandpath(source),
3227 3226 opts.get('branch'))
3228 3227 other = hg.peer(repo, opts, source)
3229 3228 if 'bookmarks' not in other.listkeys('namespaces'):
3230 3229 ui.warn(_("remote doesn't support bookmarks\n"))
3231 3230 return 0
3232 3231 ui.pager('incoming')
3233 3232 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3234 3233 return bookmarks.incoming(ui, repo, other)
3235 3234
3236 3235 repo._subtoppath = ui.expandpath(source)
3237 3236 try:
3238 3237 return hg.incoming(ui, repo, source, opts)
3239 3238 finally:
3240 3239 del repo._subtoppath
3241 3240
3242 3241
3243 3242 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3244 3243 norepo=True)
3245 3244 def init(ui, dest=".", **opts):
3246 3245 """create a new repository in the given directory
3247 3246
3248 3247 Initialize a new repository in the given directory. If the given
3249 3248 directory does not exist, it will be created.
3250 3249
3251 3250 If no directory is given, the current directory is used.
3252 3251
3253 3252 It is possible to specify an ``ssh://`` URL as the destination.
3254 3253 See :hg:`help urls` for more information.
3255 3254
3256 3255 Returns 0 on success.
3257 3256 """
3258 3257 opts = pycompat.byteskwargs(opts)
3259 3258 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3260 3259
3261 3260 @command('locate',
3262 3261 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3263 3262 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3264 3263 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3265 3264 ] + walkopts,
3266 3265 _('[OPTION]... [PATTERN]...'))
3267 3266 def locate(ui, repo, *pats, **opts):
3268 3267 """locate files matching specific patterns (DEPRECATED)
3269 3268
3270 3269 Print files under Mercurial control in the working directory whose
3271 3270 names match the given patterns.
3272 3271
3273 3272 By default, this command searches all directories in the working
3274 3273 directory. To search just the current directory and its
3275 3274 subdirectories, use "--include .".
3276 3275
3277 3276 If no patterns are given to match, this command prints the names
3278 3277 of all files under Mercurial control in the working directory.
3279 3278
3280 3279 If you want to feed the output of this command into the "xargs"
3281 3280 command, use the -0 option to both this command and "xargs". This
3282 3281 will avoid the problem of "xargs" treating single filenames that
3283 3282 contain whitespace as multiple filenames.
3284 3283
3285 3284 See :hg:`help files` for a more versatile command.
3286 3285
3287 3286 Returns 0 if a match is found, 1 otherwise.
3288 3287 """
3289 3288 opts = pycompat.byteskwargs(opts)
3290 3289 if opts.get('print0'):
3291 3290 end = '\0'
3292 3291 else:
3293 3292 end = '\n'
3294 3293 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3295 3294
3296 3295 ret = 1
3297 3296 ctx = repo[rev]
3298 3297 m = scmutil.match(ctx, pats, opts, default='relglob',
3299 3298 badfn=lambda x, y: False)
3300 3299
3301 3300 ui.pager('locate')
3302 3301 for abs in ctx.matches(m):
3303 3302 if opts.get('fullpath'):
3304 3303 ui.write(repo.wjoin(abs), end)
3305 3304 else:
3306 3305 ui.write(((pats and m.rel(abs)) or abs), end)
3307 3306 ret = 0
3308 3307
3309 3308 return ret
3310 3309
3311 3310 @command('^log|history',
3312 3311 [('f', 'follow', None,
3313 3312 _('follow changeset history, or file history across copies and renames')),
3314 3313 ('', 'follow-first', None,
3315 3314 _('only follow the first parent of merge changesets (DEPRECATED)')),
3316 3315 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3317 3316 ('C', 'copies', None, _('show copied files')),
3318 3317 ('k', 'keyword', [],
3319 3318 _('do case-insensitive search for a given text'), _('TEXT')),
3320 3319 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3321 3320 ('', 'removed', None, _('include revisions where files were removed')),
3322 3321 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3323 3322 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3324 3323 ('', 'only-branch', [],
3325 3324 _('show only changesets within the given named branch (DEPRECATED)'),
3326 3325 _('BRANCH')),
3327 3326 ('b', 'branch', [],
3328 3327 _('show changesets within the given named branch'), _('BRANCH')),
3329 3328 ('P', 'prune', [],
3330 3329 _('do not display revision or any of its ancestors'), _('REV')),
3331 3330 ] + logopts + walkopts,
3332 3331 _('[OPTION]... [FILE]'),
3333 3332 inferrepo=True)
3334 3333 def log(ui, repo, *pats, **opts):
3335 3334 """show revision history of entire repository or files
3336 3335
3337 3336 Print the revision history of the specified files or the entire
3338 3337 project.
3339 3338
3340 3339 If no revision range is specified, the default is ``tip:0`` unless
3341 3340 --follow is set, in which case the working directory parent is
3342 3341 used as the starting revision.
3343 3342
3344 3343 File history is shown without following rename or copy history of
3345 3344 files. Use -f/--follow with a filename to follow history across
3346 3345 renames and copies. --follow without a filename will only show
3347 3346 ancestors or descendants of the starting revision.
3348 3347
3349 3348 By default this command prints revision number and changeset id,
3350 3349 tags, non-trivial parents, user, date and time, and a summary for
3351 3350 each commit. When the -v/--verbose switch is used, the list of
3352 3351 changed files and full commit message are shown.
3353 3352
3354 3353 With --graph the revisions are shown as an ASCII art DAG with the most
3355 3354 recent changeset at the top.
3356 3355 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3357 3356 and '+' represents a fork where the changeset from the lines below is a
3358 3357 parent of the 'o' merge on the same line.
3359 3358 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3360 3359 of a '|' indicates one or more revisions in a path are omitted.
3361 3360
3362 3361 .. note::
3363 3362
3364 3363 :hg:`log --patch` may generate unexpected diff output for merge
3365 3364 changesets, as it will only compare the merge changeset against
3366 3365 its first parent. Also, only files different from BOTH parents
3367 3366 will appear in files:.
3368 3367
3369 3368 .. note::
3370 3369
3371 3370 For performance reasons, :hg:`log FILE` may omit duplicate changes
3372 3371 made on branches and will not show removals or mode changes. To
3373 3372 see all such changes, use the --removed switch.
3374 3373
3375 3374 .. container:: verbose
3376 3375
3377 3376 Some examples:
3378 3377
3379 3378 - changesets with full descriptions and file lists::
3380 3379
3381 3380 hg log -v
3382 3381
3383 3382 - changesets ancestral to the working directory::
3384 3383
3385 3384 hg log -f
3386 3385
3387 3386 - last 10 commits on the current branch::
3388 3387
3389 3388 hg log -l 10 -b .
3390 3389
3391 3390 - changesets showing all modifications of a file, including removals::
3392 3391
3393 3392 hg log --removed file.c
3394 3393
3395 3394 - all changesets that touch a directory, with diffs, excluding merges::
3396 3395
3397 3396 hg log -Mp lib/
3398 3397
3399 3398 - all revision numbers that match a keyword::
3400 3399
3401 3400 hg log -k bug --template "{rev}\\n"
3402 3401
3403 3402 - the full hash identifier of the working directory parent::
3404 3403
3405 3404 hg log -r . --template "{node}\\n"
3406 3405
3407 3406 - list available log templates::
3408 3407
3409 3408 hg log -T list
3410 3409
3411 3410 - check if a given changeset is included in a tagged release::
3412 3411
3413 3412 hg log -r "a21ccf and ancestor(1.9)"
3414 3413
3415 3414 - find all changesets by some user in a date range::
3416 3415
3417 3416 hg log -k alice -d "may 2008 to jul 2008"
3418 3417
3419 3418 - summary of all changesets after the last tag::
3420 3419
3421 3420 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3422 3421
3423 3422 See :hg:`help dates` for a list of formats valid for -d/--date.
3424 3423
3425 3424 See :hg:`help revisions` for more about specifying and ordering
3426 3425 revisions.
3427 3426
3428 3427 See :hg:`help templates` for more about pre-packaged styles and
3429 3428 specifying custom templates.
3430 3429
3431 3430 Returns 0 on success.
3432 3431
3433 3432 """
3434 3433 opts = pycompat.byteskwargs(opts)
3435 3434 if opts.get('follow') and opts.get('rev'):
3436 3435 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3437 3436 del opts['follow']
3438 3437
3439 3438 if opts.get('graph'):
3440 3439 return cmdutil.graphlog(ui, repo, pats, opts)
3441 3440
3442 3441 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3443 3442 limit = cmdutil.loglimit(opts)
3444 3443 count = 0
3445 3444
3446 3445 getrenamed = None
3447 3446 if opts.get('copies'):
3448 3447 endrev = None
3449 3448 if opts.get('rev'):
3450 3449 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3451 3450 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3452 3451
3453 3452 ui.pager('log')
3454 3453 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3455 3454 for rev in revs:
3456 3455 if count == limit:
3457 3456 break
3458 3457 ctx = repo[rev]
3459 3458 copies = None
3460 3459 if getrenamed is not None and rev:
3461 3460 copies = []
3462 3461 for fn in ctx.files():
3463 3462 rename = getrenamed(fn, rev)
3464 3463 if rename:
3465 3464 copies.append((fn, rename[0]))
3466 3465 if filematcher:
3467 3466 revmatchfn = filematcher(ctx.rev())
3468 3467 else:
3469 3468 revmatchfn = None
3470 3469 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3471 3470 if displayer.flush(ctx):
3472 3471 count += 1
3473 3472
3474 3473 displayer.close()
3475 3474
3476 3475 @command('manifest',
3477 3476 [('r', 'rev', '', _('revision to display'), _('REV')),
3478 3477 ('', 'all', False, _("list files from all revisions"))]
3479 3478 + formatteropts,
3480 3479 _('[-r REV]'))
3481 3480 def manifest(ui, repo, node=None, rev=None, **opts):
3482 3481 """output the current or given revision of the project manifest
3483 3482
3484 3483 Print a list of version controlled files for the given revision.
3485 3484 If no revision is given, the first parent of the working directory
3486 3485 is used, or the null revision if no revision is checked out.
3487 3486
3488 3487 With -v, print file permissions, symlink and executable bits.
3489 3488 With --debug, print file revision hashes.
3490 3489
3491 3490 If option --all is specified, the list of all files from all revisions
3492 3491 is printed. This includes deleted and renamed files.
3493 3492
3494 3493 Returns 0 on success.
3495 3494 """
3496 3495 opts = pycompat.byteskwargs(opts)
3497 3496 fm = ui.formatter('manifest', opts)
3498 3497
3499 3498 if opts.get('all'):
3500 3499 if rev or node:
3501 3500 raise error.Abort(_("can't specify a revision with --all"))
3502 3501
3503 3502 res = []
3504 3503 prefix = "data/"
3505 3504 suffix = ".i"
3506 3505 plen = len(prefix)
3507 3506 slen = len(suffix)
3508 3507 with repo.lock():
3509 3508 for fn, b, size in repo.store.datafiles():
3510 3509 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3511 3510 res.append(fn[plen:-slen])
3512 3511 ui.pager('manifest')
3513 3512 for f in res:
3514 3513 fm.startitem()
3515 3514 fm.write("path", '%s\n', f)
3516 3515 fm.end()
3517 3516 return
3518 3517
3519 3518 if rev and node:
3520 3519 raise error.Abort(_("please specify just one revision"))
3521 3520
3522 3521 if not node:
3523 3522 node = rev
3524 3523
3525 3524 char = {'l': '@', 'x': '*', '': ''}
3526 3525 mode = {'l': '644', 'x': '755', '': '644'}
3527 3526 ctx = scmutil.revsingle(repo, node)
3528 3527 mf = ctx.manifest()
3529 3528 ui.pager('manifest')
3530 3529 for f in ctx:
3531 3530 fm.startitem()
3532 3531 fl = ctx[f].flags()
3533 3532 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3534 3533 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3535 3534 fm.write('path', '%s\n', f)
3536 3535 fm.end()
3537 3536
3538 3537 @command('^merge',
3539 3538 [('f', 'force', None,
3540 3539 _('force a merge including outstanding changes (DEPRECATED)')),
3541 3540 ('r', 'rev', '', _('revision to merge'), _('REV')),
3542 3541 ('P', 'preview', None,
3543 3542 _('review revisions to merge (no merge is performed)'))
3544 3543 ] + mergetoolopts,
3545 3544 _('[-P] [[-r] REV]'))
3546 3545 def merge(ui, repo, node=None, **opts):
3547 3546 """merge another revision into working directory
3548 3547
3549 3548 The current working directory is updated with all changes made in
3550 3549 the requested revision since the last common predecessor revision.
3551 3550
3552 3551 Files that changed between either parent are marked as changed for
3553 3552 the next commit and a commit must be performed before any further
3554 3553 updates to the repository are allowed. The next commit will have
3555 3554 two parents.
3556 3555
3557 3556 ``--tool`` can be used to specify the merge tool used for file
3558 3557 merges. It overrides the HGMERGE environment variable and your
3559 3558 configuration files. See :hg:`help merge-tools` for options.
3560 3559
3561 3560 If no revision is specified, the working directory's parent is a
3562 3561 head revision, and the current branch contains exactly one other
3563 3562 head, the other head is merged with by default. Otherwise, an
3564 3563 explicit revision with which to merge with must be provided.
3565 3564
3566 3565 See :hg:`help resolve` for information on handling file conflicts.
3567 3566
3568 3567 To undo an uncommitted merge, use :hg:`update --clean .` which
3569 3568 will check out a clean copy of the original merge parent, losing
3570 3569 all changes.
3571 3570
3572 3571 Returns 0 on success, 1 if there are unresolved files.
3573 3572 """
3574 3573
3575 3574 opts = pycompat.byteskwargs(opts)
3576 3575 if opts.get('rev') and node:
3577 3576 raise error.Abort(_("please specify just one revision"))
3578 3577 if not node:
3579 3578 node = opts.get('rev')
3580 3579
3581 3580 if node:
3582 3581 node = scmutil.revsingle(repo, node).node()
3583 3582
3584 3583 if not node:
3585 3584 node = repo[destutil.destmerge(repo)].node()
3586 3585
3587 3586 if opts.get('preview'):
3588 3587 # find nodes that are ancestors of p2 but not of p1
3589 3588 p1 = repo.lookup('.')
3590 3589 p2 = repo.lookup(node)
3591 3590 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3592 3591
3593 3592 displayer = cmdutil.show_changeset(ui, repo, opts)
3594 3593 for node in nodes:
3595 3594 displayer.show(repo[node])
3596 3595 displayer.close()
3597 3596 return 0
3598 3597
3599 3598 try:
3600 3599 # ui.forcemerge is an internal variable, do not document
3601 3600 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3602 3601 force = opts.get('force')
3603 3602 labels = ['working copy', 'merge rev']
3604 3603 return hg.merge(repo, node, force=force, mergeforce=force,
3605 3604 labels=labels)
3606 3605 finally:
3607 3606 ui.setconfig('ui', 'forcemerge', '', 'merge')
3608 3607
3609 3608 @command('outgoing|out',
3610 3609 [('f', 'force', None, _('run even when the destination is unrelated')),
3611 3610 ('r', 'rev', [],
3612 3611 _('a changeset intended to be included in the destination'), _('REV')),
3613 3612 ('n', 'newest-first', None, _('show newest record first')),
3614 3613 ('B', 'bookmarks', False, _('compare bookmarks')),
3615 3614 ('b', 'branch', [], _('a specific branch you would like to push'),
3616 3615 _('BRANCH')),
3617 3616 ] + logopts + remoteopts + subrepoopts,
3618 3617 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3619 3618 def outgoing(ui, repo, dest=None, **opts):
3620 3619 """show changesets not found in the destination
3621 3620
3622 3621 Show changesets not found in the specified destination repository
3623 3622 or the default push location. These are the changesets that would
3624 3623 be pushed if a push was requested.
3625 3624
3626 3625 See pull for details of valid destination formats.
3627 3626
3628 3627 .. container:: verbose
3629 3628
3630 3629 With -B/--bookmarks, the result of bookmark comparison between
3631 3630 local and remote repositories is displayed. With -v/--verbose,
3632 3631 status is also displayed for each bookmark like below::
3633 3632
3634 3633 BM1 01234567890a added
3635 3634 BM2 deleted
3636 3635 BM3 234567890abc advanced
3637 3636 BM4 34567890abcd diverged
3638 3637 BM5 4567890abcde changed
3639 3638
3640 3639 The action taken when pushing depends on the
3641 3640 status of each bookmark:
3642 3641
3643 3642 :``added``: push with ``-B`` will create it
3644 3643 :``deleted``: push with ``-B`` will delete it
3645 3644 :``advanced``: push will update it
3646 3645 :``diverged``: push with ``-B`` will update it
3647 3646 :``changed``: push with ``-B`` will update it
3648 3647
3649 3648 From the point of view of pushing behavior, bookmarks
3650 3649 existing only in the remote repository are treated as
3651 3650 ``deleted``, even if it is in fact added remotely.
3652 3651
3653 3652 Returns 0 if there are outgoing changes, 1 otherwise.
3654 3653 """
3655 3654 opts = pycompat.byteskwargs(opts)
3656 3655 if opts.get('graph'):
3657 3656 cmdutil.checkunsupportedgraphflags([], opts)
3658 3657 o, other = hg._outgoing(ui, repo, dest, opts)
3659 3658 if not o:
3660 3659 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3661 3660 return
3662 3661
3663 3662 revdag = cmdutil.graphrevs(repo, o, opts)
3664 3663 ui.pager('outgoing')
3665 3664 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3666 3665 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3667 3666 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3668 3667 return 0
3669 3668
3670 3669 if opts.get('bookmarks'):
3671 3670 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3672 3671 dest, branches = hg.parseurl(dest, opts.get('branch'))
3673 3672 other = hg.peer(repo, opts, dest)
3674 3673 if 'bookmarks' not in other.listkeys('namespaces'):
3675 3674 ui.warn(_("remote doesn't support bookmarks\n"))
3676 3675 return 0
3677 3676 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3678 3677 ui.pager('outgoing')
3679 3678 return bookmarks.outgoing(ui, repo, other)
3680 3679
3681 3680 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3682 3681 try:
3683 3682 return hg.outgoing(ui, repo, dest, opts)
3684 3683 finally:
3685 3684 del repo._subtoppath
3686 3685
3687 3686 @command('parents',
3688 3687 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3689 3688 ] + templateopts,
3690 3689 _('[-r REV] [FILE]'),
3691 3690 inferrepo=True)
3692 3691 def parents(ui, repo, file_=None, **opts):
3693 3692 """show the parents of the working directory or revision (DEPRECATED)
3694 3693
3695 3694 Print the working directory's parent revisions. If a revision is
3696 3695 given via -r/--rev, the parent of that revision will be printed.
3697 3696 If a file argument is given, the revision in which the file was
3698 3697 last changed (before the working directory revision or the
3699 3698 argument to --rev if given) is printed.
3700 3699
3701 3700 This command is equivalent to::
3702 3701
3703 3702 hg log -r "p1()+p2()" or
3704 3703 hg log -r "p1(REV)+p2(REV)" or
3705 3704 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3706 3705 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3707 3706
3708 3707 See :hg:`summary` and :hg:`help revsets` for related information.
3709 3708
3710 3709 Returns 0 on success.
3711 3710 """
3712 3711
3713 3712 opts = pycompat.byteskwargs(opts)
3714 3713 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3715 3714
3716 3715 if file_:
3717 3716 m = scmutil.match(ctx, (file_,), opts)
3718 3717 if m.anypats() or len(m.files()) != 1:
3719 3718 raise error.Abort(_('can only specify an explicit filename'))
3720 3719 file_ = m.files()[0]
3721 3720 filenodes = []
3722 3721 for cp in ctx.parents():
3723 3722 if not cp:
3724 3723 continue
3725 3724 try:
3726 3725 filenodes.append(cp.filenode(file_))
3727 3726 except error.LookupError:
3728 3727 pass
3729 3728 if not filenodes:
3730 3729 raise error.Abort(_("'%s' not found in manifest!") % file_)
3731 3730 p = []
3732 3731 for fn in filenodes:
3733 3732 fctx = repo.filectx(file_, fileid=fn)
3734 3733 p.append(fctx.node())
3735 3734 else:
3736 3735 p = [cp.node() for cp in ctx.parents()]
3737 3736
3738 3737 displayer = cmdutil.show_changeset(ui, repo, opts)
3739 3738 for n in p:
3740 3739 if n != nullid:
3741 3740 displayer.show(repo[n])
3742 3741 displayer.close()
3743 3742
3744 3743 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3745 3744 def paths(ui, repo, search=None, **opts):
3746 3745 """show aliases for remote repositories
3747 3746
3748 3747 Show definition of symbolic path name NAME. If no name is given,
3749 3748 show definition of all available names.
3750 3749
3751 3750 Option -q/--quiet suppresses all output when searching for NAME
3752 3751 and shows only the path names when listing all definitions.
3753 3752
3754 3753 Path names are defined in the [paths] section of your
3755 3754 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3756 3755 repository, ``.hg/hgrc`` is used, too.
3757 3756
3758 3757 The path names ``default`` and ``default-push`` have a special
3759 3758 meaning. When performing a push or pull operation, they are used
3760 3759 as fallbacks if no location is specified on the command-line.
3761 3760 When ``default-push`` is set, it will be used for push and
3762 3761 ``default`` will be used for pull; otherwise ``default`` is used
3763 3762 as the fallback for both. When cloning a repository, the clone
3764 3763 source is written as ``default`` in ``.hg/hgrc``.
3765 3764
3766 3765 .. note::
3767 3766
3768 3767 ``default`` and ``default-push`` apply to all inbound (e.g.
3769 3768 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3770 3769 and :hg:`bundle`) operations.
3771 3770
3772 3771 See :hg:`help urls` for more information.
3773 3772
3774 3773 Returns 0 on success.
3775 3774 """
3776 3775
3777 3776 opts = pycompat.byteskwargs(opts)
3778 3777 ui.pager('paths')
3779 3778 if search:
3780 3779 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3781 3780 if name == search]
3782 3781 else:
3783 3782 pathitems = sorted(ui.paths.iteritems())
3784 3783
3785 3784 fm = ui.formatter('paths', opts)
3786 3785 if fm.isplain():
3787 3786 hidepassword = util.hidepassword
3788 3787 else:
3789 3788 hidepassword = str
3790 3789 if ui.quiet:
3791 3790 namefmt = '%s\n'
3792 3791 else:
3793 3792 namefmt = '%s = '
3794 3793 showsubopts = not search and not ui.quiet
3795 3794
3796 3795 for name, path in pathitems:
3797 3796 fm.startitem()
3798 3797 fm.condwrite(not search, 'name', namefmt, name)
3799 3798 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3800 3799 for subopt, value in sorted(path.suboptions.items()):
3801 3800 assert subopt not in ('name', 'url')
3802 3801 if showsubopts:
3803 3802 fm.plain('%s:%s = ' % (name, subopt))
3804 3803 fm.condwrite(showsubopts, subopt, '%s\n', value)
3805 3804
3806 3805 fm.end()
3807 3806
3808 3807 if search and not pathitems:
3809 3808 if not ui.quiet:
3810 3809 ui.warn(_("not found!\n"))
3811 3810 return 1
3812 3811 else:
3813 3812 return 0
3814 3813
3815 3814 @command('phase',
3816 3815 [('p', 'public', False, _('set changeset phase to public')),
3817 3816 ('d', 'draft', False, _('set changeset phase to draft')),
3818 3817 ('s', 'secret', False, _('set changeset phase to secret')),
3819 3818 ('f', 'force', False, _('allow to move boundary backward')),
3820 3819 ('r', 'rev', [], _('target revision'), _('REV')),
3821 3820 ],
3822 3821 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3823 3822 def phase(ui, repo, *revs, **opts):
3824 3823 """set or show the current phase name
3825 3824
3826 3825 With no argument, show the phase name of the current revision(s).
3827 3826
3828 3827 With one of -p/--public, -d/--draft or -s/--secret, change the
3829 3828 phase value of the specified revisions.
3830 3829
3831 3830 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3832 3831 lower phase to an higher phase. Phases are ordered as follows::
3833 3832
3834 3833 public < draft < secret
3835 3834
3836 3835 Returns 0 on success, 1 if some phases could not be changed.
3837 3836
3838 3837 (For more information about the phases concept, see :hg:`help phases`.)
3839 3838 """
3840 3839 opts = pycompat.byteskwargs(opts)
3841 3840 # search for a unique phase argument
3842 3841 targetphase = None
3843 3842 for idx, name in enumerate(phases.phasenames):
3844 3843 if opts[name]:
3845 3844 if targetphase is not None:
3846 3845 raise error.Abort(_('only one phase can be specified'))
3847 3846 targetphase = idx
3848 3847
3849 3848 # look for specified revision
3850 3849 revs = list(revs)
3851 3850 revs.extend(opts['rev'])
3852 3851 if not revs:
3853 3852 # display both parents as the second parent phase can influence
3854 3853 # the phase of a merge commit
3855 3854 revs = [c.rev() for c in repo[None].parents()]
3856 3855
3857 3856 revs = scmutil.revrange(repo, revs)
3858 3857
3859 3858 lock = None
3860 3859 ret = 0
3861 3860 if targetphase is None:
3862 3861 # display
3863 3862 for r in revs:
3864 3863 ctx = repo[r]
3865 3864 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3866 3865 else:
3867 3866 tr = None
3868 3867 lock = repo.lock()
3869 3868 try:
3870 3869 tr = repo.transaction("phase")
3871 3870 # set phase
3872 3871 if not revs:
3873 3872 raise error.Abort(_('empty revision set'))
3874 3873 nodes = [repo[r].node() for r in revs]
3875 3874 # moving revision from public to draft may hide them
3876 3875 # We have to check result on an unfiltered repository
3877 3876 unfi = repo.unfiltered()
3878 3877 getphase = unfi._phasecache.phase
3879 3878 olddata = [getphase(unfi, r) for r in unfi]
3880 3879 phases.advanceboundary(repo, tr, targetphase, nodes)
3881 3880 if opts['force']:
3882 3881 phases.retractboundary(repo, tr, targetphase, nodes)
3883 3882 tr.close()
3884 3883 finally:
3885 3884 if tr is not None:
3886 3885 tr.release()
3887 3886 lock.release()
3888 3887 getphase = unfi._phasecache.phase
3889 3888 newdata = [getphase(unfi, r) for r in unfi]
3890 3889 changes = sum(newdata[r] != olddata[r] for r in unfi)
3891 3890 cl = unfi.changelog
3892 3891 rejected = [n for n in nodes
3893 3892 if newdata[cl.rev(n)] < targetphase]
3894 3893 if rejected:
3895 3894 ui.warn(_('cannot move %i changesets to a higher '
3896 3895 'phase, use --force\n') % len(rejected))
3897 3896 ret = 1
3898 3897 if changes:
3899 3898 msg = _('phase changed for %i changesets\n') % changes
3900 3899 if ret:
3901 3900 ui.status(msg)
3902 3901 else:
3903 3902 ui.note(msg)
3904 3903 else:
3905 3904 ui.warn(_('no phases changed\n'))
3906 3905 return ret
3907 3906
3908 3907 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3909 3908 """Run after a changegroup has been added via pull/unbundle
3910 3909
3911 3910 This takes arguments below:
3912 3911
3913 3912 :modheads: change of heads by pull/unbundle
3914 3913 :optupdate: updating working directory is needed or not
3915 3914 :checkout: update destination revision (or None to default destination)
3916 3915 :brev: a name, which might be a bookmark to be activated after updating
3917 3916 """
3918 3917 if modheads == 0:
3919 3918 return
3920 3919 if optupdate:
3921 3920 try:
3922 3921 return hg.updatetotally(ui, repo, checkout, brev)
3923 3922 except error.UpdateAbort as inst:
3924 3923 msg = _("not updating: %s") % str(inst)
3925 3924 hint = inst.hint
3926 3925 raise error.UpdateAbort(msg, hint=hint)
3927 3926 if modheads > 1:
3928 3927 currentbranchheads = len(repo.branchheads())
3929 3928 if currentbranchheads == modheads:
3930 3929 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3931 3930 elif currentbranchheads > 1:
3932 3931 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3933 3932 "merge)\n"))
3934 3933 else:
3935 3934 ui.status(_("(run 'hg heads' to see heads)\n"))
3936 3935 else:
3937 3936 ui.status(_("(run 'hg update' to get a working copy)\n"))
3938 3937
3939 3938 @command('^pull',
3940 3939 [('u', 'update', None,
3941 3940 _('update to new branch head if changesets were pulled')),
3942 3941 ('f', 'force', None, _('run even when remote repository is unrelated')),
3943 3942 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3944 3943 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3945 3944 ('b', 'branch', [], _('a specific branch you would like to pull'),
3946 3945 _('BRANCH')),
3947 3946 ] + remoteopts,
3948 3947 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3949 3948 def pull(ui, repo, source="default", **opts):
3950 3949 """pull changes from the specified source
3951 3950
3952 3951 Pull changes from a remote repository to a local one.
3953 3952
3954 3953 This finds all changes from the repository at the specified path
3955 3954 or URL and adds them to a local repository (the current one unless
3956 3955 -R is specified). By default, this does not update the copy of the
3957 3956 project in the working directory.
3958 3957
3959 3958 Use :hg:`incoming` if you want to see what would have been added
3960 3959 by a pull at the time you issued this command. If you then decide
3961 3960 to add those changes to the repository, you should use :hg:`pull
3962 3961 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3963 3962
3964 3963 If SOURCE is omitted, the 'default' path will be used.
3965 3964 See :hg:`help urls` for more information.
3966 3965
3967 3966 Specifying bookmark as ``.`` is equivalent to specifying the active
3968 3967 bookmark's name.
3969 3968
3970 3969 Returns 0 on success, 1 if an update had unresolved files.
3971 3970 """
3972 3971
3973 3972 opts = pycompat.byteskwargs(opts)
3974 3973 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3975 3974 msg = _('update destination required by configuration')
3976 3975 hint = _('use hg pull followed by hg update DEST')
3977 3976 raise error.Abort(msg, hint=hint)
3978 3977
3979 3978 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3980 3979 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3981 3980 other = hg.peer(repo, opts, source)
3982 3981 try:
3983 3982 revs, checkout = hg.addbranchrevs(repo, other, branches,
3984 3983 opts.get('rev'))
3985 3984
3986 3985
3987 3986 pullopargs = {}
3988 3987 if opts.get('bookmark'):
3989 3988 if not revs:
3990 3989 revs = []
3991 3990 # The list of bookmark used here is not the one used to actually
3992 3991 # update the bookmark name. This can result in the revision pulled
3993 3992 # not ending up with the name of the bookmark because of a race
3994 3993 # condition on the server. (See issue 4689 for details)
3995 3994 remotebookmarks = other.listkeys('bookmarks')
3996 3995 pullopargs['remotebookmarks'] = remotebookmarks
3997 3996 for b in opts['bookmark']:
3998 3997 b = repo._bookmarks.expandname(b)
3999 3998 if b not in remotebookmarks:
4000 3999 raise error.Abort(_('remote bookmark %s not found!') % b)
4001 4000 revs.append(remotebookmarks[b])
4002 4001
4003 4002 if revs:
4004 4003 try:
4005 4004 # When 'rev' is a bookmark name, we cannot guarantee that it
4006 4005 # will be updated with that name because of a race condition
4007 4006 # server side. (See issue 4689 for details)
4008 4007 oldrevs = revs
4009 4008 revs = [] # actually, nodes
4010 4009 for r in oldrevs:
4011 4010 node = other.lookup(r)
4012 4011 revs.append(node)
4013 4012 if r == checkout:
4014 4013 checkout = node
4015 4014 except error.CapabilityError:
4016 4015 err = _("other repository doesn't support revision lookup, "
4017 4016 "so a rev cannot be specified.")
4018 4017 raise error.Abort(err)
4019 4018
4020 4019 pullopargs.update(opts.get('opargs', {}))
4021 4020 modheads = exchange.pull(repo, other, heads=revs,
4022 4021 force=opts.get('force'),
4023 4022 bookmarks=opts.get('bookmark', ()),
4024 4023 opargs=pullopargs).cgresult
4025 4024
4026 4025 # brev is a name, which might be a bookmark to be activated at
4027 4026 # the end of the update. In other words, it is an explicit
4028 4027 # destination of the update
4029 4028 brev = None
4030 4029
4031 4030 if checkout:
4032 4031 checkout = str(repo.changelog.rev(checkout))
4033 4032
4034 4033 # order below depends on implementation of
4035 4034 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4036 4035 # because 'checkout' is determined without it.
4037 4036 if opts.get('rev'):
4038 4037 brev = opts['rev'][0]
4039 4038 elif opts.get('branch'):
4040 4039 brev = opts['branch'][0]
4041 4040 else:
4042 4041 brev = branches[0]
4043 4042 repo._subtoppath = source
4044 4043 try:
4045 4044 ret = postincoming(ui, repo, modheads, opts.get('update'),
4046 4045 checkout, brev)
4047 4046
4048 4047 finally:
4049 4048 del repo._subtoppath
4050 4049
4051 4050 finally:
4052 4051 other.close()
4053 4052 return ret
4054 4053
4055 4054 @command('^push',
4056 4055 [('f', 'force', None, _('force push')),
4057 4056 ('r', 'rev', [],
4058 4057 _('a changeset intended to be included in the destination'),
4059 4058 _('REV')),
4060 4059 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4061 4060 ('b', 'branch', [],
4062 4061 _('a specific branch you would like to push'), _('BRANCH')),
4063 4062 ('', 'new-branch', False, _('allow pushing a new branch')),
4064 4063 ] + remoteopts,
4065 4064 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4066 4065 def push(ui, repo, dest=None, **opts):
4067 4066 """push changes to the specified destination
4068 4067
4069 4068 Push changesets from the local repository to the specified
4070 4069 destination.
4071 4070
4072 4071 This operation is symmetrical to pull: it is identical to a pull
4073 4072 in the destination repository from the current one.
4074 4073
4075 4074 By default, push will not allow creation of new heads at the
4076 4075 destination, since multiple heads would make it unclear which head
4077 4076 to use. In this situation, it is recommended to pull and merge
4078 4077 before pushing.
4079 4078
4080 4079 Use --new-branch if you want to allow push to create a new named
4081 4080 branch that is not present at the destination. This allows you to
4082 4081 only create a new branch without forcing other changes.
4083 4082
4084 4083 .. note::
4085 4084
4086 4085 Extra care should be taken with the -f/--force option,
4087 4086 which will push all new heads on all branches, an action which will
4088 4087 almost always cause confusion for collaborators.
4089 4088
4090 4089 If -r/--rev is used, the specified revision and all its ancestors
4091 4090 will be pushed to the remote repository.
4092 4091
4093 4092 If -B/--bookmark is used, the specified bookmarked revision, its
4094 4093 ancestors, and the bookmark will be pushed to the remote
4095 4094 repository. Specifying ``.`` is equivalent to specifying the active
4096 4095 bookmark's name.
4097 4096
4098 4097 Please see :hg:`help urls` for important details about ``ssh://``
4099 4098 URLs. If DESTINATION is omitted, a default path will be used.
4100 4099
4101 4100 Returns 0 if push was successful, 1 if nothing to push.
4102 4101 """
4103 4102
4104 4103 opts = pycompat.byteskwargs(opts)
4105 4104 if opts.get('bookmark'):
4106 4105 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4107 4106 for b in opts['bookmark']:
4108 4107 # translate -B options to -r so changesets get pushed
4109 4108 b = repo._bookmarks.expandname(b)
4110 4109 if b in repo._bookmarks:
4111 4110 opts.setdefault('rev', []).append(b)
4112 4111 else:
4113 4112 # if we try to push a deleted bookmark, translate it to null
4114 4113 # this lets simultaneous -r, -b options continue working
4115 4114 opts.setdefault('rev', []).append("null")
4116 4115
4117 4116 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4118 4117 if not path:
4119 4118 raise error.Abort(_('default repository not configured!'),
4120 4119 hint=_("see 'hg help config.paths'"))
4121 4120 dest = path.pushloc or path.loc
4122 4121 branches = (path.branch, opts.get('branch') or [])
4123 4122 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4124 4123 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4125 4124 other = hg.peer(repo, opts, dest)
4126 4125
4127 4126 if revs:
4128 4127 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4129 4128 if not revs:
4130 4129 raise error.Abort(_("specified revisions evaluate to an empty set"),
4131 4130 hint=_("use different revision arguments"))
4132 4131 elif path.pushrev:
4133 4132 # It doesn't make any sense to specify ancestor revisions. So limit
4134 4133 # to DAG heads to make discovery simpler.
4135 4134 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4136 4135 revs = scmutil.revrange(repo, [expr])
4137 4136 revs = [repo[rev].node() for rev in revs]
4138 4137 if not revs:
4139 4138 raise error.Abort(_('default push revset for path evaluates to an '
4140 4139 'empty set'))
4141 4140
4142 4141 repo._subtoppath = dest
4143 4142 try:
4144 4143 # push subrepos depth-first for coherent ordering
4145 4144 c = repo['']
4146 4145 subs = c.substate # only repos that are committed
4147 4146 for s in sorted(subs):
4148 4147 result = c.sub(s).push(opts)
4149 4148 if result == 0:
4150 4149 return not result
4151 4150 finally:
4152 4151 del repo._subtoppath
4153 4152 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4154 4153 newbranch=opts.get('new_branch'),
4155 4154 bookmarks=opts.get('bookmark', ()),
4156 4155 opargs=opts.get('opargs'))
4157 4156
4158 4157 result = not pushop.cgresult
4159 4158
4160 4159 if pushop.bkresult is not None:
4161 4160 if pushop.bkresult == 2:
4162 4161 result = 2
4163 4162 elif not result and pushop.bkresult:
4164 4163 result = 2
4165 4164
4166 4165 return result
4167 4166
4168 4167 @command('recover', [])
4169 4168 def recover(ui, repo):
4170 4169 """roll back an interrupted transaction
4171 4170
4172 4171 Recover from an interrupted commit or pull.
4173 4172
4174 4173 This command tries to fix the repository status after an
4175 4174 interrupted operation. It should only be necessary when Mercurial
4176 4175 suggests it.
4177 4176
4178 4177 Returns 0 if successful, 1 if nothing to recover or verify fails.
4179 4178 """
4180 4179 if repo.recover():
4181 4180 return hg.verify(repo)
4182 4181 return 1
4183 4182
4184 4183 @command('^remove|rm',
4185 4184 [('A', 'after', None, _('record delete for missing files')),
4186 4185 ('f', 'force', None,
4187 4186 _('forget added files, delete modified files')),
4188 4187 ] + subrepoopts + walkopts,
4189 4188 _('[OPTION]... FILE...'),
4190 4189 inferrepo=True)
4191 4190 def remove(ui, repo, *pats, **opts):
4192 4191 """remove the specified files on the next commit
4193 4192
4194 4193 Schedule the indicated files for removal from the current branch.
4195 4194
4196 4195 This command schedules the files to be removed at the next commit.
4197 4196 To undo a remove before that, see :hg:`revert`. To undo added
4198 4197 files, see :hg:`forget`.
4199 4198
4200 4199 .. container:: verbose
4201 4200
4202 4201 -A/--after can be used to remove only files that have already
4203 4202 been deleted, -f/--force can be used to force deletion, and -Af
4204 4203 can be used to remove files from the next revision without
4205 4204 deleting them from the working directory.
4206 4205
4207 4206 The following table details the behavior of remove for different
4208 4207 file states (columns) and option combinations (rows). The file
4209 4208 states are Added [A], Clean [C], Modified [M] and Missing [!]
4210 4209 (as reported by :hg:`status`). The actions are Warn, Remove
4211 4210 (from branch) and Delete (from disk):
4212 4211
4213 4212 ========= == == == ==
4214 4213 opt/state A C M !
4215 4214 ========= == == == ==
4216 4215 none W RD W R
4217 4216 -f R RD RD R
4218 4217 -A W W W R
4219 4218 -Af R R R R
4220 4219 ========= == == == ==
4221 4220
4222 4221 .. note::
4223 4222
4224 4223 :hg:`remove` never deletes files in Added [A] state from the
4225 4224 working directory, not even if ``--force`` is specified.
4226 4225
4227 4226 Returns 0 on success, 1 if any warnings encountered.
4228 4227 """
4229 4228
4230 4229 opts = pycompat.byteskwargs(opts)
4231 4230 after, force = opts.get('after'), opts.get('force')
4232 4231 if not pats and not after:
4233 4232 raise error.Abort(_('no files specified'))
4234 4233
4235 4234 m = scmutil.match(repo[None], pats, opts)
4236 4235 subrepos = opts.get('subrepos')
4237 4236 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4238 4237
4239 4238 @command('rename|move|mv',
4240 4239 [('A', 'after', None, _('record a rename that has already occurred')),
4241 4240 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4242 4241 ] + walkopts + dryrunopts,
4243 4242 _('[OPTION]... SOURCE... DEST'))
4244 4243 def rename(ui, repo, *pats, **opts):
4245 4244 """rename files; equivalent of copy + remove
4246 4245
4247 4246 Mark dest as copies of sources; mark sources for deletion. If dest
4248 4247 is a directory, copies are put in that directory. If dest is a
4249 4248 file, there can only be one source.
4250 4249
4251 4250 By default, this command copies the contents of files as they
4252 4251 exist in the working directory. If invoked with -A/--after, the
4253 4252 operation is recorded, but no copying is performed.
4254 4253
4255 4254 This command takes effect at the next commit. To undo a rename
4256 4255 before that, see :hg:`revert`.
4257 4256
4258 4257 Returns 0 on success, 1 if errors are encountered.
4259 4258 """
4260 4259 opts = pycompat.byteskwargs(opts)
4261 4260 with repo.wlock(False):
4262 4261 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4263 4262
4264 4263 @command('resolve',
4265 4264 [('a', 'all', None, _('select all unresolved files')),
4266 4265 ('l', 'list', None, _('list state of files needing merge')),
4267 4266 ('m', 'mark', None, _('mark files as resolved')),
4268 4267 ('u', 'unmark', None, _('mark files as unresolved')),
4269 4268 ('n', 'no-status', None, _('hide status prefix'))]
4270 4269 + mergetoolopts + walkopts + formatteropts,
4271 4270 _('[OPTION]... [FILE]...'),
4272 4271 inferrepo=True)
4273 4272 def resolve(ui, repo, *pats, **opts):
4274 4273 """redo merges or set/view the merge status of files
4275 4274
4276 4275 Merges with unresolved conflicts are often the result of
4277 4276 non-interactive merging using the ``internal:merge`` configuration
4278 4277 setting, or a command-line merge tool like ``diff3``. The resolve
4279 4278 command is used to manage the files involved in a merge, after
4280 4279 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4281 4280 working directory must have two parents). See :hg:`help
4282 4281 merge-tools` for information on configuring merge tools.
4283 4282
4284 4283 The resolve command can be used in the following ways:
4285 4284
4286 4285 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4287 4286 files, discarding any previous merge attempts. Re-merging is not
4288 4287 performed for files already marked as resolved. Use ``--all/-a``
4289 4288 to select all unresolved files. ``--tool`` can be used to specify
4290 4289 the merge tool used for the given files. It overrides the HGMERGE
4291 4290 environment variable and your configuration files. Previous file
4292 4291 contents are saved with a ``.orig`` suffix.
4293 4292
4294 4293 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4295 4294 (e.g. after having manually fixed-up the files). The default is
4296 4295 to mark all unresolved files.
4297 4296
4298 4297 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4299 4298 default is to mark all resolved files.
4300 4299
4301 4300 - :hg:`resolve -l`: list files which had or still have conflicts.
4302 4301 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4303 4302 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4304 4303 the list. See :hg:`help filesets` for details.
4305 4304
4306 4305 .. note::
4307 4306
4308 4307 Mercurial will not let you commit files with unresolved merge
4309 4308 conflicts. You must use :hg:`resolve -m ...` before you can
4310 4309 commit after a conflicting merge.
4311 4310
4312 4311 Returns 0 on success, 1 if any files fail a resolve attempt.
4313 4312 """
4314 4313
4315 4314 opts = pycompat.byteskwargs(opts)
4316 4315 flaglist = 'all mark unmark list no_status'.split()
4317 4316 all, mark, unmark, show, nostatus = \
4318 4317 [opts.get(o) for o in flaglist]
4319 4318
4320 4319 if (show and (mark or unmark)) or (mark and unmark):
4321 4320 raise error.Abort(_("too many options specified"))
4322 4321 if pats and all:
4323 4322 raise error.Abort(_("can't specify --all and patterns"))
4324 4323 if not (all or pats or show or mark or unmark):
4325 4324 raise error.Abort(_('no files or directories specified'),
4326 4325 hint=('use --all to re-merge all unresolved files'))
4327 4326
4328 4327 if show:
4329 4328 ui.pager('resolve')
4330 4329 fm = ui.formatter('resolve', opts)
4331 4330 ms = mergemod.mergestate.read(repo)
4332 4331 m = scmutil.match(repo[None], pats, opts)
4333 4332 for f in ms:
4334 4333 if not m(f):
4335 4334 continue
4336 4335 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4337 4336 'd': 'driverresolved'}[ms[f]]
4338 4337 fm.startitem()
4339 4338 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4340 4339 fm.write('path', '%s\n', f, label=l)
4341 4340 fm.end()
4342 4341 return 0
4343 4342
4344 4343 with repo.wlock():
4345 4344 ms = mergemod.mergestate.read(repo)
4346 4345
4347 4346 if not (ms.active() or repo.dirstate.p2() != nullid):
4348 4347 raise error.Abort(
4349 4348 _('resolve command not applicable when not merging'))
4350 4349
4351 4350 wctx = repo[None]
4352 4351
4353 4352 if ms.mergedriver and ms.mdstate() == 'u':
4354 4353 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4355 4354 ms.commit()
4356 4355 # allow mark and unmark to go through
4357 4356 if not mark and not unmark and not proceed:
4358 4357 return 1
4359 4358
4360 4359 m = scmutil.match(wctx, pats, opts)
4361 4360 ret = 0
4362 4361 didwork = False
4363 4362 runconclude = False
4364 4363
4365 4364 tocomplete = []
4366 4365 for f in ms:
4367 4366 if not m(f):
4368 4367 continue
4369 4368
4370 4369 didwork = True
4371 4370
4372 4371 # don't let driver-resolved files be marked, and run the conclude
4373 4372 # step if asked to resolve
4374 4373 if ms[f] == "d":
4375 4374 exact = m.exact(f)
4376 4375 if mark:
4377 4376 if exact:
4378 4377 ui.warn(_('not marking %s as it is driver-resolved\n')
4379 4378 % f)
4380 4379 elif unmark:
4381 4380 if exact:
4382 4381 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4383 4382 % f)
4384 4383 else:
4385 4384 runconclude = True
4386 4385 continue
4387 4386
4388 4387 if mark:
4389 4388 ms.mark(f, "r")
4390 4389 elif unmark:
4391 4390 ms.mark(f, "u")
4392 4391 else:
4393 4392 # backup pre-resolve (merge uses .orig for its own purposes)
4394 4393 a = repo.wjoin(f)
4395 4394 try:
4396 4395 util.copyfile(a, a + ".resolve")
4397 4396 except (IOError, OSError) as inst:
4398 4397 if inst.errno != errno.ENOENT:
4399 4398 raise
4400 4399
4401 4400 try:
4402 4401 # preresolve file
4403 4402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4404 4403 'resolve')
4405 4404 complete, r = ms.preresolve(f, wctx)
4406 4405 if not complete:
4407 4406 tocomplete.append(f)
4408 4407 elif r:
4409 4408 ret = 1
4410 4409 finally:
4411 4410 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4412 4411 ms.commit()
4413 4412
4414 4413 # replace filemerge's .orig file with our resolve file, but only
4415 4414 # for merges that are complete
4416 4415 if complete:
4417 4416 try:
4418 4417 util.rename(a + ".resolve",
4419 4418 scmutil.origpath(ui, repo, a))
4420 4419 except OSError as inst:
4421 4420 if inst.errno != errno.ENOENT:
4422 4421 raise
4423 4422
4424 4423 for f in tocomplete:
4425 4424 try:
4426 4425 # resolve file
4427 4426 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4428 4427 'resolve')
4429 4428 r = ms.resolve(f, wctx)
4430 4429 if r:
4431 4430 ret = 1
4432 4431 finally:
4433 4432 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4434 4433 ms.commit()
4435 4434
4436 4435 # replace filemerge's .orig file with our resolve file
4437 4436 a = repo.wjoin(f)
4438 4437 try:
4439 4438 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4440 4439 except OSError as inst:
4441 4440 if inst.errno != errno.ENOENT:
4442 4441 raise
4443 4442
4444 4443 ms.commit()
4445 4444 ms.recordactions()
4446 4445
4447 4446 if not didwork and pats:
4448 4447 hint = None
4449 4448 if not any([p for p in pats if p.find(':') >= 0]):
4450 4449 pats = ['path:%s' % p for p in pats]
4451 4450 m = scmutil.match(wctx, pats, opts)
4452 4451 for f in ms:
4453 4452 if not m(f):
4454 4453 continue
4455 4454 flags = ''.join(['-%s ' % o[0] for o in flaglist
4456 4455 if opts.get(o)])
4457 4456 hint = _("(try: hg resolve %s%s)\n") % (
4458 4457 flags,
4459 4458 ' '.join(pats))
4460 4459 break
4461 4460 ui.warn(_("arguments do not match paths that need resolving\n"))
4462 4461 if hint:
4463 4462 ui.warn(hint)
4464 4463 elif ms.mergedriver and ms.mdstate() != 's':
4465 4464 # run conclude step when either a driver-resolved file is requested
4466 4465 # or there are no driver-resolved files
4467 4466 # we can't use 'ret' to determine whether any files are unresolved
4468 4467 # because we might not have tried to resolve some
4469 4468 if ((runconclude or not list(ms.driverresolved()))
4470 4469 and not list(ms.unresolved())):
4471 4470 proceed = mergemod.driverconclude(repo, ms, wctx)
4472 4471 ms.commit()
4473 4472 if not proceed:
4474 4473 return 1
4475 4474
4476 4475 # Nudge users into finishing an unfinished operation
4477 4476 unresolvedf = list(ms.unresolved())
4478 4477 driverresolvedf = list(ms.driverresolved())
4479 4478 if not unresolvedf and not driverresolvedf:
4480 4479 ui.status(_('(no more unresolved files)\n'))
4481 4480 cmdutil.checkafterresolved(repo)
4482 4481 elif not unresolvedf:
4483 4482 ui.status(_('(no more unresolved files -- '
4484 4483 'run "hg resolve --all" to conclude)\n'))
4485 4484
4486 4485 return ret
4487 4486
4488 4487 @command('revert',
4489 4488 [('a', 'all', None, _('revert all changes when no arguments given')),
4490 4489 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4491 4490 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4492 4491 ('C', 'no-backup', None, _('do not save backup copies of files')),
4493 4492 ('i', 'interactive', None,
4494 4493 _('interactively select the changes (EXPERIMENTAL)')),
4495 4494 ] + walkopts + dryrunopts,
4496 4495 _('[OPTION]... [-r REV] [NAME]...'))
4497 4496 def revert(ui, repo, *pats, **opts):
4498 4497 """restore files to their checkout state
4499 4498
4500 4499 .. note::
4501 4500
4502 4501 To check out earlier revisions, you should use :hg:`update REV`.
4503 4502 To cancel an uncommitted merge (and lose your changes),
4504 4503 use :hg:`update --clean .`.
4505 4504
4506 4505 With no revision specified, revert the specified files or directories
4507 4506 to the contents they had in the parent of the working directory.
4508 4507 This restores the contents of files to an unmodified
4509 4508 state and unschedules adds, removes, copies, and renames. If the
4510 4509 working directory has two parents, you must explicitly specify a
4511 4510 revision.
4512 4511
4513 4512 Using the -r/--rev or -d/--date options, revert the given files or
4514 4513 directories to their states as of a specific revision. Because
4515 4514 revert does not change the working directory parents, this will
4516 4515 cause these files to appear modified. This can be helpful to "back
4517 4516 out" some or all of an earlier change. See :hg:`backout` for a
4518 4517 related method.
4519 4518
4520 4519 Modified files are saved with a .orig suffix before reverting.
4521 4520 To disable these backups, use --no-backup. It is possible to store
4522 4521 the backup files in a custom directory relative to the root of the
4523 4522 repository by setting the ``ui.origbackuppath`` configuration
4524 4523 option.
4525 4524
4526 4525 See :hg:`help dates` for a list of formats valid for -d/--date.
4527 4526
4528 4527 See :hg:`help backout` for a way to reverse the effect of an
4529 4528 earlier changeset.
4530 4529
4531 4530 Returns 0 on success.
4532 4531 """
4533 4532
4534 4533 if opts.get("date"):
4535 4534 if opts.get("rev"):
4536 4535 raise error.Abort(_("you can't specify a revision and a date"))
4537 4536 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4538 4537
4539 4538 parent, p2 = repo.dirstate.parents()
4540 4539 if not opts.get('rev') and p2 != nullid:
4541 4540 # revert after merge is a trap for new users (issue2915)
4542 4541 raise error.Abort(_('uncommitted merge with no revision specified'),
4543 4542 hint=_("use 'hg update' or see 'hg help revert'"))
4544 4543
4545 4544 ctx = scmutil.revsingle(repo, opts.get('rev'))
4546 4545
4547 4546 if (not (pats or opts.get('include') or opts.get('exclude') or
4548 4547 opts.get('all') or opts.get('interactive'))):
4549 4548 msg = _("no files or directories specified")
4550 4549 if p2 != nullid:
4551 4550 hint = _("uncommitted merge, use --all to discard all changes,"
4552 4551 " or 'hg update -C .' to abort the merge")
4553 4552 raise error.Abort(msg, hint=hint)
4554 4553 dirty = any(repo.status())
4555 4554 node = ctx.node()
4556 4555 if node != parent:
4557 4556 if dirty:
4558 4557 hint = _("uncommitted changes, use --all to discard all"
4559 4558 " changes, or 'hg update %s' to update") % ctx.rev()
4560 4559 else:
4561 4560 hint = _("use --all to revert all files,"
4562 4561 " or 'hg update %s' to update") % ctx.rev()
4563 4562 elif dirty:
4564 4563 hint = _("uncommitted changes, use --all to discard all changes")
4565 4564 else:
4566 4565 hint = _("use --all to revert all files")
4567 4566 raise error.Abort(msg, hint=hint)
4568 4567
4569 4568 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4570 4569
4571 4570 @command('rollback', dryrunopts +
4572 4571 [('f', 'force', False, _('ignore safety measures'))])
4573 4572 def rollback(ui, repo, **opts):
4574 4573 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4575 4574
4576 4575 Please use :hg:`commit --amend` instead of rollback to correct
4577 4576 mistakes in the last commit.
4578 4577
4579 4578 This command should be used with care. There is only one level of
4580 4579 rollback, and there is no way to undo a rollback. It will also
4581 4580 restore the dirstate at the time of the last transaction, losing
4582 4581 any dirstate changes since that time. This command does not alter
4583 4582 the working directory.
4584 4583
4585 4584 Transactions are used to encapsulate the effects of all commands
4586 4585 that create new changesets or propagate existing changesets into a
4587 4586 repository.
4588 4587
4589 4588 .. container:: verbose
4590 4589
4591 4590 For example, the following commands are transactional, and their
4592 4591 effects can be rolled back:
4593 4592
4594 4593 - commit
4595 4594 - import
4596 4595 - pull
4597 4596 - push (with this repository as the destination)
4598 4597 - unbundle
4599 4598
4600 4599 To avoid permanent data loss, rollback will refuse to rollback a
4601 4600 commit transaction if it isn't checked out. Use --force to
4602 4601 override this protection.
4603 4602
4604 4603 The rollback command can be entirely disabled by setting the
4605 4604 ``ui.rollback`` configuration setting to false. If you're here
4606 4605 because you want to use rollback and it's disabled, you can
4607 4606 re-enable the command by setting ``ui.rollback`` to true.
4608 4607
4609 4608 This command is not intended for use on public repositories. Once
4610 4609 changes are visible for pull by other users, rolling a transaction
4611 4610 back locally is ineffective (someone else may already have pulled
4612 4611 the changes). Furthermore, a race is possible with readers of the
4613 4612 repository; for example an in-progress pull from the repository
4614 4613 may fail if a rollback is performed.
4615 4614
4616 4615 Returns 0 on success, 1 if no rollback data is available.
4617 4616 """
4618 4617 if not ui.configbool('ui', 'rollback', True):
4619 4618 raise error.Abort(_('rollback is disabled because it is unsafe'),
4620 4619 hint=('see `hg help -v rollback` for information'))
4621 4620 return repo.rollback(dryrun=opts.get(r'dry_run'),
4622 4621 force=opts.get(r'force'))
4623 4622
4624 4623 @command('root', [])
4625 4624 def root(ui, repo):
4626 4625 """print the root (top) of the current working directory
4627 4626
4628 4627 Print the root directory of the current repository.
4629 4628
4630 4629 Returns 0 on success.
4631 4630 """
4632 4631 ui.write(repo.root + "\n")
4633 4632
4634 4633 @command('^serve',
4635 4634 [('A', 'accesslog', '', _('name of access log file to write to'),
4636 4635 _('FILE')),
4637 4636 ('d', 'daemon', None, _('run server in background')),
4638 4637 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4639 4638 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4640 4639 # use string type, then we can check if something was passed
4641 4640 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4642 4641 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4643 4642 _('ADDR')),
4644 4643 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4645 4644 _('PREFIX')),
4646 4645 ('n', 'name', '',
4647 4646 _('name to show in web pages (default: working directory)'), _('NAME')),
4648 4647 ('', 'web-conf', '',
4649 4648 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4650 4649 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4651 4650 _('FILE')),
4652 4651 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4653 4652 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4654 4653 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4655 4654 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4656 4655 ('', 'style', '', _('template style to use'), _('STYLE')),
4657 4656 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4658 4657 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4659 4658 + subrepoopts,
4660 4659 _('[OPTION]...'),
4661 4660 optionalrepo=True)
4662 4661 def serve(ui, repo, **opts):
4663 4662 """start stand-alone webserver
4664 4663
4665 4664 Start a local HTTP repository browser and pull server. You can use
4666 4665 this for ad-hoc sharing and browsing of repositories. It is
4667 4666 recommended to use a real web server to serve a repository for
4668 4667 longer periods of time.
4669 4668
4670 4669 Please note that the server does not implement access control.
4671 4670 This means that, by default, anybody can read from the server and
4672 4671 nobody can write to it by default. Set the ``web.allow_push``
4673 4672 option to ``*`` to allow everybody to push to the server. You
4674 4673 should use a real web server if you need to authenticate users.
4675 4674
4676 4675 By default, the server logs accesses to stdout and errors to
4677 4676 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4678 4677 files.
4679 4678
4680 4679 To have the server choose a free port number to listen on, specify
4681 4680 a port number of 0; in this case, the server will print the port
4682 4681 number it uses.
4683 4682
4684 4683 Returns 0 on success.
4685 4684 """
4686 4685
4687 4686 opts = pycompat.byteskwargs(opts)
4688 4687 if opts["stdio"] and opts["cmdserver"]:
4689 4688 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4690 4689
4691 4690 if opts["stdio"]:
4692 4691 if repo is None:
4693 4692 raise error.RepoError(_("there is no Mercurial repository here"
4694 4693 " (.hg not found)"))
4695 4694 s = sshserver.sshserver(ui, repo)
4696 4695 s.serve_forever()
4697 4696
4698 4697 service = server.createservice(ui, repo, opts)
4699 4698 return server.runservice(opts, initfn=service.init, runfn=service.run)
4700 4699
4701 4700 @command('^status|st',
4702 4701 [('A', 'all', None, _('show status of all files')),
4703 4702 ('m', 'modified', None, _('show only modified files')),
4704 4703 ('a', 'added', None, _('show only added files')),
4705 4704 ('r', 'removed', None, _('show only removed files')),
4706 4705 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4707 4706 ('c', 'clean', None, _('show only files without changes')),
4708 4707 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4709 4708 ('i', 'ignored', None, _('show only ignored files')),
4710 4709 ('n', 'no-status', None, _('hide status prefix')),
4711 4710 ('C', 'copies', None, _('show source of copied files')),
4712 4711 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4713 4712 ('', 'rev', [], _('show difference from revision'), _('REV')),
4714 4713 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4715 4714 ] + walkopts + subrepoopts + formatteropts,
4716 4715 _('[OPTION]... [FILE]...'),
4717 4716 inferrepo=True)
4718 4717 def status(ui, repo, *pats, **opts):
4719 4718 """show changed files in the working directory
4720 4719
4721 4720 Show status of files in the repository. If names are given, only
4722 4721 files that match are shown. Files that are clean or ignored or
4723 4722 the source of a copy/move operation, are not listed unless
4724 4723 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4725 4724 Unless options described with "show only ..." are given, the
4726 4725 options -mardu are used.
4727 4726
4728 4727 Option -q/--quiet hides untracked (unknown and ignored) files
4729 4728 unless explicitly requested with -u/--unknown or -i/--ignored.
4730 4729
4731 4730 .. note::
4732 4731
4733 4732 :hg:`status` may appear to disagree with diff if permissions have
4734 4733 changed or a merge has occurred. The standard diff format does
4735 4734 not report permission changes and diff only reports changes
4736 4735 relative to one merge parent.
4737 4736
4738 4737 If one revision is given, it is used as the base revision.
4739 4738 If two revisions are given, the differences between them are
4740 4739 shown. The --change option can also be used as a shortcut to list
4741 4740 the changed files of a revision from its first parent.
4742 4741
4743 4742 The codes used to show the status of files are::
4744 4743
4745 4744 M = modified
4746 4745 A = added
4747 4746 R = removed
4748 4747 C = clean
4749 4748 ! = missing (deleted by non-hg command, but still tracked)
4750 4749 ? = not tracked
4751 4750 I = ignored
4752 4751 = origin of the previous file (with --copies)
4753 4752
4754 4753 .. container:: verbose
4755 4754
4756 4755 Examples:
4757 4756
4758 4757 - show changes in the working directory relative to a
4759 4758 changeset::
4760 4759
4761 4760 hg status --rev 9353
4762 4761
4763 4762 - show changes in the working directory relative to the
4764 4763 current directory (see :hg:`help patterns` for more information)::
4765 4764
4766 4765 hg status re:
4767 4766
4768 4767 - show all changes including copies in an existing changeset::
4769 4768
4770 4769 hg status --copies --change 9353
4771 4770
4772 4771 - get a NUL separated list of added files, suitable for xargs::
4773 4772
4774 4773 hg status -an0
4775 4774
4776 4775 Returns 0 on success.
4777 4776 """
4778 4777
4779 4778 opts = pycompat.byteskwargs(opts)
4780 4779 revs = opts.get('rev')
4781 4780 change = opts.get('change')
4782 4781
4783 4782 if revs and change:
4784 4783 msg = _('cannot specify --rev and --change at the same time')
4785 4784 raise error.Abort(msg)
4786 4785 elif change:
4787 4786 node2 = scmutil.revsingle(repo, change, None).node()
4788 4787 node1 = repo[node2].p1().node()
4789 4788 else:
4790 4789 node1, node2 = scmutil.revpair(repo, revs)
4791 4790
4792 4791 if pats or ui.configbool('commands', 'status.relative'):
4793 4792 cwd = repo.getcwd()
4794 4793 else:
4795 4794 cwd = ''
4796 4795
4797 4796 if opts.get('print0'):
4798 4797 end = '\0'
4799 4798 else:
4800 4799 end = '\n'
4801 4800 copy = {}
4802 4801 states = 'modified added removed deleted unknown ignored clean'.split()
4803 4802 show = [k for k in states if opts.get(k)]
4804 4803 if opts.get('all'):
4805 4804 show += ui.quiet and (states[:4] + ['clean']) or states
4806 4805 if not show:
4807 4806 if ui.quiet:
4808 4807 show = states[:4]
4809 4808 else:
4810 4809 show = states[:5]
4811 4810
4812 4811 m = scmutil.match(repo[node2], pats, opts)
4813 4812 stat = repo.status(node1, node2, m,
4814 4813 'ignored' in show, 'clean' in show, 'unknown' in show,
4815 4814 opts.get('subrepos'))
4816 4815 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4817 4816
4818 4817 if (opts.get('all') or opts.get('copies')
4819 4818 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4820 4819 copy = copies.pathcopies(repo[node1], repo[node2], m)
4821 4820
4822 4821 ui.pager('status')
4823 4822 fm = ui.formatter('status', opts)
4824 4823 fmt = '%s' + end
4825 4824 showchar = not opts.get('no_status')
4826 4825
4827 4826 for state, char, files in changestates:
4828 4827 if state in show:
4829 4828 label = 'status.' + state
4830 4829 for f in files:
4831 4830 fm.startitem()
4832 4831 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4833 4832 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4834 4833 if f in copy:
4835 4834 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4836 4835 label='status.copied')
4837 4836 fm.end()
4838 4837
4839 4838 @command('^summary|sum',
4840 4839 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4841 4840 def summary(ui, repo, **opts):
4842 4841 """summarize working directory state
4843 4842
4844 4843 This generates a brief summary of the working directory state,
4845 4844 including parents, branch, commit status, phase and available updates.
4846 4845
4847 4846 With the --remote option, this will check the default paths for
4848 4847 incoming and outgoing changes. This can be time-consuming.
4849 4848
4850 4849 Returns 0 on success.
4851 4850 """
4852 4851
4853 4852 opts = pycompat.byteskwargs(opts)
4854 4853 ui.pager('summary')
4855 4854 ctx = repo[None]
4856 4855 parents = ctx.parents()
4857 4856 pnode = parents[0].node()
4858 4857 marks = []
4859 4858
4860 4859 ms = None
4861 4860 try:
4862 4861 ms = mergemod.mergestate.read(repo)
4863 4862 except error.UnsupportedMergeRecords as e:
4864 4863 s = ' '.join(e.recordtypes)
4865 4864 ui.warn(
4866 4865 _('warning: merge state has unsupported record types: %s\n') % s)
4867 4866 unresolved = 0
4868 4867 else:
4869 4868 unresolved = [f for f in ms if ms[f] == 'u']
4870 4869
4871 4870 for p in parents:
4872 4871 # label with log.changeset (instead of log.parent) since this
4873 4872 # shows a working directory parent *changeset*:
4874 4873 # i18n: column positioning for "hg summary"
4875 4874 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4876 4875 label=cmdutil._changesetlabels(p))
4877 4876 ui.write(' '.join(p.tags()), label='log.tag')
4878 4877 if p.bookmarks():
4879 4878 marks.extend(p.bookmarks())
4880 4879 if p.rev() == -1:
4881 4880 if not len(repo):
4882 4881 ui.write(_(' (empty repository)'))
4883 4882 else:
4884 4883 ui.write(_(' (no revision checked out)'))
4885 4884 if p.obsolete():
4886 4885 ui.write(_(' (obsolete)'))
4887 4886 if p.troubled():
4888 4887 ui.write(' ('
4889 4888 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4890 4889 for trouble in p.troubles())
4891 4890 + ')')
4892 4891 ui.write('\n')
4893 4892 if p.description():
4894 4893 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4895 4894 label='log.summary')
4896 4895
4897 4896 branch = ctx.branch()
4898 4897 bheads = repo.branchheads(branch)
4899 4898 # i18n: column positioning for "hg summary"
4900 4899 m = _('branch: %s\n') % branch
4901 4900 if branch != 'default':
4902 4901 ui.write(m, label='log.branch')
4903 4902 else:
4904 4903 ui.status(m, label='log.branch')
4905 4904
4906 4905 if marks:
4907 4906 active = repo._activebookmark
4908 4907 # i18n: column positioning for "hg summary"
4909 4908 ui.write(_('bookmarks:'), label='log.bookmark')
4910 4909 if active is not None:
4911 4910 if active in marks:
4912 4911 ui.write(' *' + active, label=activebookmarklabel)
4913 4912 marks.remove(active)
4914 4913 else:
4915 4914 ui.write(' [%s]' % active, label=activebookmarklabel)
4916 4915 for m in marks:
4917 4916 ui.write(' ' + m, label='log.bookmark')
4918 4917 ui.write('\n', label='log.bookmark')
4919 4918
4920 4919 status = repo.status(unknown=True)
4921 4920
4922 4921 c = repo.dirstate.copies()
4923 4922 copied, renamed = [], []
4924 4923 for d, s in c.iteritems():
4925 4924 if s in status.removed:
4926 4925 status.removed.remove(s)
4927 4926 renamed.append(d)
4928 4927 else:
4929 4928 copied.append(d)
4930 4929 if d in status.added:
4931 4930 status.added.remove(d)
4932 4931
4933 4932 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4934 4933
4935 4934 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4936 4935 (ui.label(_('%d added'), 'status.added'), status.added),
4937 4936 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4938 4937 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4939 4938 (ui.label(_('%d copied'), 'status.copied'), copied),
4940 4939 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4941 4940 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4942 4941 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4943 4942 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4944 4943 t = []
4945 4944 for l, s in labels:
4946 4945 if s:
4947 4946 t.append(l % len(s))
4948 4947
4949 4948 t = ', '.join(t)
4950 4949 cleanworkdir = False
4951 4950
4952 4951 if repo.vfs.exists('graftstate'):
4953 4952 t += _(' (graft in progress)')
4954 4953 if repo.vfs.exists('updatestate'):
4955 4954 t += _(' (interrupted update)')
4956 4955 elif len(parents) > 1:
4957 4956 t += _(' (merge)')
4958 4957 elif branch != parents[0].branch():
4959 4958 t += _(' (new branch)')
4960 4959 elif (parents[0].closesbranch() and
4961 4960 pnode in repo.branchheads(branch, closed=True)):
4962 4961 t += _(' (head closed)')
4963 4962 elif not (status.modified or status.added or status.removed or renamed or
4964 4963 copied or subs):
4965 4964 t += _(' (clean)')
4966 4965 cleanworkdir = True
4967 4966 elif pnode not in bheads:
4968 4967 t += _(' (new branch head)')
4969 4968
4970 4969 if parents:
4971 4970 pendingphase = max(p.phase() for p in parents)
4972 4971 else:
4973 4972 pendingphase = phases.public
4974 4973
4975 4974 if pendingphase > phases.newcommitphase(ui):
4976 4975 t += ' (%s)' % phases.phasenames[pendingphase]
4977 4976
4978 4977 if cleanworkdir:
4979 4978 # i18n: column positioning for "hg summary"
4980 4979 ui.status(_('commit: %s\n') % t.strip())
4981 4980 else:
4982 4981 # i18n: column positioning for "hg summary"
4983 4982 ui.write(_('commit: %s\n') % t.strip())
4984 4983
4985 4984 # all ancestors of branch heads - all ancestors of parent = new csets
4986 4985 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4987 4986 bheads))
4988 4987
4989 4988 if new == 0:
4990 4989 # i18n: column positioning for "hg summary"
4991 4990 ui.status(_('update: (current)\n'))
4992 4991 elif pnode not in bheads:
4993 4992 # i18n: column positioning for "hg summary"
4994 4993 ui.write(_('update: %d new changesets (update)\n') % new)
4995 4994 else:
4996 4995 # i18n: column positioning for "hg summary"
4997 4996 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4998 4997 (new, len(bheads)))
4999 4998
5000 4999 t = []
5001 5000 draft = len(repo.revs('draft()'))
5002 5001 if draft:
5003 5002 t.append(_('%d draft') % draft)
5004 5003 secret = len(repo.revs('secret()'))
5005 5004 if secret:
5006 5005 t.append(_('%d secret') % secret)
5007 5006
5008 5007 if draft or secret:
5009 5008 ui.status(_('phases: %s\n') % ', '.join(t))
5010 5009
5011 5010 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5012 5011 for trouble in ("unstable", "divergent", "bumped"):
5013 5012 numtrouble = len(repo.revs(trouble + "()"))
5014 5013 # We write all the possibilities to ease translation
5015 5014 troublemsg = {
5016 5015 "unstable": _("unstable: %d changesets"),
5017 5016 "divergent": _("divergent: %d changesets"),
5018 5017 "bumped": _("bumped: %d changesets"),
5019 5018 }
5020 5019 if numtrouble > 0:
5021 5020 ui.status(troublemsg[trouble] % numtrouble + "\n")
5022 5021
5023 5022 cmdutil.summaryhooks(ui, repo)
5024 5023
5025 5024 if opts.get('remote'):
5026 5025 needsincoming, needsoutgoing = True, True
5027 5026 else:
5028 5027 needsincoming, needsoutgoing = False, False
5029 5028 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5030 5029 if i:
5031 5030 needsincoming = True
5032 5031 if o:
5033 5032 needsoutgoing = True
5034 5033 if not needsincoming and not needsoutgoing:
5035 5034 return
5036 5035
5037 5036 def getincoming():
5038 5037 source, branches = hg.parseurl(ui.expandpath('default'))
5039 5038 sbranch = branches[0]
5040 5039 try:
5041 5040 other = hg.peer(repo, {}, source)
5042 5041 except error.RepoError:
5043 5042 if opts.get('remote'):
5044 5043 raise
5045 5044 return source, sbranch, None, None, None
5046 5045 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5047 5046 if revs:
5048 5047 revs = [other.lookup(rev) for rev in revs]
5049 5048 ui.debug('comparing with %s\n' % util.hidepassword(source))
5050 5049 repo.ui.pushbuffer()
5051 5050 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5052 5051 repo.ui.popbuffer()
5053 5052 return source, sbranch, other, commoninc, commoninc[1]
5054 5053
5055 5054 if needsincoming:
5056 5055 source, sbranch, sother, commoninc, incoming = getincoming()
5057 5056 else:
5058 5057 source = sbranch = sother = commoninc = incoming = None
5059 5058
5060 5059 def getoutgoing():
5061 5060 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5062 5061 dbranch = branches[0]
5063 5062 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5064 5063 if source != dest:
5065 5064 try:
5066 5065 dother = hg.peer(repo, {}, dest)
5067 5066 except error.RepoError:
5068 5067 if opts.get('remote'):
5069 5068 raise
5070 5069 return dest, dbranch, None, None
5071 5070 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5072 5071 elif sother is None:
5073 5072 # there is no explicit destination peer, but source one is invalid
5074 5073 return dest, dbranch, None, None
5075 5074 else:
5076 5075 dother = sother
5077 5076 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5078 5077 common = None
5079 5078 else:
5080 5079 common = commoninc
5081 5080 if revs:
5082 5081 revs = [repo.lookup(rev) for rev in revs]
5083 5082 repo.ui.pushbuffer()
5084 5083 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5085 5084 commoninc=common)
5086 5085 repo.ui.popbuffer()
5087 5086 return dest, dbranch, dother, outgoing
5088 5087
5089 5088 if needsoutgoing:
5090 5089 dest, dbranch, dother, outgoing = getoutgoing()
5091 5090 else:
5092 5091 dest = dbranch = dother = outgoing = None
5093 5092
5094 5093 if opts.get('remote'):
5095 5094 t = []
5096 5095 if incoming:
5097 5096 t.append(_('1 or more incoming'))
5098 5097 o = outgoing.missing
5099 5098 if o:
5100 5099 t.append(_('%d outgoing') % len(o))
5101 5100 other = dother or sother
5102 5101 if 'bookmarks' in other.listkeys('namespaces'):
5103 5102 counts = bookmarks.summary(repo, other)
5104 5103 if counts[0] > 0:
5105 5104 t.append(_('%d incoming bookmarks') % counts[0])
5106 5105 if counts[1] > 0:
5107 5106 t.append(_('%d outgoing bookmarks') % counts[1])
5108 5107
5109 5108 if t:
5110 5109 # i18n: column positioning for "hg summary"
5111 5110 ui.write(_('remote: %s\n') % (', '.join(t)))
5112 5111 else:
5113 5112 # i18n: column positioning for "hg summary"
5114 5113 ui.status(_('remote: (synced)\n'))
5115 5114
5116 5115 cmdutil.summaryremotehooks(ui, repo, opts,
5117 5116 ((source, sbranch, sother, commoninc),
5118 5117 (dest, dbranch, dother, outgoing)))
5119 5118
5120 5119 @command('tag',
5121 5120 [('f', 'force', None, _('force tag')),
5122 5121 ('l', 'local', None, _('make the tag local')),
5123 5122 ('r', 'rev', '', _('revision to tag'), _('REV')),
5124 5123 ('', 'remove', None, _('remove a tag')),
5125 5124 # -l/--local is already there, commitopts cannot be used
5126 5125 ('e', 'edit', None, _('invoke editor on commit messages')),
5127 5126 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5128 5127 ] + commitopts2,
5129 5128 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5130 5129 def tag(ui, repo, name1, *names, **opts):
5131 5130 """add one or more tags for the current or given revision
5132 5131
5133 5132 Name a particular revision using <name>.
5134 5133
5135 5134 Tags are used to name particular revisions of the repository and are
5136 5135 very useful to compare different revisions, to go back to significant
5137 5136 earlier versions or to mark branch points as releases, etc. Changing
5138 5137 an existing tag is normally disallowed; use -f/--force to override.
5139 5138
5140 5139 If no revision is given, the parent of the working directory is
5141 5140 used.
5142 5141
5143 5142 To facilitate version control, distribution, and merging of tags,
5144 5143 they are stored as a file named ".hgtags" which is managed similarly
5145 5144 to other project files and can be hand-edited if necessary. This
5146 5145 also means that tagging creates a new commit. The file
5147 5146 ".hg/localtags" is used for local tags (not shared among
5148 5147 repositories).
5149 5148
5150 5149 Tag commits are usually made at the head of a branch. If the parent
5151 5150 of the working directory is not a branch head, :hg:`tag` aborts; use
5152 5151 -f/--force to force the tag commit to be based on a non-head
5153 5152 changeset.
5154 5153
5155 5154 See :hg:`help dates` for a list of formats valid for -d/--date.
5156 5155
5157 5156 Since tag names have priority over branch names during revision
5158 5157 lookup, using an existing branch name as a tag name is discouraged.
5159 5158
5160 5159 Returns 0 on success.
5161 5160 """
5162 5161 opts = pycompat.byteskwargs(opts)
5163 5162 wlock = lock = None
5164 5163 try:
5165 5164 wlock = repo.wlock()
5166 5165 lock = repo.lock()
5167 5166 rev_ = "."
5168 5167 names = [t.strip() for t in (name1,) + names]
5169 5168 if len(names) != len(set(names)):
5170 5169 raise error.Abort(_('tag names must be unique'))
5171 5170 for n in names:
5172 5171 scmutil.checknewlabel(repo, n, 'tag')
5173 5172 if not n:
5174 5173 raise error.Abort(_('tag names cannot consist entirely of '
5175 5174 'whitespace'))
5176 5175 if opts.get('rev') and opts.get('remove'):
5177 5176 raise error.Abort(_("--rev and --remove are incompatible"))
5178 5177 if opts.get('rev'):
5179 5178 rev_ = opts['rev']
5180 5179 message = opts.get('message')
5181 5180 if opts.get('remove'):
5182 5181 if opts.get('local'):
5183 5182 expectedtype = 'local'
5184 5183 else:
5185 5184 expectedtype = 'global'
5186 5185
5187 5186 for n in names:
5188 5187 if not repo.tagtype(n):
5189 5188 raise error.Abort(_("tag '%s' does not exist") % n)
5190 5189 if repo.tagtype(n) != expectedtype:
5191 5190 if expectedtype == 'global':
5192 5191 raise error.Abort(_("tag '%s' is not a global tag") % n)
5193 5192 else:
5194 5193 raise error.Abort(_("tag '%s' is not a local tag") % n)
5195 5194 rev_ = 'null'
5196 5195 if not message:
5197 5196 # we don't translate commit messages
5198 5197 message = 'Removed tag %s' % ', '.join(names)
5199 5198 elif not opts.get('force'):
5200 5199 for n in names:
5201 5200 if n in repo.tags():
5202 5201 raise error.Abort(_("tag '%s' already exists "
5203 5202 "(use -f to force)") % n)
5204 5203 if not opts.get('local'):
5205 5204 p1, p2 = repo.dirstate.parents()
5206 5205 if p2 != nullid:
5207 5206 raise error.Abort(_('uncommitted merge'))
5208 5207 bheads = repo.branchheads()
5209 5208 if not opts.get('force') and bheads and p1 not in bheads:
5210 5209 raise error.Abort(_('working directory is not at a branch head '
5211 5210 '(use -f to force)'))
5212 5211 r = scmutil.revsingle(repo, rev_).node()
5213 5212
5214 5213 if not message:
5215 5214 # we don't translate commit messages
5216 5215 message = ('Added tag %s for changeset %s' %
5217 5216 (', '.join(names), short(r)))
5218 5217
5219 5218 date = opts.get('date')
5220 5219 if date:
5221 5220 date = util.parsedate(date)
5222 5221
5223 5222 if opts.get('remove'):
5224 5223 editform = 'tag.remove'
5225 5224 else:
5226 5225 editform = 'tag.add'
5227 5226 editor = cmdutil.getcommiteditor(editform=editform, **opts)
5228 5227
5229 5228 # don't allow tagging the null rev
5230 5229 if (not opts.get('remove') and
5231 5230 scmutil.revsingle(repo, rev_).rev() == nullrev):
5232 5231 raise error.Abort(_("cannot tag null revision"))
5233 5232
5234 5233 tagsmod.tag(repo, names, r, message, opts.get('local'),
5235 5234 opts.get('user'), date, editor=editor)
5236 5235 finally:
5237 5236 release(lock, wlock)
5238 5237
5239 5238 @command('tags', formatteropts, '')
5240 5239 def tags(ui, repo, **opts):
5241 5240 """list repository tags
5242 5241
5243 5242 This lists both regular and local tags. When the -v/--verbose
5244 5243 switch is used, a third column "local" is printed for local tags.
5245 5244 When the -q/--quiet switch is used, only the tag name is printed.
5246 5245
5247 5246 Returns 0 on success.
5248 5247 """
5249 5248
5250 5249 opts = pycompat.byteskwargs(opts)
5251 5250 ui.pager('tags')
5252 5251 fm = ui.formatter('tags', opts)
5253 5252 hexfunc = fm.hexfunc
5254 5253 tagtype = ""
5255 5254
5256 5255 for t, n in reversed(repo.tagslist()):
5257 5256 hn = hexfunc(n)
5258 5257 label = 'tags.normal'
5259 5258 tagtype = ''
5260 5259 if repo.tagtype(t) == 'local':
5261 5260 label = 'tags.local'
5262 5261 tagtype = 'local'
5263 5262
5264 5263 fm.startitem()
5265 5264 fm.write('tag', '%s', t, label=label)
5266 5265 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5267 5266 fm.condwrite(not ui.quiet, 'rev node', fmt,
5268 5267 repo.changelog.rev(n), hn, label=label)
5269 5268 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5270 5269 tagtype, label=label)
5271 5270 fm.plain('\n')
5272 5271 fm.end()
5273 5272
5274 5273 @command('tip',
5275 5274 [('p', 'patch', None, _('show patch')),
5276 5275 ('g', 'git', None, _('use git extended diff format')),
5277 5276 ] + templateopts,
5278 5277 _('[-p] [-g]'))
5279 5278 def tip(ui, repo, **opts):
5280 5279 """show the tip revision (DEPRECATED)
5281 5280
5282 5281 The tip revision (usually just called the tip) is the changeset
5283 5282 most recently added to the repository (and therefore the most
5284 5283 recently changed head).
5285 5284
5286 5285 If you have just made a commit, that commit will be the tip. If
5287 5286 you have just pulled changes from another repository, the tip of
5288 5287 that repository becomes the current tip. The "tip" tag is special
5289 5288 and cannot be renamed or assigned to a different changeset.
5290 5289
5291 5290 This command is deprecated, please use :hg:`heads` instead.
5292 5291
5293 5292 Returns 0 on success.
5294 5293 """
5295 5294 opts = pycompat.byteskwargs(opts)
5296 5295 displayer = cmdutil.show_changeset(ui, repo, opts)
5297 5296 displayer.show(repo['tip'])
5298 5297 displayer.close()
5299 5298
5300 5299 @command('unbundle',
5301 5300 [('u', 'update', None,
5302 5301 _('update to new branch head if changesets were unbundled'))],
5303 5302 _('[-u] FILE...'))
5304 5303 def unbundle(ui, repo, fname1, *fnames, **opts):
5305 5304 """apply one or more bundle files
5306 5305
5307 5306 Apply one or more bundle files generated by :hg:`bundle`.
5308 5307
5309 5308 Returns 0 on success, 1 if an update has unresolved files.
5310 5309 """
5311 5310 fnames = (fname1,) + fnames
5312 5311
5313 5312 with repo.lock():
5314 5313 for fname in fnames:
5315 5314 f = hg.openpath(ui, fname)
5316 5315 gen = exchange.readbundle(ui, f, fname)
5317 5316 if isinstance(gen, bundle2.unbundle20):
5318 5317 tr = repo.transaction('unbundle')
5319 5318 try:
5320 5319 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5321 5320 url='bundle:' + fname)
5322 5321 tr.close()
5323 5322 except error.BundleUnknownFeatureError as exc:
5324 5323 raise error.Abort(_('%s: unknown bundle feature, %s')
5325 5324 % (fname, exc),
5326 5325 hint=_("see https://mercurial-scm.org/"
5327 5326 "wiki/BundleFeature for more "
5328 5327 "information"))
5329 5328 finally:
5330 5329 if tr:
5331 5330 tr.release()
5332 5331 changes = [r.get('return', 0)
5333 5332 for r in op.records['changegroup']]
5334 5333 modheads = changegroup.combineresults(changes)
5335 5334 elif isinstance(gen, streamclone.streamcloneapplier):
5336 5335 raise error.Abort(
5337 5336 _('packed bundles cannot be applied with '
5338 5337 '"hg unbundle"'),
5339 5338 hint=_('use "hg debugapplystreamclonebundle"'))
5340 5339 else:
5341 5340 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5342 5341
5343 5342 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5344 5343
5345 5344 @command('^update|up|checkout|co',
5346 5345 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5347 5346 ('c', 'check', None, _('require clean working directory')),
5348 5347 ('m', 'merge', None, _('merge uncommitted changes')),
5349 5348 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5350 5349 ('r', 'rev', '', _('revision'), _('REV'))
5351 5350 ] + mergetoolopts,
5352 5351 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5353 5352 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5354 5353 merge=None, tool=None):
5355 5354 """update working directory (or switch revisions)
5356 5355
5357 5356 Update the repository's working directory to the specified
5358 5357 changeset. If no changeset is specified, update to the tip of the
5359 5358 current named branch and move the active bookmark (see :hg:`help
5360 5359 bookmarks`).
5361 5360
5362 5361 Update sets the working directory's parent revision to the specified
5363 5362 changeset (see :hg:`help parents`).
5364 5363
5365 5364 If the changeset is not a descendant or ancestor of the working
5366 5365 directory's parent and there are uncommitted changes, the update is
5367 5366 aborted. With the -c/--check option, the working directory is checked
5368 5367 for uncommitted changes; if none are found, the working directory is
5369 5368 updated to the specified changeset.
5370 5369
5371 5370 .. container:: verbose
5372 5371
5373 5372 The -C/--clean, -c/--check, and -m/--merge options control what
5374 5373 happens if the working directory contains uncommitted changes.
5375 5374 At most of one of them can be specified.
5376 5375
5377 5376 1. If no option is specified, and if
5378 5377 the requested changeset is an ancestor or descendant of
5379 5378 the working directory's parent, the uncommitted changes
5380 5379 are merged into the requested changeset and the merged
5381 5380 result is left uncommitted. If the requested changeset is
5382 5381 not an ancestor or descendant (that is, it is on another
5383 5382 branch), the update is aborted and the uncommitted changes
5384 5383 are preserved.
5385 5384
5386 5385 2. With the -m/--merge option, the update is allowed even if the
5387 5386 requested changeset is not an ancestor or descendant of
5388 5387 the working directory's parent.
5389 5388
5390 5389 3. With the -c/--check option, the update is aborted and the
5391 5390 uncommitted changes are preserved.
5392 5391
5393 5392 4. With the -C/--clean option, uncommitted changes are discarded and
5394 5393 the working directory is updated to the requested changeset.
5395 5394
5396 5395 To cancel an uncommitted merge (and lose your changes), use
5397 5396 :hg:`update --clean .`.
5398 5397
5399 5398 Use null as the changeset to remove the working directory (like
5400 5399 :hg:`clone -U`).
5401 5400
5402 5401 If you want to revert just one file to an older revision, use
5403 5402 :hg:`revert [-r REV] NAME`.
5404 5403
5405 5404 See :hg:`help dates` for a list of formats valid for -d/--date.
5406 5405
5407 5406 Returns 0 on success, 1 if there are unresolved files.
5408 5407 """
5409 5408 if rev and node:
5410 5409 raise error.Abort(_("please specify just one revision"))
5411 5410
5412 5411 if ui.configbool('commands', 'update.requiredest'):
5413 5412 if not node and not rev and not date:
5414 5413 raise error.Abort(_('you must specify a destination'),
5415 5414 hint=_('for example: hg update ".::"'))
5416 5415
5417 5416 if rev is None or rev == '':
5418 5417 rev = node
5419 5418
5420 5419 if date and rev is not None:
5421 5420 raise error.Abort(_("you can't specify a revision and a date"))
5422 5421
5423 5422 if len([x for x in (clean, check, merge) if x]) > 1:
5424 5423 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5425 5424 "or -m/merge"))
5426 5425
5427 5426 updatecheck = None
5428 5427 if check:
5429 5428 updatecheck = 'abort'
5430 5429 elif merge:
5431 5430 updatecheck = 'none'
5432 5431
5433 5432 with repo.wlock():
5434 5433 cmdutil.clearunfinished(repo)
5435 5434
5436 5435 if date:
5437 5436 rev = cmdutil.finddate(ui, repo, date)
5438 5437
5439 5438 # if we defined a bookmark, we have to remember the original name
5440 5439 brev = rev
5441 5440 rev = scmutil.revsingle(repo, rev, rev).rev()
5442 5441
5443 5442 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5444 5443
5445 5444 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5446 5445 updatecheck=updatecheck)
5447 5446
5448 5447 @command('verify', [])
5449 5448 def verify(ui, repo):
5450 5449 """verify the integrity of the repository
5451 5450
5452 5451 Verify the integrity of the current repository.
5453 5452
5454 5453 This will perform an extensive check of the repository's
5455 5454 integrity, validating the hashes and checksums of each entry in
5456 5455 the changelog, manifest, and tracked files, as well as the
5457 5456 integrity of their crosslinks and indices.
5458 5457
5459 5458 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5460 5459 for more information about recovery from corruption of the
5461 5460 repository.
5462 5461
5463 5462 Returns 0 on success, 1 if errors are encountered.
5464 5463 """
5465 5464 return hg.verify(repo)
5466 5465
5467 5466 @command('version', [] + formatteropts, norepo=True)
5468 5467 def version_(ui, **opts):
5469 5468 """output version and copyright information"""
5470 5469 opts = pycompat.byteskwargs(opts)
5471 5470 if ui.verbose:
5472 5471 ui.pager('version')
5473 5472 fm = ui.formatter("version", opts)
5474 5473 fm.startitem()
5475 5474 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5476 5475 util.version())
5477 5476 license = _(
5478 5477 "(see https://mercurial-scm.org for more information)\n"
5479 5478 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5480 5479 "This is free software; see the source for copying conditions. "
5481 5480 "There is NO\nwarranty; "
5482 5481 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5483 5482 )
5484 5483 if not ui.quiet:
5485 5484 fm.plain(license)
5486 5485
5487 5486 if ui.verbose:
5488 5487 fm.plain(_("\nEnabled extensions:\n\n"))
5489 5488 # format names and versions into columns
5490 5489 names = []
5491 5490 vers = []
5492 5491 isinternals = []
5493 5492 for name, module in extensions.extensions():
5494 5493 names.append(name)
5495 5494 vers.append(extensions.moduleversion(module) or None)
5496 5495 isinternals.append(extensions.ismoduleinternal(module))
5497 5496 fn = fm.nested("extensions")
5498 5497 if names:
5499 5498 namefmt = " %%-%ds " % max(len(n) for n in names)
5500 5499 places = [_("external"), _("internal")]
5501 5500 for n, v, p in zip(names, vers, isinternals):
5502 5501 fn.startitem()
5503 5502 fn.condwrite(ui.verbose, "name", namefmt, n)
5504 5503 if ui.verbose:
5505 5504 fn.plain("%s " % places[p])
5506 5505 fn.data(bundled=p)
5507 5506 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5508 5507 if ui.verbose:
5509 5508 fn.plain("\n")
5510 5509 fn.end()
5511 5510 fm.end()
5512 5511
5513 5512 def loadcmdtable(ui, name, cmdtable):
5514 5513 """Load command functions from specified cmdtable
5515 5514 """
5516 5515 overrides = [cmd for cmd in cmdtable if cmd in table]
5517 5516 if overrides:
5518 5517 ui.warn(_("extension '%s' overrides commands: %s\n")
5519 5518 % (name, " ".join(overrides)))
5520 5519 table.update(cmdtable)
@@ -1,1987 +1,1987 b''
1 1 # subrepo.py - sub-repository handling for Mercurial
2 2 #
3 3 # Copyright 2009-2010 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 copy
11 11 import errno
12 12 import hashlib
13 13 import os
14 14 import posixpath
15 15 import re
16 16 import stat
17 17 import subprocess
18 18 import sys
19 19 import tarfile
20 20 import xml.dom.minidom
21 21
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 cmdutil,
26 26 config,
27 27 encoding,
28 28 error,
29 29 exchange,
30 30 filemerge,
31 31 match as matchmod,
32 32 node,
33 33 pathutil,
34 34 phases,
35 35 pycompat,
36 36 scmutil,
37 37 util,
38 38 vfs as vfsmod,
39 39 )
40 40
41 41 hg = None
42 42 propertycache = util.propertycache
43 43
44 44 nullstate = ('', '', 'empty')
45 45
46 46 def _expandedabspath(path):
47 47 '''
48 48 get a path or url and if it is a path expand it and return an absolute path
49 49 '''
50 50 expandedpath = util.urllocalpath(util.expandpath(path))
51 51 u = util.url(expandedpath)
52 52 if not u.scheme:
53 53 path = util.normpath(os.path.abspath(u.path))
54 54 return path
55 55
56 56 def _getstorehashcachename(remotepath):
57 57 '''get a unique filename for the store hash cache of a remote repository'''
58 58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
59 59
60 60 class SubrepoAbort(error.Abort):
61 61 """Exception class used to avoid handling a subrepo error more than once"""
62 62 def __init__(self, *args, **kw):
63 63 self.subrepo = kw.pop('subrepo', None)
64 64 self.cause = kw.pop('cause', None)
65 65 error.Abort.__init__(self, *args, **kw)
66 66
67 67 def annotatesubrepoerror(func):
68 68 def decoratedmethod(self, *args, **kargs):
69 69 try:
70 70 res = func(self, *args, **kargs)
71 71 except SubrepoAbort as ex:
72 72 # This exception has already been handled
73 73 raise ex
74 74 except error.Abort as ex:
75 75 subrepo = subrelpath(self)
76 76 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
77 77 # avoid handling this exception by raising a SubrepoAbort exception
78 78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
79 79 cause=sys.exc_info())
80 80 return res
81 81 return decoratedmethod
82 82
83 83 def state(ctx, ui):
84 84 """return a state dict, mapping subrepo paths configured in .hgsub
85 85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
86 86 (key in types dict))
87 87 """
88 88 p = config.config()
89 89 repo = ctx.repo()
90 90 def read(f, sections=None, remap=None):
91 91 if f in ctx:
92 92 try:
93 93 data = ctx[f].data()
94 94 except IOError as err:
95 95 if err.errno != errno.ENOENT:
96 96 raise
97 97 # handle missing subrepo spec files as removed
98 98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
99 99 repo.pathto(f))
100 100 return
101 101 p.parse(f, data, sections, remap, read)
102 102 else:
103 103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
104 104 repo.pathto(f))
105 105 if '.hgsub' in ctx:
106 106 read('.hgsub')
107 107
108 108 for path, src in ui.configitems('subpaths'):
109 109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
110 110
111 111 rev = {}
112 112 if '.hgsubstate' in ctx:
113 113 try:
114 114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
115 115 l = l.lstrip()
116 116 if not l:
117 117 continue
118 118 try:
119 119 revision, path = l.split(" ", 1)
120 120 except ValueError:
121 121 raise error.Abort(_("invalid subrepository revision "
122 122 "specifier in \'%s\' line %d")
123 123 % (repo.pathto('.hgsubstate'), (i + 1)))
124 124 rev[path] = revision
125 125 except IOError as err:
126 126 if err.errno != errno.ENOENT:
127 127 raise
128 128
129 129 def remap(src):
130 130 for pattern, repl in p.items('subpaths'):
131 131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
132 132 # does a string decode.
133 133 repl = util.escapestr(repl)
134 134 # However, we still want to allow back references to go
135 135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
136 136 # extra escapes are needed because re.sub string decodes.
137 137 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
138 138 try:
139 139 src = re.sub(pattern, repl, src, 1)
140 140 except re.error as e:
141 141 raise error.Abort(_("bad subrepository pattern in %s: %s")
142 142 % (p.source('subpaths', pattern), e))
143 143 return src
144 144
145 145 state = {}
146 146 for path, src in p[''].items():
147 147 kind = 'hg'
148 148 if src.startswith('['):
149 149 if ']' not in src:
150 150 raise error.Abort(_('missing ] in subrepo source'))
151 151 kind, src = src.split(']', 1)
152 152 kind = kind[1:]
153 153 src = src.lstrip() # strip any extra whitespace after ']'
154 154
155 155 if not util.url(src).isabs():
156 156 parent = _abssource(repo, abort=False)
157 157 if parent:
158 158 parent = util.url(parent)
159 159 parent.path = posixpath.join(parent.path or '', src)
160 160 parent.path = posixpath.normpath(parent.path)
161 161 joined = str(parent)
162 162 # Remap the full joined path and use it if it changes,
163 163 # else remap the original source.
164 164 remapped = remap(joined)
165 165 if remapped == joined:
166 166 src = remap(src)
167 167 else:
168 168 src = remapped
169 169
170 170 src = remap(src)
171 171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
172 172
173 173 return state
174 174
175 175 def writestate(repo, state):
176 176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
177 177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
178 178 if state[s][1] != nullstate[1]]
179 179 repo.wwrite('.hgsubstate', ''.join(lines), '')
180 180
181 181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
182 182 """delegated from merge.applyupdates: merging of .hgsubstate file
183 183 in working context, merging context and ancestor context"""
184 184 if mctx == actx: # backwards?
185 185 actx = wctx.p1()
186 186 s1 = wctx.substate
187 187 s2 = mctx.substate
188 188 sa = actx.substate
189 189 sm = {}
190 190
191 191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
192 192
193 193 def debug(s, msg, r=""):
194 194 if r:
195 195 r = "%s:%s:%s" % r
196 196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
197 197
198 198 promptssrc = filemerge.partextras(labels)
199 199 for s, l in sorted(s1.iteritems()):
200 200 prompts = None
201 201 a = sa.get(s, nullstate)
202 202 ld = l # local state with possible dirty flag for compares
203 203 if wctx.sub(s).dirty():
204 204 ld = (l[0], l[1] + "+")
205 205 if wctx == actx: # overwrite
206 206 a = ld
207 207
208 208 prompts = promptssrc.copy()
209 209 prompts['s'] = s
210 210 if s in s2:
211 211 r = s2[s]
212 212 if ld == r or r == a: # no change or local is newer
213 213 sm[s] = l
214 214 continue
215 215 elif ld == a: # other side changed
216 216 debug(s, "other changed, get", r)
217 217 wctx.sub(s).get(r, overwrite)
218 218 sm[s] = r
219 219 elif ld[0] != r[0]: # sources differ
220 220 prompts['lo'] = l[0]
221 221 prompts['ro'] = r[0]
222 222 if repo.ui.promptchoice(
223 223 _(' subrepository sources for %(s)s differ\n'
224 224 'use (l)ocal%(l)s source (%(lo)s)'
225 225 ' or (r)emote%(o)s source (%(ro)s)?'
226 226 '$$ &Local $$ &Remote') % prompts, 0):
227 227 debug(s, "prompt changed, get", r)
228 228 wctx.sub(s).get(r, overwrite)
229 229 sm[s] = r
230 230 elif ld[1] == a[1]: # local side is unchanged
231 231 debug(s, "other side changed, get", r)
232 232 wctx.sub(s).get(r, overwrite)
233 233 sm[s] = r
234 234 else:
235 235 debug(s, "both sides changed")
236 236 srepo = wctx.sub(s)
237 237 prompts['sl'] = srepo.shortid(l[1])
238 238 prompts['sr'] = srepo.shortid(r[1])
239 239 option = repo.ui.promptchoice(
240 240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
241 241 'remote revision: %(sr)s)\n'
242 242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
243 243 '$$ &Merge $$ &Local $$ &Remote')
244 244 % prompts, 0)
245 245 if option == 0:
246 246 wctx.sub(s).merge(r)
247 247 sm[s] = l
248 248 debug(s, "merge with", r)
249 249 elif option == 1:
250 250 sm[s] = l
251 251 debug(s, "keep local subrepo revision", l)
252 252 else:
253 253 wctx.sub(s).get(r, overwrite)
254 254 sm[s] = r
255 255 debug(s, "get remote subrepo revision", r)
256 256 elif ld == a: # remote removed, local unchanged
257 257 debug(s, "remote removed, remove")
258 258 wctx.sub(s).remove()
259 259 elif a == nullstate: # not present in remote or ancestor
260 260 debug(s, "local added, keep")
261 261 sm[s] = l
262 262 continue
263 263 else:
264 264 if repo.ui.promptchoice(
265 265 _(' local%(l)s changed subrepository %(s)s'
266 266 ' which remote%(o)s removed\n'
267 267 'use (c)hanged version or (d)elete?'
268 268 '$$ &Changed $$ &Delete') % prompts, 0):
269 269 debug(s, "prompt remove")
270 270 wctx.sub(s).remove()
271 271
272 272 for s, r in sorted(s2.items()):
273 273 prompts = None
274 274 if s in s1:
275 275 continue
276 276 elif s not in sa:
277 277 debug(s, "remote added, get", r)
278 278 mctx.sub(s).get(r)
279 279 sm[s] = r
280 280 elif r != sa[s]:
281 281 prompts = promptssrc.copy()
282 282 prompts['s'] = s
283 283 if repo.ui.promptchoice(
284 284 _(' remote%(o)s changed subrepository %(s)s'
285 285 ' which local%(l)s removed\n'
286 286 'use (c)hanged version or (d)elete?'
287 287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
288 288 debug(s, "prompt recreate", r)
289 289 mctx.sub(s).get(r)
290 290 sm[s] = r
291 291
292 292 # record merged .hgsubstate
293 293 writestate(repo, sm)
294 294 return sm
295 295
296 296 def _updateprompt(ui, sub, dirty, local, remote):
297 297 if dirty:
298 298 msg = (_(' subrepository sources for %s differ\n'
299 299 'use (l)ocal source (%s) or (r)emote source (%s)?'
300 300 '$$ &Local $$ &Remote')
301 301 % (subrelpath(sub), local, remote))
302 302 else:
303 303 msg = (_(' subrepository sources for %s differ (in checked out '
304 304 'version)\n'
305 305 'use (l)ocal source (%s) or (r)emote source (%s)?'
306 306 '$$ &Local $$ &Remote')
307 307 % (subrelpath(sub), local, remote))
308 308 return ui.promptchoice(msg, 0)
309 309
310 310 def reporelpath(repo):
311 311 """return path to this (sub)repo as seen from outermost repo"""
312 312 parent = repo
313 313 while util.safehasattr(parent, '_subparent'):
314 314 parent = parent._subparent
315 315 return repo.root[len(pathutil.normasprefix(parent.root)):]
316 316
317 317 def subrelpath(sub):
318 318 """return path to this subrepo as seen from outermost repo"""
319 319 return sub._relpath
320 320
321 321 def _abssource(repo, push=False, abort=True):
322 322 """return pull/push path of repo - either based on parent repo .hgsub info
323 323 or on the top repo config. Abort or return None if no source found."""
324 324 if util.safehasattr(repo, '_subparent'):
325 325 source = util.url(repo._subsource)
326 326 if source.isabs():
327 327 return str(source)
328 328 source.path = posixpath.normpath(source.path)
329 329 parent = _abssource(repo._subparent, push, abort=False)
330 330 if parent:
331 331 parent = util.url(util.pconvert(parent))
332 332 parent.path = posixpath.join(parent.path or '', source.path)
333 333 parent.path = posixpath.normpath(parent.path)
334 334 return str(parent)
335 335 else: # recursion reached top repo
336 336 if util.safehasattr(repo, '_subtoppath'):
337 337 return repo._subtoppath
338 338 if push and repo.ui.config('paths', 'default-push'):
339 339 return repo.ui.config('paths', 'default-push')
340 340 if repo.ui.config('paths', 'default'):
341 341 return repo.ui.config('paths', 'default')
342 342 if repo.shared():
343 343 # chop off the .hg component to get the default path form
344 344 return os.path.dirname(repo.sharedpath)
345 345 if abort:
346 346 raise error.Abort(_("default path for subrepository not found"))
347 347
348 348 def _sanitize(ui, vfs, ignore):
349 349 for dirname, dirs, names in vfs.walk():
350 350 for i, d in enumerate(dirs):
351 351 if d.lower() == ignore:
352 352 del dirs[i]
353 353 break
354 354 if vfs.basename(dirname).lower() != '.hg':
355 355 continue
356 356 for f in names:
357 357 if f.lower() == 'hgrc':
358 358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
359 359 "in '%s'\n") % vfs.join(dirname))
360 360 vfs.unlink(vfs.reljoin(dirname, f))
361 361
362 362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
363 363 """return instance of the right subrepo class for subrepo in path"""
364 364 # subrepo inherently violates our import layering rules
365 365 # because it wants to make repo objects from deep inside the stack
366 366 # so we manually delay the circular imports to not break
367 367 # scripts that don't use our demand-loading
368 368 global hg
369 369 from . import hg as h
370 370 hg = h
371 371
372 372 pathutil.pathauditor(ctx.repo().root)(path)
373 373 state = ctx.substate[path]
374 374 if state[2] not in types:
375 375 raise error.Abort(_('unknown subrepo type %s') % state[2])
376 376 if allowwdir:
377 377 state = (state[0], ctx.subrev(path), state[2])
378 378 return types[state[2]](ctx, path, state[:2], allowcreate)
379 379
380 380 def nullsubrepo(ctx, path, pctx):
381 381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
382 382 # subrepo inherently violates our import layering rules
383 383 # because it wants to make repo objects from deep inside the stack
384 384 # so we manually delay the circular imports to not break
385 385 # scripts that don't use our demand-loading
386 386 global hg
387 387 from . import hg as h
388 388 hg = h
389 389
390 390 pathutil.pathauditor(ctx.repo().root)(path)
391 391 state = ctx.substate[path]
392 392 if state[2] not in types:
393 393 raise error.Abort(_('unknown subrepo type %s') % state[2])
394 394 subrev = ''
395 395 if state[2] == 'hg':
396 396 subrev = "0" * 40
397 397 return types[state[2]](pctx, path, (state[0], subrev), True)
398 398
399 399 def newcommitphase(ui, ctx):
400 400 commitphase = phases.newcommitphase(ui)
401 401 substate = getattr(ctx, "substate", None)
402 402 if not substate:
403 403 return commitphase
404 404 check = ui.config('phases', 'checksubrepos', 'follow')
405 405 if check not in ('ignore', 'follow', 'abort'):
406 406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
407 407 % (check))
408 408 if check == 'ignore':
409 409 return commitphase
410 410 maxphase = phases.public
411 411 maxsub = None
412 412 for s in sorted(substate):
413 413 sub = ctx.sub(s)
414 414 subphase = sub.phase(substate[s][1])
415 415 if maxphase < subphase:
416 416 maxphase = subphase
417 417 maxsub = s
418 418 if commitphase < maxphase:
419 419 if check == 'abort':
420 420 raise error.Abort(_("can't commit in %s phase"
421 421 " conflicting %s from subrepository %s") %
422 422 (phases.phasenames[commitphase],
423 423 phases.phasenames[maxphase], maxsub))
424 424 ui.warn(_("warning: changes are committed in"
425 425 " %s phase from subrepository %s\n") %
426 426 (phases.phasenames[maxphase], maxsub))
427 427 return maxphase
428 428 return commitphase
429 429
430 430 # subrepo classes need to implement the following abstract class:
431 431
432 432 class abstractsubrepo(object):
433 433
434 434 def __init__(self, ctx, path):
435 435 """Initialize abstractsubrepo part
436 436
437 437 ``ctx`` is the context referring this subrepository in the
438 438 parent repository.
439 439
440 440 ``path`` is the path to this subrepository as seen from
441 441 innermost repository.
442 442 """
443 443 self.ui = ctx.repo().ui
444 444 self._ctx = ctx
445 445 self._path = path
446 446
447 447 def addwebdirpath(self, serverpath, webconf):
448 448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
449 449
450 450 ``serverpath`` is the path component of the URL for this repo.
451 451
452 452 ``webconf`` is the dictionary of hgwebdir entries.
453 453 """
454 454 pass
455 455
456 456 def storeclean(self, path):
457 457 """
458 458 returns true if the repository has not changed since it was last
459 459 cloned from or pushed to a given repository.
460 460 """
461 461 return False
462 462
463 463 def dirty(self, ignoreupdate=False):
464 464 """returns true if the dirstate of the subrepo is dirty or does not
465 465 match current stored state. If ignoreupdate is true, only check
466 466 whether the subrepo has uncommitted changes in its dirstate.
467 467 """
468 468 raise NotImplementedError
469 469
470 470 def dirtyreason(self, ignoreupdate=False):
471 471 """return reason string if it is ``dirty()``
472 472
473 473 Returned string should have enough information for the message
474 474 of exception.
475 475
476 476 This returns None, otherwise.
477 477 """
478 478 if self.dirty(ignoreupdate=ignoreupdate):
479 479 return _("uncommitted changes in subrepository '%s'"
480 480 ) % subrelpath(self)
481 481
482 482 def bailifchanged(self, ignoreupdate=False, hint=None):
483 483 """raise Abort if subrepository is ``dirty()``
484 484 """
485 485 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
486 486 if dirtyreason:
487 487 raise error.Abort(dirtyreason, hint=hint)
488 488
489 489 def basestate(self):
490 490 """current working directory base state, disregarding .hgsubstate
491 491 state and working directory modifications"""
492 492 raise NotImplementedError
493 493
494 494 def checknested(self, path):
495 495 """check if path is a subrepository within this repository"""
496 496 return False
497 497
498 498 def commit(self, text, user, date):
499 499 """commit the current changes to the subrepo with the given
500 500 log message. Use given user and date if possible. Return the
501 501 new state of the subrepo.
502 502 """
503 503 raise NotImplementedError
504 504
505 505 def phase(self, state):
506 506 """returns phase of specified state in the subrepository.
507 507 """
508 508 return phases.public
509 509
510 510 def remove(self):
511 511 """remove the subrepo
512 512
513 513 (should verify the dirstate is not dirty first)
514 514 """
515 515 raise NotImplementedError
516 516
517 517 def get(self, state, overwrite=False):
518 518 """run whatever commands are needed to put the subrepo into
519 519 this state
520 520 """
521 521 raise NotImplementedError
522 522
523 523 def merge(self, state):
524 524 """merge currently-saved state with the new state."""
525 525 raise NotImplementedError
526 526
527 527 def push(self, opts):
528 528 """perform whatever action is analogous to 'hg push'
529 529
530 530 This may be a no-op on some systems.
531 531 """
532 532 raise NotImplementedError
533 533
534 534 def add(self, ui, match, prefix, explicitonly, **opts):
535 535 return []
536 536
537 537 def addremove(self, matcher, prefix, opts, dry_run, similarity):
538 538 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
539 539 return 1
540 540
541 541 def cat(self, match, prefix, **opts):
542 542 return 1
543 543
544 544 def status(self, rev2, **opts):
545 545 return scmutil.status([], [], [], [], [], [], [])
546 546
547 547 def diff(self, ui, diffopts, node2, match, prefix, **opts):
548 548 pass
549 549
550 550 def outgoing(self, ui, dest, opts):
551 551 return 1
552 552
553 553 def incoming(self, ui, source, opts):
554 554 return 1
555 555
556 556 def files(self):
557 557 """return filename iterator"""
558 558 raise NotImplementedError
559 559
560 560 def filedata(self, name, decode):
561 561 """return file data, optionally passed through repo decoders"""
562 562 raise NotImplementedError
563 563
564 564 def fileflags(self, name):
565 565 """return file flags"""
566 566 return ''
567 567
568 568 def getfileset(self, expr):
569 569 """Resolve the fileset expression for this repo"""
570 570 return set()
571 571
572 572 def printfiles(self, ui, m, fm, fmt, subrepos):
573 573 """handle the files command for this subrepo"""
574 574 return 1
575 575
576 576 def archive(self, archiver, prefix, match=None, decode=True):
577 577 if match is not None:
578 578 files = [f for f in self.files() if match(f)]
579 579 else:
580 580 files = self.files()
581 581 total = len(files)
582 582 relpath = subrelpath(self)
583 583 self.ui.progress(_('archiving (%s)') % relpath, 0,
584 584 unit=_('files'), total=total)
585 585 for i, name in enumerate(files):
586 586 flags = self.fileflags(name)
587 587 mode = 'x' in flags and 0o755 or 0o644
588 588 symlink = 'l' in flags
589 589 archiver.addfile(prefix + self._path + '/' + name,
590 590 mode, symlink, self.filedata(name, decode))
591 591 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
592 592 unit=_('files'), total=total)
593 593 self.ui.progress(_('archiving (%s)') % relpath, None)
594 594 return total
595 595
596 596 def walk(self, match):
597 597 '''
598 598 walk recursively through the directory tree, finding all files
599 599 matched by the match function
600 600 '''
601 601 pass
602 602
603 603 def forget(self, match, prefix):
604 604 return ([], [])
605 605
606 606 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
607 607 """remove the matched files from the subrepository and the filesystem,
608 608 possibly by force and/or after the file has been removed from the
609 609 filesystem. Return 0 on success, 1 on any warning.
610 610 """
611 611 warnings.append(_("warning: removefiles not implemented (%s)")
612 612 % self._path)
613 613 return 1
614 614
615 615 def revert(self, substate, *pats, **opts):
616 616 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
617 617 % (substate[0], substate[2]))
618 618 return []
619 619
620 620 def shortid(self, revid):
621 621 return revid
622 622
623 623 def verify(self):
624 624 '''verify the integrity of the repository. Return 0 on success or
625 625 warning, 1 on any error.
626 626 '''
627 627 return 0
628 628
629 629 @propertycache
630 630 def wvfs(self):
631 631 """return vfs to access the working directory of this subrepository
632 632 """
633 633 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
634 634
635 635 @propertycache
636 636 def _relpath(self):
637 637 """return path to this subrepository as seen from outermost repository
638 638 """
639 639 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
640 640
641 641 class hgsubrepo(abstractsubrepo):
642 642 def __init__(self, ctx, path, state, allowcreate):
643 643 super(hgsubrepo, self).__init__(ctx, path)
644 644 self._state = state
645 645 r = ctx.repo()
646 646 root = r.wjoin(path)
647 647 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
648 648 self._repo = hg.repository(r.baseui, root, create=create)
649 649
650 650 # Propagate the parent's --hidden option
651 651 if r is r.unfiltered():
652 652 self._repo = self._repo.unfiltered()
653 653
654 654 self.ui = self._repo.ui
655 655 for s, k in [('ui', 'commitsubrepos')]:
656 656 v = r.ui.config(s, k)
657 657 if v:
658 658 self.ui.setconfig(s, k, v, 'subrepo')
659 659 # internal config: ui._usedassubrepo
660 660 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
661 661 self._initrepo(r, state[0], create)
662 662
663 663 @annotatesubrepoerror
664 664 def addwebdirpath(self, serverpath, webconf):
665 665 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
666 666
667 667 def storeclean(self, path):
668 668 with self._repo.lock():
669 669 return self._storeclean(path)
670 670
671 671 def _storeclean(self, path):
672 672 clean = True
673 673 itercache = self._calcstorehash(path)
674 674 for filehash in self._readstorehashcache(path):
675 675 if filehash != next(itercache, None):
676 676 clean = False
677 677 break
678 678 if clean:
679 679 # if not empty:
680 680 # the cached and current pull states have a different size
681 681 clean = next(itercache, None) is None
682 682 return clean
683 683
684 684 def _calcstorehash(self, remotepath):
685 685 '''calculate a unique "store hash"
686 686
687 687 This method is used to to detect when there are changes that may
688 688 require a push to a given remote path.'''
689 689 # sort the files that will be hashed in increasing (likely) file size
690 690 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
691 691 yield '# %s\n' % _expandedabspath(remotepath)
692 692 vfs = self._repo.vfs
693 693 for relname in filelist:
694 694 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
695 695 yield '%s = %s\n' % (relname, filehash)
696 696
697 697 @propertycache
698 698 def _cachestorehashvfs(self):
699 699 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
700 700
701 701 def _readstorehashcache(self, remotepath):
702 702 '''read the store hash cache for a given remote repository'''
703 703 cachefile = _getstorehashcachename(remotepath)
704 704 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
705 705
706 706 def _cachestorehash(self, remotepath):
707 707 '''cache the current store hash
708 708
709 709 Each remote repo requires its own store hash cache, because a subrepo
710 710 store may be "clean" versus a given remote repo, but not versus another
711 711 '''
712 712 cachefile = _getstorehashcachename(remotepath)
713 713 with self._repo.lock():
714 714 storehash = list(self._calcstorehash(remotepath))
715 715 vfs = self._cachestorehashvfs
716 716 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
717 717
718 718 def _getctx(self):
719 719 '''fetch the context for this subrepo revision, possibly a workingctx
720 720 '''
721 721 if self._ctx.rev() is None:
722 722 return self._repo[None] # workingctx if parent is workingctx
723 723 else:
724 724 rev = self._state[1]
725 725 return self._repo[rev]
726 726
727 727 @annotatesubrepoerror
728 728 def _initrepo(self, parentrepo, source, create):
729 729 self._repo._subparent = parentrepo
730 730 self._repo._subsource = source
731 731
732 732 if create:
733 733 lines = ['[paths]\n']
734 734
735 735 def addpathconfig(key, value):
736 736 if value:
737 737 lines.append('%s = %s\n' % (key, value))
738 738 self.ui.setconfig('paths', key, value, 'subrepo')
739 739
740 740 defpath = _abssource(self._repo, abort=False)
741 741 defpushpath = _abssource(self._repo, True, abort=False)
742 742 addpathconfig('default', defpath)
743 743 if defpath != defpushpath:
744 744 addpathconfig('default-push', defpushpath)
745 745
746 746 fp = self._repo.vfs("hgrc", "w", text=True)
747 747 try:
748 748 fp.write(''.join(lines))
749 749 finally:
750 750 fp.close()
751 751
752 752 @annotatesubrepoerror
753 753 def add(self, ui, match, prefix, explicitonly, **opts):
754 754 return cmdutil.add(ui, self._repo, match,
755 755 self.wvfs.reljoin(prefix, self._path),
756 756 explicitonly, **opts)
757 757
758 758 @annotatesubrepoerror
759 759 def addremove(self, m, prefix, opts, dry_run, similarity):
760 760 # In the same way as sub directories are processed, once in a subrepo,
761 761 # always entry any of its subrepos. Don't corrupt the options that will
762 762 # be used to process sibling subrepos however.
763 763 opts = copy.copy(opts)
764 764 opts['subrepos'] = True
765 765 return scmutil.addremove(self._repo, m,
766 766 self.wvfs.reljoin(prefix, self._path), opts,
767 767 dry_run, similarity)
768 768
769 769 @annotatesubrepoerror
770 770 def cat(self, match, prefix, **opts):
771 771 rev = self._state[1]
772 772 ctx = self._repo[rev]
773 773 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
774 774
775 775 @annotatesubrepoerror
776 776 def status(self, rev2, **opts):
777 777 try:
778 778 rev1 = self._state[1]
779 779 ctx1 = self._repo[rev1]
780 780 ctx2 = self._repo[rev2]
781 781 return self._repo.status(ctx1, ctx2, **opts)
782 782 except error.RepoLookupError as inst:
783 783 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
784 784 % (inst, subrelpath(self)))
785 785 return scmutil.status([], [], [], [], [], [], [])
786 786
787 787 @annotatesubrepoerror
788 788 def diff(self, ui, diffopts, node2, match, prefix, **opts):
789 789 try:
790 790 node1 = node.bin(self._state[1])
791 791 # We currently expect node2 to come from substate and be
792 792 # in hex format
793 793 if node2 is not None:
794 794 node2 = node.bin(node2)
795 795 cmdutil.diffordiffstat(ui, self._repo, diffopts,
796 796 node1, node2, match,
797 797 prefix=posixpath.join(prefix, self._path),
798 798 listsubrepos=True, **opts)
799 799 except error.RepoLookupError as inst:
800 800 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
801 801 % (inst, subrelpath(self)))
802 802
803 803 @annotatesubrepoerror
804 804 def archive(self, archiver, prefix, match=None, decode=True):
805 805 self._get(self._state + ('hg',))
806 806 total = abstractsubrepo.archive(self, archiver, prefix, match)
807 807 rev = self._state[1]
808 808 ctx = self._repo[rev]
809 809 for subpath in ctx.substate:
810 810 s = subrepo(ctx, subpath, True)
811 811 submatch = matchmod.subdirmatcher(subpath, match)
812 812 total += s.archive(archiver, prefix + self._path + '/', submatch,
813 813 decode)
814 814 return total
815 815
816 816 @annotatesubrepoerror
817 817 def dirty(self, ignoreupdate=False):
818 818 r = self._state[1]
819 819 if r == '' and not ignoreupdate: # no state recorded
820 820 return True
821 821 w = self._repo[None]
822 822 if r != w.p1().hex() and not ignoreupdate:
823 823 # different version checked out
824 824 return True
825 825 return w.dirty() # working directory changed
826 826
827 827 def basestate(self):
828 828 return self._repo['.'].hex()
829 829
830 830 def checknested(self, path):
831 831 return self._repo._checknested(self._repo.wjoin(path))
832 832
833 833 @annotatesubrepoerror
834 834 def commit(self, text, user, date):
835 835 # don't bother committing in the subrepo if it's only been
836 836 # updated
837 837 if not self.dirty(True):
838 838 return self._repo['.'].hex()
839 839 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
840 840 n = self._repo.commit(text, user, date)
841 841 if not n:
842 842 return self._repo['.'].hex() # different version checked out
843 843 return node.hex(n)
844 844
845 845 @annotatesubrepoerror
846 846 def phase(self, state):
847 847 return self._repo[state].phase()
848 848
849 849 @annotatesubrepoerror
850 850 def remove(self):
851 851 # we can't fully delete the repository as it may contain
852 852 # local-only history
853 853 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
854 854 hg.clean(self._repo, node.nullid, False)
855 855
856 856 def _get(self, state):
857 857 source, revision, kind = state
858 858 if revision in self._repo.unfiltered():
859 859 return True
860 860 self._repo._subsource = source
861 861 srcurl = _abssource(self._repo)
862 862 other = hg.peer(self._repo, {}, srcurl)
863 863 if len(self._repo) == 0:
864 864 self.ui.status(_('cloning subrepo %s from %s\n')
865 865 % (subrelpath(self), srcurl))
866 866 parentrepo = self._repo._subparent
867 867 # use self._repo.vfs instead of self.wvfs to remove .hg only
868 868 self._repo.vfs.rmtree()
869 869 other, cloned = hg.clone(self._repo._subparent.baseui, {},
870 870 other, self._repo.root,
871 871 update=False)
872 872 self._repo = cloned.local()
873 873 self._initrepo(parentrepo, source, create=True)
874 874 self._cachestorehash(srcurl)
875 875 else:
876 876 self.ui.status(_('pulling subrepo %s from %s\n')
877 877 % (subrelpath(self), srcurl))
878 878 cleansub = self.storeclean(srcurl)
879 879 exchange.pull(self._repo, other)
880 880 if cleansub:
881 881 # keep the repo clean after pull
882 882 self._cachestorehash(srcurl)
883 883 return False
884 884
885 885 @annotatesubrepoerror
886 886 def get(self, state, overwrite=False):
887 887 inrepo = self._get(state)
888 888 source, revision, kind = state
889 889 repo = self._repo
890 890 repo.ui.debug("getting subrepo %s\n" % self._path)
891 891 if inrepo:
892 892 urepo = repo.unfiltered()
893 893 ctx = urepo[revision]
894 894 if ctx.hidden():
895 895 urepo.ui.warn(
896 896 _('revision %s in subrepo %s is hidden\n') \
897 897 % (revision[0:12], self._path))
898 898 repo = urepo
899 899 hg.updaterepo(repo, revision, overwrite)
900 900
901 901 @annotatesubrepoerror
902 902 def merge(self, state):
903 903 self._get(state)
904 904 cur = self._repo['.']
905 905 dst = self._repo[state[1]]
906 906 anc = dst.ancestor(cur)
907 907
908 908 def mergefunc():
909 909 if anc == cur and dst.branch() == cur.branch():
910 910 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
911 911 hg.update(self._repo, state[1])
912 912 elif anc == dst:
913 913 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
914 914 else:
915 915 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
916 916 hg.merge(self._repo, state[1], remind=False)
917 917
918 918 wctx = self._repo[None]
919 919 if self.dirty():
920 920 if anc != dst:
921 921 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
922 922 mergefunc()
923 923 else:
924 924 mergefunc()
925 925 else:
926 926 mergefunc()
927 927
928 928 @annotatesubrepoerror
929 929 def push(self, opts):
930 930 force = opts.get('force')
931 931 newbranch = opts.get('new_branch')
932 932 ssh = opts.get('ssh')
933 933
934 934 # push subrepos depth-first for coherent ordering
935 935 c = self._repo['']
936 936 subs = c.substate # only repos that are committed
937 937 for s in sorted(subs):
938 938 if c.sub(s).push(opts) == 0:
939 939 return False
940 940
941 941 dsturl = _abssource(self._repo, True)
942 942 if not force:
943 943 if self.storeclean(dsturl):
944 944 self.ui.status(
945 945 _('no changes made to subrepo %s since last push to %s\n')
946 946 % (subrelpath(self), dsturl))
947 947 return None
948 948 self.ui.status(_('pushing subrepo %s to %s\n') %
949 949 (subrelpath(self), dsturl))
950 950 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
951 951 res = exchange.push(self._repo, other, force, newbranch=newbranch)
952 952
953 953 # the repo is now clean
954 954 self._cachestorehash(dsturl)
955 955 return res.cgresult
956 956
957 957 @annotatesubrepoerror
958 958 def outgoing(self, ui, dest, opts):
959 959 if 'rev' in opts or 'branch' in opts:
960 960 opts = copy.copy(opts)
961 961 opts.pop('rev', None)
962 962 opts.pop('branch', None)
963 963 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
964 964
965 965 @annotatesubrepoerror
966 966 def incoming(self, ui, source, opts):
967 967 if 'rev' in opts or 'branch' in opts:
968 968 opts = copy.copy(opts)
969 969 opts.pop('rev', None)
970 970 opts.pop('branch', None)
971 971 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
972 972
973 973 @annotatesubrepoerror
974 974 def files(self):
975 975 rev = self._state[1]
976 976 ctx = self._repo[rev]
977 977 return ctx.manifest().keys()
978 978
979 979 def filedata(self, name, decode):
980 980 rev = self._state[1]
981 981 data = self._repo[rev][name].data()
982 982 if decode:
983 983 data = self._repo.wwritedata(name, data)
984 984 return data
985 985
986 986 def fileflags(self, name):
987 987 rev = self._state[1]
988 988 ctx = self._repo[rev]
989 989 return ctx.flags(name)
990 990
991 991 @annotatesubrepoerror
992 992 def printfiles(self, ui, m, fm, fmt, subrepos):
993 993 # If the parent context is a workingctx, use the workingctx here for
994 994 # consistency.
995 995 if self._ctx.rev() is None:
996 996 ctx = self._repo[None]
997 997 else:
998 998 rev = self._state[1]
999 999 ctx = self._repo[rev]
1000 1000 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1001 1001
1002 1002 @annotatesubrepoerror
1003 1003 def getfileset(self, expr):
1004 1004 if self._ctx.rev() is None:
1005 1005 ctx = self._repo[None]
1006 1006 else:
1007 1007 rev = self._state[1]
1008 1008 ctx = self._repo[rev]
1009 1009
1010 1010 files = ctx.getfileset(expr)
1011 1011
1012 1012 for subpath in ctx.substate:
1013 1013 sub = ctx.sub(subpath)
1014 1014
1015 1015 try:
1016 1016 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1017 1017 except error.LookupError:
1018 1018 self.ui.status(_("skipping missing subrepository: %s\n")
1019 1019 % self.wvfs.reljoin(reporelpath(self), subpath))
1020 1020 return files
1021 1021
1022 1022 def walk(self, match):
1023 1023 ctx = self._repo[None]
1024 1024 return ctx.walk(match)
1025 1025
1026 1026 @annotatesubrepoerror
1027 1027 def forget(self, match, prefix):
1028 1028 return cmdutil.forget(self.ui, self._repo, match,
1029 1029 self.wvfs.reljoin(prefix, self._path), True)
1030 1030
1031 1031 @annotatesubrepoerror
1032 1032 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1033 1033 return cmdutil.remove(self.ui, self._repo, matcher,
1034 1034 self.wvfs.reljoin(prefix, self._path),
1035 1035 after, force, subrepos)
1036 1036
1037 1037 @annotatesubrepoerror
1038 1038 def revert(self, substate, *pats, **opts):
1039 1039 # reverting a subrepo is a 2 step process:
1040 1040 # 1. if the no_backup is not set, revert all modified
1041 1041 # files inside the subrepo
1042 1042 # 2. update the subrepo to the revision specified in
1043 1043 # the corresponding substate dictionary
1044 1044 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1045 1045 if not opts.get('no_backup'):
1046 1046 # Revert all files on the subrepo, creating backups
1047 1047 # Note that this will not recursively revert subrepos
1048 1048 # We could do it if there was a set:subrepos() predicate
1049 1049 opts = opts.copy()
1050 1050 opts['date'] = None
1051 1051 opts['rev'] = substate[1]
1052 1052
1053 1053 self.filerevert(*pats, **opts)
1054 1054
1055 1055 # Update the repo to the revision specified in the given substate
1056 1056 if not opts.get('dry_run'):
1057 1057 self.get(substate, overwrite=True)
1058 1058
1059 1059 def filerevert(self, *pats, **opts):
1060 1060 ctx = self._repo[opts['rev']]
1061 1061 parents = self._repo.dirstate.parents()
1062 1062 if opts.get('all'):
1063 1063 pats = ['set:modified()']
1064 1064 else:
1065 1065 pats = []
1066 1066 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1067 1067
1068 1068 def shortid(self, revid):
1069 1069 return revid[:12]
1070 1070
1071 1071 def verify(self):
1072 1072 try:
1073 1073 rev = self._state[1]
1074 1074 ctx = self._repo.unfiltered()[rev]
1075 1075 if ctx.hidden():
1076 1076 # Since hidden revisions aren't pushed/pulled, it seems worth an
1077 1077 # explicit warning.
1078 1078 ui = self._repo.ui
1079 1079 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1080 1080 (self._relpath, node.short(self._ctx.node())))
1081 1081 return 0
1082 1082 except error.RepoLookupError:
1083 1083 # A missing subrepo revision may be a case of needing to pull it, so
1084 1084 # don't treat this as an error.
1085 1085 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1086 1086 (self._relpath, node.short(self._ctx.node())))
1087 1087 return 0
1088 1088
1089 1089 @propertycache
1090 1090 def wvfs(self):
1091 1091 """return own wvfs for efficiency and consistency
1092 1092 """
1093 1093 return self._repo.wvfs
1094 1094
1095 1095 @propertycache
1096 1096 def _relpath(self):
1097 1097 """return path to this subrepository as seen from outermost repository
1098 1098 """
1099 1099 # Keep consistent dir separators by avoiding vfs.join(self._path)
1100 1100 return reporelpath(self._repo)
1101 1101
1102 1102 class svnsubrepo(abstractsubrepo):
1103 1103 def __init__(self, ctx, path, state, allowcreate):
1104 1104 super(svnsubrepo, self).__init__(ctx, path)
1105 1105 self._state = state
1106 1106 self._exe = util.findexe('svn')
1107 1107 if not self._exe:
1108 1108 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1109 1109 % self._path)
1110 1110
1111 1111 def _svncommand(self, commands, filename='', failok=False):
1112 1112 cmd = [self._exe]
1113 1113 extrakw = {}
1114 1114 if not self.ui.interactive():
1115 1115 # Making stdin be a pipe should prevent svn from behaving
1116 1116 # interactively even if we can't pass --non-interactive.
1117 1117 extrakw['stdin'] = subprocess.PIPE
1118 1118 # Starting in svn 1.5 --non-interactive is a global flag
1119 1119 # instead of being per-command, but we need to support 1.4 so
1120 1120 # we have to be intelligent about what commands take
1121 1121 # --non-interactive.
1122 1122 if commands[0] in ('update', 'checkout', 'commit'):
1123 1123 cmd.append('--non-interactive')
1124 1124 cmd.extend(commands)
1125 1125 if filename is not None:
1126 1126 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1127 1127 self._path, filename)
1128 1128 cmd.append(path)
1129 1129 env = dict(encoding.environ)
1130 1130 # Avoid localized output, preserve current locale for everything else.
1131 1131 lc_all = env.get('LC_ALL')
1132 1132 if lc_all:
1133 1133 env['LANG'] = lc_all
1134 1134 del env['LC_ALL']
1135 1135 env['LC_MESSAGES'] = 'C'
1136 1136 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1137 1137 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1138 1138 universal_newlines=True, env=env, **extrakw)
1139 1139 stdout, stderr = p.communicate()
1140 1140 stderr = stderr.strip()
1141 1141 if not failok:
1142 1142 if p.returncode:
1143 1143 raise error.Abort(stderr or 'exited with code %d'
1144 1144 % p.returncode)
1145 1145 if stderr:
1146 1146 self.ui.warn(stderr + '\n')
1147 1147 return stdout, stderr
1148 1148
1149 1149 @propertycache
1150 1150 def _svnversion(self):
1151 1151 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1152 1152 m = re.search(r'^(\d+)\.(\d+)', output)
1153 1153 if not m:
1154 1154 raise error.Abort(_('cannot retrieve svn tool version'))
1155 1155 return (int(m.group(1)), int(m.group(2)))
1156 1156
1157 1157 def _wcrevs(self):
1158 1158 # Get the working directory revision as well as the last
1159 1159 # commit revision so we can compare the subrepo state with
1160 1160 # both. We used to store the working directory one.
1161 1161 output, err = self._svncommand(['info', '--xml'])
1162 1162 doc = xml.dom.minidom.parseString(output)
1163 1163 entries = doc.getElementsByTagName('entry')
1164 1164 lastrev, rev = '0', '0'
1165 1165 if entries:
1166 1166 rev = str(entries[0].getAttribute('revision')) or '0'
1167 1167 commits = entries[0].getElementsByTagName('commit')
1168 1168 if commits:
1169 1169 lastrev = str(commits[0].getAttribute('revision')) or '0'
1170 1170 return (lastrev, rev)
1171 1171
1172 1172 def _wcrev(self):
1173 1173 return self._wcrevs()[0]
1174 1174
1175 1175 def _wcchanged(self):
1176 1176 """Return (changes, extchanges, missing) where changes is True
1177 1177 if the working directory was changed, extchanges is
1178 1178 True if any of these changes concern an external entry and missing
1179 1179 is True if any change is a missing entry.
1180 1180 """
1181 1181 output, err = self._svncommand(['status', '--xml'])
1182 1182 externals, changes, missing = [], [], []
1183 1183 doc = xml.dom.minidom.parseString(output)
1184 1184 for e in doc.getElementsByTagName('entry'):
1185 1185 s = e.getElementsByTagName('wc-status')
1186 1186 if not s:
1187 1187 continue
1188 1188 item = s[0].getAttribute('item')
1189 1189 props = s[0].getAttribute('props')
1190 1190 path = e.getAttribute('path')
1191 1191 if item == 'external':
1192 1192 externals.append(path)
1193 1193 elif item == 'missing':
1194 1194 missing.append(path)
1195 1195 if (item not in ('', 'normal', 'unversioned', 'external')
1196 1196 or props not in ('', 'none', 'normal')):
1197 1197 changes.append(path)
1198 1198 for path in changes:
1199 1199 for ext in externals:
1200 1200 if path == ext or path.startswith(ext + pycompat.ossep):
1201 1201 return True, True, bool(missing)
1202 1202 return bool(changes), False, bool(missing)
1203 1203
1204 1204 def dirty(self, ignoreupdate=False):
1205 1205 if not self._wcchanged()[0]:
1206 1206 if self._state[1] in self._wcrevs() or ignoreupdate:
1207 1207 return False
1208 1208 return True
1209 1209
1210 1210 def basestate(self):
1211 1211 lastrev, rev = self._wcrevs()
1212 1212 if lastrev != rev:
1213 1213 # Last committed rev is not the same than rev. We would
1214 1214 # like to take lastrev but we do not know if the subrepo
1215 1215 # URL exists at lastrev. Test it and fallback to rev it
1216 1216 # is not there.
1217 1217 try:
1218 1218 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1219 1219 return lastrev
1220 1220 except error.Abort:
1221 1221 pass
1222 1222 return rev
1223 1223
1224 1224 @annotatesubrepoerror
1225 1225 def commit(self, text, user, date):
1226 1226 # user and date are out of our hands since svn is centralized
1227 1227 changed, extchanged, missing = self._wcchanged()
1228 1228 if not changed:
1229 1229 return self.basestate()
1230 1230 if extchanged:
1231 1231 # Do not try to commit externals
1232 1232 raise error.Abort(_('cannot commit svn externals'))
1233 1233 if missing:
1234 1234 # svn can commit with missing entries but aborting like hg
1235 1235 # seems a better approach.
1236 1236 raise error.Abort(_('cannot commit missing svn entries'))
1237 1237 commitinfo, err = self._svncommand(['commit', '-m', text])
1238 1238 self.ui.status(commitinfo)
1239 1239 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1240 1240 if not newrev:
1241 1241 if not commitinfo.strip():
1242 1242 # Sometimes, our definition of "changed" differs from
1243 1243 # svn one. For instance, svn ignores missing files
1244 1244 # when committing. If there are only missing files, no
1245 1245 # commit is made, no output and no error code.
1246 1246 raise error.Abort(_('failed to commit svn changes'))
1247 1247 raise error.Abort(commitinfo.splitlines()[-1])
1248 1248 newrev = newrev.groups()[0]
1249 1249 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1250 1250 return newrev
1251 1251
1252 1252 @annotatesubrepoerror
1253 1253 def remove(self):
1254 1254 if self.dirty():
1255 1255 self.ui.warn(_('not removing repo %s because '
1256 1256 'it has changes.\n') % self._path)
1257 1257 return
1258 1258 self.ui.note(_('removing subrepo %s\n') % self._path)
1259 1259
1260 1260 self.wvfs.rmtree(forcibly=True)
1261 1261 try:
1262 1262 pwvfs = self._ctx.repo().wvfs
1263 1263 pwvfs.removedirs(pwvfs.dirname(self._path))
1264 1264 except OSError:
1265 1265 pass
1266 1266
1267 1267 @annotatesubrepoerror
1268 1268 def get(self, state, overwrite=False):
1269 1269 if overwrite:
1270 1270 self._svncommand(['revert', '--recursive'])
1271 1271 args = ['checkout']
1272 1272 if self._svnversion >= (1, 5):
1273 1273 args.append('--force')
1274 1274 # The revision must be specified at the end of the URL to properly
1275 1275 # update to a directory which has since been deleted and recreated.
1276 1276 args.append('%s@%s' % (state[0], state[1]))
1277 1277 status, err = self._svncommand(args, failok=True)
1278 1278 _sanitize(self.ui, self.wvfs, '.svn')
1279 1279 if not re.search('Checked out revision [0-9]+.', status):
1280 1280 if ('is already a working copy for a different URL' in err
1281 1281 and (self._wcchanged()[:2] == (False, False))):
1282 1282 # obstructed but clean working copy, so just blow it away.
1283 1283 self.remove()
1284 1284 self.get(state, overwrite=False)
1285 1285 return
1286 1286 raise error.Abort((status or err).splitlines()[-1])
1287 1287 self.ui.status(status)
1288 1288
1289 1289 @annotatesubrepoerror
1290 1290 def merge(self, state):
1291 1291 old = self._state[1]
1292 1292 new = state[1]
1293 1293 wcrev = self._wcrev()
1294 1294 if new != wcrev:
1295 1295 dirty = old == wcrev or self._wcchanged()[0]
1296 1296 if _updateprompt(self.ui, self, dirty, wcrev, new):
1297 1297 self.get(state, False)
1298 1298
1299 1299 def push(self, opts):
1300 1300 # push is a no-op for SVN
1301 1301 return True
1302 1302
1303 1303 @annotatesubrepoerror
1304 1304 def files(self):
1305 1305 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1306 1306 doc = xml.dom.minidom.parseString(output)
1307 1307 paths = []
1308 1308 for e in doc.getElementsByTagName('entry'):
1309 1309 kind = str(e.getAttribute('kind'))
1310 1310 if kind != 'file':
1311 1311 continue
1312 1312 name = ''.join(c.data for c
1313 1313 in e.getElementsByTagName('name')[0].childNodes
1314 1314 if c.nodeType == c.TEXT_NODE)
1315 1315 paths.append(name.encode('utf-8'))
1316 1316 return paths
1317 1317
1318 1318 def filedata(self, name, decode):
1319 1319 return self._svncommand(['cat'], name)[0]
1320 1320
1321 1321
1322 1322 class gitsubrepo(abstractsubrepo):
1323 1323 def __init__(self, ctx, path, state, allowcreate):
1324 1324 super(gitsubrepo, self).__init__(ctx, path)
1325 1325 self._state = state
1326 1326 self._abspath = ctx.repo().wjoin(path)
1327 1327 self._subparent = ctx.repo()
1328 1328 self._ensuregit()
1329 1329
1330 1330 def _ensuregit(self):
1331 1331 try:
1332 1332 self._gitexecutable = 'git'
1333 1333 out, err = self._gitnodir(['--version'])
1334 1334 except OSError as e:
1335 1335 genericerror = _("error executing git for subrepo '%s': %s")
1336 1336 notfoundhint = _("check git is installed and in your PATH")
1337 1337 if e.errno != errno.ENOENT:
1338 1338 raise error.Abort(genericerror % (self._path, e.strerror))
1339 1339 elif pycompat.osname == 'nt':
1340 1340 try:
1341 1341 self._gitexecutable = 'git.cmd'
1342 1342 out, err = self._gitnodir(['--version'])
1343 1343 except OSError as e2:
1344 1344 if e2.errno == errno.ENOENT:
1345 1345 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1346 1346 " for subrepo '%s'") % self._path,
1347 1347 hint=notfoundhint)
1348 1348 else:
1349 1349 raise error.Abort(genericerror % (self._path,
1350 1350 e2.strerror))
1351 1351 else:
1352 1352 raise error.Abort(_("couldn't find git for subrepo '%s'")
1353 1353 % self._path, hint=notfoundhint)
1354 1354 versionstatus = self._checkversion(out)
1355 1355 if versionstatus == 'unknown':
1356 1356 self.ui.warn(_('cannot retrieve git version\n'))
1357 1357 elif versionstatus == 'abort':
1358 1358 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1359 1359 elif versionstatus == 'warning':
1360 1360 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1361 1361
1362 1362 @staticmethod
1363 1363 def _gitversion(out):
1364 1364 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1365 1365 if m:
1366 1366 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1367 1367
1368 1368 m = re.search(r'^git version (\d+)\.(\d+)', out)
1369 1369 if m:
1370 1370 return (int(m.group(1)), int(m.group(2)), 0)
1371 1371
1372 1372 return -1
1373 1373
1374 1374 @staticmethod
1375 1375 def _checkversion(out):
1376 1376 '''ensure git version is new enough
1377 1377
1378 1378 >>> _checkversion = gitsubrepo._checkversion
1379 1379 >>> _checkversion('git version 1.6.0')
1380 1380 'ok'
1381 1381 >>> _checkversion('git version 1.8.5')
1382 1382 'ok'
1383 1383 >>> _checkversion('git version 1.4.0')
1384 1384 'abort'
1385 1385 >>> _checkversion('git version 1.5.0')
1386 1386 'warning'
1387 1387 >>> _checkversion('git version 1.9-rc0')
1388 1388 'ok'
1389 1389 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1390 1390 'ok'
1391 1391 >>> _checkversion('git version 1.9.0.GIT')
1392 1392 'ok'
1393 1393 >>> _checkversion('git version 12345')
1394 1394 'unknown'
1395 1395 >>> _checkversion('no')
1396 1396 'unknown'
1397 1397 '''
1398 1398 version = gitsubrepo._gitversion(out)
1399 1399 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1400 1400 # despite the docstring comment. For now, error on 1.4.0, warn on
1401 1401 # 1.5.0 but attempt to continue.
1402 1402 if version == -1:
1403 1403 return 'unknown'
1404 1404 if version < (1, 5, 0):
1405 1405 return 'abort'
1406 1406 elif version < (1, 6, 0):
1407 1407 return 'warning'
1408 1408 return 'ok'
1409 1409
1410 1410 def _gitcommand(self, commands, env=None, stream=False):
1411 1411 return self._gitdir(commands, env=env, stream=stream)[0]
1412 1412
1413 1413 def _gitdir(self, commands, env=None, stream=False):
1414 1414 return self._gitnodir(commands, env=env, stream=stream,
1415 1415 cwd=self._abspath)
1416 1416
1417 1417 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1418 1418 """Calls the git command
1419 1419
1420 1420 The methods tries to call the git command. versions prior to 1.6.0
1421 1421 are not supported and very probably fail.
1422 1422 """
1423 1423 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1424 1424 if env is None:
1425 1425 env = encoding.environ.copy()
1426 1426 # disable localization for Git output (issue5176)
1427 1427 env['LC_ALL'] = 'C'
1428 1428 # fix for Git CVE-2015-7545
1429 1429 if 'GIT_ALLOW_PROTOCOL' not in env:
1430 1430 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1431 1431 # unless ui.quiet is set, print git's stderr,
1432 1432 # which is mostly progress and useful info
1433 1433 errpipe = None
1434 1434 if self.ui.quiet:
1435 1435 errpipe = open(os.devnull, 'w')
1436 1436 if self.ui._colormode and len(commands) and commands[0] == "diff":
1437 1437 # insert the argument in the front,
1438 1438 # the end of git diff arguments is used for paths
1439 1439 commands.insert(1, '--color')
1440 1440 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1441 1441 cwd=cwd, env=env, close_fds=util.closefds,
1442 1442 stdout=subprocess.PIPE, stderr=errpipe)
1443 1443 if stream:
1444 1444 return p.stdout, None
1445 1445
1446 1446 retdata = p.stdout.read().strip()
1447 1447 # wait for the child to exit to avoid race condition.
1448 1448 p.wait()
1449 1449
1450 1450 if p.returncode != 0 and p.returncode != 1:
1451 1451 # there are certain error codes that are ok
1452 1452 command = commands[0]
1453 1453 if command in ('cat-file', 'symbolic-ref'):
1454 1454 return retdata, p.returncode
1455 1455 # for all others, abort
1456 1456 raise error.Abort(_('git %s error %d in %s') %
1457 1457 (command, p.returncode, self._relpath))
1458 1458
1459 1459 return retdata, p.returncode
1460 1460
1461 1461 def _gitmissing(self):
1462 1462 return not self.wvfs.exists('.git')
1463 1463
1464 1464 def _gitstate(self):
1465 1465 return self._gitcommand(['rev-parse', 'HEAD'])
1466 1466
1467 1467 def _gitcurrentbranch(self):
1468 1468 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1469 1469 if err:
1470 1470 current = None
1471 1471 return current
1472 1472
1473 1473 def _gitremote(self, remote):
1474 1474 out = self._gitcommand(['remote', 'show', '-n', remote])
1475 1475 line = out.split('\n')[1]
1476 1476 i = line.index('URL: ') + len('URL: ')
1477 1477 return line[i:]
1478 1478
1479 1479 def _githavelocally(self, revision):
1480 1480 out, code = self._gitdir(['cat-file', '-e', revision])
1481 1481 return code == 0
1482 1482
1483 1483 def _gitisancestor(self, r1, r2):
1484 1484 base = self._gitcommand(['merge-base', r1, r2])
1485 1485 return base == r1
1486 1486
1487 1487 def _gitisbare(self):
1488 1488 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1489 1489
1490 1490 def _gitupdatestat(self):
1491 1491 """This must be run before git diff-index.
1492 1492 diff-index only looks at changes to file stat;
1493 1493 this command looks at file contents and updates the stat."""
1494 1494 self._gitcommand(['update-index', '-q', '--refresh'])
1495 1495
1496 1496 def _gitbranchmap(self):
1497 1497 '''returns 2 things:
1498 1498 a map from git branch to revision
1499 1499 a map from revision to branches'''
1500 1500 branch2rev = {}
1501 1501 rev2branch = {}
1502 1502
1503 1503 out = self._gitcommand(['for-each-ref', '--format',
1504 1504 '%(objectname) %(refname)'])
1505 1505 for line in out.split('\n'):
1506 1506 revision, ref = line.split(' ')
1507 1507 if (not ref.startswith('refs/heads/') and
1508 1508 not ref.startswith('refs/remotes/')):
1509 1509 continue
1510 1510 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1511 1511 continue # ignore remote/HEAD redirects
1512 1512 branch2rev[ref] = revision
1513 1513 rev2branch.setdefault(revision, []).append(ref)
1514 1514 return branch2rev, rev2branch
1515 1515
1516 1516 def _gittracking(self, branches):
1517 1517 'return map of remote branch to local tracking branch'
1518 1518 # assumes no more than one local tracking branch for each remote
1519 1519 tracking = {}
1520 1520 for b in branches:
1521 1521 if b.startswith('refs/remotes/'):
1522 1522 continue
1523 1523 bname = b.split('/', 2)[2]
1524 1524 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1525 1525 if remote:
1526 1526 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1527 1527 tracking['refs/remotes/%s/%s' %
1528 1528 (remote, ref.split('/', 2)[2])] = b
1529 1529 return tracking
1530 1530
1531 1531 def _abssource(self, source):
1532 1532 if '://' not in source:
1533 1533 # recognize the scp syntax as an absolute source
1534 1534 colon = source.find(':')
1535 1535 if colon != -1 and '/' not in source[:colon]:
1536 1536 return source
1537 1537 self._subsource = source
1538 1538 return _abssource(self)
1539 1539
1540 1540 def _fetch(self, source, revision):
1541 1541 if self._gitmissing():
1542 1542 source = self._abssource(source)
1543 1543 self.ui.status(_('cloning subrepo %s from %s\n') %
1544 1544 (self._relpath, source))
1545 1545 self._gitnodir(['clone', source, self._abspath])
1546 1546 if self._githavelocally(revision):
1547 1547 return
1548 1548 self.ui.status(_('pulling subrepo %s from %s\n') %
1549 1549 (self._relpath, self._gitremote('origin')))
1550 1550 # try only origin: the originally cloned repo
1551 1551 self._gitcommand(['fetch'])
1552 1552 if not self._githavelocally(revision):
1553 1553 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1554 1554 (revision, self._relpath))
1555 1555
1556 1556 @annotatesubrepoerror
1557 1557 def dirty(self, ignoreupdate=False):
1558 1558 if self._gitmissing():
1559 1559 return self._state[1] != ''
1560 1560 if self._gitisbare():
1561 1561 return True
1562 1562 if not ignoreupdate and self._state[1] != self._gitstate():
1563 1563 # different version checked out
1564 1564 return True
1565 1565 # check for staged changes or modified files; ignore untracked files
1566 1566 self._gitupdatestat()
1567 1567 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1568 1568 return code == 1
1569 1569
1570 1570 def basestate(self):
1571 1571 return self._gitstate()
1572 1572
1573 1573 @annotatesubrepoerror
1574 1574 def get(self, state, overwrite=False):
1575 1575 source, revision, kind = state
1576 1576 if not revision:
1577 1577 self.remove()
1578 1578 return
1579 1579 self._fetch(source, revision)
1580 1580 # if the repo was set to be bare, unbare it
1581 1581 if self._gitisbare():
1582 1582 self._gitcommand(['config', 'core.bare', 'false'])
1583 1583 if self._gitstate() == revision:
1584 1584 self._gitcommand(['reset', '--hard', 'HEAD'])
1585 1585 return
1586 1586 elif self._gitstate() == revision:
1587 1587 if overwrite:
1588 1588 # first reset the index to unmark new files for commit, because
1589 1589 # reset --hard will otherwise throw away files added for commit,
1590 1590 # not just unmark them.
1591 1591 self._gitcommand(['reset', 'HEAD'])
1592 1592 self._gitcommand(['reset', '--hard', 'HEAD'])
1593 1593 return
1594 1594 branch2rev, rev2branch = self._gitbranchmap()
1595 1595
1596 1596 def checkout(args):
1597 1597 cmd = ['checkout']
1598 1598 if overwrite:
1599 1599 # first reset the index to unmark new files for commit, because
1600 1600 # the -f option will otherwise throw away files added for
1601 1601 # commit, not just unmark them.
1602 1602 self._gitcommand(['reset', 'HEAD'])
1603 1603 cmd.append('-f')
1604 1604 self._gitcommand(cmd + args)
1605 1605 _sanitize(self.ui, self.wvfs, '.git')
1606 1606
1607 1607 def rawcheckout():
1608 1608 # no branch to checkout, check it out with no branch
1609 1609 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1610 1610 self._relpath)
1611 1611 self.ui.warn(_('check out a git branch if you intend '
1612 1612 'to make changes\n'))
1613 1613 checkout(['-q', revision])
1614 1614
1615 1615 if revision not in rev2branch:
1616 1616 rawcheckout()
1617 1617 return
1618 1618 branches = rev2branch[revision]
1619 1619 firstlocalbranch = None
1620 1620 for b in branches:
1621 1621 if b == 'refs/heads/master':
1622 1622 # master trumps all other branches
1623 1623 checkout(['refs/heads/master'])
1624 1624 return
1625 1625 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1626 1626 firstlocalbranch = b
1627 1627 if firstlocalbranch:
1628 1628 checkout([firstlocalbranch])
1629 1629 return
1630 1630
1631 1631 tracking = self._gittracking(branch2rev.keys())
1632 1632 # choose a remote branch already tracked if possible
1633 1633 remote = branches[0]
1634 1634 if remote not in tracking:
1635 1635 for b in branches:
1636 1636 if b in tracking:
1637 1637 remote = b
1638 1638 break
1639 1639
1640 1640 if remote not in tracking:
1641 1641 # create a new local tracking branch
1642 1642 local = remote.split('/', 3)[3]
1643 1643 checkout(['-b', local, remote])
1644 1644 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1645 1645 # When updating to a tracked remote branch,
1646 1646 # if the local tracking branch is downstream of it,
1647 1647 # a normal `git pull` would have performed a "fast-forward merge"
1648 1648 # which is equivalent to updating the local branch to the remote.
1649 1649 # Since we are only looking at branching at update, we need to
1650 1650 # detect this situation and perform this action lazily.
1651 1651 if tracking[remote] != self._gitcurrentbranch():
1652 1652 checkout([tracking[remote]])
1653 1653 self._gitcommand(['merge', '--ff', remote])
1654 1654 _sanitize(self.ui, self.wvfs, '.git')
1655 1655 else:
1656 1656 # a real merge would be required, just checkout the revision
1657 1657 rawcheckout()
1658 1658
1659 1659 @annotatesubrepoerror
1660 1660 def commit(self, text, user, date):
1661 1661 if self._gitmissing():
1662 1662 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1663 1663 cmd = ['commit', '-a', '-m', text]
1664 1664 env = encoding.environ.copy()
1665 1665 if user:
1666 1666 cmd += ['--author', user]
1667 1667 if date:
1668 1668 # git's date parser silently ignores when seconds < 1e9
1669 1669 # convert to ISO8601
1670 1670 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1671 1671 '%Y-%m-%dT%H:%M:%S %1%2')
1672 1672 self._gitcommand(cmd, env=env)
1673 1673 # make sure commit works otherwise HEAD might not exist under certain
1674 1674 # circumstances
1675 1675 return self._gitstate()
1676 1676
1677 1677 @annotatesubrepoerror
1678 1678 def merge(self, state):
1679 1679 source, revision, kind = state
1680 1680 self._fetch(source, revision)
1681 1681 base = self._gitcommand(['merge-base', revision, self._state[1]])
1682 1682 self._gitupdatestat()
1683 1683 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1684 1684
1685 1685 def mergefunc():
1686 1686 if base == revision:
1687 1687 self.get(state) # fast forward merge
1688 1688 elif base != self._state[1]:
1689 1689 self._gitcommand(['merge', '--no-commit', revision])
1690 1690 _sanitize(self.ui, self.wvfs, '.git')
1691 1691
1692 1692 if self.dirty():
1693 1693 if self._gitstate() != revision:
1694 1694 dirty = self._gitstate() == self._state[1] or code != 0
1695 1695 if _updateprompt(self.ui, self, dirty,
1696 1696 self._state[1][:7], revision[:7]):
1697 1697 mergefunc()
1698 1698 else:
1699 1699 mergefunc()
1700 1700
1701 1701 @annotatesubrepoerror
1702 1702 def push(self, opts):
1703 1703 force = opts.get('force')
1704 1704
1705 1705 if not self._state[1]:
1706 1706 return True
1707 1707 if self._gitmissing():
1708 1708 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1709 1709 # if a branch in origin contains the revision, nothing to do
1710 1710 branch2rev, rev2branch = self._gitbranchmap()
1711 1711 if self._state[1] in rev2branch:
1712 1712 for b in rev2branch[self._state[1]]:
1713 1713 if b.startswith('refs/remotes/origin/'):
1714 1714 return True
1715 1715 for b, revision in branch2rev.iteritems():
1716 1716 if b.startswith('refs/remotes/origin/'):
1717 1717 if self._gitisancestor(self._state[1], revision):
1718 1718 return True
1719 1719 # otherwise, try to push the currently checked out branch
1720 1720 cmd = ['push']
1721 1721 if force:
1722 1722 cmd.append('--force')
1723 1723
1724 1724 current = self._gitcurrentbranch()
1725 1725 if current:
1726 1726 # determine if the current branch is even useful
1727 1727 if not self._gitisancestor(self._state[1], current):
1728 1728 self.ui.warn(_('unrelated git branch checked out '
1729 1729 'in subrepo %s\n') % self._relpath)
1730 1730 return False
1731 1731 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1732 1732 (current.split('/', 2)[2], self._relpath))
1733 1733 ret = self._gitdir(cmd + ['origin', current])
1734 1734 return ret[1] == 0
1735 1735 else:
1736 1736 self.ui.warn(_('no branch checked out in subrepo %s\n'
1737 1737 'cannot push revision %s\n') %
1738 1738 (self._relpath, self._state[1]))
1739 1739 return False
1740 1740
1741 1741 @annotatesubrepoerror
1742 1742 def add(self, ui, match, prefix, explicitonly, **opts):
1743 1743 if self._gitmissing():
1744 1744 return []
1745 1745
1746 1746 (modified, added, removed,
1747 1747 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1748 1748 clean=True)
1749 1749
1750 1750 tracked = set()
1751 1751 # dirstates 'amn' warn, 'r' is added again
1752 1752 for l in (modified, added, deleted, clean):
1753 1753 tracked.update(l)
1754 1754
1755 1755 # Unknown files not of interest will be rejected by the matcher
1756 1756 files = unknown
1757 1757 files.extend(match.files())
1758 1758
1759 1759 rejected = []
1760 1760
1761 1761 files = [f for f in sorted(set(files)) if match(f)]
1762 1762 for f in files:
1763 1763 exact = match.exact(f)
1764 1764 command = ["add"]
1765 1765 if exact:
1766 1766 command.append("-f") #should be added, even if ignored
1767 1767 if ui.verbose or not exact:
1768 1768 ui.status(_('adding %s\n') % match.rel(f))
1769 1769
1770 1770 if f in tracked: # hg prints 'adding' even if already tracked
1771 1771 if exact:
1772 1772 rejected.append(f)
1773 1773 continue
1774 if not opts.get('dry_run'):
1774 if not opts.get(r'dry_run'):
1775 1775 self._gitcommand(command + [f])
1776 1776
1777 1777 for f in rejected:
1778 1778 ui.warn(_("%s already tracked!\n") % match.abs(f))
1779 1779
1780 1780 return rejected
1781 1781
1782 1782 @annotatesubrepoerror
1783 1783 def remove(self):
1784 1784 if self._gitmissing():
1785 1785 return
1786 1786 if self.dirty():
1787 1787 self.ui.warn(_('not removing repo %s because '
1788 1788 'it has changes.\n') % self._relpath)
1789 1789 return
1790 1790 # we can't fully delete the repository as it may contain
1791 1791 # local-only history
1792 1792 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1793 1793 self._gitcommand(['config', 'core.bare', 'true'])
1794 1794 for f, kind in self.wvfs.readdir():
1795 1795 if f == '.git':
1796 1796 continue
1797 1797 if kind == stat.S_IFDIR:
1798 1798 self.wvfs.rmtree(f)
1799 1799 else:
1800 1800 self.wvfs.unlink(f)
1801 1801
1802 1802 def archive(self, archiver, prefix, match=None, decode=True):
1803 1803 total = 0
1804 1804 source, revision = self._state
1805 1805 if not revision:
1806 1806 return total
1807 1807 self._fetch(source, revision)
1808 1808
1809 1809 # Parse git's native archive command.
1810 1810 # This should be much faster than manually traversing the trees
1811 1811 # and objects with many subprocess calls.
1812 1812 tarstream = self._gitcommand(['archive', revision], stream=True)
1813 1813 tar = tarfile.open(fileobj=tarstream, mode='r|')
1814 1814 relpath = subrelpath(self)
1815 1815 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1816 1816 for i, info in enumerate(tar):
1817 1817 if info.isdir():
1818 1818 continue
1819 1819 if match and not match(info.name):
1820 1820 continue
1821 1821 if info.issym():
1822 1822 data = info.linkname
1823 1823 else:
1824 1824 data = tar.extractfile(info).read()
1825 1825 archiver.addfile(prefix + self._path + '/' + info.name,
1826 1826 info.mode, info.issym(), data)
1827 1827 total += 1
1828 1828 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1829 1829 unit=_('files'))
1830 1830 self.ui.progress(_('archiving (%s)') % relpath, None)
1831 1831 return total
1832 1832
1833 1833
1834 1834 @annotatesubrepoerror
1835 1835 def cat(self, match, prefix, **opts):
1836 1836 rev = self._state[1]
1837 1837 if match.anypats():
1838 1838 return 1 #No support for include/exclude yet
1839 1839
1840 1840 if not match.files():
1841 1841 return 1
1842 1842
1843 1843 for f in match.files():
1844 1844 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1845 1845 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1846 1846 self._ctx.node(),
1847 1847 pathname=self.wvfs.reljoin(prefix, f))
1848 1848 fp.write(output)
1849 1849 fp.close()
1850 1850 return 0
1851 1851
1852 1852
1853 1853 @annotatesubrepoerror
1854 1854 def status(self, rev2, **opts):
1855 1855 rev1 = self._state[1]
1856 1856 if self._gitmissing() or not rev1:
1857 1857 # if the repo is missing, return no results
1858 1858 return scmutil.status([], [], [], [], [], [], [])
1859 1859 modified, added, removed = [], [], []
1860 1860 self._gitupdatestat()
1861 1861 if rev2:
1862 1862 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1863 1863 else:
1864 1864 command = ['diff-index', '--no-renames', rev1]
1865 1865 out = self._gitcommand(command)
1866 1866 for line in out.split('\n'):
1867 1867 tab = line.find('\t')
1868 1868 if tab == -1:
1869 1869 continue
1870 1870 status, f = line[tab - 1], line[tab + 1:]
1871 1871 if status == 'M':
1872 1872 modified.append(f)
1873 1873 elif status == 'A':
1874 1874 added.append(f)
1875 1875 elif status == 'D':
1876 1876 removed.append(f)
1877 1877
1878 1878 deleted, unknown, ignored, clean = [], [], [], []
1879 1879
1880 1880 command = ['status', '--porcelain', '-z']
1881 1881 if opts.get('unknown'):
1882 1882 command += ['--untracked-files=all']
1883 1883 if opts.get('ignored'):
1884 1884 command += ['--ignored']
1885 1885 out = self._gitcommand(command)
1886 1886
1887 1887 changedfiles = set()
1888 1888 changedfiles.update(modified)
1889 1889 changedfiles.update(added)
1890 1890 changedfiles.update(removed)
1891 1891 for line in out.split('\0'):
1892 1892 if not line:
1893 1893 continue
1894 1894 st = line[0:2]
1895 1895 #moves and copies show 2 files on one line
1896 1896 if line.find('\0') >= 0:
1897 1897 filename1, filename2 = line[3:].split('\0')
1898 1898 else:
1899 1899 filename1 = line[3:]
1900 1900 filename2 = None
1901 1901
1902 1902 changedfiles.add(filename1)
1903 1903 if filename2:
1904 1904 changedfiles.add(filename2)
1905 1905
1906 1906 if st == '??':
1907 1907 unknown.append(filename1)
1908 1908 elif st == '!!':
1909 1909 ignored.append(filename1)
1910 1910
1911 1911 if opts.get('clean'):
1912 1912 out = self._gitcommand(['ls-files'])
1913 1913 for f in out.split('\n'):
1914 1914 if not f in changedfiles:
1915 1915 clean.append(f)
1916 1916
1917 1917 return scmutil.status(modified, added, removed, deleted,
1918 1918 unknown, ignored, clean)
1919 1919
1920 1920 @annotatesubrepoerror
1921 1921 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1922 1922 node1 = self._state[1]
1923 1923 cmd = ['diff', '--no-renames']
1924 1924 if opts['stat']:
1925 1925 cmd.append('--stat')
1926 1926 else:
1927 1927 # for Git, this also implies '-p'
1928 1928 cmd.append('-U%d' % diffopts.context)
1929 1929
1930 1930 gitprefix = self.wvfs.reljoin(prefix, self._path)
1931 1931
1932 1932 if diffopts.noprefix:
1933 1933 cmd.extend(['--src-prefix=%s/' % gitprefix,
1934 1934 '--dst-prefix=%s/' % gitprefix])
1935 1935 else:
1936 1936 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1937 1937 '--dst-prefix=b/%s/' % gitprefix])
1938 1938
1939 1939 if diffopts.ignorews:
1940 1940 cmd.append('--ignore-all-space')
1941 1941 if diffopts.ignorewsamount:
1942 1942 cmd.append('--ignore-space-change')
1943 1943 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1944 1944 and diffopts.ignoreblanklines:
1945 1945 cmd.append('--ignore-blank-lines')
1946 1946
1947 1947 cmd.append(node1)
1948 1948 if node2:
1949 1949 cmd.append(node2)
1950 1950
1951 1951 output = ""
1952 1952 if match.always():
1953 1953 output += self._gitcommand(cmd) + '\n'
1954 1954 else:
1955 1955 st = self.status(node2)[:3]
1956 1956 files = [f for sublist in st for f in sublist]
1957 1957 for f in files:
1958 1958 if match(f):
1959 1959 output += self._gitcommand(cmd + ['--', f]) + '\n'
1960 1960
1961 1961 if output.strip():
1962 1962 ui.write(output)
1963 1963
1964 1964 @annotatesubrepoerror
1965 1965 def revert(self, substate, *pats, **opts):
1966 1966 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1967 1967 if not opts.get('no_backup'):
1968 1968 status = self.status(None)
1969 1969 names = status.modified
1970 1970 for name in names:
1971 1971 bakname = scmutil.origpath(self.ui, self._subparent, name)
1972 1972 self.ui.note(_('saving current version of %s as %s\n') %
1973 1973 (name, bakname))
1974 1974 self.wvfs.rename(name, bakname)
1975 1975
1976 1976 if not opts.get('dry_run'):
1977 1977 self.get(substate, overwrite=True)
1978 1978 return []
1979 1979
1980 1980 def shortid(self, revid):
1981 1981 return revid[:7]
1982 1982
1983 1983 types = {
1984 1984 'hg': hgsubrepo,
1985 1985 'svn': svnsubrepo,
1986 1986 'git': gitsubrepo,
1987 1987 }
General Comments 0
You need to be logged in to leave comments. Login now