##// END OF EJS Templates
import: add --partial flag to create a changeset despite failed hunks...
Pierre-Yves David -
r21553:bee0e1cf default
parent child Browse files
Show More
@@ -1,2501 +1,2511 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 node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_('uncommitted changes'))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
113 113 """get appropriate commit message editor according to '--edit' option
114 114
115 115 'finishdesc' is a function to be called with edited commit message
116 116 (= 'description' of the new changeset) just after editing, but
117 117 before checking empty-ness. It should return actual text to be
118 118 stored into history. This allows to change description before
119 119 storing.
120 120
121 121 'extramsg' is a extra message to be shown in the editor instead of
122 122 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
123 123 is automatically added.
124 124
125 125 'getcommiteditor' returns 'commitforceeditor' regardless of
126 126 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
127 127 they are specific for usage in MQ.
128 128 """
129 129 if edit or finishdesc or extramsg:
130 130 return lambda r, c, s: commitforceeditor(r, c, s,
131 131 finishdesc=finishdesc,
132 132 extramsg=extramsg)
133 133 else:
134 134 return commiteditor
135 135
136 136 def loglimit(opts):
137 137 """get the log limit according to option -l/--limit"""
138 138 limit = opts.get('limit')
139 139 if limit:
140 140 try:
141 141 limit = int(limit)
142 142 except ValueError:
143 143 raise util.Abort(_('limit must be a positive integer'))
144 144 if limit <= 0:
145 145 raise util.Abort(_('limit must be positive'))
146 146 else:
147 147 limit = None
148 148 return limit
149 149
150 150 def makefilename(repo, pat, node, desc=None,
151 151 total=None, seqno=None, revwidth=None, pathname=None):
152 152 node_expander = {
153 153 'H': lambda: hex(node),
154 154 'R': lambda: str(repo.changelog.rev(node)),
155 155 'h': lambda: short(node),
156 156 'm': lambda: re.sub('[^\w]', '_', str(desc))
157 157 }
158 158 expander = {
159 159 '%': lambda: '%',
160 160 'b': lambda: os.path.basename(repo.root),
161 161 }
162 162
163 163 try:
164 164 if node:
165 165 expander.update(node_expander)
166 166 if node:
167 167 expander['r'] = (lambda:
168 168 str(repo.changelog.rev(node)).zfill(revwidth or 0))
169 169 if total is not None:
170 170 expander['N'] = lambda: str(total)
171 171 if seqno is not None:
172 172 expander['n'] = lambda: str(seqno)
173 173 if total is not None and seqno is not None:
174 174 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
175 175 if pathname is not None:
176 176 expander['s'] = lambda: os.path.basename(pathname)
177 177 expander['d'] = lambda: os.path.dirname(pathname) or '.'
178 178 expander['p'] = lambda: pathname
179 179
180 180 newname = []
181 181 patlen = len(pat)
182 182 i = 0
183 183 while i < patlen:
184 184 c = pat[i]
185 185 if c == '%':
186 186 i += 1
187 187 c = pat[i]
188 188 c = expander[c]()
189 189 newname.append(c)
190 190 i += 1
191 191 return ''.join(newname)
192 192 except KeyError, inst:
193 193 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
194 194 inst.args[0])
195 195
196 196 def makefileobj(repo, pat, node=None, desc=None, total=None,
197 197 seqno=None, revwidth=None, mode='wb', modemap=None,
198 198 pathname=None):
199 199
200 200 writable = mode not in ('r', 'rb')
201 201
202 202 if not pat or pat == '-':
203 203 fp = writable and repo.ui.fout or repo.ui.fin
204 204 if util.safehasattr(fp, 'fileno'):
205 205 return os.fdopen(os.dup(fp.fileno()), mode)
206 206 else:
207 207 # if this fp can't be duped properly, return
208 208 # a dummy object that can be closed
209 209 class wrappedfileobj(object):
210 210 noop = lambda x: None
211 211 def __init__(self, f):
212 212 self.f = f
213 213 def __getattr__(self, attr):
214 214 if attr == 'close':
215 215 return self.noop
216 216 else:
217 217 return getattr(self.f, attr)
218 218
219 219 return wrappedfileobj(fp)
220 220 if util.safehasattr(pat, 'write') and writable:
221 221 return pat
222 222 if util.safehasattr(pat, 'read') and 'r' in mode:
223 223 return pat
224 224 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
225 225 if modemap is not None:
226 226 mode = modemap.get(fn, mode)
227 227 if mode == 'wb':
228 228 modemap[fn] = 'ab'
229 229 return open(fn, mode)
230 230
231 231 def openrevlog(repo, cmd, file_, opts):
232 232 """opens the changelog, manifest, a filelog or a given revlog"""
233 233 cl = opts['changelog']
234 234 mf = opts['manifest']
235 235 msg = None
236 236 if cl and mf:
237 237 msg = _('cannot specify --changelog and --manifest at the same time')
238 238 elif cl or mf:
239 239 if file_:
240 240 msg = _('cannot specify filename with --changelog or --manifest')
241 241 elif not repo:
242 242 msg = _('cannot specify --changelog or --manifest '
243 243 'without a repository')
244 244 if msg:
245 245 raise util.Abort(msg)
246 246
247 247 r = None
248 248 if repo:
249 249 if cl:
250 250 r = repo.unfiltered().changelog
251 251 elif mf:
252 252 r = repo.manifest
253 253 elif file_:
254 254 filelog = repo.file(file_)
255 255 if len(filelog):
256 256 r = filelog
257 257 if not r:
258 258 if not file_:
259 259 raise error.CommandError(cmd, _('invalid arguments'))
260 260 if not os.path.isfile(file_):
261 261 raise util.Abort(_("revlog '%s' not found") % file_)
262 262 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
263 263 file_[:-2] + ".i")
264 264 return r
265 265
266 266 def copy(ui, repo, pats, opts, rename=False):
267 267 # called with the repo lock held
268 268 #
269 269 # hgsep => pathname that uses "/" to separate directories
270 270 # ossep => pathname that uses os.sep to separate directories
271 271 cwd = repo.getcwd()
272 272 targets = {}
273 273 after = opts.get("after")
274 274 dryrun = opts.get("dry_run")
275 275 wctx = repo[None]
276 276
277 277 def walkpat(pat):
278 278 srcs = []
279 279 badstates = after and '?' or '?r'
280 280 m = scmutil.match(repo[None], [pat], opts, globbed=True)
281 281 for abs in repo.walk(m):
282 282 state = repo.dirstate[abs]
283 283 rel = m.rel(abs)
284 284 exact = m.exact(abs)
285 285 if state in badstates:
286 286 if exact and state == '?':
287 287 ui.warn(_('%s: not copying - file is not managed\n') % rel)
288 288 if exact and state == 'r':
289 289 ui.warn(_('%s: not copying - file has been marked for'
290 290 ' remove\n') % rel)
291 291 continue
292 292 # abs: hgsep
293 293 # rel: ossep
294 294 srcs.append((abs, rel, exact))
295 295 return srcs
296 296
297 297 # abssrc: hgsep
298 298 # relsrc: ossep
299 299 # otarget: ossep
300 300 def copyfile(abssrc, relsrc, otarget, exact):
301 301 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
302 302 if '/' in abstarget:
303 303 # We cannot normalize abstarget itself, this would prevent
304 304 # case only renames, like a => A.
305 305 abspath, absname = abstarget.rsplit('/', 1)
306 306 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
307 307 reltarget = repo.pathto(abstarget, cwd)
308 308 target = repo.wjoin(abstarget)
309 309 src = repo.wjoin(abssrc)
310 310 state = repo.dirstate[abstarget]
311 311
312 312 scmutil.checkportable(ui, abstarget)
313 313
314 314 # check for collisions
315 315 prevsrc = targets.get(abstarget)
316 316 if prevsrc is not None:
317 317 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
318 318 (reltarget, repo.pathto(abssrc, cwd),
319 319 repo.pathto(prevsrc, cwd)))
320 320 return
321 321
322 322 # check for overwrites
323 323 exists = os.path.lexists(target)
324 324 samefile = False
325 325 if exists and abssrc != abstarget:
326 326 if (repo.dirstate.normalize(abssrc) ==
327 327 repo.dirstate.normalize(abstarget)):
328 328 if not rename:
329 329 ui.warn(_("%s: can't copy - same file\n") % reltarget)
330 330 return
331 331 exists = False
332 332 samefile = True
333 333
334 334 if not after and exists or after and state in 'mn':
335 335 if not opts['force']:
336 336 ui.warn(_('%s: not overwriting - file exists\n') %
337 337 reltarget)
338 338 return
339 339
340 340 if after:
341 341 if not exists:
342 342 if rename:
343 343 ui.warn(_('%s: not recording move - %s does not exist\n') %
344 344 (relsrc, reltarget))
345 345 else:
346 346 ui.warn(_('%s: not recording copy - %s does not exist\n') %
347 347 (relsrc, reltarget))
348 348 return
349 349 elif not dryrun:
350 350 try:
351 351 if exists:
352 352 os.unlink(target)
353 353 targetdir = os.path.dirname(target) or '.'
354 354 if not os.path.isdir(targetdir):
355 355 os.makedirs(targetdir)
356 356 if samefile:
357 357 tmp = target + "~hgrename"
358 358 os.rename(src, tmp)
359 359 os.rename(tmp, target)
360 360 else:
361 361 util.copyfile(src, target)
362 362 srcexists = True
363 363 except IOError, inst:
364 364 if inst.errno == errno.ENOENT:
365 365 ui.warn(_('%s: deleted in working copy\n') % relsrc)
366 366 srcexists = False
367 367 else:
368 368 ui.warn(_('%s: cannot copy - %s\n') %
369 369 (relsrc, inst.strerror))
370 370 return True # report a failure
371 371
372 372 if ui.verbose or not exact:
373 373 if rename:
374 374 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
375 375 else:
376 376 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
377 377
378 378 targets[abstarget] = abssrc
379 379
380 380 # fix up dirstate
381 381 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
382 382 dryrun=dryrun, cwd=cwd)
383 383 if rename and not dryrun:
384 384 if not after and srcexists and not samefile:
385 385 util.unlinkpath(repo.wjoin(abssrc))
386 386 wctx.forget([abssrc])
387 387
388 388 # pat: ossep
389 389 # dest ossep
390 390 # srcs: list of (hgsep, hgsep, ossep, bool)
391 391 # return: function that takes hgsep and returns ossep
392 392 def targetpathfn(pat, dest, srcs):
393 393 if os.path.isdir(pat):
394 394 abspfx = pathutil.canonpath(repo.root, cwd, pat)
395 395 abspfx = util.localpath(abspfx)
396 396 if destdirexists:
397 397 striplen = len(os.path.split(abspfx)[0])
398 398 else:
399 399 striplen = len(abspfx)
400 400 if striplen:
401 401 striplen += len(os.sep)
402 402 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
403 403 elif destdirexists:
404 404 res = lambda p: os.path.join(dest,
405 405 os.path.basename(util.localpath(p)))
406 406 else:
407 407 res = lambda p: dest
408 408 return res
409 409
410 410 # pat: ossep
411 411 # dest ossep
412 412 # srcs: list of (hgsep, hgsep, ossep, bool)
413 413 # return: function that takes hgsep and returns ossep
414 414 def targetpathafterfn(pat, dest, srcs):
415 415 if matchmod.patkind(pat):
416 416 # a mercurial pattern
417 417 res = lambda p: os.path.join(dest,
418 418 os.path.basename(util.localpath(p)))
419 419 else:
420 420 abspfx = pathutil.canonpath(repo.root, cwd, pat)
421 421 if len(abspfx) < len(srcs[0][0]):
422 422 # A directory. Either the target path contains the last
423 423 # component of the source path or it does not.
424 424 def evalpath(striplen):
425 425 score = 0
426 426 for s in srcs:
427 427 t = os.path.join(dest, util.localpath(s[0])[striplen:])
428 428 if os.path.lexists(t):
429 429 score += 1
430 430 return score
431 431
432 432 abspfx = util.localpath(abspfx)
433 433 striplen = len(abspfx)
434 434 if striplen:
435 435 striplen += len(os.sep)
436 436 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
437 437 score = evalpath(striplen)
438 438 striplen1 = len(os.path.split(abspfx)[0])
439 439 if striplen1:
440 440 striplen1 += len(os.sep)
441 441 if evalpath(striplen1) > score:
442 442 striplen = striplen1
443 443 res = lambda p: os.path.join(dest,
444 444 util.localpath(p)[striplen:])
445 445 else:
446 446 # a file
447 447 if destdirexists:
448 448 res = lambda p: os.path.join(dest,
449 449 os.path.basename(util.localpath(p)))
450 450 else:
451 451 res = lambda p: dest
452 452 return res
453 453
454 454
455 455 pats = scmutil.expandpats(pats)
456 456 if not pats:
457 457 raise util.Abort(_('no source or destination specified'))
458 458 if len(pats) == 1:
459 459 raise util.Abort(_('no destination specified'))
460 460 dest = pats.pop()
461 461 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
462 462 if not destdirexists:
463 463 if len(pats) > 1 or matchmod.patkind(pats[0]):
464 464 raise util.Abort(_('with multiple sources, destination must be an '
465 465 'existing directory'))
466 466 if util.endswithsep(dest):
467 467 raise util.Abort(_('destination %s is not a directory') % dest)
468 468
469 469 tfn = targetpathfn
470 470 if after:
471 471 tfn = targetpathafterfn
472 472 copylist = []
473 473 for pat in pats:
474 474 srcs = walkpat(pat)
475 475 if not srcs:
476 476 continue
477 477 copylist.append((tfn(pat, dest, srcs), srcs))
478 478 if not copylist:
479 479 raise util.Abort(_('no files to copy'))
480 480
481 481 errors = 0
482 482 for targetpath, srcs in copylist:
483 483 for abssrc, relsrc, exact in srcs:
484 484 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
485 485 errors += 1
486 486
487 487 if errors:
488 488 ui.warn(_('(consider using --after)\n'))
489 489
490 490 return errors != 0
491 491
492 492 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
493 493 runargs=None, appendpid=False):
494 494 '''Run a command as a service.'''
495 495
496 496 def writepid(pid):
497 497 if opts['pid_file']:
498 498 mode = appendpid and 'a' or 'w'
499 499 fp = open(opts['pid_file'], mode)
500 500 fp.write(str(pid) + '\n')
501 501 fp.close()
502 502
503 503 if opts['daemon'] and not opts['daemon_pipefds']:
504 504 # Signal child process startup with file removal
505 505 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
506 506 os.close(lockfd)
507 507 try:
508 508 if not runargs:
509 509 runargs = util.hgcmd() + sys.argv[1:]
510 510 runargs.append('--daemon-pipefds=%s' % lockpath)
511 511 # Don't pass --cwd to the child process, because we've already
512 512 # changed directory.
513 513 for i in xrange(1, len(runargs)):
514 514 if runargs[i].startswith('--cwd='):
515 515 del runargs[i]
516 516 break
517 517 elif runargs[i].startswith('--cwd'):
518 518 del runargs[i:i + 2]
519 519 break
520 520 def condfn():
521 521 return not os.path.exists(lockpath)
522 522 pid = util.rundetached(runargs, condfn)
523 523 if pid < 0:
524 524 raise util.Abort(_('child process failed to start'))
525 525 writepid(pid)
526 526 finally:
527 527 try:
528 528 os.unlink(lockpath)
529 529 except OSError, e:
530 530 if e.errno != errno.ENOENT:
531 531 raise
532 532 if parentfn:
533 533 return parentfn(pid)
534 534 else:
535 535 return
536 536
537 537 if initfn:
538 538 initfn()
539 539
540 540 if not opts['daemon']:
541 541 writepid(os.getpid())
542 542
543 543 if opts['daemon_pipefds']:
544 544 lockpath = opts['daemon_pipefds']
545 545 try:
546 546 os.setsid()
547 547 except AttributeError:
548 548 pass
549 549 os.unlink(lockpath)
550 550 util.hidewindow()
551 551 sys.stdout.flush()
552 552 sys.stderr.flush()
553 553
554 554 nullfd = os.open(os.devnull, os.O_RDWR)
555 555 logfilefd = nullfd
556 556 if logfile:
557 557 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
558 558 os.dup2(nullfd, 0)
559 559 os.dup2(logfilefd, 1)
560 560 os.dup2(logfilefd, 2)
561 561 if nullfd not in (0, 1, 2):
562 562 os.close(nullfd)
563 563 if logfile and logfilefd not in (0, 1, 2):
564 564 os.close(logfilefd)
565 565
566 566 if runfn:
567 567 return runfn()
568 568
569 569 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
570 570 """Utility function used by commands.import to import a single patch
571 571
572 572 This function is explicitly defined here to help the evolve extension to
573 573 wrap this part of the import logic.
574 574
575 575 The API is currently a bit ugly because it a simple code translation from
576 576 the import command. Feel free to make it better.
577 577
578 578 :hunk: a patch (as a binary string)
579 579 :parents: nodes that will be parent of the created commit
580 580 :opts: the full dict of option passed to the import command
581 581 :msgs: list to save commit message to.
582 582 (used in case we need to save it when failing)
583 583 :updatefunc: a function that update a repo to a given node
584 584 updatefunc(<repo>, <node>)
585 585 """
586 586 tmpname, message, user, date, branch, nodeid, p1, p2 = \
587 587 patch.extract(ui, hunk)
588 588
589 589 editor = getcommiteditor(**opts)
590 590 update = not opts.get('bypass')
591 591 strip = opts["strip"]
592 592 sim = float(opts.get('similarity') or 0)
593 593 if not tmpname:
594 return (None, None)
594 return (None, None, False)
595 595 msg = _('applied to working directory')
596 596
597 rejects = False
598
597 599 try:
598 600 cmdline_message = logmessage(ui, opts)
599 601 if cmdline_message:
600 602 # pickup the cmdline msg
601 603 message = cmdline_message
602 604 elif message:
603 605 # pickup the patch msg
604 606 message = message.strip()
605 607 else:
606 608 # launch the editor
607 609 message = None
608 610 ui.debug('message:\n%s\n' % message)
609 611
610 612 if len(parents) == 1:
611 613 parents.append(repo[nullid])
612 614 if opts.get('exact'):
613 615 if not nodeid or not p1:
614 616 raise util.Abort(_('not a Mercurial patch'))
615 617 p1 = repo[p1]
616 618 p2 = repo[p2 or nullid]
617 619 elif p2:
618 620 try:
619 621 p1 = repo[p1]
620 622 p2 = repo[p2]
621 623 # Without any options, consider p2 only if the
622 624 # patch is being applied on top of the recorded
623 625 # first parent.
624 626 if p1 != parents[0]:
625 627 p1 = parents[0]
626 628 p2 = repo[nullid]
627 629 except error.RepoError:
628 630 p1, p2 = parents
629 631 else:
630 632 p1, p2 = parents
631 633
632 634 n = None
633 635 if update:
634 636 if p1 != parents[0]:
635 637 updatefunc(repo, p1.node())
636 638 if p2 != parents[1]:
637 639 repo.setparents(p1.node(), p2.node())
638 640
639 641 if opts.get('exact') or opts.get('import_branch'):
640 642 repo.dirstate.setbranch(branch or 'default')
641 643
644 partial = opts.get('partial', False)
642 645 files = set()
643 patch.patch(ui, repo, tmpname, strip=strip, files=files,
644 eolmode=None, similarity=sim / 100.0)
646 try:
647 patch.patch(ui, repo, tmpname, strip=strip, files=files,
648 eolmode=None, similarity=sim / 100.0)
649 except patch.PatchError, e:
650 if not partial:
651 raise util.Abort(str(e))
652 if partial:
653 rejects = True
654
645 655 files = list(files)
646 656 if opts.get('no_commit'):
647 657 if message:
648 658 msgs.append(message)
649 659 else:
650 660 if opts.get('exact') or p2:
651 661 # If you got here, you either use --force and know what
652 662 # you are doing or used --exact or a merge patch while
653 663 # being updated to its first parent.
654 664 m = None
655 665 else:
656 666 m = scmutil.matchfiles(repo, files or [])
657 667 n = repo.commit(message, opts.get('user') or user,
658 668 opts.get('date') or date, match=m,
659 editor=editor)
669 editor=editor, force=partial)
660 670 else:
661 671 if opts.get('exact') or opts.get('import_branch'):
662 672 branch = branch or 'default'
663 673 else:
664 674 branch = p1.branch()
665 675 store = patch.filestore()
666 676 try:
667 677 files = set()
668 678 try:
669 679 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
670 680 files, eolmode=None)
671 681 except patch.PatchError, e:
672 682 raise util.Abort(str(e))
673 683 memctx = context.makememctx(repo, (p1.node(), p2.node()),
674 684 message,
675 685 opts.get('user') or user,
676 686 opts.get('date') or date,
677 687 branch, files, store,
678 688 editor=getcommiteditor())
679 689 n = memctx.commit()
680 690 finally:
681 691 store.close()
682 692 if opts.get('exact') and hex(n) != nodeid:
683 693 raise util.Abort(_('patch is damaged or loses information'))
684 694 if n:
685 695 # i18n: refers to a short changeset id
686 696 msg = _('created %s') % short(n)
687 return (msg, n)
697 return (msg, n, rejects)
688 698 finally:
689 699 os.unlink(tmpname)
690 700
691 701 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
692 702 opts=None):
693 703 '''export changesets as hg patches.'''
694 704
695 705 total = len(revs)
696 706 revwidth = max([len(str(rev)) for rev in revs])
697 707 filemode = {}
698 708
699 709 def single(rev, seqno, fp):
700 710 ctx = repo[rev]
701 711 node = ctx.node()
702 712 parents = [p.node() for p in ctx.parents() if p]
703 713 branch = ctx.branch()
704 714 if switch_parent:
705 715 parents.reverse()
706 716 prev = (parents and parents[0]) or nullid
707 717
708 718 shouldclose = False
709 719 if not fp and len(template) > 0:
710 720 desc_lines = ctx.description().rstrip().split('\n')
711 721 desc = desc_lines[0] #Commit always has a first line.
712 722 fp = makefileobj(repo, template, node, desc=desc, total=total,
713 723 seqno=seqno, revwidth=revwidth, mode='wb',
714 724 modemap=filemode)
715 725 if fp != template:
716 726 shouldclose = True
717 727 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
718 728 repo.ui.note("%s\n" % fp.name)
719 729
720 730 if not fp:
721 731 write = repo.ui.write
722 732 else:
723 733 def write(s, **kw):
724 734 fp.write(s)
725 735
726 736
727 737 write("# HG changeset patch\n")
728 738 write("# User %s\n" % ctx.user())
729 739 write("# Date %d %d\n" % ctx.date())
730 740 write("# %s\n" % util.datestr(ctx.date()))
731 741 if branch and branch != 'default':
732 742 write("# Branch %s\n" % branch)
733 743 write("# Node ID %s\n" % hex(node))
734 744 write("# Parent %s\n" % hex(prev))
735 745 if len(parents) > 1:
736 746 write("# Parent %s\n" % hex(parents[1]))
737 747 write(ctx.description().rstrip())
738 748 write("\n\n")
739 749
740 750 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
741 751 write(chunk, label=label)
742 752
743 753 if shouldclose:
744 754 fp.close()
745 755
746 756 for seqno, rev in enumerate(revs):
747 757 single(rev, seqno + 1, fp)
748 758
749 759 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
750 760 changes=None, stat=False, fp=None, prefix='',
751 761 listsubrepos=False):
752 762 '''show diff or diffstat.'''
753 763 if fp is None:
754 764 write = ui.write
755 765 else:
756 766 def write(s, **kw):
757 767 fp.write(s)
758 768
759 769 if stat:
760 770 diffopts = diffopts.copy(context=0)
761 771 width = 80
762 772 if not ui.plain():
763 773 width = ui.termwidth()
764 774 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
765 775 prefix=prefix)
766 776 for chunk, label in patch.diffstatui(util.iterlines(chunks),
767 777 width=width,
768 778 git=diffopts.git):
769 779 write(chunk, label=label)
770 780 else:
771 781 for chunk, label in patch.diffui(repo, node1, node2, match,
772 782 changes, diffopts, prefix=prefix):
773 783 write(chunk, label=label)
774 784
775 785 if listsubrepos:
776 786 ctx1 = repo[node1]
777 787 ctx2 = repo[node2]
778 788 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
779 789 tempnode2 = node2
780 790 try:
781 791 if node2 is not None:
782 792 tempnode2 = ctx2.substate[subpath][1]
783 793 except KeyError:
784 794 # A subrepo that existed in node1 was deleted between node1 and
785 795 # node2 (inclusive). Thus, ctx2's substate won't contain that
786 796 # subpath. The best we can do is to ignore it.
787 797 tempnode2 = None
788 798 submatch = matchmod.narrowmatcher(subpath, match)
789 799 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
790 800 stat=stat, fp=fp, prefix=prefix)
791 801
792 802 class changeset_printer(object):
793 803 '''show changeset information when templating not requested.'''
794 804
795 805 def __init__(self, ui, repo, patch, diffopts, buffered):
796 806 self.ui = ui
797 807 self.repo = repo
798 808 self.buffered = buffered
799 809 self.patch = patch
800 810 self.diffopts = diffopts
801 811 self.header = {}
802 812 self.hunk = {}
803 813 self.lastheader = None
804 814 self.footer = None
805 815
806 816 def flush(self, rev):
807 817 if rev in self.header:
808 818 h = self.header[rev]
809 819 if h != self.lastheader:
810 820 self.lastheader = h
811 821 self.ui.write(h)
812 822 del self.header[rev]
813 823 if rev in self.hunk:
814 824 self.ui.write(self.hunk[rev])
815 825 del self.hunk[rev]
816 826 return 1
817 827 return 0
818 828
819 829 def close(self):
820 830 if self.footer:
821 831 self.ui.write(self.footer)
822 832
823 833 def show(self, ctx, copies=None, matchfn=None, **props):
824 834 if self.buffered:
825 835 self.ui.pushbuffer()
826 836 self._show(ctx, copies, matchfn, props)
827 837 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
828 838 else:
829 839 self._show(ctx, copies, matchfn, props)
830 840
831 841 def _show(self, ctx, copies, matchfn, props):
832 842 '''show a single changeset or file revision'''
833 843 changenode = ctx.node()
834 844 rev = ctx.rev()
835 845
836 846 if self.ui.quiet:
837 847 self.ui.write("%d:%s\n" % (rev, short(changenode)),
838 848 label='log.node')
839 849 return
840 850
841 851 log = self.repo.changelog
842 852 date = util.datestr(ctx.date())
843 853
844 854 hexfunc = self.ui.debugflag and hex or short
845 855
846 856 parents = [(p, hexfunc(log.node(p)))
847 857 for p in self._meaningful_parentrevs(log, rev)]
848 858
849 859 # i18n: column positioning for "hg log"
850 860 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
851 861 label='log.changeset changeset.%s' % ctx.phasestr())
852 862
853 863 branch = ctx.branch()
854 864 # don't show the default branch name
855 865 if branch != 'default':
856 866 # i18n: column positioning for "hg log"
857 867 self.ui.write(_("branch: %s\n") % branch,
858 868 label='log.branch')
859 869 for bookmark in self.repo.nodebookmarks(changenode):
860 870 # i18n: column positioning for "hg log"
861 871 self.ui.write(_("bookmark: %s\n") % bookmark,
862 872 label='log.bookmark')
863 873 for tag in self.repo.nodetags(changenode):
864 874 # i18n: column positioning for "hg log"
865 875 self.ui.write(_("tag: %s\n") % tag,
866 876 label='log.tag')
867 877 if self.ui.debugflag and ctx.phase():
868 878 # i18n: column positioning for "hg log"
869 879 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
870 880 label='log.phase')
871 881 for parent in parents:
872 882 # i18n: column positioning for "hg log"
873 883 self.ui.write(_("parent: %d:%s\n") % parent,
874 884 label='log.parent changeset.%s' % ctx.phasestr())
875 885
876 886 if self.ui.debugflag:
877 887 mnode = ctx.manifestnode()
878 888 # i18n: column positioning for "hg log"
879 889 self.ui.write(_("manifest: %d:%s\n") %
880 890 (self.repo.manifest.rev(mnode), hex(mnode)),
881 891 label='ui.debug log.manifest')
882 892 # i18n: column positioning for "hg log"
883 893 self.ui.write(_("user: %s\n") % ctx.user(),
884 894 label='log.user')
885 895 # i18n: column positioning for "hg log"
886 896 self.ui.write(_("date: %s\n") % date,
887 897 label='log.date')
888 898
889 899 if self.ui.debugflag:
890 900 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
891 901 for key, value in zip([# i18n: column positioning for "hg log"
892 902 _("files:"),
893 903 # i18n: column positioning for "hg log"
894 904 _("files+:"),
895 905 # i18n: column positioning for "hg log"
896 906 _("files-:")], files):
897 907 if value:
898 908 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
899 909 label='ui.debug log.files')
900 910 elif ctx.files() and self.ui.verbose:
901 911 # i18n: column positioning for "hg log"
902 912 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
903 913 label='ui.note log.files')
904 914 if copies and self.ui.verbose:
905 915 copies = ['%s (%s)' % c for c in copies]
906 916 # i18n: column positioning for "hg log"
907 917 self.ui.write(_("copies: %s\n") % ' '.join(copies),
908 918 label='ui.note log.copies')
909 919
910 920 extra = ctx.extra()
911 921 if extra and self.ui.debugflag:
912 922 for key, value in sorted(extra.items()):
913 923 # i18n: column positioning for "hg log"
914 924 self.ui.write(_("extra: %s=%s\n")
915 925 % (key, value.encode('string_escape')),
916 926 label='ui.debug log.extra')
917 927
918 928 description = ctx.description().strip()
919 929 if description:
920 930 if self.ui.verbose:
921 931 self.ui.write(_("description:\n"),
922 932 label='ui.note log.description')
923 933 self.ui.write(description,
924 934 label='ui.note log.description')
925 935 self.ui.write("\n\n")
926 936 else:
927 937 # i18n: column positioning for "hg log"
928 938 self.ui.write(_("summary: %s\n") %
929 939 description.splitlines()[0],
930 940 label='log.summary')
931 941 self.ui.write("\n")
932 942
933 943 self.showpatch(changenode, matchfn)
934 944
935 945 def showpatch(self, node, matchfn):
936 946 if not matchfn:
937 947 matchfn = self.patch
938 948 if matchfn:
939 949 stat = self.diffopts.get('stat')
940 950 diff = self.diffopts.get('patch')
941 951 diffopts = patch.diffopts(self.ui, self.diffopts)
942 952 prev = self.repo.changelog.parents(node)[0]
943 953 if stat:
944 954 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
945 955 match=matchfn, stat=True)
946 956 if diff:
947 957 if stat:
948 958 self.ui.write("\n")
949 959 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
950 960 match=matchfn, stat=False)
951 961 self.ui.write("\n")
952 962
953 963 def _meaningful_parentrevs(self, log, rev):
954 964 """Return list of meaningful (or all if debug) parentrevs for rev.
955 965
956 966 For merges (two non-nullrev revisions) both parents are meaningful.
957 967 Otherwise the first parent revision is considered meaningful if it
958 968 is not the preceding revision.
959 969 """
960 970 parents = log.parentrevs(rev)
961 971 if not self.ui.debugflag and parents[1] == nullrev:
962 972 if parents[0] >= rev - 1:
963 973 parents = []
964 974 else:
965 975 parents = [parents[0]]
966 976 return parents
967 977
968 978
969 979 class changeset_templater(changeset_printer):
970 980 '''format changeset information.'''
971 981
972 982 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
973 983 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
974 984 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
975 985 defaulttempl = {
976 986 'parent': '{rev}:{node|formatnode} ',
977 987 'manifest': '{rev}:{node|formatnode}',
978 988 'file_copy': '{name} ({source})',
979 989 'extra': '{key}={value|stringescape}'
980 990 }
981 991 # filecopy is preserved for compatibility reasons
982 992 defaulttempl['filecopy'] = defaulttempl['file_copy']
983 993 self.t = templater.templater(mapfile, {'formatnode': formatnode},
984 994 cache=defaulttempl)
985 995 if tmpl:
986 996 self.t.cache['changeset'] = tmpl
987 997
988 998 self.cache = {}
989 999
990 1000 def _meaningful_parentrevs(self, ctx):
991 1001 """Return list of meaningful (or all if debug) parentrevs for rev.
992 1002 """
993 1003 parents = ctx.parents()
994 1004 if len(parents) > 1:
995 1005 return parents
996 1006 if self.ui.debugflag:
997 1007 return [parents[0], self.repo['null']]
998 1008 if parents[0].rev() >= ctx.rev() - 1:
999 1009 return []
1000 1010 return parents
1001 1011
1002 1012 def _show(self, ctx, copies, matchfn, props):
1003 1013 '''show a single changeset or file revision'''
1004 1014
1005 1015 showlist = templatekw.showlist
1006 1016
1007 1017 # showparents() behaviour depends on ui trace level which
1008 1018 # causes unexpected behaviours at templating level and makes
1009 1019 # it harder to extract it in a standalone function. Its
1010 1020 # behaviour cannot be changed so leave it here for now.
1011 1021 def showparents(**args):
1012 1022 ctx = args['ctx']
1013 1023 parents = [[('rev', p.rev()), ('node', p.hex())]
1014 1024 for p in self._meaningful_parentrevs(ctx)]
1015 1025 return showlist('parent', parents, **args)
1016 1026
1017 1027 props = props.copy()
1018 1028 props.update(templatekw.keywords)
1019 1029 props['parents'] = showparents
1020 1030 props['templ'] = self.t
1021 1031 props['ctx'] = ctx
1022 1032 props['repo'] = self.repo
1023 1033 props['revcache'] = {'copies': copies}
1024 1034 props['cache'] = self.cache
1025 1035
1026 1036 # find correct templates for current mode
1027 1037
1028 1038 tmplmodes = [
1029 1039 (True, None),
1030 1040 (self.ui.verbose, 'verbose'),
1031 1041 (self.ui.quiet, 'quiet'),
1032 1042 (self.ui.debugflag, 'debug'),
1033 1043 ]
1034 1044
1035 1045 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1036 1046 for mode, postfix in tmplmodes:
1037 1047 for type in types:
1038 1048 cur = postfix and ('%s_%s' % (type, postfix)) or type
1039 1049 if mode and cur in self.t:
1040 1050 types[type] = cur
1041 1051
1042 1052 try:
1043 1053
1044 1054 # write header
1045 1055 if types['header']:
1046 1056 h = templater.stringify(self.t(types['header'], **props))
1047 1057 if self.buffered:
1048 1058 self.header[ctx.rev()] = h
1049 1059 else:
1050 1060 if self.lastheader != h:
1051 1061 self.lastheader = h
1052 1062 self.ui.write(h)
1053 1063
1054 1064 # write changeset metadata, then patch if requested
1055 1065 key = types['changeset']
1056 1066 self.ui.write(templater.stringify(self.t(key, **props)))
1057 1067 self.showpatch(ctx.node(), matchfn)
1058 1068
1059 1069 if types['footer']:
1060 1070 if not self.footer:
1061 1071 self.footer = templater.stringify(self.t(types['footer'],
1062 1072 **props))
1063 1073
1064 1074 except KeyError, inst:
1065 1075 msg = _("%s: no key named '%s'")
1066 1076 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1067 1077 except SyntaxError, inst:
1068 1078 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1069 1079
1070 1080 def gettemplate(ui, tmpl, style):
1071 1081 """
1072 1082 Find the template matching the given template spec or style.
1073 1083 """
1074 1084
1075 1085 # ui settings
1076 1086 if not tmpl and not style:
1077 1087 tmpl = ui.config('ui', 'logtemplate')
1078 1088 if tmpl:
1079 1089 try:
1080 1090 tmpl = templater.parsestring(tmpl)
1081 1091 except SyntaxError:
1082 1092 tmpl = templater.parsestring(tmpl, quoted=False)
1083 1093 return tmpl, None
1084 1094 else:
1085 1095 style = util.expandpath(ui.config('ui', 'style', ''))
1086 1096
1087 1097 if style:
1088 1098 mapfile = style
1089 1099 if not os.path.split(mapfile)[0]:
1090 1100 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1091 1101 or templater.templatepath(mapfile))
1092 1102 if mapname:
1093 1103 mapfile = mapname
1094 1104 return None, mapfile
1095 1105
1096 1106 if not tmpl:
1097 1107 return None, None
1098 1108
1099 1109 # looks like a literal template?
1100 1110 if '{' in tmpl:
1101 1111 return tmpl, None
1102 1112
1103 1113 # perhaps a stock style?
1104 1114 if not os.path.split(tmpl)[0]:
1105 1115 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1106 1116 or templater.templatepath(tmpl))
1107 1117 if mapname and os.path.isfile(mapname):
1108 1118 return None, mapname
1109 1119
1110 1120 # perhaps it's a reference to [templates]
1111 1121 t = ui.config('templates', tmpl)
1112 1122 if t:
1113 1123 try:
1114 1124 tmpl = templater.parsestring(t)
1115 1125 except SyntaxError:
1116 1126 tmpl = templater.parsestring(t, quoted=False)
1117 1127 return tmpl, None
1118 1128
1119 1129 # perhaps it's a path to a map or a template
1120 1130 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1121 1131 # is it a mapfile for a style?
1122 1132 if os.path.basename(tmpl).startswith("map-"):
1123 1133 return None, os.path.realpath(tmpl)
1124 1134 tmpl = open(tmpl).read()
1125 1135 return tmpl, None
1126 1136
1127 1137 # constant string?
1128 1138 return tmpl, None
1129 1139
1130 1140 def show_changeset(ui, repo, opts, buffered=False):
1131 1141 """show one changeset using template or regular display.
1132 1142
1133 1143 Display format will be the first non-empty hit of:
1134 1144 1. option 'template'
1135 1145 2. option 'style'
1136 1146 3. [ui] setting 'logtemplate'
1137 1147 4. [ui] setting 'style'
1138 1148 If all of these values are either the unset or the empty string,
1139 1149 regular display via changeset_printer() is done.
1140 1150 """
1141 1151 # options
1142 1152 patch = None
1143 1153 if opts.get('patch') or opts.get('stat'):
1144 1154 patch = scmutil.matchall(repo)
1145 1155
1146 1156 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1147 1157
1148 1158 if not tmpl and not mapfile:
1149 1159 return changeset_printer(ui, repo, patch, opts, buffered)
1150 1160
1151 1161 try:
1152 1162 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1153 1163 except SyntaxError, inst:
1154 1164 raise util.Abort(inst.args[0])
1155 1165 return t
1156 1166
1157 1167 def showmarker(ui, marker):
1158 1168 """utility function to display obsolescence marker in a readable way
1159 1169
1160 1170 To be used by debug function."""
1161 1171 ui.write(hex(marker.precnode()))
1162 1172 for repl in marker.succnodes():
1163 1173 ui.write(' ')
1164 1174 ui.write(hex(repl))
1165 1175 ui.write(' %X ' % marker._data[2])
1166 1176 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1167 1177 sorted(marker.metadata().items()))))
1168 1178 ui.write('\n')
1169 1179
1170 1180 def finddate(ui, repo, date):
1171 1181 """Find the tipmost changeset that matches the given date spec"""
1172 1182
1173 1183 df = util.matchdate(date)
1174 1184 m = scmutil.matchall(repo)
1175 1185 results = {}
1176 1186
1177 1187 def prep(ctx, fns):
1178 1188 d = ctx.date()
1179 1189 if df(d[0]):
1180 1190 results[ctx.rev()] = d
1181 1191
1182 1192 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1183 1193 rev = ctx.rev()
1184 1194 if rev in results:
1185 1195 ui.status(_("found revision %s from %s\n") %
1186 1196 (rev, util.datestr(results[rev])))
1187 1197 return str(rev)
1188 1198
1189 1199 raise util.Abort(_("revision matching date not found"))
1190 1200
1191 1201 def increasingwindows(windowsize=8, sizelimit=512):
1192 1202 while True:
1193 1203 yield windowsize
1194 1204 if windowsize < sizelimit:
1195 1205 windowsize *= 2
1196 1206
1197 1207 class FileWalkError(Exception):
1198 1208 pass
1199 1209
1200 1210 def walkfilerevs(repo, match, follow, revs, fncache):
1201 1211 '''Walks the file history for the matched files.
1202 1212
1203 1213 Returns the changeset revs that are involved in the file history.
1204 1214
1205 1215 Throws FileWalkError if the file history can't be walked using
1206 1216 filelogs alone.
1207 1217 '''
1208 1218 wanted = set()
1209 1219 copies = []
1210 1220 minrev, maxrev = min(revs), max(revs)
1211 1221 def filerevgen(filelog, last):
1212 1222 """
1213 1223 Only files, no patterns. Check the history of each file.
1214 1224
1215 1225 Examines filelog entries within minrev, maxrev linkrev range
1216 1226 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1217 1227 tuples in backwards order
1218 1228 """
1219 1229 cl_count = len(repo)
1220 1230 revs = []
1221 1231 for j in xrange(0, last + 1):
1222 1232 linkrev = filelog.linkrev(j)
1223 1233 if linkrev < minrev:
1224 1234 continue
1225 1235 # only yield rev for which we have the changelog, it can
1226 1236 # happen while doing "hg log" during a pull or commit
1227 1237 if linkrev >= cl_count:
1228 1238 break
1229 1239
1230 1240 parentlinkrevs = []
1231 1241 for p in filelog.parentrevs(j):
1232 1242 if p != nullrev:
1233 1243 parentlinkrevs.append(filelog.linkrev(p))
1234 1244 n = filelog.node(j)
1235 1245 revs.append((linkrev, parentlinkrevs,
1236 1246 follow and filelog.renamed(n)))
1237 1247
1238 1248 return reversed(revs)
1239 1249 def iterfiles():
1240 1250 pctx = repo['.']
1241 1251 for filename in match.files():
1242 1252 if follow:
1243 1253 if filename not in pctx:
1244 1254 raise util.Abort(_('cannot follow file not in parent '
1245 1255 'revision: "%s"') % filename)
1246 1256 yield filename, pctx[filename].filenode()
1247 1257 else:
1248 1258 yield filename, None
1249 1259 for filename_node in copies:
1250 1260 yield filename_node
1251 1261
1252 1262 for file_, node in iterfiles():
1253 1263 filelog = repo.file(file_)
1254 1264 if not len(filelog):
1255 1265 if node is None:
1256 1266 # A zero count may be a directory or deleted file, so
1257 1267 # try to find matching entries on the slow path.
1258 1268 if follow:
1259 1269 raise util.Abort(
1260 1270 _('cannot follow nonexistent file: "%s"') % file_)
1261 1271 raise FileWalkError("Cannot walk via filelog")
1262 1272 else:
1263 1273 continue
1264 1274
1265 1275 if node is None:
1266 1276 last = len(filelog) - 1
1267 1277 else:
1268 1278 last = filelog.rev(node)
1269 1279
1270 1280
1271 1281 # keep track of all ancestors of the file
1272 1282 ancestors = set([filelog.linkrev(last)])
1273 1283
1274 1284 # iterate from latest to oldest revision
1275 1285 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1276 1286 if not follow:
1277 1287 if rev > maxrev:
1278 1288 continue
1279 1289 else:
1280 1290 # Note that last might not be the first interesting
1281 1291 # rev to us:
1282 1292 # if the file has been changed after maxrev, we'll
1283 1293 # have linkrev(last) > maxrev, and we still need
1284 1294 # to explore the file graph
1285 1295 if rev not in ancestors:
1286 1296 continue
1287 1297 # XXX insert 1327 fix here
1288 1298 if flparentlinkrevs:
1289 1299 ancestors.update(flparentlinkrevs)
1290 1300
1291 1301 fncache.setdefault(rev, []).append(file_)
1292 1302 wanted.add(rev)
1293 1303 if copied:
1294 1304 copies.append(copied)
1295 1305
1296 1306 return wanted
1297 1307
1298 1308 def walkchangerevs(repo, match, opts, prepare):
1299 1309 '''Iterate over files and the revs in which they changed.
1300 1310
1301 1311 Callers most commonly need to iterate backwards over the history
1302 1312 in which they are interested. Doing so has awful (quadratic-looking)
1303 1313 performance, so we use iterators in a "windowed" way.
1304 1314
1305 1315 We walk a window of revisions in the desired order. Within the
1306 1316 window, we first walk forwards to gather data, then in the desired
1307 1317 order (usually backwards) to display it.
1308 1318
1309 1319 This function returns an iterator yielding contexts. Before
1310 1320 yielding each context, the iterator will first call the prepare
1311 1321 function on each context in the window in forward order.'''
1312 1322
1313 1323 follow = opts.get('follow') or opts.get('follow_first')
1314 1324
1315 1325 if opts.get('rev'):
1316 1326 revs = scmutil.revrange(repo, opts.get('rev'))
1317 1327 elif follow:
1318 1328 revs = repo.revs('reverse(:.)')
1319 1329 else:
1320 1330 revs = revset.spanset(repo)
1321 1331 revs.reverse()
1322 1332 if not revs:
1323 1333 return []
1324 1334 wanted = set()
1325 1335 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1326 1336 fncache = {}
1327 1337 change = repo.changectx
1328 1338
1329 1339 # First step is to fill wanted, the set of revisions that we want to yield.
1330 1340 # When it does not induce extra cost, we also fill fncache for revisions in
1331 1341 # wanted: a cache of filenames that were changed (ctx.files()) and that
1332 1342 # match the file filtering conditions.
1333 1343
1334 1344 if not slowpath and not match.files():
1335 1345 # No files, no patterns. Display all revs.
1336 1346 wanted = revs
1337 1347
1338 1348 if not slowpath and match.files():
1339 1349 # We only have to read through the filelog to find wanted revisions
1340 1350
1341 1351 try:
1342 1352 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1343 1353 except FileWalkError:
1344 1354 slowpath = True
1345 1355
1346 1356 # We decided to fall back to the slowpath because at least one
1347 1357 # of the paths was not a file. Check to see if at least one of them
1348 1358 # existed in history, otherwise simply return
1349 1359 for path in match.files():
1350 1360 if path == '.' or path in repo.store:
1351 1361 break
1352 1362 else:
1353 1363 return []
1354 1364
1355 1365 if slowpath:
1356 1366 # We have to read the changelog to match filenames against
1357 1367 # changed files
1358 1368
1359 1369 if follow:
1360 1370 raise util.Abort(_('can only follow copies/renames for explicit '
1361 1371 'filenames'))
1362 1372
1363 1373 # The slow path checks files modified in every changeset.
1364 1374 # This is really slow on large repos, so compute the set lazily.
1365 1375 class lazywantedset(object):
1366 1376 def __init__(self):
1367 1377 self.set = set()
1368 1378 self.revs = set(revs)
1369 1379
1370 1380 # No need to worry about locality here because it will be accessed
1371 1381 # in the same order as the increasing window below.
1372 1382 def __contains__(self, value):
1373 1383 if value in self.set:
1374 1384 return True
1375 1385 elif not value in self.revs:
1376 1386 return False
1377 1387 else:
1378 1388 self.revs.discard(value)
1379 1389 ctx = change(value)
1380 1390 matches = filter(match, ctx.files())
1381 1391 if matches:
1382 1392 fncache[value] = matches
1383 1393 self.set.add(value)
1384 1394 return True
1385 1395 return False
1386 1396
1387 1397 def discard(self, value):
1388 1398 self.revs.discard(value)
1389 1399 self.set.discard(value)
1390 1400
1391 1401 wanted = lazywantedset()
1392 1402
1393 1403 class followfilter(object):
1394 1404 def __init__(self, onlyfirst=False):
1395 1405 self.startrev = nullrev
1396 1406 self.roots = set()
1397 1407 self.onlyfirst = onlyfirst
1398 1408
1399 1409 def match(self, rev):
1400 1410 def realparents(rev):
1401 1411 if self.onlyfirst:
1402 1412 return repo.changelog.parentrevs(rev)[0:1]
1403 1413 else:
1404 1414 return filter(lambda x: x != nullrev,
1405 1415 repo.changelog.parentrevs(rev))
1406 1416
1407 1417 if self.startrev == nullrev:
1408 1418 self.startrev = rev
1409 1419 return True
1410 1420
1411 1421 if rev > self.startrev:
1412 1422 # forward: all descendants
1413 1423 if not self.roots:
1414 1424 self.roots.add(self.startrev)
1415 1425 for parent in realparents(rev):
1416 1426 if parent in self.roots:
1417 1427 self.roots.add(rev)
1418 1428 return True
1419 1429 else:
1420 1430 # backwards: all parents
1421 1431 if not self.roots:
1422 1432 self.roots.update(realparents(self.startrev))
1423 1433 if rev in self.roots:
1424 1434 self.roots.remove(rev)
1425 1435 self.roots.update(realparents(rev))
1426 1436 return True
1427 1437
1428 1438 return False
1429 1439
1430 1440 # it might be worthwhile to do this in the iterator if the rev range
1431 1441 # is descending and the prune args are all within that range
1432 1442 for rev in opts.get('prune', ()):
1433 1443 rev = repo[rev].rev()
1434 1444 ff = followfilter()
1435 1445 stop = min(revs[0], revs[-1])
1436 1446 for x in xrange(rev, stop - 1, -1):
1437 1447 if ff.match(x):
1438 1448 wanted = wanted - [x]
1439 1449
1440 1450 # Now that wanted is correctly initialized, we can iterate over the
1441 1451 # revision range, yielding only revisions in wanted.
1442 1452 def iterate():
1443 1453 if follow and not match.files():
1444 1454 ff = followfilter(onlyfirst=opts.get('follow_first'))
1445 1455 def want(rev):
1446 1456 return ff.match(rev) and rev in wanted
1447 1457 else:
1448 1458 def want(rev):
1449 1459 return rev in wanted
1450 1460
1451 1461 it = iter(revs)
1452 1462 stopiteration = False
1453 1463 for windowsize in increasingwindows():
1454 1464 nrevs = []
1455 1465 for i in xrange(windowsize):
1456 1466 try:
1457 1467 rev = it.next()
1458 1468 if want(rev):
1459 1469 nrevs.append(rev)
1460 1470 except (StopIteration):
1461 1471 stopiteration = True
1462 1472 break
1463 1473 for rev in sorted(nrevs):
1464 1474 fns = fncache.get(rev)
1465 1475 ctx = change(rev)
1466 1476 if not fns:
1467 1477 def fns_generator():
1468 1478 for f in ctx.files():
1469 1479 if match(f):
1470 1480 yield f
1471 1481 fns = fns_generator()
1472 1482 prepare(ctx, fns)
1473 1483 for rev in nrevs:
1474 1484 yield change(rev)
1475 1485
1476 1486 if stopiteration:
1477 1487 break
1478 1488
1479 1489 return iterate()
1480 1490
1481 1491 def _makelogfilematcher(repo, pats, followfirst):
1482 1492 # When displaying a revision with --patch --follow FILE, we have
1483 1493 # to know which file of the revision must be diffed. With
1484 1494 # --follow, we want the names of the ancestors of FILE in the
1485 1495 # revision, stored in "fcache". "fcache" is populated by
1486 1496 # reproducing the graph traversal already done by --follow revset
1487 1497 # and relating linkrevs to file names (which is not "correct" but
1488 1498 # good enough).
1489 1499 fcache = {}
1490 1500 fcacheready = [False]
1491 1501 pctx = repo['.']
1492 1502 wctx = repo[None]
1493 1503
1494 1504 def populate():
1495 1505 for fn in pats:
1496 1506 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1497 1507 for c in i:
1498 1508 fcache.setdefault(c.linkrev(), set()).add(c.path())
1499 1509
1500 1510 def filematcher(rev):
1501 1511 if not fcacheready[0]:
1502 1512 # Lazy initialization
1503 1513 fcacheready[0] = True
1504 1514 populate()
1505 1515 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1506 1516
1507 1517 return filematcher
1508 1518
1509 1519 def _makelogrevset(repo, pats, opts, revs):
1510 1520 """Return (expr, filematcher) where expr is a revset string built
1511 1521 from log options and file patterns or None. If --stat or --patch
1512 1522 are not passed filematcher is None. Otherwise it is a callable
1513 1523 taking a revision number and returning a match objects filtering
1514 1524 the files to be detailed when displaying the revision.
1515 1525 """
1516 1526 opt2revset = {
1517 1527 'no_merges': ('not merge()', None),
1518 1528 'only_merges': ('merge()', None),
1519 1529 '_ancestors': ('ancestors(%(val)s)', None),
1520 1530 '_fancestors': ('_firstancestors(%(val)s)', None),
1521 1531 '_descendants': ('descendants(%(val)s)', None),
1522 1532 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1523 1533 '_matchfiles': ('_matchfiles(%(val)s)', None),
1524 1534 'date': ('date(%(val)r)', None),
1525 1535 'branch': ('branch(%(val)r)', ' or '),
1526 1536 '_patslog': ('filelog(%(val)r)', ' or '),
1527 1537 '_patsfollow': ('follow(%(val)r)', ' or '),
1528 1538 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1529 1539 'keyword': ('keyword(%(val)r)', ' or '),
1530 1540 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1531 1541 'user': ('user(%(val)r)', ' or '),
1532 1542 }
1533 1543
1534 1544 opts = dict(opts)
1535 1545 # follow or not follow?
1536 1546 follow = opts.get('follow') or opts.get('follow_first')
1537 1547 followfirst = opts.get('follow_first') and 1 or 0
1538 1548 # --follow with FILE behaviour depends on revs...
1539 1549 it = iter(revs)
1540 1550 startrev = it.next()
1541 1551 try:
1542 1552 followdescendants = startrev < it.next()
1543 1553 except (StopIteration):
1544 1554 followdescendants = False
1545 1555
1546 1556 # branch and only_branch are really aliases and must be handled at
1547 1557 # the same time
1548 1558 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1549 1559 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1550 1560 # pats/include/exclude are passed to match.match() directly in
1551 1561 # _matchfiles() revset but walkchangerevs() builds its matcher with
1552 1562 # scmutil.match(). The difference is input pats are globbed on
1553 1563 # platforms without shell expansion (windows).
1554 1564 pctx = repo[None]
1555 1565 match, pats = scmutil.matchandpats(pctx, pats, opts)
1556 1566 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1557 1567 if not slowpath:
1558 1568 for f in match.files():
1559 1569 if follow and f not in pctx:
1560 1570 raise util.Abort(_('cannot follow file not in parent '
1561 1571 'revision: "%s"') % f)
1562 1572 filelog = repo.file(f)
1563 1573 if not filelog:
1564 1574 # A zero count may be a directory or deleted file, so
1565 1575 # try to find matching entries on the slow path.
1566 1576 if follow:
1567 1577 raise util.Abort(
1568 1578 _('cannot follow nonexistent file: "%s"') % f)
1569 1579 slowpath = True
1570 1580
1571 1581 # We decided to fall back to the slowpath because at least one
1572 1582 # of the paths was not a file. Check to see if at least one of them
1573 1583 # existed in history - in that case, we'll continue down the
1574 1584 # slowpath; otherwise, we can turn off the slowpath
1575 1585 if slowpath:
1576 1586 for path in match.files():
1577 1587 if path == '.' or path in repo.store:
1578 1588 break
1579 1589 else:
1580 1590 slowpath = False
1581 1591
1582 1592 if slowpath:
1583 1593 # See walkchangerevs() slow path.
1584 1594 #
1585 1595 if follow:
1586 1596 raise util.Abort(_('can only follow copies/renames for explicit '
1587 1597 'filenames'))
1588 1598 # pats/include/exclude cannot be represented as separate
1589 1599 # revset expressions as their filtering logic applies at file
1590 1600 # level. For instance "-I a -X a" matches a revision touching
1591 1601 # "a" and "b" while "file(a) and not file(b)" does
1592 1602 # not. Besides, filesets are evaluated against the working
1593 1603 # directory.
1594 1604 matchargs = ['r:', 'd:relpath']
1595 1605 for p in pats:
1596 1606 matchargs.append('p:' + p)
1597 1607 for p in opts.get('include', []):
1598 1608 matchargs.append('i:' + p)
1599 1609 for p in opts.get('exclude', []):
1600 1610 matchargs.append('x:' + p)
1601 1611 matchargs = ','.join(('%r' % p) for p in matchargs)
1602 1612 opts['_matchfiles'] = matchargs
1603 1613 else:
1604 1614 if follow:
1605 1615 fpats = ('_patsfollow', '_patsfollowfirst')
1606 1616 fnopats = (('_ancestors', '_fancestors'),
1607 1617 ('_descendants', '_fdescendants'))
1608 1618 if pats:
1609 1619 # follow() revset interprets its file argument as a
1610 1620 # manifest entry, so use match.files(), not pats.
1611 1621 opts[fpats[followfirst]] = list(match.files())
1612 1622 else:
1613 1623 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1614 1624 else:
1615 1625 opts['_patslog'] = list(pats)
1616 1626
1617 1627 filematcher = None
1618 1628 if opts.get('patch') or opts.get('stat'):
1619 1629 if follow:
1620 1630 filematcher = _makelogfilematcher(repo, pats, followfirst)
1621 1631 else:
1622 1632 filematcher = lambda rev: match
1623 1633
1624 1634 expr = []
1625 1635 for op, val in opts.iteritems():
1626 1636 if not val:
1627 1637 continue
1628 1638 if op not in opt2revset:
1629 1639 continue
1630 1640 revop, andor = opt2revset[op]
1631 1641 if '%(val)' not in revop:
1632 1642 expr.append(revop)
1633 1643 else:
1634 1644 if not isinstance(val, list):
1635 1645 e = revop % {'val': val}
1636 1646 else:
1637 1647 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1638 1648 expr.append(e)
1639 1649
1640 1650 if expr:
1641 1651 expr = '(' + ' and '.join(expr) + ')'
1642 1652 else:
1643 1653 expr = None
1644 1654 return expr, filematcher
1645 1655
1646 1656 def getgraphlogrevs(repo, pats, opts):
1647 1657 """Return (revs, expr, filematcher) where revs is an iterable of
1648 1658 revision numbers, expr is a revset string built from log options
1649 1659 and file patterns or None, and used to filter 'revs'. If --stat or
1650 1660 --patch are not passed filematcher is None. Otherwise it is a
1651 1661 callable taking a revision number and returning a match objects
1652 1662 filtering the files to be detailed when displaying the revision.
1653 1663 """
1654 1664 if not len(repo):
1655 1665 return [], None, None
1656 1666 limit = loglimit(opts)
1657 1667 # Default --rev value depends on --follow but --follow behaviour
1658 1668 # depends on revisions resolved from --rev...
1659 1669 follow = opts.get('follow') or opts.get('follow_first')
1660 1670 possiblyunsorted = False # whether revs might need sorting
1661 1671 if opts.get('rev'):
1662 1672 revs = scmutil.revrange(repo, opts['rev'])
1663 1673 # Don't sort here because _makelogrevset might depend on the
1664 1674 # order of revs
1665 1675 possiblyunsorted = True
1666 1676 else:
1667 1677 if follow and len(repo) > 0:
1668 1678 revs = repo.revs('reverse(:.)')
1669 1679 else:
1670 1680 revs = revset.spanset(repo)
1671 1681 revs.reverse()
1672 1682 if not revs:
1673 1683 return revset.baseset(), None, None
1674 1684 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1675 1685 if possiblyunsorted:
1676 1686 revs.sort(reverse=True)
1677 1687 if expr:
1678 1688 # Revset matchers often operate faster on revisions in changelog
1679 1689 # order, because most filters deal with the changelog.
1680 1690 revs.reverse()
1681 1691 matcher = revset.match(repo.ui, expr)
1682 1692 # Revset matches can reorder revisions. "A or B" typically returns
1683 1693 # returns the revision matching A then the revision matching B. Sort
1684 1694 # again to fix that.
1685 1695 revs = matcher(repo, revs)
1686 1696 revs.sort(reverse=True)
1687 1697 if limit is not None:
1688 1698 limitedrevs = revset.baseset()
1689 1699 for idx, rev in enumerate(revs):
1690 1700 if idx >= limit:
1691 1701 break
1692 1702 limitedrevs.append(rev)
1693 1703 revs = limitedrevs
1694 1704
1695 1705 return revs, expr, filematcher
1696 1706
1697 1707 def getlogrevs(repo, pats, opts):
1698 1708 """Return (revs, expr, filematcher) where revs is an iterable of
1699 1709 revision numbers, expr is a revset string built from log options
1700 1710 and file patterns or None, and used to filter 'revs'. If --stat or
1701 1711 --patch are not passed filematcher is None. Otherwise it is a
1702 1712 callable taking a revision number and returning a match objects
1703 1713 filtering the files to be detailed when displaying the revision.
1704 1714 """
1705 1715 limit = loglimit(opts)
1706 1716 # Default --rev value depends on --follow but --follow behaviour
1707 1717 # depends on revisions resolved from --rev...
1708 1718 follow = opts.get('follow') or opts.get('follow_first')
1709 1719 if opts.get('rev'):
1710 1720 revs = scmutil.revrange(repo, opts['rev'])
1711 1721 elif follow:
1712 1722 revs = revset.baseset(repo.revs('reverse(:.)'))
1713 1723 else:
1714 1724 revs = revset.spanset(repo)
1715 1725 revs.reverse()
1716 1726 if not revs:
1717 1727 return revset.baseset([]), None, None
1718 1728 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1719 1729 if expr:
1720 1730 # Revset matchers often operate faster on revisions in changelog
1721 1731 # order, because most filters deal with the changelog.
1722 1732 if not opts.get('rev'):
1723 1733 revs.reverse()
1724 1734 matcher = revset.match(repo.ui, expr)
1725 1735 # Revset matches can reorder revisions. "A or B" typically returns
1726 1736 # returns the revision matching A then the revision matching B. Sort
1727 1737 # again to fix that.
1728 1738 revs = matcher(repo, revs)
1729 1739 if not opts.get('rev'):
1730 1740 revs.sort(reverse=True)
1731 1741 if limit is not None:
1732 1742 count = 0
1733 1743 limitedrevs = revset.baseset([])
1734 1744 it = iter(revs)
1735 1745 while count < limit:
1736 1746 try:
1737 1747 limitedrevs.append(it.next())
1738 1748 except (StopIteration):
1739 1749 break
1740 1750 count += 1
1741 1751 revs = limitedrevs
1742 1752
1743 1753 return revs, expr, filematcher
1744 1754
1745 1755 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1746 1756 filematcher=None):
1747 1757 seen, state = [], graphmod.asciistate()
1748 1758 for rev, type, ctx, parents in dag:
1749 1759 char = 'o'
1750 1760 if ctx.node() in showparents:
1751 1761 char = '@'
1752 1762 elif ctx.obsolete():
1753 1763 char = 'x'
1754 1764 copies = None
1755 1765 if getrenamed and ctx.rev():
1756 1766 copies = []
1757 1767 for fn in ctx.files():
1758 1768 rename = getrenamed(fn, ctx.rev())
1759 1769 if rename:
1760 1770 copies.append((fn, rename[0]))
1761 1771 revmatchfn = None
1762 1772 if filematcher is not None:
1763 1773 revmatchfn = filematcher(ctx.rev())
1764 1774 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1765 1775 lines = displayer.hunk.pop(rev).split('\n')
1766 1776 if not lines[-1]:
1767 1777 del lines[-1]
1768 1778 displayer.flush(rev)
1769 1779 edges = edgefn(type, char, lines, seen, rev, parents)
1770 1780 for type, char, lines, coldata in edges:
1771 1781 graphmod.ascii(ui, state, type, char, lines, coldata)
1772 1782 displayer.close()
1773 1783
1774 1784 def graphlog(ui, repo, *pats, **opts):
1775 1785 # Parameters are identical to log command ones
1776 1786 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1777 1787 revdag = graphmod.dagwalker(repo, revs)
1778 1788
1779 1789 getrenamed = None
1780 1790 if opts.get('copies'):
1781 1791 endrev = None
1782 1792 if opts.get('rev'):
1783 1793 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1784 1794 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1785 1795 displayer = show_changeset(ui, repo, opts, buffered=True)
1786 1796 showparents = [ctx.node() for ctx in repo[None].parents()]
1787 1797 displaygraph(ui, revdag, displayer, showparents,
1788 1798 graphmod.asciiedges, getrenamed, filematcher)
1789 1799
1790 1800 def checkunsupportedgraphflags(pats, opts):
1791 1801 for op in ["newest_first"]:
1792 1802 if op in opts and opts[op]:
1793 1803 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1794 1804 % op.replace("_", "-"))
1795 1805
1796 1806 def graphrevs(repo, nodes, opts):
1797 1807 limit = loglimit(opts)
1798 1808 nodes.reverse()
1799 1809 if limit is not None:
1800 1810 nodes = nodes[:limit]
1801 1811 return graphmod.nodes(repo, nodes)
1802 1812
1803 1813 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1804 1814 join = lambda f: os.path.join(prefix, f)
1805 1815 bad = []
1806 1816 oldbad = match.bad
1807 1817 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1808 1818 names = []
1809 1819 wctx = repo[None]
1810 1820 cca = None
1811 1821 abort, warn = scmutil.checkportabilityalert(ui)
1812 1822 if abort or warn:
1813 1823 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1814 1824 for f in repo.walk(match):
1815 1825 exact = match.exact(f)
1816 1826 if exact or not explicitonly and f not in repo.dirstate:
1817 1827 if cca:
1818 1828 cca(f)
1819 1829 names.append(f)
1820 1830 if ui.verbose or not exact:
1821 1831 ui.status(_('adding %s\n') % match.rel(join(f)))
1822 1832
1823 1833 for subpath in sorted(wctx.substate):
1824 1834 sub = wctx.sub(subpath)
1825 1835 try:
1826 1836 submatch = matchmod.narrowmatcher(subpath, match)
1827 1837 if listsubrepos:
1828 1838 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1829 1839 False))
1830 1840 else:
1831 1841 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1832 1842 True))
1833 1843 except error.LookupError:
1834 1844 ui.status(_("skipping missing subrepository: %s\n")
1835 1845 % join(subpath))
1836 1846
1837 1847 if not dryrun:
1838 1848 rejected = wctx.add(names, prefix)
1839 1849 bad.extend(f for f in rejected if f in match.files())
1840 1850 return bad
1841 1851
1842 1852 def forget(ui, repo, match, prefix, explicitonly):
1843 1853 join = lambda f: os.path.join(prefix, f)
1844 1854 bad = []
1845 1855 oldbad = match.bad
1846 1856 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1847 1857 wctx = repo[None]
1848 1858 forgot = []
1849 1859 s = repo.status(match=match, clean=True)
1850 1860 forget = sorted(s[0] + s[1] + s[3] + s[6])
1851 1861 if explicitonly:
1852 1862 forget = [f for f in forget if match.exact(f)]
1853 1863
1854 1864 for subpath in sorted(wctx.substate):
1855 1865 sub = wctx.sub(subpath)
1856 1866 try:
1857 1867 submatch = matchmod.narrowmatcher(subpath, match)
1858 1868 subbad, subforgot = sub.forget(ui, submatch, prefix)
1859 1869 bad.extend([subpath + '/' + f for f in subbad])
1860 1870 forgot.extend([subpath + '/' + f for f in subforgot])
1861 1871 except error.LookupError:
1862 1872 ui.status(_("skipping missing subrepository: %s\n")
1863 1873 % join(subpath))
1864 1874
1865 1875 if not explicitonly:
1866 1876 for f in match.files():
1867 1877 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1868 1878 if f not in forgot:
1869 1879 if os.path.exists(match.rel(join(f))):
1870 1880 ui.warn(_('not removing %s: '
1871 1881 'file is already untracked\n')
1872 1882 % match.rel(join(f)))
1873 1883 bad.append(f)
1874 1884
1875 1885 for f in forget:
1876 1886 if ui.verbose or not match.exact(f):
1877 1887 ui.status(_('removing %s\n') % match.rel(join(f)))
1878 1888
1879 1889 rejected = wctx.forget(forget, prefix)
1880 1890 bad.extend(f for f in rejected if f in match.files())
1881 1891 forgot.extend(forget)
1882 1892 return bad, forgot
1883 1893
1884 1894 def cat(ui, repo, ctx, matcher, prefix, **opts):
1885 1895 err = 1
1886 1896
1887 1897 def write(path):
1888 1898 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1889 1899 pathname=os.path.join(prefix, path))
1890 1900 data = ctx[path].data()
1891 1901 if opts.get('decode'):
1892 1902 data = repo.wwritedata(path, data)
1893 1903 fp.write(data)
1894 1904 fp.close()
1895 1905
1896 1906 # Automation often uses hg cat on single files, so special case it
1897 1907 # for performance to avoid the cost of parsing the manifest.
1898 1908 if len(matcher.files()) == 1 and not matcher.anypats():
1899 1909 file = matcher.files()[0]
1900 1910 mf = repo.manifest
1901 1911 mfnode = ctx._changeset[0]
1902 1912 if mf.find(mfnode, file)[0]:
1903 1913 write(file)
1904 1914 return 0
1905 1915
1906 1916 # Don't warn about "missing" files that are really in subrepos
1907 1917 bad = matcher.bad
1908 1918
1909 1919 def badfn(path, msg):
1910 1920 for subpath in ctx.substate:
1911 1921 if path.startswith(subpath):
1912 1922 return
1913 1923 bad(path, msg)
1914 1924
1915 1925 matcher.bad = badfn
1916 1926
1917 1927 for abs in ctx.walk(matcher):
1918 1928 write(abs)
1919 1929 err = 0
1920 1930
1921 1931 matcher.bad = bad
1922 1932
1923 1933 for subpath in sorted(ctx.substate):
1924 1934 sub = ctx.sub(subpath)
1925 1935 try:
1926 1936 submatch = matchmod.narrowmatcher(subpath, matcher)
1927 1937
1928 1938 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1929 1939 **opts):
1930 1940 err = 0
1931 1941 except error.RepoLookupError:
1932 1942 ui.status(_("skipping missing subrepository: %s\n")
1933 1943 % os.path.join(prefix, subpath))
1934 1944
1935 1945 return err
1936 1946
1937 1947 def duplicatecopies(repo, rev, fromrev):
1938 1948 '''reproduce copies from fromrev to rev in the dirstate'''
1939 1949 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1940 1950 # copies.pathcopies returns backward renames, so dst might not
1941 1951 # actually be in the dirstate
1942 1952 if repo.dirstate[dst] in "nma":
1943 1953 repo.dirstate.copy(src, dst)
1944 1954
1945 1955 def commit(ui, repo, commitfunc, pats, opts):
1946 1956 '''commit the specified files or all outstanding changes'''
1947 1957 date = opts.get('date')
1948 1958 if date:
1949 1959 opts['date'] = util.parsedate(date)
1950 1960 message = logmessage(ui, opts)
1951 1961
1952 1962 # extract addremove carefully -- this function can be called from a command
1953 1963 # that doesn't support addremove
1954 1964 if opts.get('addremove'):
1955 1965 scmutil.addremove(repo, pats, opts)
1956 1966
1957 1967 return commitfunc(ui, repo, message,
1958 1968 scmutil.match(repo[None], pats, opts), opts)
1959 1969
1960 1970 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1961 1971 ui.note(_('amending changeset %s\n') % old)
1962 1972 base = old.p1()
1963 1973
1964 1974 wlock = lock = newid = None
1965 1975 try:
1966 1976 wlock = repo.wlock()
1967 1977 lock = repo.lock()
1968 1978 tr = repo.transaction('amend')
1969 1979 try:
1970 1980 # See if we got a message from -m or -l, if not, open the editor
1971 1981 # with the message of the changeset to amend
1972 1982 message = logmessage(ui, opts)
1973 1983 # ensure logfile does not conflict with later enforcement of the
1974 1984 # message. potential logfile content has been processed by
1975 1985 # `logmessage` anyway.
1976 1986 opts.pop('logfile')
1977 1987 # First, do a regular commit to record all changes in the working
1978 1988 # directory (if there are any)
1979 1989 ui.callhooks = False
1980 1990 currentbookmark = repo._bookmarkcurrent
1981 1991 try:
1982 1992 repo._bookmarkcurrent = None
1983 1993 opts['message'] = 'temporary amend commit for %s' % old
1984 1994 node = commit(ui, repo, commitfunc, pats, opts)
1985 1995 finally:
1986 1996 repo._bookmarkcurrent = currentbookmark
1987 1997 ui.callhooks = True
1988 1998 ctx = repo[node]
1989 1999
1990 2000 # Participating changesets:
1991 2001 #
1992 2002 # node/ctx o - new (intermediate) commit that contains changes
1993 2003 # | from working dir to go into amending commit
1994 2004 # | (or a workingctx if there were no changes)
1995 2005 # |
1996 2006 # old o - changeset to amend
1997 2007 # |
1998 2008 # base o - parent of amending changeset
1999 2009
2000 2010 # Update extra dict from amended commit (e.g. to preserve graft
2001 2011 # source)
2002 2012 extra.update(old.extra())
2003 2013
2004 2014 # Also update it from the intermediate commit or from the wctx
2005 2015 extra.update(ctx.extra())
2006 2016
2007 2017 if len(old.parents()) > 1:
2008 2018 # ctx.files() isn't reliable for merges, so fall back to the
2009 2019 # slower repo.status() method
2010 2020 files = set([fn for st in repo.status(base, old)[:3]
2011 2021 for fn in st])
2012 2022 else:
2013 2023 files = set(old.files())
2014 2024
2015 2025 # Second, we use either the commit we just did, or if there were no
2016 2026 # changes the parent of the working directory as the version of the
2017 2027 # files in the final amend commit
2018 2028 if node:
2019 2029 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2020 2030
2021 2031 user = ctx.user()
2022 2032 date = ctx.date()
2023 2033 # Recompute copies (avoid recording a -> b -> a)
2024 2034 copied = copies.pathcopies(base, ctx)
2025 2035
2026 2036 # Prune files which were reverted by the updates: if old
2027 2037 # introduced file X and our intermediate commit, node,
2028 2038 # renamed that file, then those two files are the same and
2029 2039 # we can discard X from our list of files. Likewise if X
2030 2040 # was deleted, it's no longer relevant
2031 2041 files.update(ctx.files())
2032 2042
2033 2043 def samefile(f):
2034 2044 if f in ctx.manifest():
2035 2045 a = ctx.filectx(f)
2036 2046 if f in base.manifest():
2037 2047 b = base.filectx(f)
2038 2048 return (not a.cmp(b)
2039 2049 and a.flags() == b.flags())
2040 2050 else:
2041 2051 return False
2042 2052 else:
2043 2053 return f not in base.manifest()
2044 2054 files = [f for f in files if not samefile(f)]
2045 2055
2046 2056 def filectxfn(repo, ctx_, path):
2047 2057 try:
2048 2058 fctx = ctx[path]
2049 2059 flags = fctx.flags()
2050 2060 mctx = context.memfilectx(fctx.path(), fctx.data(),
2051 2061 islink='l' in flags,
2052 2062 isexec='x' in flags,
2053 2063 copied=copied.get(path))
2054 2064 return mctx
2055 2065 except KeyError:
2056 2066 raise IOError
2057 2067 else:
2058 2068 ui.note(_('copying changeset %s to %s\n') % (old, base))
2059 2069
2060 2070 # Use version of files as in the old cset
2061 2071 def filectxfn(repo, ctx_, path):
2062 2072 try:
2063 2073 return old.filectx(path)
2064 2074 except KeyError:
2065 2075 raise IOError
2066 2076
2067 2077 user = opts.get('user') or old.user()
2068 2078 date = opts.get('date') or old.date()
2069 2079 editor = getcommiteditor(**opts)
2070 2080 if not message:
2071 2081 editor = getcommiteditor(edit=True)
2072 2082 message = old.description()
2073 2083
2074 2084 pureextra = extra.copy()
2075 2085 extra['amend_source'] = old.hex()
2076 2086
2077 2087 new = context.memctx(repo,
2078 2088 parents=[base.node(), old.p2().node()],
2079 2089 text=message,
2080 2090 files=files,
2081 2091 filectxfn=filectxfn,
2082 2092 user=user,
2083 2093 date=date,
2084 2094 extra=extra,
2085 2095 editor=editor)
2086 2096
2087 2097 newdesc = changelog.stripdesc(new.description())
2088 2098 if ((not node)
2089 2099 and newdesc == old.description()
2090 2100 and user == old.user()
2091 2101 and date == old.date()
2092 2102 and pureextra == old.extra()):
2093 2103 # nothing changed. continuing here would create a new node
2094 2104 # anyway because of the amend_source noise.
2095 2105 #
2096 2106 # This not what we expect from amend.
2097 2107 return old.node()
2098 2108
2099 2109 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2100 2110 try:
2101 2111 if opts.get('secret'):
2102 2112 commitphase = 'secret'
2103 2113 else:
2104 2114 commitphase = old.phase()
2105 2115 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2106 2116 newid = repo.commitctx(new)
2107 2117 finally:
2108 2118 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2109 2119 if newid != old.node():
2110 2120 # Reroute the working copy parent to the new changeset
2111 2121 repo.setparents(newid, nullid)
2112 2122
2113 2123 # Move bookmarks from old parent to amend commit
2114 2124 bms = repo.nodebookmarks(old.node())
2115 2125 if bms:
2116 2126 marks = repo._bookmarks
2117 2127 for bm in bms:
2118 2128 marks[bm] = newid
2119 2129 marks.write()
2120 2130 #commit the whole amend process
2121 2131 if obsolete._enabled and newid != old.node():
2122 2132 # mark the new changeset as successor of the rewritten one
2123 2133 new = repo[newid]
2124 2134 obs = [(old, (new,))]
2125 2135 if node:
2126 2136 obs.append((ctx, ()))
2127 2137
2128 2138 obsolete.createmarkers(repo, obs)
2129 2139 tr.close()
2130 2140 finally:
2131 2141 tr.release()
2132 2142 if (not obsolete._enabled) and newid != old.node():
2133 2143 # Strip the intermediate commit (if there was one) and the amended
2134 2144 # commit
2135 2145 if node:
2136 2146 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2137 2147 ui.note(_('stripping amended changeset %s\n') % old)
2138 2148 repair.strip(ui, repo, old.node(), topic='amend-backup')
2139 2149 finally:
2140 2150 if newid is None:
2141 2151 repo.dirstate.invalidate()
2142 2152 lockmod.release(lock, wlock)
2143 2153 return newid
2144 2154
2145 2155 def commiteditor(repo, ctx, subs):
2146 2156 if ctx.description():
2147 2157 return ctx.description()
2148 2158 return commitforceeditor(repo, ctx, subs)
2149 2159
2150 2160 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
2151 2161 edittext = []
2152 2162 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2153 2163 if ctx.description():
2154 2164 edittext.append(ctx.description())
2155 2165 edittext.append("")
2156 2166 edittext.append("") # Empty line between message and comments.
2157 2167 edittext.append(_("HG: Enter commit message."
2158 2168 " Lines beginning with 'HG:' are removed."))
2159 2169 if extramsg:
2160 2170 edittext.append("HG: %s" % extramsg)
2161 2171 else:
2162 2172 edittext.append(_("HG: Leave message empty to abort commit."))
2163 2173 edittext.append("HG: --")
2164 2174 edittext.append(_("HG: user: %s") % ctx.user())
2165 2175 if ctx.p2():
2166 2176 edittext.append(_("HG: branch merge"))
2167 2177 if ctx.branch():
2168 2178 edittext.append(_("HG: branch '%s'") % ctx.branch())
2169 2179 if bookmarks.iscurrent(repo):
2170 2180 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2171 2181 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2172 2182 edittext.extend([_("HG: added %s") % f for f in added])
2173 2183 edittext.extend([_("HG: changed %s") % f for f in modified])
2174 2184 edittext.extend([_("HG: removed %s") % f for f in removed])
2175 2185 if not added and not modified and not removed:
2176 2186 edittext.append(_("HG: no files changed"))
2177 2187 edittext.append("")
2178 2188 # run editor in the repository root
2179 2189 olddir = os.getcwd()
2180 2190 os.chdir(repo.root)
2181 2191 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2182 2192 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2183 2193 os.chdir(olddir)
2184 2194
2185 2195 if finishdesc:
2186 2196 text = finishdesc(text)
2187 2197 if not text.strip():
2188 2198 raise util.Abort(_("empty commit message"))
2189 2199
2190 2200 return text
2191 2201
2192 2202 def commitstatus(repo, node, branch, bheads=None, opts={}):
2193 2203 ctx = repo[node]
2194 2204 parents = ctx.parents()
2195 2205
2196 2206 if (not opts.get('amend') and bheads and node not in bheads and not
2197 2207 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2198 2208 repo.ui.status(_('created new head\n'))
2199 2209 # The message is not printed for initial roots. For the other
2200 2210 # changesets, it is printed in the following situations:
2201 2211 #
2202 2212 # Par column: for the 2 parents with ...
2203 2213 # N: null or no parent
2204 2214 # B: parent is on another named branch
2205 2215 # C: parent is a regular non head changeset
2206 2216 # H: parent was a branch head of the current branch
2207 2217 # Msg column: whether we print "created new head" message
2208 2218 # In the following, it is assumed that there already exists some
2209 2219 # initial branch heads of the current branch, otherwise nothing is
2210 2220 # printed anyway.
2211 2221 #
2212 2222 # Par Msg Comment
2213 2223 # N N y additional topo root
2214 2224 #
2215 2225 # B N y additional branch root
2216 2226 # C N y additional topo head
2217 2227 # H N n usual case
2218 2228 #
2219 2229 # B B y weird additional branch root
2220 2230 # C B y branch merge
2221 2231 # H B n merge with named branch
2222 2232 #
2223 2233 # C C y additional head from merge
2224 2234 # C H n merge with a head
2225 2235 #
2226 2236 # H H n head merge: head count decreases
2227 2237
2228 2238 if not opts.get('close_branch'):
2229 2239 for r in parents:
2230 2240 if r.closesbranch() and r.branch() == branch:
2231 2241 repo.ui.status(_('reopening closed branch head %d\n') % r)
2232 2242
2233 2243 if repo.ui.debugflag:
2234 2244 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2235 2245 elif repo.ui.verbose:
2236 2246 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2237 2247
2238 2248 def revert(ui, repo, ctx, parents, *pats, **opts):
2239 2249 parent, p2 = parents
2240 2250 node = ctx.node()
2241 2251
2242 2252 mf = ctx.manifest()
2243 2253 if node == parent:
2244 2254 pmf = mf
2245 2255 else:
2246 2256 pmf = None
2247 2257
2248 2258 # need all matching names in dirstate and manifest of target rev,
2249 2259 # so have to walk both. do not print errors if files exist in one
2250 2260 # but not other.
2251 2261
2252 2262 names = {}
2253 2263
2254 2264 wlock = repo.wlock()
2255 2265 try:
2256 2266 # walk dirstate.
2257 2267
2258 2268 m = scmutil.match(repo[None], pats, opts)
2259 2269 m.bad = lambda x, y: False
2260 2270 for abs in repo.walk(m):
2261 2271 names[abs] = m.rel(abs), m.exact(abs)
2262 2272
2263 2273 # walk target manifest.
2264 2274
2265 2275 def badfn(path, msg):
2266 2276 if path in names:
2267 2277 return
2268 2278 if path in ctx.substate:
2269 2279 return
2270 2280 path_ = path + '/'
2271 2281 for f in names:
2272 2282 if f.startswith(path_):
2273 2283 return
2274 2284 ui.warn("%s: %s\n" % (m.rel(path), msg))
2275 2285
2276 2286 m = scmutil.match(ctx, pats, opts)
2277 2287 m.bad = badfn
2278 2288 for abs in ctx.walk(m):
2279 2289 if abs not in names:
2280 2290 names[abs] = m.rel(abs), m.exact(abs)
2281 2291
2282 2292 # get the list of subrepos that must be reverted
2283 2293 targetsubs = sorted(s for s in ctx.substate if m(s))
2284 2294 m = scmutil.matchfiles(repo, names)
2285 2295 changes = repo.status(match=m)[:4]
2286 2296 modified, added, removed, deleted = map(set, changes)
2287 2297
2288 2298 # if f is a rename, also revert the source
2289 2299 cwd = repo.getcwd()
2290 2300 for f in added:
2291 2301 src = repo.dirstate.copied(f)
2292 2302 if src and src not in names and repo.dirstate[src] == 'r':
2293 2303 removed.add(src)
2294 2304 names[src] = (repo.pathto(src, cwd), True)
2295 2305
2296 2306 def removeforget(abs):
2297 2307 if repo.dirstate[abs] == 'a':
2298 2308 return _('forgetting %s\n')
2299 2309 return _('removing %s\n')
2300 2310
2301 2311 revert = ([], _('reverting %s\n'))
2302 2312 add = ([], _('adding %s\n'))
2303 2313 remove = ([], removeforget)
2304 2314 undelete = ([], _('undeleting %s\n'))
2305 2315
2306 2316 disptable = (
2307 2317 # dispatch table:
2308 2318 # file state
2309 2319 # action if in target manifest
2310 2320 # action if not in target manifest
2311 2321 # make backup if in target manifest
2312 2322 # make backup if not in target manifest
2313 2323 (modified, revert, remove, True, True),
2314 2324 (added, revert, remove, True, False),
2315 2325 (removed, undelete, None, True, False),
2316 2326 (deleted, revert, remove, False, False),
2317 2327 )
2318 2328
2319 2329 for abs, (rel, exact) in sorted(names.items()):
2320 2330 mfentry = mf.get(abs)
2321 2331 target = repo.wjoin(abs)
2322 2332 def handle(xlist, dobackup):
2323 2333 xlist[0].append(abs)
2324 2334 if (dobackup and not opts.get('no_backup') and
2325 2335 os.path.lexists(target) and
2326 2336 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2327 2337 bakname = "%s.orig" % rel
2328 2338 ui.note(_('saving current version of %s as %s\n') %
2329 2339 (rel, bakname))
2330 2340 if not opts.get('dry_run'):
2331 2341 util.rename(target, bakname)
2332 2342 if ui.verbose or not exact:
2333 2343 msg = xlist[1]
2334 2344 if not isinstance(msg, basestring):
2335 2345 msg = msg(abs)
2336 2346 ui.status(msg % rel)
2337 2347 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2338 2348 if abs not in table:
2339 2349 continue
2340 2350 # file has changed in dirstate
2341 2351 if mfentry:
2342 2352 handle(hitlist, backuphit)
2343 2353 elif misslist is not None:
2344 2354 handle(misslist, backupmiss)
2345 2355 break
2346 2356 else:
2347 2357 if abs not in repo.dirstate:
2348 2358 if mfentry:
2349 2359 handle(add, True)
2350 2360 elif exact:
2351 2361 ui.warn(_('file not managed: %s\n') % rel)
2352 2362 continue
2353 2363 # file has not changed in dirstate
2354 2364 if node == parent:
2355 2365 if exact:
2356 2366 ui.warn(_('no changes needed to %s\n') % rel)
2357 2367 continue
2358 2368 if pmf is None:
2359 2369 # only need parent manifest in this unlikely case,
2360 2370 # so do not read by default
2361 2371 pmf = repo[parent].manifest()
2362 2372 if abs in pmf and mfentry:
2363 2373 # if version of file is same in parent and target
2364 2374 # manifests, do nothing
2365 2375 if (pmf[abs] != mfentry or
2366 2376 pmf.flags(abs) != mf.flags(abs)):
2367 2377 handle(revert, False)
2368 2378 else:
2369 2379 handle(remove, False)
2370 2380 if not opts.get('dry_run'):
2371 2381 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2372 2382
2373 2383 if targetsubs:
2374 2384 # Revert the subrepos on the revert list
2375 2385 for sub in targetsubs:
2376 2386 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2377 2387 finally:
2378 2388 wlock.release()
2379 2389
2380 2390 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2381 2391 """function that actually perform all the action computed for revert
2382 2392
2383 2393 This is an independent function to let extension to plug in and react to
2384 2394 the imminent revert.
2385 2395
2386 2396 Make sure you have the working directory locked when calling this function.
2387 2397 """
2388 2398 parent, p2 = parents
2389 2399 node = ctx.node()
2390 2400 def checkout(f):
2391 2401 fc = ctx[f]
2392 2402 repo.wwrite(f, fc.data(), fc.flags())
2393 2403
2394 2404 audit_path = pathutil.pathauditor(repo.root)
2395 2405 for f in remove[0]:
2396 2406 if repo.dirstate[f] == 'a':
2397 2407 repo.dirstate.drop(f)
2398 2408 continue
2399 2409 audit_path(f)
2400 2410 try:
2401 2411 util.unlinkpath(repo.wjoin(f))
2402 2412 except OSError:
2403 2413 pass
2404 2414 repo.dirstate.remove(f)
2405 2415
2406 2416 normal = None
2407 2417 if node == parent:
2408 2418 # We're reverting to our parent. If possible, we'd like status
2409 2419 # to report the file as clean. We have to use normallookup for
2410 2420 # merges to avoid losing information about merged/dirty files.
2411 2421 if p2 != nullid:
2412 2422 normal = repo.dirstate.normallookup
2413 2423 else:
2414 2424 normal = repo.dirstate.normal
2415 2425 for f in revert[0]:
2416 2426 checkout(f)
2417 2427 if normal:
2418 2428 normal(f)
2419 2429
2420 2430 for f in add[0]:
2421 2431 checkout(f)
2422 2432 repo.dirstate.add(f)
2423 2433
2424 2434 normal = repo.dirstate.normallookup
2425 2435 if node == parent and p2 == nullid:
2426 2436 normal = repo.dirstate.normal
2427 2437 for f in undelete[0]:
2428 2438 checkout(f)
2429 2439 normal(f)
2430 2440
2431 2441 copied = copies.pathcopies(repo[parent], ctx)
2432 2442
2433 2443 for f in add[0] + undelete[0] + revert[0]:
2434 2444 if f in copied:
2435 2445 repo.dirstate.copy(copied[f], f)
2436 2446
2437 2447 def command(table):
2438 2448 '''returns a function object bound to table which can be used as
2439 2449 a decorator for populating table as a command table'''
2440 2450
2441 2451 def cmd(name, options=(), synopsis=None):
2442 2452 def decorator(func):
2443 2453 if synopsis:
2444 2454 table[name] = func, list(options), synopsis
2445 2455 else:
2446 2456 table[name] = func, list(options)
2447 2457 return func
2448 2458 return decorator
2449 2459
2450 2460 return cmd
2451 2461
2452 2462 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2453 2463 # commands.outgoing. "missing" is "missing" of the result of
2454 2464 # "findcommonoutgoing()"
2455 2465 outgoinghooks = util.hooks()
2456 2466
2457 2467 # a list of (ui, repo) functions called by commands.summary
2458 2468 summaryhooks = util.hooks()
2459 2469
2460 2470 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2461 2471 #
2462 2472 # functions should return tuple of booleans below, if 'changes' is None:
2463 2473 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2464 2474 #
2465 2475 # otherwise, 'changes' is a tuple of tuples below:
2466 2476 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2467 2477 # - (desturl, destbranch, destpeer, outgoing)
2468 2478 summaryremotehooks = util.hooks()
2469 2479
2470 2480 # A list of state files kept by multistep operations like graft.
2471 2481 # Since graft cannot be aborted, it is considered 'clearable' by update.
2472 2482 # note: bisect is intentionally excluded
2473 2483 # (state file, clearable, allowcommit, error, hint)
2474 2484 unfinishedstates = [
2475 2485 ('graftstate', True, False, _('graft in progress'),
2476 2486 _("use 'hg graft --continue' or 'hg update' to abort")),
2477 2487 ('updatestate', True, False, _('last update was interrupted'),
2478 2488 _("use 'hg update' to get a consistent checkout"))
2479 2489 ]
2480 2490
2481 2491 def checkunfinished(repo, commit=False):
2482 2492 '''Look for an unfinished multistep operation, like graft, and abort
2483 2493 if found. It's probably good to check this right before
2484 2494 bailifchanged().
2485 2495 '''
2486 2496 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2487 2497 if commit and allowcommit:
2488 2498 continue
2489 2499 if repo.vfs.exists(f):
2490 2500 raise util.Abort(msg, hint=hint)
2491 2501
2492 2502 def clearunfinished(repo):
2493 2503 '''Check for unfinished operations (as above), and clear the ones
2494 2504 that are clearable.
2495 2505 '''
2496 2506 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2497 2507 if not clearable and repo.vfs.exists(f):
2498 2508 raise util.Abort(msg, hint=hint)
2499 2509 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2500 2510 if clearable and repo.vfs.exists(f):
2501 2511 util.unlink(repo.join(f))
@@ -1,5944 +1,5965 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 node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _
11 11 import os, re, difflib, time, tempfile, errno
12 12 import sys
13 13 import hg, scmutil, util, revlog, copies, error, bookmarks
14 14 import patch, help, encoding, templatekw, discovery
15 15 import archival, changegroup, cmdutil, hbisect
16 16 import sshserver, hgweb, commandserver
17 17 from hgweb import server as hgweb_server
18 18 import merge as mergemod
19 19 import minirst, revset, fileset
20 20 import dagparser, context, simplemerge, graphmod
21 21 import random
22 22 import setdiscovery, treediscovery, dagutil, pvec, localrepo
23 23 import phases, obsolete, exchange
24 24
25 25 table = {}
26 26
27 27 command = cmdutil.command(table)
28 28
29 29 # common command options
30 30
31 31 globalopts = [
32 32 ('R', 'repository', '',
33 33 _('repository root directory or name of overlay bundle file'),
34 34 _('REPO')),
35 35 ('', 'cwd', '',
36 36 _('change working directory'), _('DIR')),
37 37 ('y', 'noninteractive', None,
38 38 _('do not prompt, automatically pick the first choice for all prompts')),
39 39 ('q', 'quiet', None, _('suppress output')),
40 40 ('v', 'verbose', None, _('enable additional output')),
41 41 ('', 'config', [],
42 42 _('set/override config option (use \'section.name=value\')'),
43 43 _('CONFIG')),
44 44 ('', 'debug', None, _('enable debugging output')),
45 45 ('', 'debugger', None, _('start debugger')),
46 46 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
47 47 _('ENCODE')),
48 48 ('', 'encodingmode', encoding.encodingmode,
49 49 _('set the charset encoding mode'), _('MODE')),
50 50 ('', 'traceback', None, _('always print a traceback on exception')),
51 51 ('', 'time', None, _('time how long the command takes')),
52 52 ('', 'profile', None, _('print command execution profile')),
53 53 ('', 'version', None, _('output version information and exit')),
54 54 ('h', 'help', None, _('display help and exit')),
55 55 ('', 'hidden', False, _('consider hidden changesets')),
56 56 ]
57 57
58 58 dryrunopts = [('n', 'dry-run', None,
59 59 _('do not perform actions, just print output'))]
60 60
61 61 remoteopts = [
62 62 ('e', 'ssh', '',
63 63 _('specify ssh command to use'), _('CMD')),
64 64 ('', 'remotecmd', '',
65 65 _('specify hg command to run on the remote side'), _('CMD')),
66 66 ('', 'insecure', None,
67 67 _('do not verify server certificate (ignoring web.cacerts config)')),
68 68 ]
69 69
70 70 walkopts = [
71 71 ('I', 'include', [],
72 72 _('include names matching the given patterns'), _('PATTERN')),
73 73 ('X', 'exclude', [],
74 74 _('exclude names matching the given patterns'), _('PATTERN')),
75 75 ]
76 76
77 77 commitopts = [
78 78 ('m', 'message', '',
79 79 _('use text as commit message'), _('TEXT')),
80 80 ('l', 'logfile', '',
81 81 _('read commit message from file'), _('FILE')),
82 82 ]
83 83
84 84 commitopts2 = [
85 85 ('d', 'date', '',
86 86 _('record the specified date as commit date'), _('DATE')),
87 87 ('u', 'user', '',
88 88 _('record the specified user as committer'), _('USER')),
89 89 ]
90 90
91 91 templateopts = [
92 92 ('', 'style', '',
93 93 _('display using template map file (DEPRECATED)'), _('STYLE')),
94 94 ('T', 'template', '',
95 95 _('display with template'), _('TEMPLATE')),
96 96 ]
97 97
98 98 logopts = [
99 99 ('p', 'patch', None, _('show patch')),
100 100 ('g', 'git', None, _('use git extended diff format')),
101 101 ('l', 'limit', '',
102 102 _('limit number of changes displayed'), _('NUM')),
103 103 ('M', 'no-merges', None, _('do not show merges')),
104 104 ('', 'stat', None, _('output diffstat-style summary of changes')),
105 105 ('G', 'graph', None, _("show the revision DAG")),
106 106 ] + templateopts
107 107
108 108 diffopts = [
109 109 ('a', 'text', None, _('treat all files as text')),
110 110 ('g', 'git', None, _('use git extended diff format')),
111 111 ('', 'nodates', None, _('omit dates from diff headers'))
112 112 ]
113 113
114 114 diffwsopts = [
115 115 ('w', 'ignore-all-space', None,
116 116 _('ignore white space when comparing lines')),
117 117 ('b', 'ignore-space-change', None,
118 118 _('ignore changes in the amount of white space')),
119 119 ('B', 'ignore-blank-lines', None,
120 120 _('ignore changes whose lines are all blank')),
121 121 ]
122 122
123 123 diffopts2 = [
124 124 ('p', 'show-function', None, _('show which function each change is in')),
125 125 ('', 'reverse', None, _('produce a diff that undoes the changes')),
126 126 ] + diffwsopts + [
127 127 ('U', 'unified', '',
128 128 _('number of lines of context to show'), _('NUM')),
129 129 ('', 'stat', None, _('output diffstat-style summary of changes')),
130 130 ]
131 131
132 132 mergetoolopts = [
133 133 ('t', 'tool', '', _('specify merge tool')),
134 134 ]
135 135
136 136 similarityopts = [
137 137 ('s', 'similarity', '',
138 138 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
139 139 ]
140 140
141 141 subrepoopts = [
142 142 ('S', 'subrepos', None,
143 143 _('recurse into subrepositories'))
144 144 ]
145 145
146 146 # Commands start here, listed alphabetically
147 147
148 148 @command('^add',
149 149 walkopts + subrepoopts + dryrunopts,
150 150 _('[OPTION]... [FILE]...'))
151 151 def add(ui, repo, *pats, **opts):
152 152 """add the specified files on the next commit
153 153
154 154 Schedule files to be version controlled and added to the
155 155 repository.
156 156
157 157 The files will be added to the repository at the next commit. To
158 158 undo an add before that, see :hg:`forget`.
159 159
160 160 If no names are given, add all files to the repository.
161 161
162 162 .. container:: verbose
163 163
164 164 An example showing how new (unknown) files are added
165 165 automatically by :hg:`add`::
166 166
167 167 $ ls
168 168 foo.c
169 169 $ hg status
170 170 ? foo.c
171 171 $ hg add
172 172 adding foo.c
173 173 $ hg status
174 174 A foo.c
175 175
176 176 Returns 0 if all files are successfully added.
177 177 """
178 178
179 179 m = scmutil.match(repo[None], pats, opts)
180 180 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
181 181 opts.get('subrepos'), prefix="", explicitonly=False)
182 182 return rejected and 1 or 0
183 183
184 184 @command('addremove',
185 185 similarityopts + walkopts + dryrunopts,
186 186 _('[OPTION]... [FILE]...'))
187 187 def addremove(ui, repo, *pats, **opts):
188 188 """add all new files, delete all missing files
189 189
190 190 Add all new files and remove all missing files from the
191 191 repository.
192 192
193 193 New files are ignored if they match any of the patterns in
194 194 ``.hgignore``. As with add, these changes take effect at the next
195 195 commit.
196 196
197 197 Use the -s/--similarity option to detect renamed files. This
198 198 option takes a percentage between 0 (disabled) and 100 (files must
199 199 be identical) as its parameter. With a parameter greater than 0,
200 200 this compares every removed file with every added file and records
201 201 those similar enough as renames. Detecting renamed files this way
202 202 can be expensive. After using this option, :hg:`status -C` can be
203 203 used to check which files were identified as moved or renamed. If
204 204 not specified, -s/--similarity defaults to 100 and only renames of
205 205 identical files are detected.
206 206
207 207 Returns 0 if all files are successfully added.
208 208 """
209 209 try:
210 210 sim = float(opts.get('similarity') or 100)
211 211 except ValueError:
212 212 raise util.Abort(_('similarity must be a number'))
213 213 if sim < 0 or sim > 100:
214 214 raise util.Abort(_('similarity must be between 0 and 100'))
215 215 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
216 216
217 217 @command('^annotate|blame',
218 218 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
219 219 ('', 'follow', None,
220 220 _('follow copies/renames and list the filename (DEPRECATED)')),
221 221 ('', 'no-follow', None, _("don't follow copies and renames")),
222 222 ('a', 'text', None, _('treat all files as text')),
223 223 ('u', 'user', None, _('list the author (long with -v)')),
224 224 ('f', 'file', None, _('list the filename')),
225 225 ('d', 'date', None, _('list the date (short with -q)')),
226 226 ('n', 'number', None, _('list the revision number (default)')),
227 227 ('c', 'changeset', None, _('list the changeset')),
228 228 ('l', 'line-number', None, _('show line number at the first appearance'))
229 229 ] + diffwsopts + walkopts,
230 230 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
231 231 def annotate(ui, repo, *pats, **opts):
232 232 """show changeset information by line for each file
233 233
234 234 List changes in files, showing the revision id responsible for
235 235 each line
236 236
237 237 This command is useful for discovering when a change was made and
238 238 by whom.
239 239
240 240 Without the -a/--text option, annotate will avoid processing files
241 241 it detects as binary. With -a, annotate will annotate the file
242 242 anyway, although the results will probably be neither useful
243 243 nor desirable.
244 244
245 245 Returns 0 on success.
246 246 """
247 247 if opts.get('follow'):
248 248 # --follow is deprecated and now just an alias for -f/--file
249 249 # to mimic the behavior of Mercurial before version 1.5
250 250 opts['file'] = True
251 251
252 252 datefunc = ui.quiet and util.shortdate or util.datestr
253 253 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
254 254
255 255 if not pats:
256 256 raise util.Abort(_('at least one filename or pattern is required'))
257 257
258 258 hexfn = ui.debugflag and hex or short
259 259
260 260 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
261 261 ('number', ' ', lambda x: str(x[0].rev())),
262 262 ('changeset', ' ', lambda x: hexfn(x[0].node())),
263 263 ('date', ' ', getdate),
264 264 ('file', ' ', lambda x: x[0].path()),
265 265 ('line_number', ':', lambda x: str(x[1])),
266 266 ]
267 267
268 268 if (not opts.get('user') and not opts.get('changeset')
269 269 and not opts.get('date') and not opts.get('file')):
270 270 opts['number'] = True
271 271
272 272 linenumber = opts.get('line_number') is not None
273 273 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
274 274 raise util.Abort(_('at least one of -n/-c is required for -l'))
275 275
276 276 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
277 277 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
278 278
279 279 def bad(x, y):
280 280 raise util.Abort("%s: %s" % (x, y))
281 281
282 282 ctx = scmutil.revsingle(repo, opts.get('rev'))
283 283 m = scmutil.match(ctx, pats, opts)
284 284 m.bad = bad
285 285 follow = not opts.get('no_follow')
286 286 diffopts = patch.diffopts(ui, opts, section='annotate')
287 287 for abs in ctx.walk(m):
288 288 fctx = ctx[abs]
289 289 if not opts.get('text') and util.binary(fctx.data()):
290 290 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
291 291 continue
292 292
293 293 lines = fctx.annotate(follow=follow, linenumber=linenumber,
294 294 diffopts=diffopts)
295 295 pieces = []
296 296
297 297 for f, sep in funcmap:
298 298 l = [f(n) for n, dummy in lines]
299 299 if l:
300 300 sized = [(x, encoding.colwidth(x)) for x in l]
301 301 ml = max([w for x, w in sized])
302 302 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
303 303 for x, w in sized])
304 304
305 305 if pieces:
306 306 for p, l in zip(zip(*pieces), lines):
307 307 ui.write("%s: %s" % ("".join(p), l[1]))
308 308
309 309 if lines and not lines[-1][1].endswith('\n'):
310 310 ui.write('\n')
311 311
312 312 @command('archive',
313 313 [('', 'no-decode', None, _('do not pass files through decoders')),
314 314 ('p', 'prefix', '', _('directory prefix for files in archive'),
315 315 _('PREFIX')),
316 316 ('r', 'rev', '', _('revision to distribute'), _('REV')),
317 317 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
318 318 ] + subrepoopts + walkopts,
319 319 _('[OPTION]... DEST'))
320 320 def archive(ui, repo, dest, **opts):
321 321 '''create an unversioned archive of a repository revision
322 322
323 323 By default, the revision used is the parent of the working
324 324 directory; use -r/--rev to specify a different revision.
325 325
326 326 The archive type is automatically detected based on file
327 327 extension (or override using -t/--type).
328 328
329 329 .. container:: verbose
330 330
331 331 Examples:
332 332
333 333 - create a zip file containing the 1.0 release::
334 334
335 335 hg archive -r 1.0 project-1.0.zip
336 336
337 337 - create a tarball excluding .hg files::
338 338
339 339 hg archive project.tar.gz -X ".hg*"
340 340
341 341 Valid types are:
342 342
343 343 :``files``: a directory full of files (default)
344 344 :``tar``: tar archive, uncompressed
345 345 :``tbz2``: tar archive, compressed using bzip2
346 346 :``tgz``: tar archive, compressed using gzip
347 347 :``uzip``: zip archive, uncompressed
348 348 :``zip``: zip archive, compressed using deflate
349 349
350 350 The exact name of the destination archive or directory is given
351 351 using a format string; see :hg:`help export` for details.
352 352
353 353 Each member added to an archive file has a directory prefix
354 354 prepended. Use -p/--prefix to specify a format string for the
355 355 prefix. The default is the basename of the archive, with suffixes
356 356 removed.
357 357
358 358 Returns 0 on success.
359 359 '''
360 360
361 361 ctx = scmutil.revsingle(repo, opts.get('rev'))
362 362 if not ctx:
363 363 raise util.Abort(_('no working directory: please specify a revision'))
364 364 node = ctx.node()
365 365 dest = cmdutil.makefilename(repo, dest, node)
366 366 if os.path.realpath(dest) == repo.root:
367 367 raise util.Abort(_('repository root cannot be destination'))
368 368
369 369 kind = opts.get('type') or archival.guesskind(dest) or 'files'
370 370 prefix = opts.get('prefix')
371 371
372 372 if dest == '-':
373 373 if kind == 'files':
374 374 raise util.Abort(_('cannot archive plain files to stdout'))
375 375 dest = cmdutil.makefileobj(repo, dest)
376 376 if not prefix:
377 377 prefix = os.path.basename(repo.root) + '-%h'
378 378
379 379 prefix = cmdutil.makefilename(repo, prefix, node)
380 380 matchfn = scmutil.match(ctx, [], opts)
381 381 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
382 382 matchfn, prefix, subrepos=opts.get('subrepos'))
383 383
384 384 @command('backout',
385 385 [('', 'merge', None, _('merge with old dirstate parent after backout')),
386 386 ('', 'parent', '',
387 387 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
388 388 ('r', 'rev', '', _('revision to backout'), _('REV')),
389 389 ] + mergetoolopts + walkopts + commitopts + commitopts2,
390 390 _('[OPTION]... [-r] REV'))
391 391 def backout(ui, repo, node=None, rev=None, **opts):
392 392 '''reverse effect of earlier changeset
393 393
394 394 Prepare a new changeset with the effect of REV undone in the
395 395 current working directory.
396 396
397 397 If REV is the parent of the working directory, then this new changeset
398 398 is committed automatically. Otherwise, hg needs to merge the
399 399 changes and the merged result is left uncommitted.
400 400
401 401 .. note::
402 402
403 403 backout cannot be used to fix either an unwanted or
404 404 incorrect merge.
405 405
406 406 .. container:: verbose
407 407
408 408 By default, the pending changeset will have one parent,
409 409 maintaining a linear history. With --merge, the pending
410 410 changeset will instead have two parents: the old parent of the
411 411 working directory and a new child of REV that simply undoes REV.
412 412
413 413 Before version 1.7, the behavior without --merge was equivalent
414 414 to specifying --merge followed by :hg:`update --clean .` to
415 415 cancel the merge and leave the child of REV as a head to be
416 416 merged separately.
417 417
418 418 See :hg:`help dates` for a list of formats valid for -d/--date.
419 419
420 420 Returns 0 on success, 1 if nothing to backout or there are unresolved
421 421 files.
422 422 '''
423 423 if rev and node:
424 424 raise util.Abort(_("please specify just one revision"))
425 425
426 426 if not rev:
427 427 rev = node
428 428
429 429 if not rev:
430 430 raise util.Abort(_("please specify a revision to backout"))
431 431
432 432 date = opts.get('date')
433 433 if date:
434 434 opts['date'] = util.parsedate(date)
435 435
436 436 cmdutil.checkunfinished(repo)
437 437 cmdutil.bailifchanged(repo)
438 438 node = scmutil.revsingle(repo, rev).node()
439 439
440 440 op1, op2 = repo.dirstate.parents()
441 441 if node not in repo.changelog.commonancestorsheads(op1, node):
442 442 raise util.Abort(_('cannot backout change that is not an ancestor'))
443 443
444 444 p1, p2 = repo.changelog.parents(node)
445 445 if p1 == nullid:
446 446 raise util.Abort(_('cannot backout a change with no parents'))
447 447 if p2 != nullid:
448 448 if not opts.get('parent'):
449 449 raise util.Abort(_('cannot backout a merge changeset'))
450 450 p = repo.lookup(opts['parent'])
451 451 if p not in (p1, p2):
452 452 raise util.Abort(_('%s is not a parent of %s') %
453 453 (short(p), short(node)))
454 454 parent = p
455 455 else:
456 456 if opts.get('parent'):
457 457 raise util.Abort(_('cannot use --parent on non-merge changeset'))
458 458 parent = p1
459 459
460 460 # the backout should appear on the same branch
461 461 wlock = repo.wlock()
462 462 try:
463 463 branch = repo.dirstate.branch()
464 464 bheads = repo.branchheads(branch)
465 465 rctx = scmutil.revsingle(repo, hex(parent))
466 466 if not opts.get('merge') and op1 != node:
467 467 try:
468 468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
469 469 'backout')
470 470 stats = mergemod.update(repo, parent, True, True, False,
471 471 node, False)
472 472 repo.setparents(op1, op2)
473 473 hg._showstats(repo, stats)
474 474 if stats[3]:
475 475 repo.ui.status(_("use 'hg resolve' to retry unresolved "
476 476 "file merges\n"))
477 477 else:
478 478 msg = _("changeset %s backed out, "
479 479 "don't forget to commit.\n")
480 480 ui.status(msg % short(node))
481 481 return stats[3] > 0
482 482 finally:
483 483 ui.setconfig('ui', 'forcemerge', '', '')
484 484 else:
485 485 hg.clean(repo, node, show_stats=False)
486 486 repo.dirstate.setbranch(branch)
487 487 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
488 488
489 489
490 490 def commitfunc(ui, repo, message, match, opts):
491 491 e = cmdutil.getcommiteditor()
492 492 if not message:
493 493 # we don't translate commit messages
494 494 message = "Backed out changeset %s" % short(node)
495 495 e = cmdutil.getcommiteditor(edit=True)
496 496 return repo.commit(message, opts.get('user'), opts.get('date'),
497 497 match, editor=e)
498 498 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
499 499 if not newnode:
500 500 ui.status(_("nothing changed\n"))
501 501 return 1
502 502 cmdutil.commitstatus(repo, newnode, branch, bheads)
503 503
504 504 def nice(node):
505 505 return '%d:%s' % (repo.changelog.rev(node), short(node))
506 506 ui.status(_('changeset %s backs out changeset %s\n') %
507 507 (nice(repo.changelog.tip()), nice(node)))
508 508 if opts.get('merge') and op1 != node:
509 509 hg.clean(repo, op1, show_stats=False)
510 510 ui.status(_('merging with changeset %s\n')
511 511 % nice(repo.changelog.tip()))
512 512 try:
513 513 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
514 514 'backout')
515 515 return hg.merge(repo, hex(repo.changelog.tip()))
516 516 finally:
517 517 ui.setconfig('ui', 'forcemerge', '', '')
518 518 finally:
519 519 wlock.release()
520 520 return 0
521 521
522 522 @command('bisect',
523 523 [('r', 'reset', False, _('reset bisect state')),
524 524 ('g', 'good', False, _('mark changeset good')),
525 525 ('b', 'bad', False, _('mark changeset bad')),
526 526 ('s', 'skip', False, _('skip testing changeset')),
527 527 ('e', 'extend', False, _('extend the bisect range')),
528 528 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
529 529 ('U', 'noupdate', False, _('do not update to target'))],
530 530 _("[-gbsr] [-U] [-c CMD] [REV]"))
531 531 def bisect(ui, repo, rev=None, extra=None, command=None,
532 532 reset=None, good=None, bad=None, skip=None, extend=None,
533 533 noupdate=None):
534 534 """subdivision search of changesets
535 535
536 536 This command helps to find changesets which introduce problems. To
537 537 use, mark the earliest changeset you know exhibits the problem as
538 538 bad, then mark the latest changeset which is free from the problem
539 539 as good. Bisect will update your working directory to a revision
540 540 for testing (unless the -U/--noupdate option is specified). Once
541 541 you have performed tests, mark the working directory as good or
542 542 bad, and bisect will either update to another candidate changeset
543 543 or announce that it has found the bad revision.
544 544
545 545 As a shortcut, you can also use the revision argument to mark a
546 546 revision as good or bad without checking it out first.
547 547
548 548 If you supply a command, it will be used for automatic bisection.
549 549 The environment variable HG_NODE will contain the ID of the
550 550 changeset being tested. The exit status of the command will be
551 551 used to mark revisions as good or bad: status 0 means good, 125
552 552 means to skip the revision, 127 (command not found) will abort the
553 553 bisection, and any other non-zero exit status means the revision
554 554 is bad.
555 555
556 556 .. container:: verbose
557 557
558 558 Some examples:
559 559
560 560 - start a bisection with known bad revision 34, and good revision 12::
561 561
562 562 hg bisect --bad 34
563 563 hg bisect --good 12
564 564
565 565 - advance the current bisection by marking current revision as good or
566 566 bad::
567 567
568 568 hg bisect --good
569 569 hg bisect --bad
570 570
571 571 - mark the current revision, or a known revision, to be skipped (e.g. if
572 572 that revision is not usable because of another issue)::
573 573
574 574 hg bisect --skip
575 575 hg bisect --skip 23
576 576
577 577 - skip all revisions that do not touch directories ``foo`` or ``bar``::
578 578
579 579 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
580 580
581 581 - forget the current bisection::
582 582
583 583 hg bisect --reset
584 584
585 585 - use 'make && make tests' to automatically find the first broken
586 586 revision::
587 587
588 588 hg bisect --reset
589 589 hg bisect --bad 34
590 590 hg bisect --good 12
591 591 hg bisect --command "make && make tests"
592 592
593 593 - see all changesets whose states are already known in the current
594 594 bisection::
595 595
596 596 hg log -r "bisect(pruned)"
597 597
598 598 - see the changeset currently being bisected (especially useful
599 599 if running with -U/--noupdate)::
600 600
601 601 hg log -r "bisect(current)"
602 602
603 603 - see all changesets that took part in the current bisection::
604 604
605 605 hg log -r "bisect(range)"
606 606
607 607 - you can even get a nice graph::
608 608
609 609 hg log --graph -r "bisect(range)"
610 610
611 611 See :hg:`help revsets` for more about the `bisect()` keyword.
612 612
613 613 Returns 0 on success.
614 614 """
615 615 def extendbisectrange(nodes, good):
616 616 # bisect is incomplete when it ends on a merge node and
617 617 # one of the parent was not checked.
618 618 parents = repo[nodes[0]].parents()
619 619 if len(parents) > 1:
620 620 side = good and state['bad'] or state['good']
621 621 num = len(set(i.node() for i in parents) & set(side))
622 622 if num == 1:
623 623 return parents[0].ancestor(parents[1])
624 624 return None
625 625
626 626 def print_result(nodes, good):
627 627 displayer = cmdutil.show_changeset(ui, repo, {})
628 628 if len(nodes) == 1:
629 629 # narrowed it down to a single revision
630 630 if good:
631 631 ui.write(_("The first good revision is:\n"))
632 632 else:
633 633 ui.write(_("The first bad revision is:\n"))
634 634 displayer.show(repo[nodes[0]])
635 635 extendnode = extendbisectrange(nodes, good)
636 636 if extendnode is not None:
637 637 ui.write(_('Not all ancestors of this changeset have been'
638 638 ' checked.\nUse bisect --extend to continue the '
639 639 'bisection from\nthe common ancestor, %s.\n')
640 640 % extendnode)
641 641 else:
642 642 # multiple possible revisions
643 643 if good:
644 644 ui.write(_("Due to skipped revisions, the first "
645 645 "good revision could be any of:\n"))
646 646 else:
647 647 ui.write(_("Due to skipped revisions, the first "
648 648 "bad revision could be any of:\n"))
649 649 for n in nodes:
650 650 displayer.show(repo[n])
651 651 displayer.close()
652 652
653 653 def check_state(state, interactive=True):
654 654 if not state['good'] or not state['bad']:
655 655 if (good or bad or skip or reset) and interactive:
656 656 return
657 657 if not state['good']:
658 658 raise util.Abort(_('cannot bisect (no known good revisions)'))
659 659 else:
660 660 raise util.Abort(_('cannot bisect (no known bad revisions)'))
661 661 return True
662 662
663 663 # backward compatibility
664 664 if rev in "good bad reset init".split():
665 665 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
666 666 cmd, rev, extra = rev, extra, None
667 667 if cmd == "good":
668 668 good = True
669 669 elif cmd == "bad":
670 670 bad = True
671 671 else:
672 672 reset = True
673 673 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
674 674 raise util.Abort(_('incompatible arguments'))
675 675
676 676 cmdutil.checkunfinished(repo)
677 677
678 678 if reset:
679 679 p = repo.join("bisect.state")
680 680 if os.path.exists(p):
681 681 os.unlink(p)
682 682 return
683 683
684 684 state = hbisect.load_state(repo)
685 685
686 686 if command:
687 687 changesets = 1
688 688 if noupdate:
689 689 try:
690 690 node = state['current'][0]
691 691 except LookupError:
692 692 raise util.Abort(_('current bisect revision is unknown - '
693 693 'start a new bisect to fix'))
694 694 else:
695 695 node, p2 = repo.dirstate.parents()
696 696 if p2 != nullid:
697 697 raise util.Abort(_('current bisect revision is a merge'))
698 698 try:
699 699 while changesets:
700 700 # update state
701 701 state['current'] = [node]
702 702 hbisect.save_state(repo, state)
703 703 status = util.system(command,
704 704 environ={'HG_NODE': hex(node)},
705 705 out=ui.fout)
706 706 if status == 125:
707 707 transition = "skip"
708 708 elif status == 0:
709 709 transition = "good"
710 710 # status < 0 means process was killed
711 711 elif status == 127:
712 712 raise util.Abort(_("failed to execute %s") % command)
713 713 elif status < 0:
714 714 raise util.Abort(_("%s killed") % command)
715 715 else:
716 716 transition = "bad"
717 717 ctx = scmutil.revsingle(repo, rev, node)
718 718 rev = None # clear for future iterations
719 719 state[transition].append(ctx.node())
720 720 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
721 721 check_state(state, interactive=False)
722 722 # bisect
723 723 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
724 724 # update to next check
725 725 node = nodes[0]
726 726 if not noupdate:
727 727 cmdutil.bailifchanged(repo)
728 728 hg.clean(repo, node, show_stats=False)
729 729 finally:
730 730 state['current'] = [node]
731 731 hbisect.save_state(repo, state)
732 732 print_result(nodes, bgood)
733 733 return
734 734
735 735 # update state
736 736
737 737 if rev:
738 738 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
739 739 else:
740 740 nodes = [repo.lookup('.')]
741 741
742 742 if good or bad or skip:
743 743 if good:
744 744 state['good'] += nodes
745 745 elif bad:
746 746 state['bad'] += nodes
747 747 elif skip:
748 748 state['skip'] += nodes
749 749 hbisect.save_state(repo, state)
750 750
751 751 if not check_state(state):
752 752 return
753 753
754 754 # actually bisect
755 755 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
756 756 if extend:
757 757 if not changesets:
758 758 extendnode = extendbisectrange(nodes, good)
759 759 if extendnode is not None:
760 760 ui.write(_("Extending search to changeset %d:%s\n")
761 761 % (extendnode.rev(), extendnode))
762 762 state['current'] = [extendnode.node()]
763 763 hbisect.save_state(repo, state)
764 764 if noupdate:
765 765 return
766 766 cmdutil.bailifchanged(repo)
767 767 return hg.clean(repo, extendnode.node())
768 768 raise util.Abort(_("nothing to extend"))
769 769
770 770 if changesets == 0:
771 771 print_result(nodes, good)
772 772 else:
773 773 assert len(nodes) == 1 # only a single node can be tested next
774 774 node = nodes[0]
775 775 # compute the approximate number of remaining tests
776 776 tests, size = 0, 2
777 777 while size <= changesets:
778 778 tests, size = tests + 1, size * 2
779 779 rev = repo.changelog.rev(node)
780 780 ui.write(_("Testing changeset %d:%s "
781 781 "(%d changesets remaining, ~%d tests)\n")
782 782 % (rev, short(node), changesets, tests))
783 783 state['current'] = [node]
784 784 hbisect.save_state(repo, state)
785 785 if not noupdate:
786 786 cmdutil.bailifchanged(repo)
787 787 return hg.clean(repo, node)
788 788
789 789 @command('bookmarks|bookmark',
790 790 [('f', 'force', False, _('force')),
791 791 ('r', 'rev', '', _('revision'), _('REV')),
792 792 ('d', 'delete', False, _('delete a given bookmark')),
793 793 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
794 794 ('i', 'inactive', False, _('mark a bookmark inactive'))],
795 795 _('hg bookmarks [OPTIONS]... [NAME]...'))
796 796 def bookmark(ui, repo, *names, **opts):
797 797 '''track a line of development with movable markers
798 798
799 799 Bookmarks are pointers to certain commits that move when committing.
800 800 Bookmarks are local. They can be renamed, copied and deleted. It is
801 801 possible to use :hg:`merge NAME` to merge from a given bookmark, and
802 802 :hg:`update NAME` to update to a given bookmark.
803 803
804 804 You can use :hg:`bookmark NAME` to set a bookmark on the working
805 805 directory's parent revision with the given name. If you specify
806 806 a revision using -r REV (where REV may be an existing bookmark),
807 807 the bookmark is assigned to that revision.
808 808
809 809 Bookmarks can be pushed and pulled between repositories (see :hg:`help
810 810 push` and :hg:`help pull`). This requires both the local and remote
811 811 repositories to support bookmarks. For versions prior to 1.8, this means
812 812 the bookmarks extension must be enabled.
813 813
814 814 If you set a bookmark called '@', new clones of the repository will
815 815 have that revision checked out (and the bookmark made active) by
816 816 default.
817 817
818 818 With -i/--inactive, the new bookmark will not be made the active
819 819 bookmark. If -r/--rev is given, the new bookmark will not be made
820 820 active even if -i/--inactive is not given. If no NAME is given, the
821 821 current active bookmark will be marked inactive.
822 822 '''
823 823 force = opts.get('force')
824 824 rev = opts.get('rev')
825 825 delete = opts.get('delete')
826 826 rename = opts.get('rename')
827 827 inactive = opts.get('inactive')
828 828
829 829 def checkformat(mark):
830 830 mark = mark.strip()
831 831 if not mark:
832 832 raise util.Abort(_("bookmark names cannot consist entirely of "
833 833 "whitespace"))
834 834 scmutil.checknewlabel(repo, mark, 'bookmark')
835 835 return mark
836 836
837 837 def checkconflict(repo, mark, cur, force=False, target=None):
838 838 if mark in marks and not force:
839 839 if target:
840 840 if marks[mark] == target and target == cur:
841 841 # re-activating a bookmark
842 842 return
843 843 anc = repo.changelog.ancestors([repo[target].rev()])
844 844 bmctx = repo[marks[mark]]
845 845 divs = [repo[b].node() for b in marks
846 846 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
847 847
848 848 # allow resolving a single divergent bookmark even if moving
849 849 # the bookmark across branches when a revision is specified
850 850 # that contains a divergent bookmark
851 851 if bmctx.rev() not in anc and target in divs:
852 852 bookmarks.deletedivergent(repo, [target], mark)
853 853 return
854 854
855 855 deletefrom = [b for b in divs
856 856 if repo[b].rev() in anc or b == target]
857 857 bookmarks.deletedivergent(repo, deletefrom, mark)
858 858 if bookmarks.validdest(repo, bmctx, repo[target]):
859 859 ui.status(_("moving bookmark '%s' forward from %s\n") %
860 860 (mark, short(bmctx.node())))
861 861 return
862 862 raise util.Abort(_("bookmark '%s' already exists "
863 863 "(use -f to force)") % mark)
864 864 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
865 865 and not force):
866 866 raise util.Abort(
867 867 _("a bookmark cannot have the name of an existing branch"))
868 868
869 869 if delete and rename:
870 870 raise util.Abort(_("--delete and --rename are incompatible"))
871 871 if delete and rev:
872 872 raise util.Abort(_("--rev is incompatible with --delete"))
873 873 if rename and rev:
874 874 raise util.Abort(_("--rev is incompatible with --rename"))
875 875 if not names and (delete or rev):
876 876 raise util.Abort(_("bookmark name required"))
877 877
878 878 if delete or rename or names or inactive:
879 879 wlock = repo.wlock()
880 880 try:
881 881 cur = repo.changectx('.').node()
882 882 marks = repo._bookmarks
883 883 if delete:
884 884 for mark in names:
885 885 if mark not in marks:
886 886 raise util.Abort(_("bookmark '%s' does not exist") %
887 887 mark)
888 888 if mark == repo._bookmarkcurrent:
889 889 bookmarks.unsetcurrent(repo)
890 890 del marks[mark]
891 891 marks.write()
892 892
893 893 elif rename:
894 894 if not names:
895 895 raise util.Abort(_("new bookmark name required"))
896 896 elif len(names) > 1:
897 897 raise util.Abort(_("only one new bookmark name allowed"))
898 898 mark = checkformat(names[0])
899 899 if rename not in marks:
900 900 raise util.Abort(_("bookmark '%s' does not exist") % rename)
901 901 checkconflict(repo, mark, cur, force)
902 902 marks[mark] = marks[rename]
903 903 if repo._bookmarkcurrent == rename and not inactive:
904 904 bookmarks.setcurrent(repo, mark)
905 905 del marks[rename]
906 906 marks.write()
907 907
908 908 elif names:
909 909 newact = None
910 910 for mark in names:
911 911 mark = checkformat(mark)
912 912 if newact is None:
913 913 newact = mark
914 914 if inactive and mark == repo._bookmarkcurrent:
915 915 bookmarks.unsetcurrent(repo)
916 916 return
917 917 tgt = cur
918 918 if rev:
919 919 tgt = scmutil.revsingle(repo, rev).node()
920 920 checkconflict(repo, mark, cur, force, tgt)
921 921 marks[mark] = tgt
922 922 if not inactive and cur == marks[newact] and not rev:
923 923 bookmarks.setcurrent(repo, newact)
924 924 elif cur != tgt and newact == repo._bookmarkcurrent:
925 925 bookmarks.unsetcurrent(repo)
926 926 marks.write()
927 927
928 928 elif inactive:
929 929 if len(marks) == 0:
930 930 ui.status(_("no bookmarks set\n"))
931 931 elif not repo._bookmarkcurrent:
932 932 ui.status(_("no active bookmark\n"))
933 933 else:
934 934 bookmarks.unsetcurrent(repo)
935 935 finally:
936 936 wlock.release()
937 937 else: # show bookmarks
938 938 hexfn = ui.debugflag and hex or short
939 939 marks = repo._bookmarks
940 940 if len(marks) == 0:
941 941 ui.status(_("no bookmarks set\n"))
942 942 else:
943 943 for bmark, n in sorted(marks.iteritems()):
944 944 current = repo._bookmarkcurrent
945 945 if bmark == current:
946 946 prefix, label = '*', 'bookmarks.current'
947 947 else:
948 948 prefix, label = ' ', ''
949 949
950 950 if ui.quiet:
951 951 ui.write("%s\n" % bmark, label=label)
952 952 else:
953 953 ui.write(" %s %-25s %d:%s\n" % (
954 954 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
955 955 label=label)
956 956
957 957 @command('branch',
958 958 [('f', 'force', None,
959 959 _('set branch name even if it shadows an existing branch')),
960 960 ('C', 'clean', None, _('reset branch name to parent branch name'))],
961 961 _('[-fC] [NAME]'))
962 962 def branch(ui, repo, label=None, **opts):
963 963 """set or show the current branch name
964 964
965 965 .. note::
966 966
967 967 Branch names are permanent and global. Use :hg:`bookmark` to create a
968 968 light-weight bookmark instead. See :hg:`help glossary` for more
969 969 information about named branches and bookmarks.
970 970
971 971 With no argument, show the current branch name. With one argument,
972 972 set the working directory branch name (the branch will not exist
973 973 in the repository until the next commit). Standard practice
974 974 recommends that primary development take place on the 'default'
975 975 branch.
976 976
977 977 Unless -f/--force is specified, branch will not let you set a
978 978 branch name that already exists, even if it's inactive.
979 979
980 980 Use -C/--clean to reset the working directory branch to that of
981 981 the parent of the working directory, negating a previous branch
982 982 change.
983 983
984 984 Use the command :hg:`update` to switch to an existing branch. Use
985 985 :hg:`commit --close-branch` to mark this branch as closed.
986 986
987 987 Returns 0 on success.
988 988 """
989 989 if label:
990 990 label = label.strip()
991 991
992 992 if not opts.get('clean') and not label:
993 993 ui.write("%s\n" % repo.dirstate.branch())
994 994 return
995 995
996 996 wlock = repo.wlock()
997 997 try:
998 998 if opts.get('clean'):
999 999 label = repo[None].p1().branch()
1000 1000 repo.dirstate.setbranch(label)
1001 1001 ui.status(_('reset working directory to branch %s\n') % label)
1002 1002 elif label:
1003 1003 if not opts.get('force') and label in repo.branchmap():
1004 1004 if label not in [p.branch() for p in repo.parents()]:
1005 1005 raise util.Abort(_('a branch of the same name already'
1006 1006 ' exists'),
1007 1007 # i18n: "it" refers to an existing branch
1008 1008 hint=_("use 'hg update' to switch to it"))
1009 1009 scmutil.checknewlabel(repo, label, 'branch')
1010 1010 repo.dirstate.setbranch(label)
1011 1011 ui.status(_('marked working directory as branch %s\n') % label)
1012 1012 ui.status(_('(branches are permanent and global, '
1013 1013 'did you want a bookmark?)\n'))
1014 1014 finally:
1015 1015 wlock.release()
1016 1016
1017 1017 @command('branches',
1018 1018 [('a', 'active', False, _('show only branches that have unmerged heads')),
1019 1019 ('c', 'closed', False, _('show normal and closed branches'))],
1020 1020 _('[-ac]'))
1021 1021 def branches(ui, repo, active=False, closed=False):
1022 1022 """list repository named branches
1023 1023
1024 1024 List the repository's named branches, indicating which ones are
1025 1025 inactive. If -c/--closed is specified, also list branches which have
1026 1026 been marked closed (see :hg:`commit --close-branch`).
1027 1027
1028 1028 If -a/--active is specified, only show active branches. A branch
1029 1029 is considered active if it contains repository heads.
1030 1030
1031 1031 Use the command :hg:`update` to switch to an existing branch.
1032 1032
1033 1033 Returns 0.
1034 1034 """
1035 1035
1036 1036 hexfunc = ui.debugflag and hex or short
1037 1037
1038 1038 allheads = set(repo.heads())
1039 1039 branches = []
1040 1040 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1041 1041 isactive = not isclosed and bool(set(heads) & allheads)
1042 1042 branches.append((tag, repo[tip], isactive, not isclosed))
1043 1043 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1044 1044 reverse=True)
1045 1045
1046 1046 for tag, ctx, isactive, isopen in branches:
1047 1047 if (not active) or isactive:
1048 1048 if isactive:
1049 1049 label = 'branches.active'
1050 1050 notice = ''
1051 1051 elif not isopen:
1052 1052 if not closed:
1053 1053 continue
1054 1054 label = 'branches.closed'
1055 1055 notice = _(' (closed)')
1056 1056 else:
1057 1057 label = 'branches.inactive'
1058 1058 notice = _(' (inactive)')
1059 1059 if tag == repo.dirstate.branch():
1060 1060 label = 'branches.current'
1061 1061 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(tag))
1062 1062 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
1063 1063 'log.changeset changeset.%s' % ctx.phasestr())
1064 1064 labeledtag = ui.label(tag, label)
1065 1065 if ui.quiet:
1066 1066 ui.write("%s\n" % labeledtag)
1067 1067 else:
1068 1068 ui.write("%s %s%s\n" % (labeledtag, rev, notice))
1069 1069
1070 1070 @command('bundle',
1071 1071 [('f', 'force', None, _('run even when the destination is unrelated')),
1072 1072 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1073 1073 _('REV')),
1074 1074 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1075 1075 _('BRANCH')),
1076 1076 ('', 'base', [],
1077 1077 _('a base changeset assumed to be available at the destination'),
1078 1078 _('REV')),
1079 1079 ('a', 'all', None, _('bundle all changesets in the repository')),
1080 1080 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1081 1081 ] + remoteopts,
1082 1082 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1083 1083 def bundle(ui, repo, fname, dest=None, **opts):
1084 1084 """create a changegroup file
1085 1085
1086 1086 Generate a compressed changegroup file collecting changesets not
1087 1087 known to be in another repository.
1088 1088
1089 1089 If you omit the destination repository, then hg assumes the
1090 1090 destination will have all the nodes you specify with --base
1091 1091 parameters. To create a bundle containing all changesets, use
1092 1092 -a/--all (or --base null).
1093 1093
1094 1094 You can change compression method with the -t/--type option.
1095 1095 The available compression methods are: none, bzip2, and
1096 1096 gzip (by default, bundles are compressed using bzip2).
1097 1097
1098 1098 The bundle file can then be transferred using conventional means
1099 1099 and applied to another repository with the unbundle or pull
1100 1100 command. This is useful when direct push and pull are not
1101 1101 available or when exporting an entire repository is undesirable.
1102 1102
1103 1103 Applying bundles preserves all changeset contents including
1104 1104 permissions, copy/rename information, and revision history.
1105 1105
1106 1106 Returns 0 on success, 1 if no changes found.
1107 1107 """
1108 1108 revs = None
1109 1109 if 'rev' in opts:
1110 1110 revs = scmutil.revrange(repo, opts['rev'])
1111 1111
1112 1112 bundletype = opts.get('type', 'bzip2').lower()
1113 1113 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1114 1114 bundletype = btypes.get(bundletype)
1115 1115 if bundletype not in changegroup.bundletypes:
1116 1116 raise util.Abort(_('unknown bundle type specified with --type'))
1117 1117
1118 1118 if opts.get('all'):
1119 1119 base = ['null']
1120 1120 else:
1121 1121 base = scmutil.revrange(repo, opts.get('base'))
1122 1122 # TODO: get desired bundlecaps from command line.
1123 1123 bundlecaps = None
1124 1124 if base:
1125 1125 if dest:
1126 1126 raise util.Abort(_("--base is incompatible with specifying "
1127 1127 "a destination"))
1128 1128 common = [repo.lookup(rev) for rev in base]
1129 1129 heads = revs and map(repo.lookup, revs) or revs
1130 1130 cg = changegroup.getbundle(repo, 'bundle', heads=heads, common=common,
1131 1131 bundlecaps=bundlecaps)
1132 1132 outgoing = None
1133 1133 else:
1134 1134 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1135 1135 dest, branches = hg.parseurl(dest, opts.get('branch'))
1136 1136 other = hg.peer(repo, opts, dest)
1137 1137 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1138 1138 heads = revs and map(repo.lookup, revs) or revs
1139 1139 outgoing = discovery.findcommonoutgoing(repo, other,
1140 1140 onlyheads=heads,
1141 1141 force=opts.get('force'),
1142 1142 portable=True)
1143 1143 cg = changegroup.getlocalbundle(repo, 'bundle', outgoing, bundlecaps)
1144 1144 if not cg:
1145 1145 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1146 1146 return 1
1147 1147
1148 1148 changegroup.writebundle(cg, fname, bundletype)
1149 1149
1150 1150 @command('cat',
1151 1151 [('o', 'output', '',
1152 1152 _('print output to file with formatted name'), _('FORMAT')),
1153 1153 ('r', 'rev', '', _('print the given revision'), _('REV')),
1154 1154 ('', 'decode', None, _('apply any matching decode filter')),
1155 1155 ] + walkopts,
1156 1156 _('[OPTION]... FILE...'))
1157 1157 def cat(ui, repo, file1, *pats, **opts):
1158 1158 """output the current or given revision of files
1159 1159
1160 1160 Print the specified files as they were at the given revision. If
1161 1161 no revision is given, the parent of the working directory is used.
1162 1162
1163 1163 Output may be to a file, in which case the name of the file is
1164 1164 given using a format string. The formatting rules as follows:
1165 1165
1166 1166 :``%%``: literal "%" character
1167 1167 :``%s``: basename of file being printed
1168 1168 :``%d``: dirname of file being printed, or '.' if in repository root
1169 1169 :``%p``: root-relative path name of file being printed
1170 1170 :``%H``: changeset hash (40 hexadecimal digits)
1171 1171 :``%R``: changeset revision number
1172 1172 :``%h``: short-form changeset hash (12 hexadecimal digits)
1173 1173 :``%r``: zero-padded changeset revision number
1174 1174 :``%b``: basename of the exporting repository
1175 1175
1176 1176 Returns 0 on success.
1177 1177 """
1178 1178 ctx = scmutil.revsingle(repo, opts.get('rev'))
1179 1179 m = scmutil.match(ctx, (file1,) + pats, opts)
1180 1180
1181 1181 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1182 1182
1183 1183 @command('^clone',
1184 1184 [('U', 'noupdate', None,
1185 1185 _('the clone will include an empty working copy (only a repository)')),
1186 1186 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1187 1187 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1188 1188 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1189 1189 ('', 'pull', None, _('use pull protocol to copy metadata')),
1190 1190 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1191 1191 ] + remoteopts,
1192 1192 _('[OPTION]... SOURCE [DEST]'))
1193 1193 def clone(ui, source, dest=None, **opts):
1194 1194 """make a copy of an existing repository
1195 1195
1196 1196 Create a copy of an existing repository in a new directory.
1197 1197
1198 1198 If no destination directory name is specified, it defaults to the
1199 1199 basename of the source.
1200 1200
1201 1201 The location of the source is added to the new repository's
1202 1202 ``.hg/hgrc`` file, as the default to be used for future pulls.
1203 1203
1204 1204 Only local paths and ``ssh://`` URLs are supported as
1205 1205 destinations. For ``ssh://`` destinations, no working directory or
1206 1206 ``.hg/hgrc`` will be created on the remote side.
1207 1207
1208 1208 To pull only a subset of changesets, specify one or more revisions
1209 1209 identifiers with -r/--rev or branches with -b/--branch. The
1210 1210 resulting clone will contain only the specified changesets and
1211 1211 their ancestors. These options (or 'clone src#rev dest') imply
1212 1212 --pull, even for local source repositories. Note that specifying a
1213 1213 tag will include the tagged changeset but not the changeset
1214 1214 containing the tag.
1215 1215
1216 1216 If the source repository has a bookmark called '@' set, that
1217 1217 revision will be checked out in the new repository by default.
1218 1218
1219 1219 To check out a particular version, use -u/--update, or
1220 1220 -U/--noupdate to create a clone with no working directory.
1221 1221
1222 1222 .. container:: verbose
1223 1223
1224 1224 For efficiency, hardlinks are used for cloning whenever the
1225 1225 source and destination are on the same filesystem (note this
1226 1226 applies only to the repository data, not to the working
1227 1227 directory). Some filesystems, such as AFS, implement hardlinking
1228 1228 incorrectly, but do not report errors. In these cases, use the
1229 1229 --pull option to avoid hardlinking.
1230 1230
1231 1231 In some cases, you can clone repositories and the working
1232 1232 directory using full hardlinks with ::
1233 1233
1234 1234 $ cp -al REPO REPOCLONE
1235 1235
1236 1236 This is the fastest way to clone, but it is not always safe. The
1237 1237 operation is not atomic (making sure REPO is not modified during
1238 1238 the operation is up to you) and you have to make sure your
1239 1239 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1240 1240 so). Also, this is not compatible with certain extensions that
1241 1241 place their metadata under the .hg directory, such as mq.
1242 1242
1243 1243 Mercurial will update the working directory to the first applicable
1244 1244 revision from this list:
1245 1245
1246 1246 a) null if -U or the source repository has no changesets
1247 1247 b) if -u . and the source repository is local, the first parent of
1248 1248 the source repository's working directory
1249 1249 c) the changeset specified with -u (if a branch name, this means the
1250 1250 latest head of that branch)
1251 1251 d) the changeset specified with -r
1252 1252 e) the tipmost head specified with -b
1253 1253 f) the tipmost head specified with the url#branch source syntax
1254 1254 g) the revision marked with the '@' bookmark, if present
1255 1255 h) the tipmost head of the default branch
1256 1256 i) tip
1257 1257
1258 1258 Examples:
1259 1259
1260 1260 - clone a remote repository to a new directory named hg/::
1261 1261
1262 1262 hg clone http://selenic.com/hg
1263 1263
1264 1264 - create a lightweight local clone::
1265 1265
1266 1266 hg clone project/ project-feature/
1267 1267
1268 1268 - clone from an absolute path on an ssh server (note double-slash)::
1269 1269
1270 1270 hg clone ssh://user@server//home/projects/alpha/
1271 1271
1272 1272 - do a high-speed clone over a LAN while checking out a
1273 1273 specified version::
1274 1274
1275 1275 hg clone --uncompressed http://server/repo -u 1.5
1276 1276
1277 1277 - create a repository without changesets after a particular revision::
1278 1278
1279 1279 hg clone -r 04e544 experimental/ good/
1280 1280
1281 1281 - clone (and track) a particular named branch::
1282 1282
1283 1283 hg clone http://selenic.com/hg#stable
1284 1284
1285 1285 See :hg:`help urls` for details on specifying URLs.
1286 1286
1287 1287 Returns 0 on success.
1288 1288 """
1289 1289 if opts.get('noupdate') and opts.get('updaterev'):
1290 1290 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1291 1291
1292 1292 r = hg.clone(ui, opts, source, dest,
1293 1293 pull=opts.get('pull'),
1294 1294 stream=opts.get('uncompressed'),
1295 1295 rev=opts.get('rev'),
1296 1296 update=opts.get('updaterev') or not opts.get('noupdate'),
1297 1297 branch=opts.get('branch'))
1298 1298
1299 1299 return r is None
1300 1300
1301 1301 @command('^commit|ci',
1302 1302 [('A', 'addremove', None,
1303 1303 _('mark new/missing files as added/removed before committing')),
1304 1304 ('', 'close-branch', None,
1305 1305 _('mark a branch as closed, hiding it from the branch list')),
1306 1306 ('', 'amend', None, _('amend the parent of the working dir')),
1307 1307 ('s', 'secret', None, _('use the secret phase for committing')),
1308 1308 ('e', 'edit', None,
1309 1309 _('further edit commit message already specified')),
1310 1310 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1311 1311 _('[OPTION]... [FILE]...'))
1312 1312 def commit(ui, repo, *pats, **opts):
1313 1313 """commit the specified files or all outstanding changes
1314 1314
1315 1315 Commit changes to the given files into the repository. Unlike a
1316 1316 centralized SCM, this operation is a local operation. See
1317 1317 :hg:`push` for a way to actively distribute your changes.
1318 1318
1319 1319 If a list of files is omitted, all changes reported by :hg:`status`
1320 1320 will be committed.
1321 1321
1322 1322 If you are committing the result of a merge, do not provide any
1323 1323 filenames or -I/-X filters.
1324 1324
1325 1325 If no commit message is specified, Mercurial starts your
1326 1326 configured editor where you can enter a message. In case your
1327 1327 commit fails, you will find a backup of your message in
1328 1328 ``.hg/last-message.txt``.
1329 1329
1330 1330 The --amend flag can be used to amend the parent of the
1331 1331 working directory with a new commit that contains the changes
1332 1332 in the parent in addition to those currently reported by :hg:`status`,
1333 1333 if there are any. The old commit is stored in a backup bundle in
1334 1334 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1335 1335 on how to restore it).
1336 1336
1337 1337 Message, user and date are taken from the amended commit unless
1338 1338 specified. When a message isn't specified on the command line,
1339 1339 the editor will open with the message of the amended commit.
1340 1340
1341 1341 It is not possible to amend public changesets (see :hg:`help phases`)
1342 1342 or changesets that have children.
1343 1343
1344 1344 See :hg:`help dates` for a list of formats valid for -d/--date.
1345 1345
1346 1346 Returns 0 on success, 1 if nothing changed.
1347 1347 """
1348 1348 if opts.get('subrepos'):
1349 1349 if opts.get('amend'):
1350 1350 raise util.Abort(_('cannot amend with --subrepos'))
1351 1351 # Let --subrepos on the command line override config setting.
1352 1352 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1353 1353
1354 1354 # Save this for restoring it later
1355 1355 oldcommitphase = ui.config('phases', 'new-commit')
1356 1356
1357 1357 cmdutil.checkunfinished(repo, commit=True)
1358 1358
1359 1359 branch = repo[None].branch()
1360 1360 bheads = repo.branchheads(branch)
1361 1361
1362 1362 extra = {}
1363 1363 if opts.get('close_branch'):
1364 1364 extra['close'] = 1
1365 1365
1366 1366 if not bheads:
1367 1367 raise util.Abort(_('can only close branch heads'))
1368 1368 elif opts.get('amend'):
1369 1369 if repo.parents()[0].p1().branch() != branch and \
1370 1370 repo.parents()[0].p2().branch() != branch:
1371 1371 raise util.Abort(_('can only close branch heads'))
1372 1372
1373 1373 if opts.get('amend'):
1374 1374 if ui.configbool('ui', 'commitsubrepos'):
1375 1375 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1376 1376
1377 1377 old = repo['.']
1378 1378 if old.phase() == phases.public:
1379 1379 raise util.Abort(_('cannot amend public changesets'))
1380 1380 if len(repo[None].parents()) > 1:
1381 1381 raise util.Abort(_('cannot amend while merging'))
1382 1382 if (not obsolete._enabled) and old.children():
1383 1383 raise util.Abort(_('cannot amend changeset with children'))
1384 1384
1385 1385 # commitfunc is used only for temporary amend commit by cmdutil.amend
1386 1386 def commitfunc(ui, repo, message, match, opts):
1387 1387 return repo.commit(message,
1388 1388 opts.get('user') or old.user(),
1389 1389 opts.get('date') or old.date(),
1390 1390 match,
1391 1391 extra=extra)
1392 1392
1393 1393 current = repo._bookmarkcurrent
1394 1394 marks = old.bookmarks()
1395 1395 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1396 1396 if node == old.node():
1397 1397 ui.status(_("nothing changed\n"))
1398 1398 return 1
1399 1399 elif marks:
1400 1400 ui.debug('moving bookmarks %r from %s to %s\n' %
1401 1401 (marks, old.hex(), hex(node)))
1402 1402 newmarks = repo._bookmarks
1403 1403 for bm in marks:
1404 1404 newmarks[bm] = node
1405 1405 if bm == current:
1406 1406 bookmarks.setcurrent(repo, bm)
1407 1407 newmarks.write()
1408 1408 else:
1409 1409 def commitfunc(ui, repo, message, match, opts):
1410 1410 try:
1411 1411 if opts.get('secret'):
1412 1412 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1413 1413 # Propagate to subrepos
1414 1414 repo.baseui.setconfig('phases', 'new-commit', 'secret',
1415 1415 'commit')
1416 1416
1417 1417 return repo.commit(message, opts.get('user'), opts.get('date'),
1418 1418 match,
1419 1419 editor=cmdutil.getcommiteditor(**opts),
1420 1420 extra=extra)
1421 1421 finally:
1422 1422 ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit')
1423 1423 repo.baseui.setconfig('phases', 'new-commit', oldcommitphase,
1424 1424 'commit')
1425 1425
1426 1426
1427 1427 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1428 1428
1429 1429 if not node:
1430 1430 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1431 1431 if stat[3]:
1432 1432 ui.status(_("nothing changed (%d missing files, see "
1433 1433 "'hg status')\n") % len(stat[3]))
1434 1434 else:
1435 1435 ui.status(_("nothing changed\n"))
1436 1436 return 1
1437 1437
1438 1438 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1439 1439
1440 1440 @command('config|showconfig|debugconfig',
1441 1441 [('u', 'untrusted', None, _('show untrusted configuration options')),
1442 1442 ('e', 'edit', None, _('edit user config')),
1443 1443 ('l', 'local', None, _('edit repository config')),
1444 1444 ('g', 'global', None, _('edit global config'))],
1445 1445 _('[-u] [NAME]...'))
1446 1446 def config(ui, repo, *values, **opts):
1447 1447 """show combined config settings from all hgrc files
1448 1448
1449 1449 With no arguments, print names and values of all config items.
1450 1450
1451 1451 With one argument of the form section.name, print just the value
1452 1452 of that config item.
1453 1453
1454 1454 With multiple arguments, print names and values of all config
1455 1455 items with matching section names.
1456 1456
1457 1457 With --edit, start an editor on the user-level config file. With
1458 1458 --global, edit the system-wide config file. With --local, edit the
1459 1459 repository-level config file.
1460 1460
1461 1461 With --debug, the source (filename and line number) is printed
1462 1462 for each config item.
1463 1463
1464 1464 See :hg:`help config` for more information about config files.
1465 1465
1466 1466 Returns 0 on success.
1467 1467
1468 1468 """
1469 1469
1470 1470 if opts.get('edit') or opts.get('local') or opts.get('global'):
1471 1471 if opts.get('local') and opts.get('global'):
1472 1472 raise util.Abort(_("can't use --local and --global together"))
1473 1473
1474 1474 if opts.get('local'):
1475 1475 if not repo:
1476 1476 raise util.Abort(_("can't use --local outside a repository"))
1477 1477 paths = [repo.join('hgrc')]
1478 1478 elif opts.get('global'):
1479 1479 paths = scmutil.systemrcpath()
1480 1480 else:
1481 1481 paths = scmutil.userrcpath()
1482 1482
1483 1483 for f in paths:
1484 1484 if os.path.exists(f):
1485 1485 break
1486 1486 else:
1487 1487 f = paths[0]
1488 1488 fp = open(f, "w")
1489 1489 fp.write(
1490 1490 '# example config (see "hg help config" for more info)\n'
1491 1491 '\n'
1492 1492 '[ui]\n'
1493 1493 '# name and email, e.g.\n'
1494 1494 '# username = Jane Doe <jdoe@example.com>\n'
1495 1495 'username =\n'
1496 1496 '\n'
1497 1497 '[extensions]\n'
1498 1498 '# uncomment these lines to enable some popular extensions\n'
1499 1499 '# (see "hg help extensions" for more info)\n'
1500 1500 '# pager =\n'
1501 1501 '# progress =\n'
1502 1502 '# color =\n')
1503 1503 fp.close()
1504 1504
1505 1505 editor = ui.geteditor()
1506 1506 util.system("%s \"%s\"" % (editor, f),
1507 1507 onerr=util.Abort, errprefix=_("edit failed"),
1508 1508 out=ui.fout)
1509 1509 return
1510 1510
1511 1511 for f in scmutil.rcpath():
1512 1512 ui.debug('read config from: %s\n' % f)
1513 1513 untrusted = bool(opts.get('untrusted'))
1514 1514 if values:
1515 1515 sections = [v for v in values if '.' not in v]
1516 1516 items = [v for v in values if '.' in v]
1517 1517 if len(items) > 1 or items and sections:
1518 1518 raise util.Abort(_('only one config item permitted'))
1519 1519 for section, name, value in ui.walkconfig(untrusted=untrusted):
1520 1520 value = str(value).replace('\n', '\\n')
1521 1521 sectname = section + '.' + name
1522 1522 if values:
1523 1523 for v in values:
1524 1524 if v == section:
1525 1525 ui.debug('%s: ' %
1526 1526 ui.configsource(section, name, untrusted))
1527 1527 ui.write('%s=%s\n' % (sectname, value))
1528 1528 elif v == sectname:
1529 1529 ui.debug('%s: ' %
1530 1530 ui.configsource(section, name, untrusted))
1531 1531 ui.write(value, '\n')
1532 1532 else:
1533 1533 ui.debug('%s: ' %
1534 1534 ui.configsource(section, name, untrusted))
1535 1535 ui.write('%s=%s\n' % (sectname, value))
1536 1536
1537 1537 @command('copy|cp',
1538 1538 [('A', 'after', None, _('record a copy that has already occurred')),
1539 1539 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1540 1540 ] + walkopts + dryrunopts,
1541 1541 _('[OPTION]... [SOURCE]... DEST'))
1542 1542 def copy(ui, repo, *pats, **opts):
1543 1543 """mark files as copied for the next commit
1544 1544
1545 1545 Mark dest as having copies of source files. If dest is a
1546 1546 directory, copies are put in that directory. If dest is a file,
1547 1547 the source must be a single file.
1548 1548
1549 1549 By default, this command copies the contents of files as they
1550 1550 exist in the working directory. If invoked with -A/--after, the
1551 1551 operation is recorded, but no copying is performed.
1552 1552
1553 1553 This command takes effect with the next commit. To undo a copy
1554 1554 before that, see :hg:`revert`.
1555 1555
1556 1556 Returns 0 on success, 1 if errors are encountered.
1557 1557 """
1558 1558 wlock = repo.wlock(False)
1559 1559 try:
1560 1560 return cmdutil.copy(ui, repo, pats, opts)
1561 1561 finally:
1562 1562 wlock.release()
1563 1563
1564 1564 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1565 1565 def debugancestor(ui, repo, *args):
1566 1566 """find the ancestor revision of two revisions in a given index"""
1567 1567 if len(args) == 3:
1568 1568 index, rev1, rev2 = args
1569 1569 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1570 1570 lookup = r.lookup
1571 1571 elif len(args) == 2:
1572 1572 if not repo:
1573 1573 raise util.Abort(_("there is no Mercurial repository here "
1574 1574 "(.hg not found)"))
1575 1575 rev1, rev2 = args
1576 1576 r = repo.changelog
1577 1577 lookup = repo.lookup
1578 1578 else:
1579 1579 raise util.Abort(_('either two or three arguments required'))
1580 1580 a = r.ancestor(lookup(rev1), lookup(rev2))
1581 1581 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1582 1582
1583 1583 @command('debugbuilddag',
1584 1584 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1585 1585 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1586 1586 ('n', 'new-file', None, _('add new file at each rev'))],
1587 1587 _('[OPTION]... [TEXT]'))
1588 1588 def debugbuilddag(ui, repo, text=None,
1589 1589 mergeable_file=False,
1590 1590 overwritten_file=False,
1591 1591 new_file=False):
1592 1592 """builds a repo with a given DAG from scratch in the current empty repo
1593 1593
1594 1594 The description of the DAG is read from stdin if not given on the
1595 1595 command line.
1596 1596
1597 1597 Elements:
1598 1598
1599 1599 - "+n" is a linear run of n nodes based on the current default parent
1600 1600 - "." is a single node based on the current default parent
1601 1601 - "$" resets the default parent to null (implied at the start);
1602 1602 otherwise the default parent is always the last node created
1603 1603 - "<p" sets the default parent to the backref p
1604 1604 - "*p" is a fork at parent p, which is a backref
1605 1605 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1606 1606 - "/p2" is a merge of the preceding node and p2
1607 1607 - ":tag" defines a local tag for the preceding node
1608 1608 - "@branch" sets the named branch for subsequent nodes
1609 1609 - "#...\\n" is a comment up to the end of the line
1610 1610
1611 1611 Whitespace between the above elements is ignored.
1612 1612
1613 1613 A backref is either
1614 1614
1615 1615 - a number n, which references the node curr-n, where curr is the current
1616 1616 node, or
1617 1617 - the name of a local tag you placed earlier using ":tag", or
1618 1618 - empty to denote the default parent.
1619 1619
1620 1620 All string valued-elements are either strictly alphanumeric, or must
1621 1621 be enclosed in double quotes ("..."), with "\\" as escape character.
1622 1622 """
1623 1623
1624 1624 if text is None:
1625 1625 ui.status(_("reading DAG from stdin\n"))
1626 1626 text = ui.fin.read()
1627 1627
1628 1628 cl = repo.changelog
1629 1629 if len(cl) > 0:
1630 1630 raise util.Abort(_('repository is not empty'))
1631 1631
1632 1632 # determine number of revs in DAG
1633 1633 total = 0
1634 1634 for type, data in dagparser.parsedag(text):
1635 1635 if type == 'n':
1636 1636 total += 1
1637 1637
1638 1638 if mergeable_file:
1639 1639 linesperrev = 2
1640 1640 # make a file with k lines per rev
1641 1641 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1642 1642 initialmergedlines.append("")
1643 1643
1644 1644 tags = []
1645 1645
1646 1646 lock = tr = None
1647 1647 try:
1648 1648 lock = repo.lock()
1649 1649 tr = repo.transaction("builddag")
1650 1650
1651 1651 at = -1
1652 1652 atbranch = 'default'
1653 1653 nodeids = []
1654 1654 id = 0
1655 1655 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1656 1656 for type, data in dagparser.parsedag(text):
1657 1657 if type == 'n':
1658 1658 ui.note(('node %s\n' % str(data)))
1659 1659 id, ps = data
1660 1660
1661 1661 files = []
1662 1662 fctxs = {}
1663 1663
1664 1664 p2 = None
1665 1665 if mergeable_file:
1666 1666 fn = "mf"
1667 1667 p1 = repo[ps[0]]
1668 1668 if len(ps) > 1:
1669 1669 p2 = repo[ps[1]]
1670 1670 pa = p1.ancestor(p2)
1671 1671 base, local, other = [x[fn].data() for x in (pa, p1,
1672 1672 p2)]
1673 1673 m3 = simplemerge.Merge3Text(base, local, other)
1674 1674 ml = [l.strip() for l in m3.merge_lines()]
1675 1675 ml.append("")
1676 1676 elif at > 0:
1677 1677 ml = p1[fn].data().split("\n")
1678 1678 else:
1679 1679 ml = initialmergedlines
1680 1680 ml[id * linesperrev] += " r%i" % id
1681 1681 mergedtext = "\n".join(ml)
1682 1682 files.append(fn)
1683 1683 fctxs[fn] = context.memfilectx(fn, mergedtext)
1684 1684
1685 1685 if overwritten_file:
1686 1686 fn = "of"
1687 1687 files.append(fn)
1688 1688 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1689 1689
1690 1690 if new_file:
1691 1691 fn = "nf%i" % id
1692 1692 files.append(fn)
1693 1693 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1694 1694 if len(ps) > 1:
1695 1695 if not p2:
1696 1696 p2 = repo[ps[1]]
1697 1697 for fn in p2:
1698 1698 if fn.startswith("nf"):
1699 1699 files.append(fn)
1700 1700 fctxs[fn] = p2[fn]
1701 1701
1702 1702 def fctxfn(repo, cx, path):
1703 1703 return fctxs.get(path)
1704 1704
1705 1705 if len(ps) == 0 or ps[0] < 0:
1706 1706 pars = [None, None]
1707 1707 elif len(ps) == 1:
1708 1708 pars = [nodeids[ps[0]], None]
1709 1709 else:
1710 1710 pars = [nodeids[p] for p in ps]
1711 1711 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1712 1712 date=(id, 0),
1713 1713 user="debugbuilddag",
1714 1714 extra={'branch': atbranch})
1715 1715 nodeid = repo.commitctx(cx)
1716 1716 nodeids.append(nodeid)
1717 1717 at = id
1718 1718 elif type == 'l':
1719 1719 id, name = data
1720 1720 ui.note(('tag %s\n' % name))
1721 1721 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1722 1722 elif type == 'a':
1723 1723 ui.note(('branch %s\n' % data))
1724 1724 atbranch = data
1725 1725 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1726 1726 tr.close()
1727 1727
1728 1728 if tags:
1729 1729 repo.opener.write("localtags", "".join(tags))
1730 1730 finally:
1731 1731 ui.progress(_('building'), None)
1732 1732 release(tr, lock)
1733 1733
1734 1734 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1735 1735 def debugbundle(ui, bundlepath, all=None, **opts):
1736 1736 """lists the contents of a bundle"""
1737 1737 f = hg.openpath(ui, bundlepath)
1738 1738 try:
1739 1739 gen = exchange.readbundle(ui, f, bundlepath)
1740 1740 if all:
1741 1741 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1742 1742
1743 1743 def showchunks(named):
1744 1744 ui.write("\n%s\n" % named)
1745 1745 chain = None
1746 1746 while True:
1747 1747 chunkdata = gen.deltachunk(chain)
1748 1748 if not chunkdata:
1749 1749 break
1750 1750 node = chunkdata['node']
1751 1751 p1 = chunkdata['p1']
1752 1752 p2 = chunkdata['p2']
1753 1753 cs = chunkdata['cs']
1754 1754 deltabase = chunkdata['deltabase']
1755 1755 delta = chunkdata['delta']
1756 1756 ui.write("%s %s %s %s %s %s\n" %
1757 1757 (hex(node), hex(p1), hex(p2),
1758 1758 hex(cs), hex(deltabase), len(delta)))
1759 1759 chain = node
1760 1760
1761 1761 chunkdata = gen.changelogheader()
1762 1762 showchunks("changelog")
1763 1763 chunkdata = gen.manifestheader()
1764 1764 showchunks("manifest")
1765 1765 while True:
1766 1766 chunkdata = gen.filelogheader()
1767 1767 if not chunkdata:
1768 1768 break
1769 1769 fname = chunkdata['filename']
1770 1770 showchunks(fname)
1771 1771 else:
1772 1772 chunkdata = gen.changelogheader()
1773 1773 chain = None
1774 1774 while True:
1775 1775 chunkdata = gen.deltachunk(chain)
1776 1776 if not chunkdata:
1777 1777 break
1778 1778 node = chunkdata['node']
1779 1779 ui.write("%s\n" % hex(node))
1780 1780 chain = node
1781 1781 finally:
1782 1782 f.close()
1783 1783
1784 1784 @command('debugcheckstate', [], '')
1785 1785 def debugcheckstate(ui, repo):
1786 1786 """validate the correctness of the current dirstate"""
1787 1787 parent1, parent2 = repo.dirstate.parents()
1788 1788 m1 = repo[parent1].manifest()
1789 1789 m2 = repo[parent2].manifest()
1790 1790 errors = 0
1791 1791 for f in repo.dirstate:
1792 1792 state = repo.dirstate[f]
1793 1793 if state in "nr" and f not in m1:
1794 1794 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1795 1795 errors += 1
1796 1796 if state in "a" and f in m1:
1797 1797 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1798 1798 errors += 1
1799 1799 if state in "m" and f not in m1 and f not in m2:
1800 1800 ui.warn(_("%s in state %s, but not in either manifest\n") %
1801 1801 (f, state))
1802 1802 errors += 1
1803 1803 for f in m1:
1804 1804 state = repo.dirstate[f]
1805 1805 if state not in "nrm":
1806 1806 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1807 1807 errors += 1
1808 1808 if errors:
1809 1809 error = _(".hg/dirstate inconsistent with current parent's manifest")
1810 1810 raise util.Abort(error)
1811 1811
1812 1812 @command('debugcommands', [], _('[COMMAND]'))
1813 1813 def debugcommands(ui, cmd='', *args):
1814 1814 """list all available commands and options"""
1815 1815 for cmd, vals in sorted(table.iteritems()):
1816 1816 cmd = cmd.split('|')[0].strip('^')
1817 1817 opts = ', '.join([i[1] for i in vals[1]])
1818 1818 ui.write('%s: %s\n' % (cmd, opts))
1819 1819
1820 1820 @command('debugcomplete',
1821 1821 [('o', 'options', None, _('show the command options'))],
1822 1822 _('[-o] CMD'))
1823 1823 def debugcomplete(ui, cmd='', **opts):
1824 1824 """returns the completion list associated with the given command"""
1825 1825
1826 1826 if opts.get('options'):
1827 1827 options = []
1828 1828 otables = [globalopts]
1829 1829 if cmd:
1830 1830 aliases, entry = cmdutil.findcmd(cmd, table, False)
1831 1831 otables.append(entry[1])
1832 1832 for t in otables:
1833 1833 for o in t:
1834 1834 if "(DEPRECATED)" in o[3]:
1835 1835 continue
1836 1836 if o[0]:
1837 1837 options.append('-%s' % o[0])
1838 1838 options.append('--%s' % o[1])
1839 1839 ui.write("%s\n" % "\n".join(options))
1840 1840 return
1841 1841
1842 1842 cmdlist = cmdutil.findpossible(cmd, table)
1843 1843 if ui.verbose:
1844 1844 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1845 1845 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1846 1846
1847 1847 @command('debugdag',
1848 1848 [('t', 'tags', None, _('use tags as labels')),
1849 1849 ('b', 'branches', None, _('annotate with branch names')),
1850 1850 ('', 'dots', None, _('use dots for runs')),
1851 1851 ('s', 'spaces', None, _('separate elements by spaces'))],
1852 1852 _('[OPTION]... [FILE [REV]...]'))
1853 1853 def debugdag(ui, repo, file_=None, *revs, **opts):
1854 1854 """format the changelog or an index DAG as a concise textual description
1855 1855
1856 1856 If you pass a revlog index, the revlog's DAG is emitted. If you list
1857 1857 revision numbers, they get labeled in the output as rN.
1858 1858
1859 1859 Otherwise, the changelog DAG of the current repo is emitted.
1860 1860 """
1861 1861 spaces = opts.get('spaces')
1862 1862 dots = opts.get('dots')
1863 1863 if file_:
1864 1864 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1865 1865 revs = set((int(r) for r in revs))
1866 1866 def events():
1867 1867 for r in rlog:
1868 1868 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1869 1869 if p != -1)))
1870 1870 if r in revs:
1871 1871 yield 'l', (r, "r%i" % r)
1872 1872 elif repo:
1873 1873 cl = repo.changelog
1874 1874 tags = opts.get('tags')
1875 1875 branches = opts.get('branches')
1876 1876 if tags:
1877 1877 labels = {}
1878 1878 for l, n in repo.tags().items():
1879 1879 labels.setdefault(cl.rev(n), []).append(l)
1880 1880 def events():
1881 1881 b = "default"
1882 1882 for r in cl:
1883 1883 if branches:
1884 1884 newb = cl.read(cl.node(r))[5]['branch']
1885 1885 if newb != b:
1886 1886 yield 'a', newb
1887 1887 b = newb
1888 1888 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1889 1889 if p != -1)))
1890 1890 if tags:
1891 1891 ls = labels.get(r)
1892 1892 if ls:
1893 1893 for l in ls:
1894 1894 yield 'l', (r, l)
1895 1895 else:
1896 1896 raise util.Abort(_('need repo for changelog dag'))
1897 1897
1898 1898 for line in dagparser.dagtextlines(events(),
1899 1899 addspaces=spaces,
1900 1900 wraplabels=True,
1901 1901 wrapannotations=True,
1902 1902 wrapnonlinear=dots,
1903 1903 usedots=dots,
1904 1904 maxlinewidth=70):
1905 1905 ui.write(line)
1906 1906 ui.write("\n")
1907 1907
1908 1908 @command('debugdata',
1909 1909 [('c', 'changelog', False, _('open changelog')),
1910 1910 ('m', 'manifest', False, _('open manifest'))],
1911 1911 _('-c|-m|FILE REV'))
1912 1912 def debugdata(ui, repo, file_, rev=None, **opts):
1913 1913 """dump the contents of a data file revision"""
1914 1914 if opts.get('changelog') or opts.get('manifest'):
1915 1915 file_, rev = None, file_
1916 1916 elif rev is None:
1917 1917 raise error.CommandError('debugdata', _('invalid arguments'))
1918 1918 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1919 1919 try:
1920 1920 ui.write(r.revision(r.lookup(rev)))
1921 1921 except KeyError:
1922 1922 raise util.Abort(_('invalid revision identifier %s') % rev)
1923 1923
1924 1924 @command('debugdate',
1925 1925 [('e', 'extended', None, _('try extended date formats'))],
1926 1926 _('[-e] DATE [RANGE]'))
1927 1927 def debugdate(ui, date, range=None, **opts):
1928 1928 """parse and display a date"""
1929 1929 if opts["extended"]:
1930 1930 d = util.parsedate(date, util.extendeddateformats)
1931 1931 else:
1932 1932 d = util.parsedate(date)
1933 1933 ui.write(("internal: %s %s\n") % d)
1934 1934 ui.write(("standard: %s\n") % util.datestr(d))
1935 1935 if range:
1936 1936 m = util.matchdate(range)
1937 1937 ui.write(("match: %s\n") % m(d[0]))
1938 1938
1939 1939 @command('debugdiscovery',
1940 1940 [('', 'old', None, _('use old-style discovery')),
1941 1941 ('', 'nonheads', None,
1942 1942 _('use old-style discovery with non-heads included')),
1943 1943 ] + remoteopts,
1944 1944 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1945 1945 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1946 1946 """runs the changeset discovery protocol in isolation"""
1947 1947 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1948 1948 opts.get('branch'))
1949 1949 remote = hg.peer(repo, opts, remoteurl)
1950 1950 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1951 1951
1952 1952 # make sure tests are repeatable
1953 1953 random.seed(12323)
1954 1954
1955 1955 def doit(localheads, remoteheads, remote=remote):
1956 1956 if opts.get('old'):
1957 1957 if localheads:
1958 1958 raise util.Abort('cannot use localheads with old style '
1959 1959 'discovery')
1960 1960 if not util.safehasattr(remote, 'branches'):
1961 1961 # enable in-client legacy support
1962 1962 remote = localrepo.locallegacypeer(remote.local())
1963 1963 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1964 1964 force=True)
1965 1965 common = set(common)
1966 1966 if not opts.get('nonheads'):
1967 1967 ui.write(("unpruned common: %s\n") %
1968 1968 " ".join(sorted(short(n) for n in common)))
1969 1969 dag = dagutil.revlogdag(repo.changelog)
1970 1970 all = dag.ancestorset(dag.internalizeall(common))
1971 1971 common = dag.externalizeall(dag.headsetofconnecteds(all))
1972 1972 else:
1973 1973 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1974 1974 common = set(common)
1975 1975 rheads = set(hds)
1976 1976 lheads = set(repo.heads())
1977 1977 ui.write(("common heads: %s\n") %
1978 1978 " ".join(sorted(short(n) for n in common)))
1979 1979 if lheads <= common:
1980 1980 ui.write(("local is subset\n"))
1981 1981 elif rheads <= common:
1982 1982 ui.write(("remote is subset\n"))
1983 1983
1984 1984 serverlogs = opts.get('serverlog')
1985 1985 if serverlogs:
1986 1986 for filename in serverlogs:
1987 1987 logfile = open(filename, 'r')
1988 1988 try:
1989 1989 line = logfile.readline()
1990 1990 while line:
1991 1991 parts = line.strip().split(';')
1992 1992 op = parts[1]
1993 1993 if op == 'cg':
1994 1994 pass
1995 1995 elif op == 'cgss':
1996 1996 doit(parts[2].split(' '), parts[3].split(' '))
1997 1997 elif op == 'unb':
1998 1998 doit(parts[3].split(' '), parts[2].split(' '))
1999 1999 line = logfile.readline()
2000 2000 finally:
2001 2001 logfile.close()
2002 2002
2003 2003 else:
2004 2004 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2005 2005 opts.get('remote_head'))
2006 2006 localrevs = opts.get('local_head')
2007 2007 doit(localrevs, remoterevs)
2008 2008
2009 2009 @command('debugfileset',
2010 2010 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2011 2011 _('[-r REV] FILESPEC'))
2012 2012 def debugfileset(ui, repo, expr, **opts):
2013 2013 '''parse and apply a fileset specification'''
2014 2014 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2015 2015 if ui.verbose:
2016 2016 tree = fileset.parse(expr)[0]
2017 2017 ui.note(tree, "\n")
2018 2018
2019 2019 for f in ctx.getfileset(expr):
2020 2020 ui.write("%s\n" % f)
2021 2021
2022 2022 @command('debugfsinfo', [], _('[PATH]'))
2023 2023 def debugfsinfo(ui, path="."):
2024 2024 """show information detected about current filesystem"""
2025 2025 util.writefile('.debugfsinfo', '')
2026 2026 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2027 2027 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2028 2028 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2029 2029 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2030 2030 and 'yes' or 'no'))
2031 2031 os.unlink('.debugfsinfo')
2032 2032
2033 2033 @command('debuggetbundle',
2034 2034 [('H', 'head', [], _('id of head node'), _('ID')),
2035 2035 ('C', 'common', [], _('id of common node'), _('ID')),
2036 2036 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2037 2037 _('REPO FILE [-H|-C ID]...'))
2038 2038 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2039 2039 """retrieves a bundle from a repo
2040 2040
2041 2041 Every ID must be a full-length hex node id string. Saves the bundle to the
2042 2042 given file.
2043 2043 """
2044 2044 repo = hg.peer(ui, opts, repopath)
2045 2045 if not repo.capable('getbundle'):
2046 2046 raise util.Abort("getbundle() not supported by target repository")
2047 2047 args = {}
2048 2048 if common:
2049 2049 args['common'] = [bin(s) for s in common]
2050 2050 if head:
2051 2051 args['heads'] = [bin(s) for s in head]
2052 2052 # TODO: get desired bundlecaps from command line.
2053 2053 args['bundlecaps'] = None
2054 2054 bundle = repo.getbundle('debug', **args)
2055 2055
2056 2056 bundletype = opts.get('type', 'bzip2').lower()
2057 2057 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2058 2058 bundletype = btypes.get(bundletype)
2059 2059 if bundletype not in changegroup.bundletypes:
2060 2060 raise util.Abort(_('unknown bundle type specified with --type'))
2061 2061 changegroup.writebundle(bundle, bundlepath, bundletype)
2062 2062
2063 2063 @command('debugignore', [], '')
2064 2064 def debugignore(ui, repo, *values, **opts):
2065 2065 """display the combined ignore pattern"""
2066 2066 ignore = repo.dirstate._ignore
2067 2067 includepat = getattr(ignore, 'includepat', None)
2068 2068 if includepat is not None:
2069 2069 ui.write("%s\n" % includepat)
2070 2070 else:
2071 2071 raise util.Abort(_("no ignore patterns found"))
2072 2072
2073 2073 @command('debugindex',
2074 2074 [('c', 'changelog', False, _('open changelog')),
2075 2075 ('m', 'manifest', False, _('open manifest')),
2076 2076 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2077 2077 _('[-f FORMAT] -c|-m|FILE'))
2078 2078 def debugindex(ui, repo, file_=None, **opts):
2079 2079 """dump the contents of an index file"""
2080 2080 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2081 2081 format = opts.get('format', 0)
2082 2082 if format not in (0, 1):
2083 2083 raise util.Abort(_("unknown format %d") % format)
2084 2084
2085 2085 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2086 2086 if generaldelta:
2087 2087 basehdr = ' delta'
2088 2088 else:
2089 2089 basehdr = ' base'
2090 2090
2091 2091 if format == 0:
2092 2092 ui.write(" rev offset length " + basehdr + " linkrev"
2093 2093 " nodeid p1 p2\n")
2094 2094 elif format == 1:
2095 2095 ui.write(" rev flag offset length"
2096 2096 " size " + basehdr + " link p1 p2"
2097 2097 " nodeid\n")
2098 2098
2099 2099 for i in r:
2100 2100 node = r.node(i)
2101 2101 if generaldelta:
2102 2102 base = r.deltaparent(i)
2103 2103 else:
2104 2104 base = r.chainbase(i)
2105 2105 if format == 0:
2106 2106 try:
2107 2107 pp = r.parents(node)
2108 2108 except Exception:
2109 2109 pp = [nullid, nullid]
2110 2110 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2111 2111 i, r.start(i), r.length(i), base, r.linkrev(i),
2112 2112 short(node), short(pp[0]), short(pp[1])))
2113 2113 elif format == 1:
2114 2114 pr = r.parentrevs(i)
2115 2115 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2116 2116 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2117 2117 base, r.linkrev(i), pr[0], pr[1], short(node)))
2118 2118
2119 2119 @command('debugindexdot', [], _('FILE'))
2120 2120 def debugindexdot(ui, repo, file_):
2121 2121 """dump an index DAG as a graphviz dot file"""
2122 2122 r = None
2123 2123 if repo:
2124 2124 filelog = repo.file(file_)
2125 2125 if len(filelog):
2126 2126 r = filelog
2127 2127 if not r:
2128 2128 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2129 2129 ui.write(("digraph G {\n"))
2130 2130 for i in r:
2131 2131 node = r.node(i)
2132 2132 pp = r.parents(node)
2133 2133 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2134 2134 if pp[1] != nullid:
2135 2135 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2136 2136 ui.write("}\n")
2137 2137
2138 2138 @command('debuginstall', [], '')
2139 2139 def debuginstall(ui):
2140 2140 '''test Mercurial installation
2141 2141
2142 2142 Returns 0 on success.
2143 2143 '''
2144 2144
2145 2145 def writetemp(contents):
2146 2146 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2147 2147 f = os.fdopen(fd, "wb")
2148 2148 f.write(contents)
2149 2149 f.close()
2150 2150 return name
2151 2151
2152 2152 problems = 0
2153 2153
2154 2154 # encoding
2155 2155 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2156 2156 try:
2157 2157 encoding.fromlocal("test")
2158 2158 except util.Abort, inst:
2159 2159 ui.write(" %s\n" % inst)
2160 2160 ui.write(_(" (check that your locale is properly set)\n"))
2161 2161 problems += 1
2162 2162
2163 2163 # Python
2164 2164 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2165 2165 ui.status(_("checking Python version (%s)\n")
2166 2166 % ("%s.%s.%s" % sys.version_info[:3]))
2167 2167 ui.status(_("checking Python lib (%s)...\n")
2168 2168 % os.path.dirname(os.__file__))
2169 2169
2170 2170 # compiled modules
2171 2171 ui.status(_("checking installed modules (%s)...\n")
2172 2172 % os.path.dirname(__file__))
2173 2173 try:
2174 2174 import bdiff, mpatch, base85, osutil
2175 2175 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2176 2176 except Exception, inst:
2177 2177 ui.write(" %s\n" % inst)
2178 2178 ui.write(_(" One or more extensions could not be found"))
2179 2179 ui.write(_(" (check that you compiled the extensions)\n"))
2180 2180 problems += 1
2181 2181
2182 2182 # templates
2183 2183 import templater
2184 2184 p = templater.templatepath()
2185 2185 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2186 2186 if p:
2187 2187 m = templater.templatepath("map-cmdline.default")
2188 2188 if m:
2189 2189 # template found, check if it is working
2190 2190 try:
2191 2191 templater.templater(m)
2192 2192 except Exception, inst:
2193 2193 ui.write(" %s\n" % inst)
2194 2194 p = None
2195 2195 else:
2196 2196 ui.write(_(" template 'default' not found\n"))
2197 2197 p = None
2198 2198 else:
2199 2199 ui.write(_(" no template directories found\n"))
2200 2200 if not p:
2201 2201 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2202 2202 problems += 1
2203 2203
2204 2204 # editor
2205 2205 ui.status(_("checking commit editor...\n"))
2206 2206 editor = ui.geteditor()
2207 2207 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2208 2208 if not cmdpath:
2209 2209 if editor == 'vi':
2210 2210 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2211 2211 ui.write(_(" (specify a commit editor in your configuration"
2212 2212 " file)\n"))
2213 2213 else:
2214 2214 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2215 2215 ui.write(_(" (specify a commit editor in your configuration"
2216 2216 " file)\n"))
2217 2217 problems += 1
2218 2218
2219 2219 # check username
2220 2220 ui.status(_("checking username...\n"))
2221 2221 try:
2222 2222 ui.username()
2223 2223 except util.Abort, e:
2224 2224 ui.write(" %s\n" % e)
2225 2225 ui.write(_(" (specify a username in your configuration file)\n"))
2226 2226 problems += 1
2227 2227
2228 2228 if not problems:
2229 2229 ui.status(_("no problems detected\n"))
2230 2230 else:
2231 2231 ui.write(_("%s problems detected,"
2232 2232 " please check your install!\n") % problems)
2233 2233
2234 2234 return problems
2235 2235
2236 2236 @command('debugknown', [], _('REPO ID...'))
2237 2237 def debugknown(ui, repopath, *ids, **opts):
2238 2238 """test whether node ids are known to a repo
2239 2239
2240 2240 Every ID must be a full-length hex node id string. Returns a list of 0s
2241 2241 and 1s indicating unknown/known.
2242 2242 """
2243 2243 repo = hg.peer(ui, opts, repopath)
2244 2244 if not repo.capable('known'):
2245 2245 raise util.Abort("known() not supported by target repository")
2246 2246 flags = repo.known([bin(s) for s in ids])
2247 2247 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2248 2248
2249 2249 @command('debuglabelcomplete', [], _('LABEL...'))
2250 2250 def debuglabelcomplete(ui, repo, *args):
2251 2251 '''complete "labels" - tags, open branch names, bookmark names'''
2252 2252
2253 2253 labels = set()
2254 2254 labels.update(t[0] for t in repo.tagslist())
2255 2255 labels.update(repo._bookmarks.keys())
2256 2256 labels.update(tag for (tag, heads, tip, closed)
2257 2257 in repo.branchmap().iterbranches() if not closed)
2258 2258 completions = set()
2259 2259 if not args:
2260 2260 args = ['']
2261 2261 for a in args:
2262 2262 completions.update(l for l in labels if l.startswith(a))
2263 2263 ui.write('\n'.join(sorted(completions)))
2264 2264 ui.write('\n')
2265 2265
2266 2266 @command('debugobsolete',
2267 2267 [('', 'flags', 0, _('markers flag')),
2268 2268 ] + commitopts2,
2269 2269 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2270 2270 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2271 2271 """create arbitrary obsolete marker
2272 2272
2273 2273 With no arguments, displays the list of obsolescence markers."""
2274 2274 def parsenodeid(s):
2275 2275 try:
2276 2276 # We do not use revsingle/revrange functions here to accept
2277 2277 # arbitrary node identifiers, possibly not present in the
2278 2278 # local repository.
2279 2279 n = bin(s)
2280 2280 if len(n) != len(nullid):
2281 2281 raise TypeError()
2282 2282 return n
2283 2283 except TypeError:
2284 2284 raise util.Abort('changeset references must be full hexadecimal '
2285 2285 'node identifiers')
2286 2286
2287 2287 if precursor is not None:
2288 2288 metadata = {}
2289 2289 if 'date' in opts:
2290 2290 metadata['date'] = opts['date']
2291 2291 metadata['user'] = opts['user'] or ui.username()
2292 2292 succs = tuple(parsenodeid(succ) for succ in successors)
2293 2293 l = repo.lock()
2294 2294 try:
2295 2295 tr = repo.transaction('debugobsolete')
2296 2296 try:
2297 2297 repo.obsstore.create(tr, parsenodeid(precursor), succs,
2298 2298 opts['flags'], metadata)
2299 2299 tr.close()
2300 2300 finally:
2301 2301 tr.release()
2302 2302 finally:
2303 2303 l.release()
2304 2304 else:
2305 2305 for m in obsolete.allmarkers(repo):
2306 2306 cmdutil.showmarker(ui, m)
2307 2307
2308 2308 @command('debugpathcomplete',
2309 2309 [('f', 'full', None, _('complete an entire path')),
2310 2310 ('n', 'normal', None, _('show only normal files')),
2311 2311 ('a', 'added', None, _('show only added files')),
2312 2312 ('r', 'removed', None, _('show only removed files'))],
2313 2313 _('FILESPEC...'))
2314 2314 def debugpathcomplete(ui, repo, *specs, **opts):
2315 2315 '''complete part or all of a tracked path
2316 2316
2317 2317 This command supports shells that offer path name completion. It
2318 2318 currently completes only files already known to the dirstate.
2319 2319
2320 2320 Completion extends only to the next path segment unless
2321 2321 --full is specified, in which case entire paths are used.'''
2322 2322
2323 2323 def complete(path, acceptable):
2324 2324 dirstate = repo.dirstate
2325 2325 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2326 2326 rootdir = repo.root + os.sep
2327 2327 if spec != repo.root and not spec.startswith(rootdir):
2328 2328 return [], []
2329 2329 if os.path.isdir(spec):
2330 2330 spec += '/'
2331 2331 spec = spec[len(rootdir):]
2332 2332 fixpaths = os.sep != '/'
2333 2333 if fixpaths:
2334 2334 spec = spec.replace(os.sep, '/')
2335 2335 speclen = len(spec)
2336 2336 fullpaths = opts['full']
2337 2337 files, dirs = set(), set()
2338 2338 adddir, addfile = dirs.add, files.add
2339 2339 for f, st in dirstate.iteritems():
2340 2340 if f.startswith(spec) and st[0] in acceptable:
2341 2341 if fixpaths:
2342 2342 f = f.replace('/', os.sep)
2343 2343 if fullpaths:
2344 2344 addfile(f)
2345 2345 continue
2346 2346 s = f.find(os.sep, speclen)
2347 2347 if s >= 0:
2348 2348 adddir(f[:s])
2349 2349 else:
2350 2350 addfile(f)
2351 2351 return files, dirs
2352 2352
2353 2353 acceptable = ''
2354 2354 if opts['normal']:
2355 2355 acceptable += 'nm'
2356 2356 if opts['added']:
2357 2357 acceptable += 'a'
2358 2358 if opts['removed']:
2359 2359 acceptable += 'r'
2360 2360 cwd = repo.getcwd()
2361 2361 if not specs:
2362 2362 specs = ['.']
2363 2363
2364 2364 files, dirs = set(), set()
2365 2365 for spec in specs:
2366 2366 f, d = complete(spec, acceptable or 'nmar')
2367 2367 files.update(f)
2368 2368 dirs.update(d)
2369 2369 files.update(dirs)
2370 2370 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2371 2371 ui.write('\n')
2372 2372
2373 2373 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2374 2374 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2375 2375 '''access the pushkey key/value protocol
2376 2376
2377 2377 With two args, list the keys in the given namespace.
2378 2378
2379 2379 With five args, set a key to new if it currently is set to old.
2380 2380 Reports success or failure.
2381 2381 '''
2382 2382
2383 2383 target = hg.peer(ui, {}, repopath)
2384 2384 if keyinfo:
2385 2385 key, old, new = keyinfo
2386 2386 r = target.pushkey(namespace, key, old, new)
2387 2387 ui.status(str(r) + '\n')
2388 2388 return not r
2389 2389 else:
2390 2390 for k, v in sorted(target.listkeys(namespace).iteritems()):
2391 2391 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2392 2392 v.encode('string-escape')))
2393 2393
2394 2394 @command('debugpvec', [], _('A B'))
2395 2395 def debugpvec(ui, repo, a, b=None):
2396 2396 ca = scmutil.revsingle(repo, a)
2397 2397 cb = scmutil.revsingle(repo, b)
2398 2398 pa = pvec.ctxpvec(ca)
2399 2399 pb = pvec.ctxpvec(cb)
2400 2400 if pa == pb:
2401 2401 rel = "="
2402 2402 elif pa > pb:
2403 2403 rel = ">"
2404 2404 elif pa < pb:
2405 2405 rel = "<"
2406 2406 elif pa | pb:
2407 2407 rel = "|"
2408 2408 ui.write(_("a: %s\n") % pa)
2409 2409 ui.write(_("b: %s\n") % pb)
2410 2410 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2411 2411 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2412 2412 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2413 2413 pa.distance(pb), rel))
2414 2414
2415 2415 @command('debugrebuilddirstate|debugrebuildstate',
2416 2416 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2417 2417 _('[-r REV]'))
2418 2418 def debugrebuilddirstate(ui, repo, rev):
2419 2419 """rebuild the dirstate as it would look like for the given revision
2420 2420
2421 2421 If no revision is specified the first current parent will be used.
2422 2422
2423 2423 The dirstate will be set to the files of the given revision.
2424 2424 The actual working directory content or existing dirstate
2425 2425 information such as adds or removes is not considered.
2426 2426
2427 2427 One use of this command is to make the next :hg:`status` invocation
2428 2428 check the actual file content.
2429 2429 """
2430 2430 ctx = scmutil.revsingle(repo, rev)
2431 2431 wlock = repo.wlock()
2432 2432 try:
2433 2433 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2434 2434 finally:
2435 2435 wlock.release()
2436 2436
2437 2437 @command('debugrename',
2438 2438 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2439 2439 _('[-r REV] FILE'))
2440 2440 def debugrename(ui, repo, file1, *pats, **opts):
2441 2441 """dump rename information"""
2442 2442
2443 2443 ctx = scmutil.revsingle(repo, opts.get('rev'))
2444 2444 m = scmutil.match(ctx, (file1,) + pats, opts)
2445 2445 for abs in ctx.walk(m):
2446 2446 fctx = ctx[abs]
2447 2447 o = fctx.filelog().renamed(fctx.filenode())
2448 2448 rel = m.rel(abs)
2449 2449 if o:
2450 2450 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2451 2451 else:
2452 2452 ui.write(_("%s not renamed\n") % rel)
2453 2453
2454 2454 @command('debugrevlog',
2455 2455 [('c', 'changelog', False, _('open changelog')),
2456 2456 ('m', 'manifest', False, _('open manifest')),
2457 2457 ('d', 'dump', False, _('dump index data'))],
2458 2458 _('-c|-m|FILE'))
2459 2459 def debugrevlog(ui, repo, file_=None, **opts):
2460 2460 """show data and statistics about a revlog"""
2461 2461 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2462 2462
2463 2463 if opts.get("dump"):
2464 2464 numrevs = len(r)
2465 2465 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2466 2466 " rawsize totalsize compression heads\n")
2467 2467 ts = 0
2468 2468 heads = set()
2469 2469 for rev in xrange(numrevs):
2470 2470 dbase = r.deltaparent(rev)
2471 2471 if dbase == -1:
2472 2472 dbase = rev
2473 2473 cbase = r.chainbase(rev)
2474 2474 p1, p2 = r.parentrevs(rev)
2475 2475 rs = r.rawsize(rev)
2476 2476 ts = ts + rs
2477 2477 heads -= set(r.parentrevs(rev))
2478 2478 heads.add(rev)
2479 2479 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d %11d %5d\n" %
2480 2480 (rev, p1, p2, r.start(rev), r.end(rev),
2481 2481 r.start(dbase), r.start(cbase),
2482 2482 r.start(p1), r.start(p2),
2483 2483 rs, ts, ts / r.end(rev), len(heads)))
2484 2484 return 0
2485 2485
2486 2486 v = r.version
2487 2487 format = v & 0xFFFF
2488 2488 flags = []
2489 2489 gdelta = False
2490 2490 if v & revlog.REVLOGNGINLINEDATA:
2491 2491 flags.append('inline')
2492 2492 if v & revlog.REVLOGGENERALDELTA:
2493 2493 gdelta = True
2494 2494 flags.append('generaldelta')
2495 2495 if not flags:
2496 2496 flags = ['(none)']
2497 2497
2498 2498 nummerges = 0
2499 2499 numfull = 0
2500 2500 numprev = 0
2501 2501 nump1 = 0
2502 2502 nump2 = 0
2503 2503 numother = 0
2504 2504 nump1prev = 0
2505 2505 nump2prev = 0
2506 2506 chainlengths = []
2507 2507
2508 2508 datasize = [None, 0, 0L]
2509 2509 fullsize = [None, 0, 0L]
2510 2510 deltasize = [None, 0, 0L]
2511 2511
2512 2512 def addsize(size, l):
2513 2513 if l[0] is None or size < l[0]:
2514 2514 l[0] = size
2515 2515 if size > l[1]:
2516 2516 l[1] = size
2517 2517 l[2] += size
2518 2518
2519 2519 numrevs = len(r)
2520 2520 for rev in xrange(numrevs):
2521 2521 p1, p2 = r.parentrevs(rev)
2522 2522 delta = r.deltaparent(rev)
2523 2523 if format > 0:
2524 2524 addsize(r.rawsize(rev), datasize)
2525 2525 if p2 != nullrev:
2526 2526 nummerges += 1
2527 2527 size = r.length(rev)
2528 2528 if delta == nullrev:
2529 2529 chainlengths.append(0)
2530 2530 numfull += 1
2531 2531 addsize(size, fullsize)
2532 2532 else:
2533 2533 chainlengths.append(chainlengths[delta] + 1)
2534 2534 addsize(size, deltasize)
2535 2535 if delta == rev - 1:
2536 2536 numprev += 1
2537 2537 if delta == p1:
2538 2538 nump1prev += 1
2539 2539 elif delta == p2:
2540 2540 nump2prev += 1
2541 2541 elif delta == p1:
2542 2542 nump1 += 1
2543 2543 elif delta == p2:
2544 2544 nump2 += 1
2545 2545 elif delta != nullrev:
2546 2546 numother += 1
2547 2547
2548 2548 # Adjust size min value for empty cases
2549 2549 for size in (datasize, fullsize, deltasize):
2550 2550 if size[0] is None:
2551 2551 size[0] = 0
2552 2552
2553 2553 numdeltas = numrevs - numfull
2554 2554 numoprev = numprev - nump1prev - nump2prev
2555 2555 totalrawsize = datasize[2]
2556 2556 datasize[2] /= numrevs
2557 2557 fulltotal = fullsize[2]
2558 2558 fullsize[2] /= numfull
2559 2559 deltatotal = deltasize[2]
2560 2560 if numrevs - numfull > 0:
2561 2561 deltasize[2] /= numrevs - numfull
2562 2562 totalsize = fulltotal + deltatotal
2563 2563 avgchainlen = sum(chainlengths) / numrevs
2564 2564 compratio = totalrawsize / totalsize
2565 2565
2566 2566 basedfmtstr = '%%%dd\n'
2567 2567 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2568 2568
2569 2569 def dfmtstr(max):
2570 2570 return basedfmtstr % len(str(max))
2571 2571 def pcfmtstr(max, padding=0):
2572 2572 return basepcfmtstr % (len(str(max)), ' ' * padding)
2573 2573
2574 2574 def pcfmt(value, total):
2575 2575 return (value, 100 * float(value) / total)
2576 2576
2577 2577 ui.write(('format : %d\n') % format)
2578 2578 ui.write(('flags : %s\n') % ', '.join(flags))
2579 2579
2580 2580 ui.write('\n')
2581 2581 fmt = pcfmtstr(totalsize)
2582 2582 fmt2 = dfmtstr(totalsize)
2583 2583 ui.write(('revisions : ') + fmt2 % numrevs)
2584 2584 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2585 2585 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2586 2586 ui.write(('revisions : ') + fmt2 % numrevs)
2587 2587 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2588 2588 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2589 2589 ui.write(('revision size : ') + fmt2 % totalsize)
2590 2590 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2591 2591 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2592 2592
2593 2593 ui.write('\n')
2594 2594 fmt = dfmtstr(max(avgchainlen, compratio))
2595 2595 ui.write(('avg chain length : ') + fmt % avgchainlen)
2596 2596 ui.write(('compression ratio : ') + fmt % compratio)
2597 2597
2598 2598 if format > 0:
2599 2599 ui.write('\n')
2600 2600 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2601 2601 % tuple(datasize))
2602 2602 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2603 2603 % tuple(fullsize))
2604 2604 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2605 2605 % tuple(deltasize))
2606 2606
2607 2607 if numdeltas > 0:
2608 2608 ui.write('\n')
2609 2609 fmt = pcfmtstr(numdeltas)
2610 2610 fmt2 = pcfmtstr(numdeltas, 4)
2611 2611 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2612 2612 if numprev > 0:
2613 2613 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2614 2614 numprev))
2615 2615 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2616 2616 numprev))
2617 2617 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2618 2618 numprev))
2619 2619 if gdelta:
2620 2620 ui.write(('deltas against p1 : ')
2621 2621 + fmt % pcfmt(nump1, numdeltas))
2622 2622 ui.write(('deltas against p2 : ')
2623 2623 + fmt % pcfmt(nump2, numdeltas))
2624 2624 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2625 2625 numdeltas))
2626 2626
2627 2627 @command('debugrevspec',
2628 2628 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2629 2629 ('REVSPEC'))
2630 2630 def debugrevspec(ui, repo, expr, **opts):
2631 2631 """parse and apply a revision specification
2632 2632
2633 2633 Use --verbose to print the parsed tree before and after aliases
2634 2634 expansion.
2635 2635 """
2636 2636 if ui.verbose:
2637 2637 tree = revset.parse(expr)[0]
2638 2638 ui.note(revset.prettyformat(tree), "\n")
2639 2639 newtree = revset.findaliases(ui, tree)
2640 2640 if newtree != tree:
2641 2641 ui.note(revset.prettyformat(newtree), "\n")
2642 2642 if opts["optimize"]:
2643 2643 weight, optimizedtree = revset.optimize(newtree, True)
2644 2644 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2645 2645 func = revset.match(ui, expr)
2646 2646 for c in func(repo, revset.spanset(repo)):
2647 2647 ui.write("%s\n" % c)
2648 2648
2649 2649 @command('debugsetparents', [], _('REV1 [REV2]'))
2650 2650 def debugsetparents(ui, repo, rev1, rev2=None):
2651 2651 """manually set the parents of the current working directory
2652 2652
2653 2653 This is useful for writing repository conversion tools, but should
2654 2654 be used with care.
2655 2655
2656 2656 Returns 0 on success.
2657 2657 """
2658 2658
2659 2659 r1 = scmutil.revsingle(repo, rev1).node()
2660 2660 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2661 2661
2662 2662 wlock = repo.wlock()
2663 2663 try:
2664 2664 repo.setparents(r1, r2)
2665 2665 finally:
2666 2666 wlock.release()
2667 2667
2668 2668 @command('debugdirstate|debugstate',
2669 2669 [('', 'nodates', None, _('do not display the saved mtime')),
2670 2670 ('', 'datesort', None, _('sort by saved mtime'))],
2671 2671 _('[OPTION]...'))
2672 2672 def debugstate(ui, repo, nodates=None, datesort=None):
2673 2673 """show the contents of the current dirstate"""
2674 2674 timestr = ""
2675 2675 showdate = not nodates
2676 2676 if datesort:
2677 2677 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2678 2678 else:
2679 2679 keyfunc = None # sort by filename
2680 2680 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2681 2681 if showdate:
2682 2682 if ent[3] == -1:
2683 2683 # Pad or slice to locale representation
2684 2684 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2685 2685 time.localtime(0)))
2686 2686 timestr = 'unset'
2687 2687 timestr = (timestr[:locale_len] +
2688 2688 ' ' * (locale_len - len(timestr)))
2689 2689 else:
2690 2690 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2691 2691 time.localtime(ent[3]))
2692 2692 if ent[1] & 020000:
2693 2693 mode = 'lnk'
2694 2694 else:
2695 2695 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2696 2696 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2697 2697 for f in repo.dirstate.copies():
2698 2698 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2699 2699
2700 2700 @command('debugsub',
2701 2701 [('r', 'rev', '',
2702 2702 _('revision to check'), _('REV'))],
2703 2703 _('[-r REV] [REV]'))
2704 2704 def debugsub(ui, repo, rev=None):
2705 2705 ctx = scmutil.revsingle(repo, rev, None)
2706 2706 for k, v in sorted(ctx.substate.items()):
2707 2707 ui.write(('path %s\n') % k)
2708 2708 ui.write((' source %s\n') % v[0])
2709 2709 ui.write((' revision %s\n') % v[1])
2710 2710
2711 2711 @command('debugsuccessorssets',
2712 2712 [],
2713 2713 _('[REV]'))
2714 2714 def debugsuccessorssets(ui, repo, *revs):
2715 2715 """show set of successors for revision
2716 2716
2717 2717 A successors set of changeset A is a consistent group of revisions that
2718 2718 succeed A. It contains non-obsolete changesets only.
2719 2719
2720 2720 In most cases a changeset A has a single successors set containing a single
2721 2721 successor (changeset A replaced by A').
2722 2722
2723 2723 A changeset that is made obsolete with no successors are called "pruned".
2724 2724 Such changesets have no successors sets at all.
2725 2725
2726 2726 A changeset that has been "split" will have a successors set containing
2727 2727 more than one successor.
2728 2728
2729 2729 A changeset that has been rewritten in multiple different ways is called
2730 2730 "divergent". Such changesets have multiple successor sets (each of which
2731 2731 may also be split, i.e. have multiple successors).
2732 2732
2733 2733 Results are displayed as follows::
2734 2734
2735 2735 <rev1>
2736 2736 <successors-1A>
2737 2737 <rev2>
2738 2738 <successors-2A>
2739 2739 <successors-2B1> <successors-2B2> <successors-2B3>
2740 2740
2741 2741 Here rev2 has two possible (i.e. divergent) successors sets. The first
2742 2742 holds one element, whereas the second holds three (i.e. the changeset has
2743 2743 been split).
2744 2744 """
2745 2745 # passed to successorssets caching computation from one call to another
2746 2746 cache = {}
2747 2747 ctx2str = str
2748 2748 node2str = short
2749 2749 if ui.debug():
2750 2750 def ctx2str(ctx):
2751 2751 return ctx.hex()
2752 2752 node2str = hex
2753 2753 for rev in scmutil.revrange(repo, revs):
2754 2754 ctx = repo[rev]
2755 2755 ui.write('%s\n'% ctx2str(ctx))
2756 2756 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2757 2757 if succsset:
2758 2758 ui.write(' ')
2759 2759 ui.write(node2str(succsset[0]))
2760 2760 for node in succsset[1:]:
2761 2761 ui.write(' ')
2762 2762 ui.write(node2str(node))
2763 2763 ui.write('\n')
2764 2764
2765 2765 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2766 2766 def debugwalk(ui, repo, *pats, **opts):
2767 2767 """show how files match on given patterns"""
2768 2768 m = scmutil.match(repo[None], pats, opts)
2769 2769 items = list(repo.walk(m))
2770 2770 if not items:
2771 2771 return
2772 2772 f = lambda fn: fn
2773 2773 if ui.configbool('ui', 'slash') and os.sep != '/':
2774 2774 f = lambda fn: util.normpath(fn)
2775 2775 fmt = 'f %%-%ds %%-%ds %%s' % (
2776 2776 max([len(abs) for abs in items]),
2777 2777 max([len(m.rel(abs)) for abs in items]))
2778 2778 for abs in items:
2779 2779 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2780 2780 ui.write("%s\n" % line.rstrip())
2781 2781
2782 2782 @command('debugwireargs',
2783 2783 [('', 'three', '', 'three'),
2784 2784 ('', 'four', '', 'four'),
2785 2785 ('', 'five', '', 'five'),
2786 2786 ] + remoteopts,
2787 2787 _('REPO [OPTIONS]... [ONE [TWO]]'))
2788 2788 def debugwireargs(ui, repopath, *vals, **opts):
2789 2789 repo = hg.peer(ui, opts, repopath)
2790 2790 for opt in remoteopts:
2791 2791 del opts[opt[1]]
2792 2792 args = {}
2793 2793 for k, v in opts.iteritems():
2794 2794 if v:
2795 2795 args[k] = v
2796 2796 # run twice to check that we don't mess up the stream for the next command
2797 2797 res1 = repo.debugwireargs(*vals, **args)
2798 2798 res2 = repo.debugwireargs(*vals, **args)
2799 2799 ui.write("%s\n" % res1)
2800 2800 if res1 != res2:
2801 2801 ui.warn("%s\n" % res2)
2802 2802
2803 2803 @command('^diff',
2804 2804 [('r', 'rev', [], _('revision'), _('REV')),
2805 2805 ('c', 'change', '', _('change made by revision'), _('REV'))
2806 2806 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2807 2807 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2808 2808 def diff(ui, repo, *pats, **opts):
2809 2809 """diff repository (or selected files)
2810 2810
2811 2811 Show differences between revisions for the specified files.
2812 2812
2813 2813 Differences between files are shown using the unified diff format.
2814 2814
2815 2815 .. note::
2816 2816
2817 2817 diff may generate unexpected results for merges, as it will
2818 2818 default to comparing against the working directory's first
2819 2819 parent changeset if no revisions are specified.
2820 2820
2821 2821 When two revision arguments are given, then changes are shown
2822 2822 between those revisions. If only one revision is specified then
2823 2823 that revision is compared to the working directory, and, when no
2824 2824 revisions are specified, the working directory files are compared
2825 2825 to its parent.
2826 2826
2827 2827 Alternatively you can specify -c/--change with a revision to see
2828 2828 the changes in that changeset relative to its first parent.
2829 2829
2830 2830 Without the -a/--text option, diff will avoid generating diffs of
2831 2831 files it detects as binary. With -a, diff will generate a diff
2832 2832 anyway, probably with undesirable results.
2833 2833
2834 2834 Use the -g/--git option to generate diffs in the git extended diff
2835 2835 format. For more information, read :hg:`help diffs`.
2836 2836
2837 2837 .. container:: verbose
2838 2838
2839 2839 Examples:
2840 2840
2841 2841 - compare a file in the current working directory to its parent::
2842 2842
2843 2843 hg diff foo.c
2844 2844
2845 2845 - compare two historical versions of a directory, with rename info::
2846 2846
2847 2847 hg diff --git -r 1.0:1.2 lib/
2848 2848
2849 2849 - get change stats relative to the last change on some date::
2850 2850
2851 2851 hg diff --stat -r "date('may 2')"
2852 2852
2853 2853 - diff all newly-added files that contain a keyword::
2854 2854
2855 2855 hg diff "set:added() and grep(GNU)"
2856 2856
2857 2857 - compare a revision and its parents::
2858 2858
2859 2859 hg diff -c 9353 # compare against first parent
2860 2860 hg diff -r 9353^:9353 # same using revset syntax
2861 2861 hg diff -r 9353^2:9353 # compare against the second parent
2862 2862
2863 2863 Returns 0 on success.
2864 2864 """
2865 2865
2866 2866 revs = opts.get('rev')
2867 2867 change = opts.get('change')
2868 2868 stat = opts.get('stat')
2869 2869 reverse = opts.get('reverse')
2870 2870
2871 2871 if revs and change:
2872 2872 msg = _('cannot specify --rev and --change at the same time')
2873 2873 raise util.Abort(msg)
2874 2874 elif change:
2875 2875 node2 = scmutil.revsingle(repo, change, None).node()
2876 2876 node1 = repo[node2].p1().node()
2877 2877 else:
2878 2878 node1, node2 = scmutil.revpair(repo, revs)
2879 2879
2880 2880 if reverse:
2881 2881 node1, node2 = node2, node1
2882 2882
2883 2883 diffopts = patch.diffopts(ui, opts)
2884 2884 m = scmutil.match(repo[node2], pats, opts)
2885 2885 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2886 2886 listsubrepos=opts.get('subrepos'))
2887 2887
2888 2888 @command('^export',
2889 2889 [('o', 'output', '',
2890 2890 _('print output to file with formatted name'), _('FORMAT')),
2891 2891 ('', 'switch-parent', None, _('diff against the second parent')),
2892 2892 ('r', 'rev', [], _('revisions to export'), _('REV')),
2893 2893 ] + diffopts,
2894 2894 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
2895 2895 def export(ui, repo, *changesets, **opts):
2896 2896 """dump the header and diffs for one or more changesets
2897 2897
2898 2898 Print the changeset header and diffs for one or more revisions.
2899 2899 If no revision is given, the parent of the working directory is used.
2900 2900
2901 2901 The information shown in the changeset header is: author, date,
2902 2902 branch name (if non-default), changeset hash, parent(s) and commit
2903 2903 comment.
2904 2904
2905 2905 .. note::
2906 2906
2907 2907 export may generate unexpected diff output for merge
2908 2908 changesets, as it will compare the merge changeset against its
2909 2909 first parent only.
2910 2910
2911 2911 Output may be to a file, in which case the name of the file is
2912 2912 given using a format string. The formatting rules are as follows:
2913 2913
2914 2914 :``%%``: literal "%" character
2915 2915 :``%H``: changeset hash (40 hexadecimal digits)
2916 2916 :``%N``: number of patches being generated
2917 2917 :``%R``: changeset revision number
2918 2918 :``%b``: basename of the exporting repository
2919 2919 :``%h``: short-form changeset hash (12 hexadecimal digits)
2920 2920 :``%m``: first line of the commit message (only alphanumeric characters)
2921 2921 :``%n``: zero-padded sequence number, starting at 1
2922 2922 :``%r``: zero-padded changeset revision number
2923 2923
2924 2924 Without the -a/--text option, export will avoid generating diffs
2925 2925 of files it detects as binary. With -a, export will generate a
2926 2926 diff anyway, probably with undesirable results.
2927 2927
2928 2928 Use the -g/--git option to generate diffs in the git extended diff
2929 2929 format. See :hg:`help diffs` for more information.
2930 2930
2931 2931 With the --switch-parent option, the diff will be against the
2932 2932 second parent. It can be useful to review a merge.
2933 2933
2934 2934 .. container:: verbose
2935 2935
2936 2936 Examples:
2937 2937
2938 2938 - use export and import to transplant a bugfix to the current
2939 2939 branch::
2940 2940
2941 2941 hg export -r 9353 | hg import -
2942 2942
2943 2943 - export all the changesets between two revisions to a file with
2944 2944 rename information::
2945 2945
2946 2946 hg export --git -r 123:150 > changes.txt
2947 2947
2948 2948 - split outgoing changes into a series of patches with
2949 2949 descriptive names::
2950 2950
2951 2951 hg export -r "outgoing()" -o "%n-%m.patch"
2952 2952
2953 2953 Returns 0 on success.
2954 2954 """
2955 2955 changesets += tuple(opts.get('rev', []))
2956 2956 if not changesets:
2957 2957 changesets = ['.']
2958 2958 revs = scmutil.revrange(repo, changesets)
2959 2959 if not revs:
2960 2960 raise util.Abort(_("export requires at least one changeset"))
2961 2961 if len(revs) > 1:
2962 2962 ui.note(_('exporting patches:\n'))
2963 2963 else:
2964 2964 ui.note(_('exporting patch:\n'))
2965 2965 cmdutil.export(repo, revs, template=opts.get('output'),
2966 2966 switch_parent=opts.get('switch_parent'),
2967 2967 opts=patch.diffopts(ui, opts))
2968 2968
2969 2969 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2970 2970 def forget(ui, repo, *pats, **opts):
2971 2971 """forget the specified files on the next commit
2972 2972
2973 2973 Mark the specified files so they will no longer be tracked
2974 2974 after the next commit.
2975 2975
2976 2976 This only removes files from the current branch, not from the
2977 2977 entire project history, and it does not delete them from the
2978 2978 working directory.
2979 2979
2980 2980 To undo a forget before the next commit, see :hg:`add`.
2981 2981
2982 2982 .. container:: verbose
2983 2983
2984 2984 Examples:
2985 2985
2986 2986 - forget newly-added binary files::
2987 2987
2988 2988 hg forget "set:added() and binary()"
2989 2989
2990 2990 - forget files that would be excluded by .hgignore::
2991 2991
2992 2992 hg forget "set:hgignore()"
2993 2993
2994 2994 Returns 0 on success.
2995 2995 """
2996 2996
2997 2997 if not pats:
2998 2998 raise util.Abort(_('no files specified'))
2999 2999
3000 3000 m = scmutil.match(repo[None], pats, opts)
3001 3001 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3002 3002 return rejected and 1 or 0
3003 3003
3004 3004 @command(
3005 3005 'graft',
3006 3006 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3007 3007 ('c', 'continue', False, _('resume interrupted graft')),
3008 3008 ('e', 'edit', False, _('invoke editor on commit messages')),
3009 3009 ('', 'log', None, _('append graft info to log message')),
3010 3010 ('D', 'currentdate', False,
3011 3011 _('record the current date as commit date')),
3012 3012 ('U', 'currentuser', False,
3013 3013 _('record the current user as committer'), _('DATE'))]
3014 3014 + commitopts2 + mergetoolopts + dryrunopts,
3015 3015 _('[OPTION]... [-r] REV...'))
3016 3016 def graft(ui, repo, *revs, **opts):
3017 3017 '''copy changes from other branches onto the current branch
3018 3018
3019 3019 This command uses Mercurial's merge logic to copy individual
3020 3020 changes from other branches without merging branches in the
3021 3021 history graph. This is sometimes known as 'backporting' or
3022 3022 'cherry-picking'. By default, graft will copy user, date, and
3023 3023 description from the source changesets.
3024 3024
3025 3025 Changesets that are ancestors of the current revision, that have
3026 3026 already been grafted, or that are merges will be skipped.
3027 3027
3028 3028 If --log is specified, log messages will have a comment appended
3029 3029 of the form::
3030 3030
3031 3031 (grafted from CHANGESETHASH)
3032 3032
3033 3033 If a graft merge results in conflicts, the graft process is
3034 3034 interrupted so that the current merge can be manually resolved.
3035 3035 Once all conflicts are addressed, the graft process can be
3036 3036 continued with the -c/--continue option.
3037 3037
3038 3038 .. note::
3039 3039
3040 3040 The -c/--continue option does not reapply earlier options.
3041 3041
3042 3042 .. container:: verbose
3043 3043
3044 3044 Examples:
3045 3045
3046 3046 - copy a single change to the stable branch and edit its description::
3047 3047
3048 3048 hg update stable
3049 3049 hg graft --edit 9393
3050 3050
3051 3051 - graft a range of changesets with one exception, updating dates::
3052 3052
3053 3053 hg graft -D "2085::2093 and not 2091"
3054 3054
3055 3055 - continue a graft after resolving conflicts::
3056 3056
3057 3057 hg graft -c
3058 3058
3059 3059 - show the source of a grafted changeset::
3060 3060
3061 3061 hg log --debug -r .
3062 3062
3063 3063 Returns 0 on successful completion.
3064 3064 '''
3065 3065
3066 3066 revs = list(revs)
3067 3067 revs.extend(opts['rev'])
3068 3068
3069 3069 if not opts.get('user') and opts.get('currentuser'):
3070 3070 opts['user'] = ui.username()
3071 3071 if not opts.get('date') and opts.get('currentdate'):
3072 3072 opts['date'] = "%d %d" % util.makedate()
3073 3073
3074 3074 editor = cmdutil.getcommiteditor(**opts)
3075 3075
3076 3076 cont = False
3077 3077 if opts['continue']:
3078 3078 cont = True
3079 3079 if revs:
3080 3080 raise util.Abort(_("can't specify --continue and revisions"))
3081 3081 # read in unfinished revisions
3082 3082 try:
3083 3083 nodes = repo.opener.read('graftstate').splitlines()
3084 3084 revs = [repo[node].rev() for node in nodes]
3085 3085 except IOError, inst:
3086 3086 if inst.errno != errno.ENOENT:
3087 3087 raise
3088 3088 raise util.Abort(_("no graft state found, can't continue"))
3089 3089 else:
3090 3090 cmdutil.checkunfinished(repo)
3091 3091 cmdutil.bailifchanged(repo)
3092 3092 if not revs:
3093 3093 raise util.Abort(_('no revisions specified'))
3094 3094 revs = scmutil.revrange(repo, revs)
3095 3095
3096 3096 # check for merges
3097 3097 for rev in repo.revs('%ld and merge()', revs):
3098 3098 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3099 3099 revs.remove(rev)
3100 3100 if not revs:
3101 3101 return -1
3102 3102
3103 3103 # check for ancestors of dest branch
3104 3104 crev = repo['.'].rev()
3105 3105 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3106 3106 # Cannot use x.remove(y) on smart set, this has to be a list.
3107 3107 # XXX make this lazy in the future
3108 3108 revs = list(revs)
3109 3109 # don't mutate while iterating, create a copy
3110 3110 for rev in list(revs):
3111 3111 if rev in ancestors:
3112 3112 ui.warn(_('skipping ancestor revision %s\n') % rev)
3113 3113 # XXX remove on list is slow
3114 3114 revs.remove(rev)
3115 3115 if not revs:
3116 3116 return -1
3117 3117
3118 3118 # analyze revs for earlier grafts
3119 3119 ids = {}
3120 3120 for ctx in repo.set("%ld", revs):
3121 3121 ids[ctx.hex()] = ctx.rev()
3122 3122 n = ctx.extra().get('source')
3123 3123 if n:
3124 3124 ids[n] = ctx.rev()
3125 3125
3126 3126 # check ancestors for earlier grafts
3127 3127 ui.debug('scanning for duplicate grafts\n')
3128 3128
3129 3129 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3130 3130 ctx = repo[rev]
3131 3131 n = ctx.extra().get('source')
3132 3132 if n in ids:
3133 3133 r = repo[n].rev()
3134 3134 if r in revs:
3135 3135 ui.warn(_('skipping revision %s (already grafted to %s)\n')
3136 3136 % (r, rev))
3137 3137 revs.remove(r)
3138 3138 elif ids[n] in revs:
3139 3139 ui.warn(_('skipping already grafted revision %s '
3140 3140 '(%s also has origin %d)\n') % (ids[n], rev, r))
3141 3141 revs.remove(ids[n])
3142 3142 elif ctx.hex() in ids:
3143 3143 r = ids[ctx.hex()]
3144 3144 ui.warn(_('skipping already grafted revision %s '
3145 3145 '(was grafted from %d)\n') % (r, rev))
3146 3146 revs.remove(r)
3147 3147 if not revs:
3148 3148 return -1
3149 3149
3150 3150 wlock = repo.wlock()
3151 3151 try:
3152 3152 current = repo['.']
3153 3153 for pos, ctx in enumerate(repo.set("%ld", revs)):
3154 3154
3155 3155 ui.status(_('grafting revision %s\n') % ctx.rev())
3156 3156 if opts.get('dry_run'):
3157 3157 continue
3158 3158
3159 3159 source = ctx.extra().get('source')
3160 3160 if not source:
3161 3161 source = ctx.hex()
3162 3162 extra = {'source': source}
3163 3163 user = ctx.user()
3164 3164 if opts.get('user'):
3165 3165 user = opts['user']
3166 3166 date = ctx.date()
3167 3167 if opts.get('date'):
3168 3168 date = opts['date']
3169 3169 message = ctx.description()
3170 3170 if opts.get('log'):
3171 3171 message += '\n(grafted from %s)' % ctx.hex()
3172 3172
3173 3173 # we don't merge the first commit when continuing
3174 3174 if not cont:
3175 3175 # perform the graft merge with p1(rev) as 'ancestor'
3176 3176 try:
3177 3177 # ui.forcemerge is an internal variable, do not document
3178 3178 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3179 3179 'graft')
3180 3180 stats = mergemod.update(repo, ctx.node(), True, True, False,
3181 3181 ctx.p1().node(),
3182 3182 labels=['local', 'graft'])
3183 3183 finally:
3184 3184 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3185 3185 # report any conflicts
3186 3186 if stats and stats[3] > 0:
3187 3187 # write out state for --continue
3188 3188 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3189 3189 repo.opener.write('graftstate', ''.join(nodelines))
3190 3190 raise util.Abort(
3191 3191 _("unresolved conflicts, can't continue"),
3192 3192 hint=_('use hg resolve and hg graft --continue'))
3193 3193 else:
3194 3194 cont = False
3195 3195
3196 3196 # drop the second merge parent
3197 3197 repo.setparents(current.node(), nullid)
3198 3198 repo.dirstate.write()
3199 3199 # fix up dirstate for copies and renames
3200 3200 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
3201 3201
3202 3202 # commit
3203 3203 node = repo.commit(text=message, user=user,
3204 3204 date=date, extra=extra, editor=editor)
3205 3205 if node is None:
3206 3206 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
3207 3207 else:
3208 3208 current = repo[node]
3209 3209 finally:
3210 3210 wlock.release()
3211 3211
3212 3212 # remove state when we complete successfully
3213 3213 if not opts.get('dry_run'):
3214 3214 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3215 3215
3216 3216 return 0
3217 3217
3218 3218 @command('grep',
3219 3219 [('0', 'print0', None, _('end fields with NUL')),
3220 3220 ('', 'all', None, _('print all revisions that match')),
3221 3221 ('a', 'text', None, _('treat all files as text')),
3222 3222 ('f', 'follow', None,
3223 3223 _('follow changeset history,'
3224 3224 ' or file history across copies and renames')),
3225 3225 ('i', 'ignore-case', None, _('ignore case when matching')),
3226 3226 ('l', 'files-with-matches', None,
3227 3227 _('print only filenames and revisions that match')),
3228 3228 ('n', 'line-number', None, _('print matching line numbers')),
3229 3229 ('r', 'rev', [],
3230 3230 _('only search files changed within revision range'), _('REV')),
3231 3231 ('u', 'user', None, _('list the author (long with -v)')),
3232 3232 ('d', 'date', None, _('list the date (short with -q)')),
3233 3233 ] + walkopts,
3234 3234 _('[OPTION]... PATTERN [FILE]...'))
3235 3235 def grep(ui, repo, pattern, *pats, **opts):
3236 3236 """search for a pattern in specified files and revisions
3237 3237
3238 3238 Search revisions of files for a regular expression.
3239 3239
3240 3240 This command behaves differently than Unix grep. It only accepts
3241 3241 Python/Perl regexps. It searches repository history, not the
3242 3242 working directory. It always prints the revision number in which a
3243 3243 match appears.
3244 3244
3245 3245 By default, grep only prints output for the first revision of a
3246 3246 file in which it finds a match. To get it to print every revision
3247 3247 that contains a change in match status ("-" for a match that
3248 3248 becomes a non-match, or "+" for a non-match that becomes a match),
3249 3249 use the --all flag.
3250 3250
3251 3251 Returns 0 if a match is found, 1 otherwise.
3252 3252 """
3253 3253 reflags = re.M
3254 3254 if opts.get('ignore_case'):
3255 3255 reflags |= re.I
3256 3256 try:
3257 3257 regexp = util.compilere(pattern, reflags)
3258 3258 except re.error, inst:
3259 3259 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3260 3260 return 1
3261 3261 sep, eol = ':', '\n'
3262 3262 if opts.get('print0'):
3263 3263 sep = eol = '\0'
3264 3264
3265 3265 getfile = util.lrucachefunc(repo.file)
3266 3266
3267 3267 def matchlines(body):
3268 3268 begin = 0
3269 3269 linenum = 0
3270 3270 while begin < len(body):
3271 3271 match = regexp.search(body, begin)
3272 3272 if not match:
3273 3273 break
3274 3274 mstart, mend = match.span()
3275 3275 linenum += body.count('\n', begin, mstart) + 1
3276 3276 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3277 3277 begin = body.find('\n', mend) + 1 or len(body) + 1
3278 3278 lend = begin - 1
3279 3279 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3280 3280
3281 3281 class linestate(object):
3282 3282 def __init__(self, line, linenum, colstart, colend):
3283 3283 self.line = line
3284 3284 self.linenum = linenum
3285 3285 self.colstart = colstart
3286 3286 self.colend = colend
3287 3287
3288 3288 def __hash__(self):
3289 3289 return hash((self.linenum, self.line))
3290 3290
3291 3291 def __eq__(self, other):
3292 3292 return self.line == other.line
3293 3293
3294 3294 def __iter__(self):
3295 3295 yield (self.line[:self.colstart], '')
3296 3296 yield (self.line[self.colstart:self.colend], 'grep.match')
3297 3297 rest = self.line[self.colend:]
3298 3298 while rest != '':
3299 3299 match = regexp.search(rest)
3300 3300 if not match:
3301 3301 yield (rest, '')
3302 3302 break
3303 3303 mstart, mend = match.span()
3304 3304 yield (rest[:mstart], '')
3305 3305 yield (rest[mstart:mend], 'grep.match')
3306 3306 rest = rest[mend:]
3307 3307
3308 3308 matches = {}
3309 3309 copies = {}
3310 3310 def grepbody(fn, rev, body):
3311 3311 matches[rev].setdefault(fn, [])
3312 3312 m = matches[rev][fn]
3313 3313 for lnum, cstart, cend, line in matchlines(body):
3314 3314 s = linestate(line, lnum, cstart, cend)
3315 3315 m.append(s)
3316 3316
3317 3317 def difflinestates(a, b):
3318 3318 sm = difflib.SequenceMatcher(None, a, b)
3319 3319 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3320 3320 if tag == 'insert':
3321 3321 for i in xrange(blo, bhi):
3322 3322 yield ('+', b[i])
3323 3323 elif tag == 'delete':
3324 3324 for i in xrange(alo, ahi):
3325 3325 yield ('-', a[i])
3326 3326 elif tag == 'replace':
3327 3327 for i in xrange(alo, ahi):
3328 3328 yield ('-', a[i])
3329 3329 for i in xrange(blo, bhi):
3330 3330 yield ('+', b[i])
3331 3331
3332 3332 def display(fn, ctx, pstates, states):
3333 3333 rev = ctx.rev()
3334 3334 datefunc = ui.quiet and util.shortdate or util.datestr
3335 3335 found = False
3336 3336 @util.cachefunc
3337 3337 def binary():
3338 3338 flog = getfile(fn)
3339 3339 return util.binary(flog.read(ctx.filenode(fn)))
3340 3340
3341 3341 if opts.get('all'):
3342 3342 iter = difflinestates(pstates, states)
3343 3343 else:
3344 3344 iter = [('', l) for l in states]
3345 3345 for change, l in iter:
3346 3346 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3347 3347
3348 3348 if opts.get('line_number'):
3349 3349 cols.append((str(l.linenum), 'grep.linenumber'))
3350 3350 if opts.get('all'):
3351 3351 cols.append((change, 'grep.change'))
3352 3352 if opts.get('user'):
3353 3353 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3354 3354 if opts.get('date'):
3355 3355 cols.append((datefunc(ctx.date()), 'grep.date'))
3356 3356 for col, label in cols[:-1]:
3357 3357 ui.write(col, label=label)
3358 3358 ui.write(sep, label='grep.sep')
3359 3359 ui.write(cols[-1][0], label=cols[-1][1])
3360 3360 if not opts.get('files_with_matches'):
3361 3361 ui.write(sep, label='grep.sep')
3362 3362 if not opts.get('text') and binary():
3363 3363 ui.write(" Binary file matches")
3364 3364 else:
3365 3365 for s, label in l:
3366 3366 ui.write(s, label=label)
3367 3367 ui.write(eol)
3368 3368 found = True
3369 3369 if opts.get('files_with_matches'):
3370 3370 break
3371 3371 return found
3372 3372
3373 3373 skip = {}
3374 3374 revfiles = {}
3375 3375 matchfn = scmutil.match(repo[None], pats, opts)
3376 3376 found = False
3377 3377 follow = opts.get('follow')
3378 3378
3379 3379 def prep(ctx, fns):
3380 3380 rev = ctx.rev()
3381 3381 pctx = ctx.p1()
3382 3382 parent = pctx.rev()
3383 3383 matches.setdefault(rev, {})
3384 3384 matches.setdefault(parent, {})
3385 3385 files = revfiles.setdefault(rev, [])
3386 3386 for fn in fns:
3387 3387 flog = getfile(fn)
3388 3388 try:
3389 3389 fnode = ctx.filenode(fn)
3390 3390 except error.LookupError:
3391 3391 continue
3392 3392
3393 3393 copied = flog.renamed(fnode)
3394 3394 copy = follow and copied and copied[0]
3395 3395 if copy:
3396 3396 copies.setdefault(rev, {})[fn] = copy
3397 3397 if fn in skip:
3398 3398 if copy:
3399 3399 skip[copy] = True
3400 3400 continue
3401 3401 files.append(fn)
3402 3402
3403 3403 if fn not in matches[rev]:
3404 3404 grepbody(fn, rev, flog.read(fnode))
3405 3405
3406 3406 pfn = copy or fn
3407 3407 if pfn not in matches[parent]:
3408 3408 try:
3409 3409 fnode = pctx.filenode(pfn)
3410 3410 grepbody(pfn, parent, flog.read(fnode))
3411 3411 except error.LookupError:
3412 3412 pass
3413 3413
3414 3414 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3415 3415 rev = ctx.rev()
3416 3416 parent = ctx.p1().rev()
3417 3417 for fn in sorted(revfiles.get(rev, [])):
3418 3418 states = matches[rev][fn]
3419 3419 copy = copies.get(rev, {}).get(fn)
3420 3420 if fn in skip:
3421 3421 if copy:
3422 3422 skip[copy] = True
3423 3423 continue
3424 3424 pstates = matches.get(parent, {}).get(copy or fn, [])
3425 3425 if pstates or states:
3426 3426 r = display(fn, ctx, pstates, states)
3427 3427 found = found or r
3428 3428 if r and not opts.get('all'):
3429 3429 skip[fn] = True
3430 3430 if copy:
3431 3431 skip[copy] = True
3432 3432 del matches[rev]
3433 3433 del revfiles[rev]
3434 3434
3435 3435 return not found
3436 3436
3437 3437 @command('heads',
3438 3438 [('r', 'rev', '',
3439 3439 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3440 3440 ('t', 'topo', False, _('show topological heads only')),
3441 3441 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3442 3442 ('c', 'closed', False, _('show normal and closed branch heads')),
3443 3443 ] + templateopts,
3444 3444 _('[-ct] [-r STARTREV] [REV]...'))
3445 3445 def heads(ui, repo, *branchrevs, **opts):
3446 3446 """show branch heads
3447 3447
3448 3448 With no arguments, show all open branch heads in the repository.
3449 3449 Branch heads are changesets that have no descendants on the
3450 3450 same branch. They are where development generally takes place and
3451 3451 are the usual targets for update and merge operations.
3452 3452
3453 3453 If one or more REVs are given, only open branch heads on the
3454 3454 branches associated with the specified changesets are shown. This
3455 3455 means that you can use :hg:`heads .` to see the heads on the
3456 3456 currently checked-out branch.
3457 3457
3458 3458 If -c/--closed is specified, also show branch heads marked closed
3459 3459 (see :hg:`commit --close-branch`).
3460 3460
3461 3461 If STARTREV is specified, only those heads that are descendants of
3462 3462 STARTREV will be displayed.
3463 3463
3464 3464 If -t/--topo is specified, named branch mechanics will be ignored and only
3465 3465 topological heads (changesets with no children) will be shown.
3466 3466
3467 3467 Returns 0 if matching heads are found, 1 if not.
3468 3468 """
3469 3469
3470 3470 start = None
3471 3471 if 'rev' in opts:
3472 3472 start = scmutil.revsingle(repo, opts['rev'], None).node()
3473 3473
3474 3474 if opts.get('topo'):
3475 3475 heads = [repo[h] for h in repo.heads(start)]
3476 3476 else:
3477 3477 heads = []
3478 3478 for branch in repo.branchmap():
3479 3479 heads += repo.branchheads(branch, start, opts.get('closed'))
3480 3480 heads = [repo[h] for h in heads]
3481 3481
3482 3482 if branchrevs:
3483 3483 branches = set(repo[br].branch() for br in branchrevs)
3484 3484 heads = [h for h in heads if h.branch() in branches]
3485 3485
3486 3486 if opts.get('active') and branchrevs:
3487 3487 dagheads = repo.heads(start)
3488 3488 heads = [h for h in heads if h.node() in dagheads]
3489 3489
3490 3490 if branchrevs:
3491 3491 haveheads = set(h.branch() for h in heads)
3492 3492 if branches - haveheads:
3493 3493 headless = ', '.join(b for b in branches - haveheads)
3494 3494 msg = _('no open branch heads found on branches %s')
3495 3495 if opts.get('rev'):
3496 3496 msg += _(' (started at %s)') % opts['rev']
3497 3497 ui.warn((msg + '\n') % headless)
3498 3498
3499 3499 if not heads:
3500 3500 return 1
3501 3501
3502 3502 heads = sorted(heads, key=lambda x: -x.rev())
3503 3503 displayer = cmdutil.show_changeset(ui, repo, opts)
3504 3504 for ctx in heads:
3505 3505 displayer.show(ctx)
3506 3506 displayer.close()
3507 3507
3508 3508 @command('help',
3509 3509 [('e', 'extension', None, _('show only help for extensions')),
3510 3510 ('c', 'command', None, _('show only help for commands')),
3511 3511 ('k', 'keyword', '', _('show topics matching keyword')),
3512 3512 ],
3513 3513 _('[-ec] [TOPIC]'))
3514 3514 def help_(ui, name=None, **opts):
3515 3515 """show help for a given topic or a help overview
3516 3516
3517 3517 With no arguments, print a list of commands with short help messages.
3518 3518
3519 3519 Given a topic, extension, or command name, print help for that
3520 3520 topic.
3521 3521
3522 3522 Returns 0 if successful.
3523 3523 """
3524 3524
3525 3525 textwidth = min(ui.termwidth(), 80) - 2
3526 3526
3527 3527 keep = ui.verbose and ['verbose'] or []
3528 3528 text = help.help_(ui, name, **opts)
3529 3529
3530 3530 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3531 3531 if 'verbose' in pruned:
3532 3532 keep.append('omitted')
3533 3533 else:
3534 3534 keep.append('notomitted')
3535 3535 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3536 3536 ui.write(formatted)
3537 3537
3538 3538
3539 3539 @command('identify|id',
3540 3540 [('r', 'rev', '',
3541 3541 _('identify the specified revision'), _('REV')),
3542 3542 ('n', 'num', None, _('show local revision number')),
3543 3543 ('i', 'id', None, _('show global revision id')),
3544 3544 ('b', 'branch', None, _('show branch')),
3545 3545 ('t', 'tags', None, _('show tags')),
3546 3546 ('B', 'bookmarks', None, _('show bookmarks')),
3547 3547 ] + remoteopts,
3548 3548 _('[-nibtB] [-r REV] [SOURCE]'))
3549 3549 def identify(ui, repo, source=None, rev=None,
3550 3550 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3551 3551 """identify the working copy or specified revision
3552 3552
3553 3553 Print a summary identifying the repository state at REV using one or
3554 3554 two parent hash identifiers, followed by a "+" if the working
3555 3555 directory has uncommitted changes, the branch name (if not default),
3556 3556 a list of tags, and a list of bookmarks.
3557 3557
3558 3558 When REV is not given, print a summary of the current state of the
3559 3559 repository.
3560 3560
3561 3561 Specifying a path to a repository root or Mercurial bundle will
3562 3562 cause lookup to operate on that repository/bundle.
3563 3563
3564 3564 .. container:: verbose
3565 3565
3566 3566 Examples:
3567 3567
3568 3568 - generate a build identifier for the working directory::
3569 3569
3570 3570 hg id --id > build-id.dat
3571 3571
3572 3572 - find the revision corresponding to a tag::
3573 3573
3574 3574 hg id -n -r 1.3
3575 3575
3576 3576 - check the most recent revision of a remote repository::
3577 3577
3578 3578 hg id -r tip http://selenic.com/hg/
3579 3579
3580 3580 Returns 0 if successful.
3581 3581 """
3582 3582
3583 3583 if not repo and not source:
3584 3584 raise util.Abort(_("there is no Mercurial repository here "
3585 3585 "(.hg not found)"))
3586 3586
3587 3587 hexfunc = ui.debugflag and hex or short
3588 3588 default = not (num or id or branch or tags or bookmarks)
3589 3589 output = []
3590 3590 revs = []
3591 3591
3592 3592 if source:
3593 3593 source, branches = hg.parseurl(ui.expandpath(source))
3594 3594 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3595 3595 repo = peer.local()
3596 3596 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3597 3597
3598 3598 if not repo:
3599 3599 if num or branch or tags:
3600 3600 raise util.Abort(
3601 3601 _("can't query remote revision number, branch, or tags"))
3602 3602 if not rev and revs:
3603 3603 rev = revs[0]
3604 3604 if not rev:
3605 3605 rev = "tip"
3606 3606
3607 3607 remoterev = peer.lookup(rev)
3608 3608 if default or id:
3609 3609 output = [hexfunc(remoterev)]
3610 3610
3611 3611 def getbms():
3612 3612 bms = []
3613 3613
3614 3614 if 'bookmarks' in peer.listkeys('namespaces'):
3615 3615 hexremoterev = hex(remoterev)
3616 3616 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3617 3617 if bmr == hexremoterev]
3618 3618
3619 3619 return sorted(bms)
3620 3620
3621 3621 if bookmarks:
3622 3622 output.extend(getbms())
3623 3623 elif default and not ui.quiet:
3624 3624 # multiple bookmarks for a single parent separated by '/'
3625 3625 bm = '/'.join(getbms())
3626 3626 if bm:
3627 3627 output.append(bm)
3628 3628 else:
3629 3629 if not rev:
3630 3630 ctx = repo[None]
3631 3631 parents = ctx.parents()
3632 3632 changed = ""
3633 3633 if default or id or num:
3634 3634 if (util.any(repo.status())
3635 3635 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3636 3636 changed = '+'
3637 3637 if default or id:
3638 3638 output = ["%s%s" %
3639 3639 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3640 3640 if num:
3641 3641 output.append("%s%s" %
3642 3642 ('+'.join([str(p.rev()) for p in parents]), changed))
3643 3643 else:
3644 3644 ctx = scmutil.revsingle(repo, rev)
3645 3645 if default or id:
3646 3646 output = [hexfunc(ctx.node())]
3647 3647 if num:
3648 3648 output.append(str(ctx.rev()))
3649 3649
3650 3650 if default and not ui.quiet:
3651 3651 b = ctx.branch()
3652 3652 if b != 'default':
3653 3653 output.append("(%s)" % b)
3654 3654
3655 3655 # multiple tags for a single parent separated by '/'
3656 3656 t = '/'.join(ctx.tags())
3657 3657 if t:
3658 3658 output.append(t)
3659 3659
3660 3660 # multiple bookmarks for a single parent separated by '/'
3661 3661 bm = '/'.join(ctx.bookmarks())
3662 3662 if bm:
3663 3663 output.append(bm)
3664 3664 else:
3665 3665 if branch:
3666 3666 output.append(ctx.branch())
3667 3667
3668 3668 if tags:
3669 3669 output.extend(ctx.tags())
3670 3670
3671 3671 if bookmarks:
3672 3672 output.extend(ctx.bookmarks())
3673 3673
3674 3674 ui.write("%s\n" % ' '.join(output))
3675 3675
3676 3676 @command('import|patch',
3677 3677 [('p', 'strip', 1,
3678 3678 _('directory strip option for patch. This has the same '
3679 3679 'meaning as the corresponding patch option'), _('NUM')),
3680 3680 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3681 3681 ('e', 'edit', False, _('invoke editor on commit messages')),
3682 3682 ('f', 'force', None,
3683 3683 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3684 3684 ('', 'no-commit', None,
3685 3685 _("don't commit, just update the working directory")),
3686 3686 ('', 'bypass', None,
3687 3687 _("apply patch without touching the working directory")),
3688 ('', 'partial', None,
3689 _('commit even if some hunks fail')),
3688 3690 ('', 'exact', None,
3689 3691 _('apply patch to the nodes from which it was generated')),
3690 3692 ('', 'import-branch', None,
3691 3693 _('use any branch information in patch (implied by --exact)'))] +
3692 3694 commitopts + commitopts2 + similarityopts,
3693 3695 _('[OPTION]... PATCH...'))
3694 3696 def import_(ui, repo, patch1=None, *patches, **opts):
3695 3697 """import an ordered set of patches
3696 3698
3697 3699 Import a list of patches and commit them individually (unless
3698 3700 --no-commit is specified).
3699 3701
3700 3702 Because import first applies changes to the working directory,
3701 3703 import will abort if there are outstanding changes.
3702 3704
3703 3705 You can import a patch straight from a mail message. Even patches
3704 3706 as attachments work (to use the body part, it must have type
3705 3707 text/plain or text/x-patch). From and Subject headers of email
3706 3708 message are used as default committer and commit message. All
3707 3709 text/plain body parts before first diff are added to commit
3708 3710 message.
3709 3711
3710 3712 If the imported patch was generated by :hg:`export`, user and
3711 3713 description from patch override values from message headers and
3712 3714 body. Values given on command line with -m/--message and -u/--user
3713 3715 override these.
3714 3716
3715 3717 If --exact is specified, import will set the working directory to
3716 3718 the parent of each patch before applying it, and will abort if the
3717 3719 resulting changeset has a different ID than the one recorded in
3718 3720 the patch. This may happen due to character set problems or other
3719 3721 deficiencies in the text patch format.
3720 3722
3721 3723 Use --bypass to apply and commit patches directly to the
3722 3724 repository, not touching the working directory. Without --exact,
3723 3725 patches will be applied on top of the working directory parent
3724 3726 revision.
3725 3727
3726 3728 With -s/--similarity, hg will attempt to discover renames and
3727 3729 copies in the patch in the same way as :hg:`addremove`.
3728 3730
3731 Use --partial to ensure a changeset will be created from the patch
3732 even if some hunks fail to apply. Hunks that fail to apply will be
3733 written to a <target-file>.rej file. Conflicts can then be resolved
3734 by hand before :hg:`commit --amend` is run to update the created
3735 changeset. This flag exists to let people import patches that
3736 partially apply without losing the associated metadata (author,
3737 date, description, ...), Note that when none of the hunk applies
3738 cleanly, :hg:`import --partial` will create an empty changeset,
3739 importing only the patch metadata.
3740
3729 3741 To read a patch from standard input, use "-" as the patch name. If
3730 3742 a URL is specified, the patch will be downloaded from it.
3731 3743 See :hg:`help dates` for a list of formats valid for -d/--date.
3732 3744
3733 3745 .. container:: verbose
3734 3746
3735 3747 Examples:
3736 3748
3737 3749 - import a traditional patch from a website and detect renames::
3738 3750
3739 3751 hg import -s 80 http://example.com/bugfix.patch
3740 3752
3741 3753 - import a changeset from an hgweb server::
3742 3754
3743 3755 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3744 3756
3745 3757 - import all the patches in an Unix-style mbox::
3746 3758
3747 3759 hg import incoming-patches.mbox
3748 3760
3749 3761 - attempt to exactly restore an exported changeset (not always
3750 3762 possible)::
3751 3763
3752 3764 hg import --exact proposed-fix.patch
3753 3765
3754 Returns 0 on success.
3766 Returns 0 on success, 1 on partial success (see --partial).
3755 3767 """
3756 3768
3757 3769 if not patch1:
3758 3770 raise util.Abort(_('need at least one patch to import'))
3759 3771
3760 3772 patches = (patch1,) + patches
3761 3773
3762 3774 date = opts.get('date')
3763 3775 if date:
3764 3776 opts['date'] = util.parsedate(date)
3765 3777
3766 3778 update = not opts.get('bypass')
3767 3779 if not update and opts.get('no_commit'):
3768 3780 raise util.Abort(_('cannot use --no-commit with --bypass'))
3769 3781 try:
3770 3782 sim = float(opts.get('similarity') or 0)
3771 3783 except ValueError:
3772 3784 raise util.Abort(_('similarity must be a number'))
3773 3785 if sim < 0 or sim > 100:
3774 3786 raise util.Abort(_('similarity must be between 0 and 100'))
3775 3787 if sim and not update:
3776 3788 raise util.Abort(_('cannot use --similarity with --bypass'))
3777 3789
3778 3790 if update:
3779 3791 cmdutil.checkunfinished(repo)
3780 3792 if (opts.get('exact') or not opts.get('force')) and update:
3781 3793 cmdutil.bailifchanged(repo)
3782 3794
3783 3795 base = opts["base"]
3784 3796 wlock = lock = tr = None
3785 3797 msgs = []
3798 ret = 0
3786 3799
3787 3800
3788 3801 try:
3789 3802 try:
3790 3803 wlock = repo.wlock()
3791 3804 if not opts.get('no_commit'):
3792 3805 lock = repo.lock()
3793 3806 tr = repo.transaction('import')
3794 3807 parents = repo.parents()
3795 3808 for patchurl in patches:
3796 3809 if patchurl == '-':
3797 3810 ui.status(_('applying patch from stdin\n'))
3798 3811 patchfile = ui.fin
3799 3812 patchurl = 'stdin' # for error message
3800 3813 else:
3801 3814 patchurl = os.path.join(base, patchurl)
3802 3815 ui.status(_('applying %s\n') % patchurl)
3803 3816 patchfile = hg.openpath(ui, patchurl)
3804 3817
3805 3818 haspatch = False
3806 3819 for hunk in patch.split(patchfile):
3807 (msg, node) = cmdutil.tryimportone(ui, repo, hunk, parents,
3808 opts, msgs, hg.clean)
3820 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3821 parents, opts,
3822 msgs, hg.clean)
3809 3823 if msg:
3810 3824 haspatch = True
3811 3825 ui.note(msg + '\n')
3812 3826 if update or opts.get('exact'):
3813 3827 parents = repo.parents()
3814 3828 else:
3815 3829 parents = [repo[node]]
3830 if rej:
3831 ui.write_err(_("patch applied partially\n"))
3832 ui.write_err(("(fix the .rej files and run "
3833 "`hg commit --amend`)\n"))
3834 ret = 1
3835 break
3816 3836
3817 3837 if not haspatch:
3818 3838 raise util.Abort(_('%s: no diffs found') % patchurl)
3819 3839
3820 3840 if tr:
3821 3841 tr.close()
3822 3842 if msgs:
3823 3843 repo.savecommitmessage('\n* * *\n'.join(msgs))
3844 return ret
3824 3845 except: # re-raises
3825 3846 # wlock.release() indirectly calls dirstate.write(): since
3826 3847 # we're crashing, we do not want to change the working dir
3827 3848 # parent after all, so make sure it writes nothing
3828 3849 repo.dirstate.invalidate()
3829 3850 raise
3830 3851 finally:
3831 3852 if tr:
3832 3853 tr.release()
3833 3854 release(lock, wlock)
3834 3855
3835 3856 @command('incoming|in',
3836 3857 [('f', 'force', None,
3837 3858 _('run even if remote repository is unrelated')),
3838 3859 ('n', 'newest-first', None, _('show newest record first')),
3839 3860 ('', 'bundle', '',
3840 3861 _('file to store the bundles into'), _('FILE')),
3841 3862 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3842 3863 ('B', 'bookmarks', False, _("compare bookmarks")),
3843 3864 ('b', 'branch', [],
3844 3865 _('a specific branch you would like to pull'), _('BRANCH')),
3845 3866 ] + logopts + remoteopts + subrepoopts,
3846 3867 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3847 3868 def incoming(ui, repo, source="default", **opts):
3848 3869 """show new changesets found in source
3849 3870
3850 3871 Show new changesets found in the specified path/URL or the default
3851 3872 pull location. These are the changesets that would have been pulled
3852 3873 if a pull at the time you issued this command.
3853 3874
3854 3875 For remote repository, using --bundle avoids downloading the
3855 3876 changesets twice if the incoming is followed by a pull.
3856 3877
3857 3878 See pull for valid source format details.
3858 3879
3859 3880 .. container:: verbose
3860 3881
3861 3882 Examples:
3862 3883
3863 3884 - show incoming changes with patches and full description::
3864 3885
3865 3886 hg incoming -vp
3866 3887
3867 3888 - show incoming changes excluding merges, store a bundle::
3868 3889
3869 3890 hg in -vpM --bundle incoming.hg
3870 3891 hg pull incoming.hg
3871 3892
3872 3893 - briefly list changes inside a bundle::
3873 3894
3874 3895 hg in changes.hg -T "{desc|firstline}\\n"
3875 3896
3876 3897 Returns 0 if there are incoming changes, 1 otherwise.
3877 3898 """
3878 3899 if opts.get('graph'):
3879 3900 cmdutil.checkunsupportedgraphflags([], opts)
3880 3901 def display(other, chlist, displayer):
3881 3902 revdag = cmdutil.graphrevs(other, chlist, opts)
3882 3903 showparents = [ctx.node() for ctx in repo[None].parents()]
3883 3904 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3884 3905 graphmod.asciiedges)
3885 3906
3886 3907 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3887 3908 return 0
3888 3909
3889 3910 if opts.get('bundle') and opts.get('subrepos'):
3890 3911 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3891 3912
3892 3913 if opts.get('bookmarks'):
3893 3914 source, branches = hg.parseurl(ui.expandpath(source),
3894 3915 opts.get('branch'))
3895 3916 other = hg.peer(repo, opts, source)
3896 3917 if 'bookmarks' not in other.listkeys('namespaces'):
3897 3918 ui.warn(_("remote doesn't support bookmarks\n"))
3898 3919 return 0
3899 3920 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3900 3921 return bookmarks.diff(ui, repo, other)
3901 3922
3902 3923 repo._subtoppath = ui.expandpath(source)
3903 3924 try:
3904 3925 return hg.incoming(ui, repo, source, opts)
3905 3926 finally:
3906 3927 del repo._subtoppath
3907 3928
3908 3929
3909 3930 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3910 3931 def init(ui, dest=".", **opts):
3911 3932 """create a new repository in the given directory
3912 3933
3913 3934 Initialize a new repository in the given directory. If the given
3914 3935 directory does not exist, it will be created.
3915 3936
3916 3937 If no directory is given, the current directory is used.
3917 3938
3918 3939 It is possible to specify an ``ssh://`` URL as the destination.
3919 3940 See :hg:`help urls` for more information.
3920 3941
3921 3942 Returns 0 on success.
3922 3943 """
3923 3944 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3924 3945
3925 3946 @command('locate',
3926 3947 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3927 3948 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3928 3949 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3929 3950 ] + walkopts,
3930 3951 _('[OPTION]... [PATTERN]...'))
3931 3952 def locate(ui, repo, *pats, **opts):
3932 3953 """locate files matching specific patterns
3933 3954
3934 3955 Print files under Mercurial control in the working directory whose
3935 3956 names match the given patterns.
3936 3957
3937 3958 By default, this command searches all directories in the working
3938 3959 directory. To search just the current directory and its
3939 3960 subdirectories, use "--include .".
3940 3961
3941 3962 If no patterns are given to match, this command prints the names
3942 3963 of all files under Mercurial control in the working directory.
3943 3964
3944 3965 If you want to feed the output of this command into the "xargs"
3945 3966 command, use the -0 option to both this command and "xargs". This
3946 3967 will avoid the problem of "xargs" treating single filenames that
3947 3968 contain whitespace as multiple filenames.
3948 3969
3949 3970 Returns 0 if a match is found, 1 otherwise.
3950 3971 """
3951 3972 end = opts.get('print0') and '\0' or '\n'
3952 3973 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3953 3974
3954 3975 ret = 1
3955 3976 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3956 3977 m.bad = lambda x, y: False
3957 3978 for abs in repo[rev].walk(m):
3958 3979 if not rev and abs not in repo.dirstate:
3959 3980 continue
3960 3981 if opts.get('fullpath'):
3961 3982 ui.write(repo.wjoin(abs), end)
3962 3983 else:
3963 3984 ui.write(((pats and m.rel(abs)) or abs), end)
3964 3985 ret = 0
3965 3986
3966 3987 return ret
3967 3988
3968 3989 @command('^log|history',
3969 3990 [('f', 'follow', None,
3970 3991 _('follow changeset history, or file history across copies and renames')),
3971 3992 ('', 'follow-first', None,
3972 3993 _('only follow the first parent of merge changesets (DEPRECATED)')),
3973 3994 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3974 3995 ('C', 'copies', None, _('show copied files')),
3975 3996 ('k', 'keyword', [],
3976 3997 _('do case-insensitive search for a given text'), _('TEXT')),
3977 3998 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3978 3999 ('', 'removed', None, _('include revisions where files were removed')),
3979 4000 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3980 4001 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3981 4002 ('', 'only-branch', [],
3982 4003 _('show only changesets within the given named branch (DEPRECATED)'),
3983 4004 _('BRANCH')),
3984 4005 ('b', 'branch', [],
3985 4006 _('show changesets within the given named branch'), _('BRANCH')),
3986 4007 ('P', 'prune', [],
3987 4008 _('do not display revision or any of its ancestors'), _('REV')),
3988 4009 ] + logopts + walkopts,
3989 4010 _('[OPTION]... [FILE]'))
3990 4011 def log(ui, repo, *pats, **opts):
3991 4012 """show revision history of entire repository or files
3992 4013
3993 4014 Print the revision history of the specified files or the entire
3994 4015 project.
3995 4016
3996 4017 If no revision range is specified, the default is ``tip:0`` unless
3997 4018 --follow is set, in which case the working directory parent is
3998 4019 used as the starting revision.
3999 4020
4000 4021 File history is shown without following rename or copy history of
4001 4022 files. Use -f/--follow with a filename to follow history across
4002 4023 renames and copies. --follow without a filename will only show
4003 4024 ancestors or descendants of the starting revision.
4004 4025
4005 4026 By default this command prints revision number and changeset id,
4006 4027 tags, non-trivial parents, user, date and time, and a summary for
4007 4028 each commit. When the -v/--verbose switch is used, the list of
4008 4029 changed files and full commit message are shown.
4009 4030
4010 4031 With --graph the revisions are shown as an ASCII art DAG with the most
4011 4032 recent changeset at the top.
4012 4033 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4013 4034 and '+' represents a fork where the changeset from the lines below is a
4014 4035 parent of the 'o' merge on the same line.
4015 4036
4016 4037 .. note::
4017 4038
4018 4039 log -p/--patch may generate unexpected diff output for merge
4019 4040 changesets, as it will only compare the merge changeset against
4020 4041 its first parent. Also, only files different from BOTH parents
4021 4042 will appear in files:.
4022 4043
4023 4044 .. note::
4024 4045
4025 4046 for performance reasons, log FILE may omit duplicate changes
4026 4047 made on branches and will not show deletions. To see all
4027 4048 changes including duplicates and deletions, use the --removed
4028 4049 switch.
4029 4050
4030 4051 .. container:: verbose
4031 4052
4032 4053 Some examples:
4033 4054
4034 4055 - changesets with full descriptions and file lists::
4035 4056
4036 4057 hg log -v
4037 4058
4038 4059 - changesets ancestral to the working directory::
4039 4060
4040 4061 hg log -f
4041 4062
4042 4063 - last 10 commits on the current branch::
4043 4064
4044 4065 hg log -l 10 -b .
4045 4066
4046 4067 - changesets showing all modifications of a file, including removals::
4047 4068
4048 4069 hg log --removed file.c
4049 4070
4050 4071 - all changesets that touch a directory, with diffs, excluding merges::
4051 4072
4052 4073 hg log -Mp lib/
4053 4074
4054 4075 - all revision numbers that match a keyword::
4055 4076
4056 4077 hg log -k bug --template "{rev}\\n"
4057 4078
4058 4079 - check if a given changeset is included is a tagged release::
4059 4080
4060 4081 hg log -r "a21ccf and ancestor(1.9)"
4061 4082
4062 4083 - find all changesets by some user in a date range::
4063 4084
4064 4085 hg log -k alice -d "may 2008 to jul 2008"
4065 4086
4066 4087 - summary of all changesets after the last tag::
4067 4088
4068 4089 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4069 4090
4070 4091 See :hg:`help dates` for a list of formats valid for -d/--date.
4071 4092
4072 4093 See :hg:`help revisions` and :hg:`help revsets` for more about
4073 4094 specifying revisions.
4074 4095
4075 4096 See :hg:`help templates` for more about pre-packaged styles and
4076 4097 specifying custom templates.
4077 4098
4078 4099 Returns 0 on success.
4079 4100 """
4080 4101 if opts.get('graph'):
4081 4102 return cmdutil.graphlog(ui, repo, *pats, **opts)
4082 4103
4083 4104 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4084 4105 limit = cmdutil.loglimit(opts)
4085 4106 count = 0
4086 4107
4087 4108 getrenamed = None
4088 4109 if opts.get('copies'):
4089 4110 endrev = None
4090 4111 if opts.get('rev'):
4091 4112 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4092 4113 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4093 4114
4094 4115 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4095 4116 for rev in revs:
4096 4117 if count == limit:
4097 4118 break
4098 4119 ctx = repo[rev]
4099 4120 copies = None
4100 4121 if getrenamed is not None and rev:
4101 4122 copies = []
4102 4123 for fn in ctx.files():
4103 4124 rename = getrenamed(fn, rev)
4104 4125 if rename:
4105 4126 copies.append((fn, rename[0]))
4106 4127 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4107 4128 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4108 4129 if displayer.flush(rev):
4109 4130 count += 1
4110 4131
4111 4132 displayer.close()
4112 4133
4113 4134 @command('manifest',
4114 4135 [('r', 'rev', '', _('revision to display'), _('REV')),
4115 4136 ('', 'all', False, _("list files from all revisions"))],
4116 4137 _('[-r REV]'))
4117 4138 def manifest(ui, repo, node=None, rev=None, **opts):
4118 4139 """output the current or given revision of the project manifest
4119 4140
4120 4141 Print a list of version controlled files for the given revision.
4121 4142 If no revision is given, the first parent of the working directory
4122 4143 is used, or the null revision if no revision is checked out.
4123 4144
4124 4145 With -v, print file permissions, symlink and executable bits.
4125 4146 With --debug, print file revision hashes.
4126 4147
4127 4148 If option --all is specified, the list of all files from all revisions
4128 4149 is printed. This includes deleted and renamed files.
4129 4150
4130 4151 Returns 0 on success.
4131 4152 """
4132 4153
4133 4154 fm = ui.formatter('manifest', opts)
4134 4155
4135 4156 if opts.get('all'):
4136 4157 if rev or node:
4137 4158 raise util.Abort(_("can't specify a revision with --all"))
4138 4159
4139 4160 res = []
4140 4161 prefix = "data/"
4141 4162 suffix = ".i"
4142 4163 plen = len(prefix)
4143 4164 slen = len(suffix)
4144 4165 lock = repo.lock()
4145 4166 try:
4146 4167 for fn, b, size in repo.store.datafiles():
4147 4168 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4148 4169 res.append(fn[plen:-slen])
4149 4170 finally:
4150 4171 lock.release()
4151 4172 for f in res:
4152 4173 fm.startitem()
4153 4174 fm.write("path", '%s\n', f)
4154 4175 fm.end()
4155 4176 return
4156 4177
4157 4178 if rev and node:
4158 4179 raise util.Abort(_("please specify just one revision"))
4159 4180
4160 4181 if not node:
4161 4182 node = rev
4162 4183
4163 4184 char = {'l': '@', 'x': '*', '': ''}
4164 4185 mode = {'l': '644', 'x': '755', '': '644'}
4165 4186 ctx = scmutil.revsingle(repo, node)
4166 4187 mf = ctx.manifest()
4167 4188 for f in ctx:
4168 4189 fm.startitem()
4169 4190 fl = ctx[f].flags()
4170 4191 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4171 4192 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4172 4193 fm.write('path', '%s\n', f)
4173 4194 fm.end()
4174 4195
4175 4196 @command('^merge',
4176 4197 [('f', 'force', None,
4177 4198 _('force a merge including outstanding changes (DEPRECATED)')),
4178 4199 ('r', 'rev', '', _('revision to merge'), _('REV')),
4179 4200 ('P', 'preview', None,
4180 4201 _('review revisions to merge (no merge is performed)'))
4181 4202 ] + mergetoolopts,
4182 4203 _('[-P] [-f] [[-r] REV]'))
4183 4204 def merge(ui, repo, node=None, **opts):
4184 4205 """merge working directory with another revision
4185 4206
4186 4207 The current working directory is updated with all changes made in
4187 4208 the requested revision since the last common predecessor revision.
4188 4209
4189 4210 Files that changed between either parent are marked as changed for
4190 4211 the next commit and a commit must be performed before any further
4191 4212 updates to the repository are allowed. The next commit will have
4192 4213 two parents.
4193 4214
4194 4215 ``--tool`` can be used to specify the merge tool used for file
4195 4216 merges. It overrides the HGMERGE environment variable and your
4196 4217 configuration files. See :hg:`help merge-tools` for options.
4197 4218
4198 4219 If no revision is specified, the working directory's parent is a
4199 4220 head revision, and the current branch contains exactly one other
4200 4221 head, the other head is merged with by default. Otherwise, an
4201 4222 explicit revision with which to merge with must be provided.
4202 4223
4203 4224 :hg:`resolve` must be used to resolve unresolved files.
4204 4225
4205 4226 To undo an uncommitted merge, use :hg:`update --clean .` which
4206 4227 will check out a clean copy of the original merge parent, losing
4207 4228 all changes.
4208 4229
4209 4230 Returns 0 on success, 1 if there are unresolved files.
4210 4231 """
4211 4232
4212 4233 if opts.get('rev') and node:
4213 4234 raise util.Abort(_("please specify just one revision"))
4214 4235 if not node:
4215 4236 node = opts.get('rev')
4216 4237
4217 4238 if node:
4218 4239 node = scmutil.revsingle(repo, node).node()
4219 4240
4220 4241 if not node and repo._bookmarkcurrent:
4221 4242 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4222 4243 curhead = repo[repo._bookmarkcurrent].node()
4223 4244 if len(bmheads) == 2:
4224 4245 if curhead == bmheads[0]:
4225 4246 node = bmheads[1]
4226 4247 else:
4227 4248 node = bmheads[0]
4228 4249 elif len(bmheads) > 2:
4229 4250 raise util.Abort(_("multiple matching bookmarks to merge - "
4230 4251 "please merge with an explicit rev or bookmark"),
4231 4252 hint=_("run 'hg heads' to see all heads"))
4232 4253 elif len(bmheads) <= 1:
4233 4254 raise util.Abort(_("no matching bookmark to merge - "
4234 4255 "please merge with an explicit rev or bookmark"),
4235 4256 hint=_("run 'hg heads' to see all heads"))
4236 4257
4237 4258 if not node and not repo._bookmarkcurrent:
4238 4259 branch = repo[None].branch()
4239 4260 bheads = repo.branchheads(branch)
4240 4261 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4241 4262
4242 4263 if len(nbhs) > 2:
4243 4264 raise util.Abort(_("branch '%s' has %d heads - "
4244 4265 "please merge with an explicit rev")
4245 4266 % (branch, len(bheads)),
4246 4267 hint=_("run 'hg heads .' to see heads"))
4247 4268
4248 4269 parent = repo.dirstate.p1()
4249 4270 if len(nbhs) <= 1:
4250 4271 if len(bheads) > 1:
4251 4272 raise util.Abort(_("heads are bookmarked - "
4252 4273 "please merge with an explicit rev"),
4253 4274 hint=_("run 'hg heads' to see all heads"))
4254 4275 if len(repo.heads()) > 1:
4255 4276 raise util.Abort(_("branch '%s' has one head - "
4256 4277 "please merge with an explicit rev")
4257 4278 % branch,
4258 4279 hint=_("run 'hg heads' to see all heads"))
4259 4280 msg, hint = _('nothing to merge'), None
4260 4281 if parent != repo.lookup(branch):
4261 4282 hint = _("use 'hg update' instead")
4262 4283 raise util.Abort(msg, hint=hint)
4263 4284
4264 4285 if parent not in bheads:
4265 4286 raise util.Abort(_('working directory not at a head revision'),
4266 4287 hint=_("use 'hg update' or merge with an "
4267 4288 "explicit revision"))
4268 4289 if parent == nbhs[0]:
4269 4290 node = nbhs[-1]
4270 4291 else:
4271 4292 node = nbhs[0]
4272 4293
4273 4294 if opts.get('preview'):
4274 4295 # find nodes that are ancestors of p2 but not of p1
4275 4296 p1 = repo.lookup('.')
4276 4297 p2 = repo.lookup(node)
4277 4298 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4278 4299
4279 4300 displayer = cmdutil.show_changeset(ui, repo, opts)
4280 4301 for node in nodes:
4281 4302 displayer.show(repo[node])
4282 4303 displayer.close()
4283 4304 return 0
4284 4305
4285 4306 try:
4286 4307 # ui.forcemerge is an internal variable, do not document
4287 4308 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4288 4309 return hg.merge(repo, node, force=opts.get('force'))
4289 4310 finally:
4290 4311 ui.setconfig('ui', 'forcemerge', '', 'merge')
4291 4312
4292 4313 @command('outgoing|out',
4293 4314 [('f', 'force', None, _('run even when the destination is unrelated')),
4294 4315 ('r', 'rev', [],
4295 4316 _('a changeset intended to be included in the destination'), _('REV')),
4296 4317 ('n', 'newest-first', None, _('show newest record first')),
4297 4318 ('B', 'bookmarks', False, _('compare bookmarks')),
4298 4319 ('b', 'branch', [], _('a specific branch you would like to push'),
4299 4320 _('BRANCH')),
4300 4321 ] + logopts + remoteopts + subrepoopts,
4301 4322 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4302 4323 def outgoing(ui, repo, dest=None, **opts):
4303 4324 """show changesets not found in the destination
4304 4325
4305 4326 Show changesets not found in the specified destination repository
4306 4327 or the default push location. These are the changesets that would
4307 4328 be pushed if a push was requested.
4308 4329
4309 4330 See pull for details of valid destination formats.
4310 4331
4311 4332 Returns 0 if there are outgoing changes, 1 otherwise.
4312 4333 """
4313 4334 if opts.get('graph'):
4314 4335 cmdutil.checkunsupportedgraphflags([], opts)
4315 4336 o, other = hg._outgoing(ui, repo, dest, opts)
4316 4337 if not o:
4317 4338 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4318 4339 return
4319 4340
4320 4341 revdag = cmdutil.graphrevs(repo, o, opts)
4321 4342 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4322 4343 showparents = [ctx.node() for ctx in repo[None].parents()]
4323 4344 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4324 4345 graphmod.asciiedges)
4325 4346 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4326 4347 return 0
4327 4348
4328 4349 if opts.get('bookmarks'):
4329 4350 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4330 4351 dest, branches = hg.parseurl(dest, opts.get('branch'))
4331 4352 other = hg.peer(repo, opts, dest)
4332 4353 if 'bookmarks' not in other.listkeys('namespaces'):
4333 4354 ui.warn(_("remote doesn't support bookmarks\n"))
4334 4355 return 0
4335 4356 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4336 4357 return bookmarks.diff(ui, other, repo)
4337 4358
4338 4359 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4339 4360 try:
4340 4361 return hg.outgoing(ui, repo, dest, opts)
4341 4362 finally:
4342 4363 del repo._subtoppath
4343 4364
4344 4365 @command('parents',
4345 4366 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4346 4367 ] + templateopts,
4347 4368 _('[-r REV] [FILE]'))
4348 4369 def parents(ui, repo, file_=None, **opts):
4349 4370 """show the parents of the working directory or revision
4350 4371
4351 4372 Print the working directory's parent revisions. If a revision is
4352 4373 given via -r/--rev, the parent of that revision will be printed.
4353 4374 If a file argument is given, the revision in which the file was
4354 4375 last changed (before the working directory revision or the
4355 4376 argument to --rev if given) is printed.
4356 4377
4357 4378 Returns 0 on success.
4358 4379 """
4359 4380
4360 4381 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4361 4382
4362 4383 if file_:
4363 4384 m = scmutil.match(ctx, (file_,), opts)
4364 4385 if m.anypats() or len(m.files()) != 1:
4365 4386 raise util.Abort(_('can only specify an explicit filename'))
4366 4387 file_ = m.files()[0]
4367 4388 filenodes = []
4368 4389 for cp in ctx.parents():
4369 4390 if not cp:
4370 4391 continue
4371 4392 try:
4372 4393 filenodes.append(cp.filenode(file_))
4373 4394 except error.LookupError:
4374 4395 pass
4375 4396 if not filenodes:
4376 4397 raise util.Abort(_("'%s' not found in manifest!") % file_)
4377 4398 p = []
4378 4399 for fn in filenodes:
4379 4400 fctx = repo.filectx(file_, fileid=fn)
4380 4401 p.append(fctx.node())
4381 4402 else:
4382 4403 p = [cp.node() for cp in ctx.parents()]
4383 4404
4384 4405 displayer = cmdutil.show_changeset(ui, repo, opts)
4385 4406 for n in p:
4386 4407 if n != nullid:
4387 4408 displayer.show(repo[n])
4388 4409 displayer.close()
4389 4410
4390 4411 @command('paths', [], _('[NAME]'))
4391 4412 def paths(ui, repo, search=None):
4392 4413 """show aliases for remote repositories
4393 4414
4394 4415 Show definition of symbolic path name NAME. If no name is given,
4395 4416 show definition of all available names.
4396 4417
4397 4418 Option -q/--quiet suppresses all output when searching for NAME
4398 4419 and shows only the path names when listing all definitions.
4399 4420
4400 4421 Path names are defined in the [paths] section of your
4401 4422 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4402 4423 repository, ``.hg/hgrc`` is used, too.
4403 4424
4404 4425 The path names ``default`` and ``default-push`` have a special
4405 4426 meaning. When performing a push or pull operation, they are used
4406 4427 as fallbacks if no location is specified on the command-line.
4407 4428 When ``default-push`` is set, it will be used for push and
4408 4429 ``default`` will be used for pull; otherwise ``default`` is used
4409 4430 as the fallback for both. When cloning a repository, the clone
4410 4431 source is written as ``default`` in ``.hg/hgrc``. Note that
4411 4432 ``default`` and ``default-push`` apply to all inbound (e.g.
4412 4433 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4413 4434 :hg:`bundle`) operations.
4414 4435
4415 4436 See :hg:`help urls` for more information.
4416 4437
4417 4438 Returns 0 on success.
4418 4439 """
4419 4440 if search:
4420 4441 for name, path in ui.configitems("paths"):
4421 4442 if name == search:
4422 4443 ui.status("%s\n" % util.hidepassword(path))
4423 4444 return
4424 4445 if not ui.quiet:
4425 4446 ui.warn(_("not found!\n"))
4426 4447 return 1
4427 4448 else:
4428 4449 for name, path in ui.configitems("paths"):
4429 4450 if ui.quiet:
4430 4451 ui.write("%s\n" % name)
4431 4452 else:
4432 4453 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4433 4454
4434 4455 @command('phase',
4435 4456 [('p', 'public', False, _('set changeset phase to public')),
4436 4457 ('d', 'draft', False, _('set changeset phase to draft')),
4437 4458 ('s', 'secret', False, _('set changeset phase to secret')),
4438 4459 ('f', 'force', False, _('allow to move boundary backward')),
4439 4460 ('r', 'rev', [], _('target revision'), _('REV')),
4440 4461 ],
4441 4462 _('[-p|-d|-s] [-f] [-r] REV...'))
4442 4463 def phase(ui, repo, *revs, **opts):
4443 4464 """set or show the current phase name
4444 4465
4445 4466 With no argument, show the phase name of specified revisions.
4446 4467
4447 4468 With one of -p/--public, -d/--draft or -s/--secret, change the
4448 4469 phase value of the specified revisions.
4449 4470
4450 4471 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4451 4472 lower phase to an higher phase. Phases are ordered as follows::
4452 4473
4453 4474 public < draft < secret
4454 4475
4455 4476 Returns 0 on success, 1 if no phases were changed or some could not
4456 4477 be changed.
4457 4478 """
4458 4479 # search for a unique phase argument
4459 4480 targetphase = None
4460 4481 for idx, name in enumerate(phases.phasenames):
4461 4482 if opts[name]:
4462 4483 if targetphase is not None:
4463 4484 raise util.Abort(_('only one phase can be specified'))
4464 4485 targetphase = idx
4465 4486
4466 4487 # look for specified revision
4467 4488 revs = list(revs)
4468 4489 revs.extend(opts['rev'])
4469 4490 if not revs:
4470 4491 raise util.Abort(_('no revisions specified'))
4471 4492
4472 4493 revs = scmutil.revrange(repo, revs)
4473 4494
4474 4495 lock = None
4475 4496 ret = 0
4476 4497 if targetphase is None:
4477 4498 # display
4478 4499 for r in revs:
4479 4500 ctx = repo[r]
4480 4501 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4481 4502 else:
4482 4503 lock = repo.lock()
4483 4504 try:
4484 4505 # set phase
4485 4506 if not revs:
4486 4507 raise util.Abort(_('empty revision set'))
4487 4508 nodes = [repo[r].node() for r in revs]
4488 4509 olddata = repo._phasecache.getphaserevs(repo)[:]
4489 4510 phases.advanceboundary(repo, targetphase, nodes)
4490 4511 if opts['force']:
4491 4512 phases.retractboundary(repo, targetphase, nodes)
4492 4513 finally:
4493 4514 lock.release()
4494 4515 # moving revision from public to draft may hide them
4495 4516 # We have to check result on an unfiltered repository
4496 4517 unfi = repo.unfiltered()
4497 4518 newdata = repo._phasecache.getphaserevs(unfi)
4498 4519 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4499 4520 cl = unfi.changelog
4500 4521 rejected = [n for n in nodes
4501 4522 if newdata[cl.rev(n)] < targetphase]
4502 4523 if rejected:
4503 4524 ui.warn(_('cannot move %i changesets to a higher '
4504 4525 'phase, use --force\n') % len(rejected))
4505 4526 ret = 1
4506 4527 if changes:
4507 4528 msg = _('phase changed for %i changesets\n') % changes
4508 4529 if ret:
4509 4530 ui.status(msg)
4510 4531 else:
4511 4532 ui.note(msg)
4512 4533 else:
4513 4534 ui.warn(_('no phases changed\n'))
4514 4535 ret = 1
4515 4536 return ret
4516 4537
4517 4538 def postincoming(ui, repo, modheads, optupdate, checkout):
4518 4539 if modheads == 0:
4519 4540 return
4520 4541 if optupdate:
4521 4542 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4522 4543 try:
4523 4544 ret = hg.update(repo, checkout)
4524 4545 except util.Abort, inst:
4525 4546 ui.warn(_("not updating: %s\n") % str(inst))
4526 4547 if inst.hint:
4527 4548 ui.warn(_("(%s)\n") % inst.hint)
4528 4549 return 0
4529 4550 if not ret and not checkout:
4530 4551 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4531 4552 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4532 4553 return ret
4533 4554 if modheads > 1:
4534 4555 currentbranchheads = len(repo.branchheads())
4535 4556 if currentbranchheads == modheads:
4536 4557 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4537 4558 elif currentbranchheads > 1:
4538 4559 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4539 4560 "merge)\n"))
4540 4561 else:
4541 4562 ui.status(_("(run 'hg heads' to see heads)\n"))
4542 4563 else:
4543 4564 ui.status(_("(run 'hg update' to get a working copy)\n"))
4544 4565
4545 4566 @command('^pull',
4546 4567 [('u', 'update', None,
4547 4568 _('update to new branch head if changesets were pulled')),
4548 4569 ('f', 'force', None, _('run even when remote repository is unrelated')),
4549 4570 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4550 4571 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4551 4572 ('b', 'branch', [], _('a specific branch you would like to pull'),
4552 4573 _('BRANCH')),
4553 4574 ] + remoteopts,
4554 4575 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4555 4576 def pull(ui, repo, source="default", **opts):
4556 4577 """pull changes from the specified source
4557 4578
4558 4579 Pull changes from a remote repository to a local one.
4559 4580
4560 4581 This finds all changes from the repository at the specified path
4561 4582 or URL and adds them to a local repository (the current one unless
4562 4583 -R is specified). By default, this does not update the copy of the
4563 4584 project in the working directory.
4564 4585
4565 4586 Use :hg:`incoming` if you want to see what would have been added
4566 4587 by a pull at the time you issued this command. If you then decide
4567 4588 to add those changes to the repository, you should use :hg:`pull
4568 4589 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4569 4590
4570 4591 If SOURCE is omitted, the 'default' path will be used.
4571 4592 See :hg:`help urls` for more information.
4572 4593
4573 4594 Returns 0 on success, 1 if an update had unresolved files.
4574 4595 """
4575 4596 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4576 4597 other = hg.peer(repo, opts, source)
4577 4598 try:
4578 4599 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4579 4600 revs, checkout = hg.addbranchrevs(repo, other, branches,
4580 4601 opts.get('rev'))
4581 4602
4582 4603 remotebookmarks = other.listkeys('bookmarks')
4583 4604
4584 4605 if opts.get('bookmark'):
4585 4606 if not revs:
4586 4607 revs = []
4587 4608 for b in opts['bookmark']:
4588 4609 if b not in remotebookmarks:
4589 4610 raise util.Abort(_('remote bookmark %s not found!') % b)
4590 4611 revs.append(remotebookmarks[b])
4591 4612
4592 4613 if revs:
4593 4614 try:
4594 4615 revs = [other.lookup(rev) for rev in revs]
4595 4616 except error.CapabilityError:
4596 4617 err = _("other repository doesn't support revision lookup, "
4597 4618 "so a rev cannot be specified.")
4598 4619 raise util.Abort(err)
4599 4620
4600 4621 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4601 4622 bookmarks.updatefromremote(ui, repo, remotebookmarks, source)
4602 4623 if checkout:
4603 4624 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4604 4625 repo._subtoppath = source
4605 4626 try:
4606 4627 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4607 4628
4608 4629 finally:
4609 4630 del repo._subtoppath
4610 4631
4611 4632 # update specified bookmarks
4612 4633 if opts.get('bookmark'):
4613 4634 marks = repo._bookmarks
4614 4635 for b in opts['bookmark']:
4615 4636 # explicit pull overrides local bookmark if any
4616 4637 ui.status(_("importing bookmark %s\n") % b)
4617 4638 marks[b] = repo[remotebookmarks[b]].node()
4618 4639 marks.write()
4619 4640 finally:
4620 4641 other.close()
4621 4642 return ret
4622 4643
4623 4644 @command('^push',
4624 4645 [('f', 'force', None, _('force push')),
4625 4646 ('r', 'rev', [],
4626 4647 _('a changeset intended to be included in the destination'),
4627 4648 _('REV')),
4628 4649 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4629 4650 ('b', 'branch', [],
4630 4651 _('a specific branch you would like to push'), _('BRANCH')),
4631 4652 ('', 'new-branch', False, _('allow pushing a new branch')),
4632 4653 ] + remoteopts,
4633 4654 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4634 4655 def push(ui, repo, dest=None, **opts):
4635 4656 """push changes to the specified destination
4636 4657
4637 4658 Push changesets from the local repository to the specified
4638 4659 destination.
4639 4660
4640 4661 This operation is symmetrical to pull: it is identical to a pull
4641 4662 in the destination repository from the current one.
4642 4663
4643 4664 By default, push will not allow creation of new heads at the
4644 4665 destination, since multiple heads would make it unclear which head
4645 4666 to use. In this situation, it is recommended to pull and merge
4646 4667 before pushing.
4647 4668
4648 4669 Use --new-branch if you want to allow push to create a new named
4649 4670 branch that is not present at the destination. This allows you to
4650 4671 only create a new branch without forcing other changes.
4651 4672
4652 4673 .. note::
4653 4674
4654 4675 Extra care should be taken with the -f/--force option,
4655 4676 which will push all new heads on all branches, an action which will
4656 4677 almost always cause confusion for collaborators.
4657 4678
4658 4679 If -r/--rev is used, the specified revision and all its ancestors
4659 4680 will be pushed to the remote repository.
4660 4681
4661 4682 If -B/--bookmark is used, the specified bookmarked revision, its
4662 4683 ancestors, and the bookmark will be pushed to the remote
4663 4684 repository.
4664 4685
4665 4686 Please see :hg:`help urls` for important details about ``ssh://``
4666 4687 URLs. If DESTINATION is omitted, a default path will be used.
4667 4688
4668 4689 Returns 0 if push was successful, 1 if nothing to push.
4669 4690 """
4670 4691
4671 4692 if opts.get('bookmark'):
4672 4693 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4673 4694 for b in opts['bookmark']:
4674 4695 # translate -B options to -r so changesets get pushed
4675 4696 if b in repo._bookmarks:
4676 4697 opts.setdefault('rev', []).append(b)
4677 4698 else:
4678 4699 # if we try to push a deleted bookmark, translate it to null
4679 4700 # this lets simultaneous -r, -b options continue working
4680 4701 opts.setdefault('rev', []).append("null")
4681 4702
4682 4703 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4683 4704 dest, branches = hg.parseurl(dest, opts.get('branch'))
4684 4705 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4685 4706 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4686 4707 try:
4687 4708 other = hg.peer(repo, opts, dest)
4688 4709 except error.RepoError:
4689 4710 if dest == "default-push":
4690 4711 raise util.Abort(_("default repository not configured!"),
4691 4712 hint=_('see the "path" section in "hg help config"'))
4692 4713 else:
4693 4714 raise
4694 4715
4695 4716 if revs:
4696 4717 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4697 4718
4698 4719 repo._subtoppath = dest
4699 4720 try:
4700 4721 # push subrepos depth-first for coherent ordering
4701 4722 c = repo['']
4702 4723 subs = c.substate # only repos that are committed
4703 4724 for s in sorted(subs):
4704 4725 result = c.sub(s).push(opts)
4705 4726 if result == 0:
4706 4727 return not result
4707 4728 finally:
4708 4729 del repo._subtoppath
4709 4730 result = repo.push(other, opts.get('force'), revs=revs,
4710 4731 newbranch=opts.get('new_branch'))
4711 4732
4712 4733 result = not result
4713 4734
4714 4735 if opts.get('bookmark'):
4715 4736 bresult = bookmarks.pushtoremote(ui, repo, other, opts['bookmark'])
4716 4737 if bresult == 2:
4717 4738 return 2
4718 4739 if not result and bresult:
4719 4740 result = 2
4720 4741
4721 4742 return result
4722 4743
4723 4744 @command('recover', [])
4724 4745 def recover(ui, repo):
4725 4746 """roll back an interrupted transaction
4726 4747
4727 4748 Recover from an interrupted commit or pull.
4728 4749
4729 4750 This command tries to fix the repository status after an
4730 4751 interrupted operation. It should only be necessary when Mercurial
4731 4752 suggests it.
4732 4753
4733 4754 Returns 0 if successful, 1 if nothing to recover or verify fails.
4734 4755 """
4735 4756 if repo.recover():
4736 4757 return hg.verify(repo)
4737 4758 return 1
4738 4759
4739 4760 @command('^remove|rm',
4740 4761 [('A', 'after', None, _('record delete for missing files')),
4741 4762 ('f', 'force', None,
4742 4763 _('remove (and delete) file even if added or modified')),
4743 4764 ] + walkopts,
4744 4765 _('[OPTION]... FILE...'))
4745 4766 def remove(ui, repo, *pats, **opts):
4746 4767 """remove the specified files on the next commit
4747 4768
4748 4769 Schedule the indicated files for removal from the current branch.
4749 4770
4750 4771 This command schedules the files to be removed at the next commit.
4751 4772 To undo a remove before that, see :hg:`revert`. To undo added
4752 4773 files, see :hg:`forget`.
4753 4774
4754 4775 .. container:: verbose
4755 4776
4756 4777 -A/--after can be used to remove only files that have already
4757 4778 been deleted, -f/--force can be used to force deletion, and -Af
4758 4779 can be used to remove files from the next revision without
4759 4780 deleting them from the working directory.
4760 4781
4761 4782 The following table details the behavior of remove for different
4762 4783 file states (columns) and option combinations (rows). The file
4763 4784 states are Added [A], Clean [C], Modified [M] and Missing [!]
4764 4785 (as reported by :hg:`status`). The actions are Warn, Remove
4765 4786 (from branch) and Delete (from disk):
4766 4787
4767 4788 ========= == == == ==
4768 4789 opt/state A C M !
4769 4790 ========= == == == ==
4770 4791 none W RD W R
4771 4792 -f R RD RD R
4772 4793 -A W W W R
4773 4794 -Af R R R R
4774 4795 ========= == == == ==
4775 4796
4776 4797 Note that remove never deletes files in Added [A] state from the
4777 4798 working directory, not even if option --force is specified.
4778 4799
4779 4800 Returns 0 on success, 1 if any warnings encountered.
4780 4801 """
4781 4802
4782 4803 ret = 0
4783 4804 after, force = opts.get('after'), opts.get('force')
4784 4805 if not pats and not after:
4785 4806 raise util.Abort(_('no files specified'))
4786 4807
4787 4808 m = scmutil.match(repo[None], pats, opts)
4788 4809 s = repo.status(match=m, clean=True)
4789 4810 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4790 4811
4791 4812 # warn about failure to delete explicit files/dirs
4792 4813 wctx = repo[None]
4793 4814 for f in m.files():
4794 4815 if f in repo.dirstate or f in wctx.dirs():
4795 4816 continue
4796 4817 if os.path.exists(m.rel(f)):
4797 4818 if os.path.isdir(m.rel(f)):
4798 4819 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
4799 4820 else:
4800 4821 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4801 4822 # missing files will generate a warning elsewhere
4802 4823 ret = 1
4803 4824
4804 4825 if force:
4805 4826 list = modified + deleted + clean + added
4806 4827 elif after:
4807 4828 list = deleted
4808 4829 for f in modified + added + clean:
4809 4830 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
4810 4831 ret = 1
4811 4832 else:
4812 4833 list = deleted + clean
4813 4834 for f in modified:
4814 4835 ui.warn(_('not removing %s: file is modified (use -f'
4815 4836 ' to force removal)\n') % m.rel(f))
4816 4837 ret = 1
4817 4838 for f in added:
4818 4839 ui.warn(_('not removing %s: file has been marked for add'
4819 4840 ' (use forget to undo)\n') % m.rel(f))
4820 4841 ret = 1
4821 4842
4822 4843 for f in sorted(list):
4823 4844 if ui.verbose or not m.exact(f):
4824 4845 ui.status(_('removing %s\n') % m.rel(f))
4825 4846
4826 4847 wlock = repo.wlock()
4827 4848 try:
4828 4849 if not after:
4829 4850 for f in list:
4830 4851 if f in added:
4831 4852 continue # we never unlink added files on remove
4832 4853 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
4833 4854 repo[None].forget(list)
4834 4855 finally:
4835 4856 wlock.release()
4836 4857
4837 4858 return ret
4838 4859
4839 4860 @command('rename|move|mv',
4840 4861 [('A', 'after', None, _('record a rename that has already occurred')),
4841 4862 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4842 4863 ] + walkopts + dryrunopts,
4843 4864 _('[OPTION]... SOURCE... DEST'))
4844 4865 def rename(ui, repo, *pats, **opts):
4845 4866 """rename files; equivalent of copy + remove
4846 4867
4847 4868 Mark dest as copies of sources; mark sources for deletion. If dest
4848 4869 is a directory, copies are put in that directory. If dest is a
4849 4870 file, there can only be one source.
4850 4871
4851 4872 By default, this command copies the contents of files as they
4852 4873 exist in the working directory. If invoked with -A/--after, the
4853 4874 operation is recorded, but no copying is performed.
4854 4875
4855 4876 This command takes effect at the next commit. To undo a rename
4856 4877 before that, see :hg:`revert`.
4857 4878
4858 4879 Returns 0 on success, 1 if errors are encountered.
4859 4880 """
4860 4881 wlock = repo.wlock(False)
4861 4882 try:
4862 4883 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4863 4884 finally:
4864 4885 wlock.release()
4865 4886
4866 4887 @command('resolve',
4867 4888 [('a', 'all', None, _('select all unresolved files')),
4868 4889 ('l', 'list', None, _('list state of files needing merge')),
4869 4890 ('m', 'mark', None, _('mark files as resolved')),
4870 4891 ('u', 'unmark', None, _('mark files as unresolved')),
4871 4892 ('n', 'no-status', None, _('hide status prefix'))]
4872 4893 + mergetoolopts + walkopts,
4873 4894 _('[OPTION]... [FILE]...'))
4874 4895 def resolve(ui, repo, *pats, **opts):
4875 4896 """redo merges or set/view the merge status of files
4876 4897
4877 4898 Merges with unresolved conflicts are often the result of
4878 4899 non-interactive merging using the ``internal:merge`` configuration
4879 4900 setting, or a command-line merge tool like ``diff3``. The resolve
4880 4901 command is used to manage the files involved in a merge, after
4881 4902 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4882 4903 working directory must have two parents). See :hg:`help
4883 4904 merge-tools` for information on configuring merge tools.
4884 4905
4885 4906 The resolve command can be used in the following ways:
4886 4907
4887 4908 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4888 4909 files, discarding any previous merge attempts. Re-merging is not
4889 4910 performed for files already marked as resolved. Use ``--all/-a``
4890 4911 to select all unresolved files. ``--tool`` can be used to specify
4891 4912 the merge tool used for the given files. It overrides the HGMERGE
4892 4913 environment variable and your configuration files. Previous file
4893 4914 contents are saved with a ``.orig`` suffix.
4894 4915
4895 4916 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4896 4917 (e.g. after having manually fixed-up the files). The default is
4897 4918 to mark all unresolved files.
4898 4919
4899 4920 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4900 4921 default is to mark all resolved files.
4901 4922
4902 4923 - :hg:`resolve -l`: list files which had or still have conflicts.
4903 4924 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4904 4925
4905 4926 Note that Mercurial will not let you commit files with unresolved
4906 4927 merge conflicts. You must use :hg:`resolve -m ...` before you can
4907 4928 commit after a conflicting merge.
4908 4929
4909 4930 Returns 0 on success, 1 if any files fail a resolve attempt.
4910 4931 """
4911 4932
4912 4933 all, mark, unmark, show, nostatus = \
4913 4934 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4914 4935
4915 4936 if (show and (mark or unmark)) or (mark and unmark):
4916 4937 raise util.Abort(_("too many options specified"))
4917 4938 if pats and all:
4918 4939 raise util.Abort(_("can't specify --all and patterns"))
4919 4940 if not (all or pats or show or mark or unmark):
4920 4941 raise util.Abort(_('no files or directories specified; '
4921 4942 'use --all to remerge all files'))
4922 4943
4923 4944 ms = mergemod.mergestate(repo)
4924 4945
4925 4946 if not ms.active() and not show:
4926 4947 raise util.Abort(_('resolve command not applicable when not merging'))
4927 4948
4928 4949 m = scmutil.match(repo[None], pats, opts)
4929 4950 ret = 0
4930 4951
4931 4952 didwork = False
4932 4953 for f in ms:
4933 4954 if not m(f):
4934 4955 continue
4935 4956
4936 4957 didwork = True
4937 4958
4938 4959 if show:
4939 4960 if nostatus:
4940 4961 ui.write("%s\n" % f)
4941 4962 else:
4942 4963 ui.write("%s %s\n" % (ms[f].upper(), f),
4943 4964 label='resolve.' +
4944 4965 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4945 4966 elif mark:
4946 4967 ms.mark(f, "r")
4947 4968 elif unmark:
4948 4969 ms.mark(f, "u")
4949 4970 else:
4950 4971 wctx = repo[None]
4951 4972
4952 4973 # backup pre-resolve (merge uses .orig for its own purposes)
4953 4974 a = repo.wjoin(f)
4954 4975 util.copyfile(a, a + ".resolve")
4955 4976
4956 4977 try:
4957 4978 # resolve file
4958 4979 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4959 4980 'resolve')
4960 4981 if ms.resolve(f, wctx):
4961 4982 ret = 1
4962 4983 finally:
4963 4984 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4964 4985 ms.commit()
4965 4986
4966 4987 # replace filemerge's .orig file with our resolve file
4967 4988 util.rename(a + ".resolve", a + ".orig")
4968 4989
4969 4990 ms.commit()
4970 4991
4971 4992 if not didwork and pats:
4972 4993 ui.warn(_("arguments do not match paths that need resolved\n"))
4973 4994
4974 4995 # Nudge users into finishing an unfinished operation. We don't print
4975 4996 # this with the list/show operation because we want list/show to remain
4976 4997 # machine readable.
4977 4998 if not list(ms.unresolved()) and not show:
4978 4999 ui.status(_('no more unresolved files\n'))
4979 5000
4980 5001 return ret
4981 5002
4982 5003 @command('revert',
4983 5004 [('a', 'all', None, _('revert all changes when no arguments given')),
4984 5005 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4985 5006 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4986 5007 ('C', 'no-backup', None, _('do not save backup copies of files')),
4987 5008 ] + walkopts + dryrunopts,
4988 5009 _('[OPTION]... [-r REV] [NAME]...'))
4989 5010 def revert(ui, repo, *pats, **opts):
4990 5011 """restore files to their checkout state
4991 5012
4992 5013 .. note::
4993 5014
4994 5015 To check out earlier revisions, you should use :hg:`update REV`.
4995 5016 To cancel an uncommitted merge (and lose your changes),
4996 5017 use :hg:`update --clean .`.
4997 5018
4998 5019 With no revision specified, revert the specified files or directories
4999 5020 to the contents they had in the parent of the working directory.
5000 5021 This restores the contents of files to an unmodified
5001 5022 state and unschedules adds, removes, copies, and renames. If the
5002 5023 working directory has two parents, you must explicitly specify a
5003 5024 revision.
5004 5025
5005 5026 Using the -r/--rev or -d/--date options, revert the given files or
5006 5027 directories to their states as of a specific revision. Because
5007 5028 revert does not change the working directory parents, this will
5008 5029 cause these files to appear modified. This can be helpful to "back
5009 5030 out" some or all of an earlier change. See :hg:`backout` for a
5010 5031 related method.
5011 5032
5012 5033 Modified files are saved with a .orig suffix before reverting.
5013 5034 To disable these backups, use --no-backup.
5014 5035
5015 5036 See :hg:`help dates` for a list of formats valid for -d/--date.
5016 5037
5017 5038 Returns 0 on success.
5018 5039 """
5019 5040
5020 5041 if opts.get("date"):
5021 5042 if opts.get("rev"):
5022 5043 raise util.Abort(_("you can't specify a revision and a date"))
5023 5044 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5024 5045
5025 5046 parent, p2 = repo.dirstate.parents()
5026 5047 if not opts.get('rev') and p2 != nullid:
5027 5048 # revert after merge is a trap for new users (issue2915)
5028 5049 raise util.Abort(_('uncommitted merge with no revision specified'),
5029 5050 hint=_('use "hg update" or see "hg help revert"'))
5030 5051
5031 5052 ctx = scmutil.revsingle(repo, opts.get('rev'))
5032 5053
5033 5054 if not pats and not opts.get('all'):
5034 5055 msg = _("no files or directories specified")
5035 5056 if p2 != nullid:
5036 5057 hint = _("uncommitted merge, use --all to discard all changes,"
5037 5058 " or 'hg update -C .' to abort the merge")
5038 5059 raise util.Abort(msg, hint=hint)
5039 5060 dirty = util.any(repo.status())
5040 5061 node = ctx.node()
5041 5062 if node != parent:
5042 5063 if dirty:
5043 5064 hint = _("uncommitted changes, use --all to discard all"
5044 5065 " changes, or 'hg update %s' to update") % ctx.rev()
5045 5066 else:
5046 5067 hint = _("use --all to revert all files,"
5047 5068 " or 'hg update %s' to update") % ctx.rev()
5048 5069 elif dirty:
5049 5070 hint = _("uncommitted changes, use --all to discard all changes")
5050 5071 else:
5051 5072 hint = _("use --all to revert all files")
5052 5073 raise util.Abort(msg, hint=hint)
5053 5074
5054 5075 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5055 5076
5056 5077 @command('rollback', dryrunopts +
5057 5078 [('f', 'force', False, _('ignore safety measures'))])
5058 5079 def rollback(ui, repo, **opts):
5059 5080 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5060 5081
5061 5082 Please use :hg:`commit --amend` instead of rollback to correct
5062 5083 mistakes in the last commit.
5063 5084
5064 5085 This command should be used with care. There is only one level of
5065 5086 rollback, and there is no way to undo a rollback. It will also
5066 5087 restore the dirstate at the time of the last transaction, losing
5067 5088 any dirstate changes since that time. This command does not alter
5068 5089 the working directory.
5069 5090
5070 5091 Transactions are used to encapsulate the effects of all commands
5071 5092 that create new changesets or propagate existing changesets into a
5072 5093 repository.
5073 5094
5074 5095 .. container:: verbose
5075 5096
5076 5097 For example, the following commands are transactional, and their
5077 5098 effects can be rolled back:
5078 5099
5079 5100 - commit
5080 5101 - import
5081 5102 - pull
5082 5103 - push (with this repository as the destination)
5083 5104 - unbundle
5084 5105
5085 5106 To avoid permanent data loss, rollback will refuse to rollback a
5086 5107 commit transaction if it isn't checked out. Use --force to
5087 5108 override this protection.
5088 5109
5089 5110 This command is not intended for use on public repositories. Once
5090 5111 changes are visible for pull by other users, rolling a transaction
5091 5112 back locally is ineffective (someone else may already have pulled
5092 5113 the changes). Furthermore, a race is possible with readers of the
5093 5114 repository; for example an in-progress pull from the repository
5094 5115 may fail if a rollback is performed.
5095 5116
5096 5117 Returns 0 on success, 1 if no rollback data is available.
5097 5118 """
5098 5119 return repo.rollback(dryrun=opts.get('dry_run'),
5099 5120 force=opts.get('force'))
5100 5121
5101 5122 @command('root', [])
5102 5123 def root(ui, repo):
5103 5124 """print the root (top) of the current working directory
5104 5125
5105 5126 Print the root directory of the current repository.
5106 5127
5107 5128 Returns 0 on success.
5108 5129 """
5109 5130 ui.write(repo.root + "\n")
5110 5131
5111 5132 @command('^serve',
5112 5133 [('A', 'accesslog', '', _('name of access log file to write to'),
5113 5134 _('FILE')),
5114 5135 ('d', 'daemon', None, _('run server in background')),
5115 5136 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5116 5137 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5117 5138 # use string type, then we can check if something was passed
5118 5139 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5119 5140 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5120 5141 _('ADDR')),
5121 5142 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5122 5143 _('PREFIX')),
5123 5144 ('n', 'name', '',
5124 5145 _('name to show in web pages (default: working directory)'), _('NAME')),
5125 5146 ('', 'web-conf', '',
5126 5147 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5127 5148 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5128 5149 _('FILE')),
5129 5150 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5130 5151 ('', 'stdio', None, _('for remote clients')),
5131 5152 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5132 5153 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5133 5154 ('', 'style', '', _('template style to use'), _('STYLE')),
5134 5155 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5135 5156 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5136 5157 _('[OPTION]...'))
5137 5158 def serve(ui, repo, **opts):
5138 5159 """start stand-alone webserver
5139 5160
5140 5161 Start a local HTTP repository browser and pull server. You can use
5141 5162 this for ad-hoc sharing and browsing of repositories. It is
5142 5163 recommended to use a real web server to serve a repository for
5143 5164 longer periods of time.
5144 5165
5145 5166 Please note that the server does not implement access control.
5146 5167 This means that, by default, anybody can read from the server and
5147 5168 nobody can write to it by default. Set the ``web.allow_push``
5148 5169 option to ``*`` to allow everybody to push to the server. You
5149 5170 should use a real web server if you need to authenticate users.
5150 5171
5151 5172 By default, the server logs accesses to stdout and errors to
5152 5173 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5153 5174 files.
5154 5175
5155 5176 To have the server choose a free port number to listen on, specify
5156 5177 a port number of 0; in this case, the server will print the port
5157 5178 number it uses.
5158 5179
5159 5180 Returns 0 on success.
5160 5181 """
5161 5182
5162 5183 if opts["stdio"] and opts["cmdserver"]:
5163 5184 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5164 5185
5165 5186 def checkrepo():
5166 5187 if repo is None:
5167 5188 raise error.RepoError(_("there is no Mercurial repository here"
5168 5189 " (.hg not found)"))
5169 5190
5170 5191 if opts["stdio"]:
5171 5192 checkrepo()
5172 5193 s = sshserver.sshserver(ui, repo)
5173 5194 s.serve_forever()
5174 5195
5175 5196 if opts["cmdserver"]:
5176 5197 s = commandserver.server(ui, repo, opts["cmdserver"])
5177 5198 return s.serve()
5178 5199
5179 5200 # this way we can check if something was given in the command-line
5180 5201 if opts.get('port'):
5181 5202 opts['port'] = util.getport(opts.get('port'))
5182 5203
5183 5204 baseui = repo and repo.baseui or ui
5184 5205 optlist = ("name templates style address port prefix ipv6"
5185 5206 " accesslog errorlog certificate encoding")
5186 5207 for o in optlist.split():
5187 5208 val = opts.get(o, '')
5188 5209 if val in (None, ''): # should check against default options instead
5189 5210 continue
5190 5211 baseui.setconfig("web", o, val, 'serve')
5191 5212 if repo and repo.ui != baseui:
5192 5213 repo.ui.setconfig("web", o, val, 'serve')
5193 5214
5194 5215 o = opts.get('web_conf') or opts.get('webdir_conf')
5195 5216 if not o:
5196 5217 if not repo:
5197 5218 raise error.RepoError(_("there is no Mercurial repository"
5198 5219 " here (.hg not found)"))
5199 5220 o = repo
5200 5221
5201 5222 app = hgweb.hgweb(o, baseui=baseui)
5202 5223 service = httpservice(ui, app, opts)
5203 5224 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5204 5225
5205 5226 class httpservice(object):
5206 5227 def __init__(self, ui, app, opts):
5207 5228 self.ui = ui
5208 5229 self.app = app
5209 5230 self.opts = opts
5210 5231
5211 5232 def init(self):
5212 5233 util.setsignalhandler()
5213 5234 self.httpd = hgweb_server.create_server(self.ui, self.app)
5214 5235
5215 5236 if self.opts['port'] and not self.ui.verbose:
5216 5237 return
5217 5238
5218 5239 if self.httpd.prefix:
5219 5240 prefix = self.httpd.prefix.strip('/') + '/'
5220 5241 else:
5221 5242 prefix = ''
5222 5243
5223 5244 port = ':%d' % self.httpd.port
5224 5245 if port == ':80':
5225 5246 port = ''
5226 5247
5227 5248 bindaddr = self.httpd.addr
5228 5249 if bindaddr == '0.0.0.0':
5229 5250 bindaddr = '*'
5230 5251 elif ':' in bindaddr: # IPv6
5231 5252 bindaddr = '[%s]' % bindaddr
5232 5253
5233 5254 fqaddr = self.httpd.fqaddr
5234 5255 if ':' in fqaddr:
5235 5256 fqaddr = '[%s]' % fqaddr
5236 5257 if self.opts['port']:
5237 5258 write = self.ui.status
5238 5259 else:
5239 5260 write = self.ui.write
5240 5261 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5241 5262 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5242 5263
5243 5264 def run(self):
5244 5265 self.httpd.serve_forever()
5245 5266
5246 5267
5247 5268 @command('^status|st',
5248 5269 [('A', 'all', None, _('show status of all files')),
5249 5270 ('m', 'modified', None, _('show only modified files')),
5250 5271 ('a', 'added', None, _('show only added files')),
5251 5272 ('r', 'removed', None, _('show only removed files')),
5252 5273 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5253 5274 ('c', 'clean', None, _('show only files without changes')),
5254 5275 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5255 5276 ('i', 'ignored', None, _('show only ignored files')),
5256 5277 ('n', 'no-status', None, _('hide status prefix')),
5257 5278 ('C', 'copies', None, _('show source of copied files')),
5258 5279 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5259 5280 ('', 'rev', [], _('show difference from revision'), _('REV')),
5260 5281 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5261 5282 ] + walkopts + subrepoopts,
5262 5283 _('[OPTION]... [FILE]...'))
5263 5284 def status(ui, repo, *pats, **opts):
5264 5285 """show changed files in the working directory
5265 5286
5266 5287 Show status of files in the repository. If names are given, only
5267 5288 files that match are shown. Files that are clean or ignored or
5268 5289 the source of a copy/move operation, are not listed unless
5269 5290 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5270 5291 Unless options described with "show only ..." are given, the
5271 5292 options -mardu are used.
5272 5293
5273 5294 Option -q/--quiet hides untracked (unknown and ignored) files
5274 5295 unless explicitly requested with -u/--unknown or -i/--ignored.
5275 5296
5276 5297 .. note::
5277 5298
5278 5299 status may appear to disagree with diff if permissions have
5279 5300 changed or a merge has occurred. The standard diff format does
5280 5301 not report permission changes and diff only reports changes
5281 5302 relative to one merge parent.
5282 5303
5283 5304 If one revision is given, it is used as the base revision.
5284 5305 If two revisions are given, the differences between them are
5285 5306 shown. The --change option can also be used as a shortcut to list
5286 5307 the changed files of a revision from its first parent.
5287 5308
5288 5309 The codes used to show the status of files are::
5289 5310
5290 5311 M = modified
5291 5312 A = added
5292 5313 R = removed
5293 5314 C = clean
5294 5315 ! = missing (deleted by non-hg command, but still tracked)
5295 5316 ? = not tracked
5296 5317 I = ignored
5297 5318 = origin of the previous file (with --copies)
5298 5319
5299 5320 .. container:: verbose
5300 5321
5301 5322 Examples:
5302 5323
5303 5324 - show changes in the working directory relative to a
5304 5325 changeset::
5305 5326
5306 5327 hg status --rev 9353
5307 5328
5308 5329 - show all changes including copies in an existing changeset::
5309 5330
5310 5331 hg status --copies --change 9353
5311 5332
5312 5333 - get a NUL separated list of added files, suitable for xargs::
5313 5334
5314 5335 hg status -an0
5315 5336
5316 5337 Returns 0 on success.
5317 5338 """
5318 5339
5319 5340 revs = opts.get('rev')
5320 5341 change = opts.get('change')
5321 5342
5322 5343 if revs and change:
5323 5344 msg = _('cannot specify --rev and --change at the same time')
5324 5345 raise util.Abort(msg)
5325 5346 elif change:
5326 5347 node2 = scmutil.revsingle(repo, change, None).node()
5327 5348 node1 = repo[node2].p1().node()
5328 5349 else:
5329 5350 node1, node2 = scmutil.revpair(repo, revs)
5330 5351
5331 5352 cwd = (pats and repo.getcwd()) or ''
5332 5353 end = opts.get('print0') and '\0' or '\n'
5333 5354 copy = {}
5334 5355 states = 'modified added removed deleted unknown ignored clean'.split()
5335 5356 show = [k for k in states if opts.get(k)]
5336 5357 if opts.get('all'):
5337 5358 show += ui.quiet and (states[:4] + ['clean']) or states
5338 5359 if not show:
5339 5360 show = ui.quiet and states[:4] or states[:5]
5340 5361
5341 5362 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5342 5363 'ignored' in show, 'clean' in show, 'unknown' in show,
5343 5364 opts.get('subrepos'))
5344 5365 changestates = zip(states, 'MAR!?IC', stat)
5345 5366
5346 5367 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5347 5368 copy = copies.pathcopies(repo[node1], repo[node2])
5348 5369
5349 5370 fm = ui.formatter('status', opts)
5350 5371 fmt = '%s' + end
5351 5372 showchar = not opts.get('no_status')
5352 5373
5353 5374 for state, char, files in changestates:
5354 5375 if state in show:
5355 5376 label = 'status.' + state
5356 5377 for f in files:
5357 5378 fm.startitem()
5358 5379 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5359 5380 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5360 5381 if f in copy:
5361 5382 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5362 5383 label='status.copied')
5363 5384 fm.end()
5364 5385
5365 5386 @command('^summary|sum',
5366 5387 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5367 5388 def summary(ui, repo, **opts):
5368 5389 """summarize working directory state
5369 5390
5370 5391 This generates a brief summary of the working directory state,
5371 5392 including parents, branch, commit status, and available updates.
5372 5393
5373 5394 With the --remote option, this will check the default paths for
5374 5395 incoming and outgoing changes. This can be time-consuming.
5375 5396
5376 5397 Returns 0 on success.
5377 5398 """
5378 5399
5379 5400 ctx = repo[None]
5380 5401 parents = ctx.parents()
5381 5402 pnode = parents[0].node()
5382 5403 marks = []
5383 5404
5384 5405 for p in parents:
5385 5406 # label with log.changeset (instead of log.parent) since this
5386 5407 # shows a working directory parent *changeset*:
5387 5408 # i18n: column positioning for "hg summary"
5388 5409 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5389 5410 label='log.changeset changeset.%s' % p.phasestr())
5390 5411 ui.write(' '.join(p.tags()), label='log.tag')
5391 5412 if p.bookmarks():
5392 5413 marks.extend(p.bookmarks())
5393 5414 if p.rev() == -1:
5394 5415 if not len(repo):
5395 5416 ui.write(_(' (empty repository)'))
5396 5417 else:
5397 5418 ui.write(_(' (no revision checked out)'))
5398 5419 ui.write('\n')
5399 5420 if p.description():
5400 5421 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5401 5422 label='log.summary')
5402 5423
5403 5424 branch = ctx.branch()
5404 5425 bheads = repo.branchheads(branch)
5405 5426 # i18n: column positioning for "hg summary"
5406 5427 m = _('branch: %s\n') % branch
5407 5428 if branch != 'default':
5408 5429 ui.write(m, label='log.branch')
5409 5430 else:
5410 5431 ui.status(m, label='log.branch')
5411 5432
5412 5433 if marks:
5413 5434 current = repo._bookmarkcurrent
5414 5435 # i18n: column positioning for "hg summary"
5415 5436 ui.write(_('bookmarks:'), label='log.bookmark')
5416 5437 if current is not None:
5417 5438 if current in marks:
5418 5439 ui.write(' *' + current, label='bookmarks.current')
5419 5440 marks.remove(current)
5420 5441 else:
5421 5442 ui.write(' [%s]' % current, label='bookmarks.current')
5422 5443 for m in marks:
5423 5444 ui.write(' ' + m, label='log.bookmark')
5424 5445 ui.write('\n', label='log.bookmark')
5425 5446
5426 5447 st = list(repo.status(unknown=True))[:6]
5427 5448
5428 5449 c = repo.dirstate.copies()
5429 5450 copied, renamed = [], []
5430 5451 for d, s in c.iteritems():
5431 5452 if s in st[2]:
5432 5453 st[2].remove(s)
5433 5454 renamed.append(d)
5434 5455 else:
5435 5456 copied.append(d)
5436 5457 if d in st[1]:
5437 5458 st[1].remove(d)
5438 5459 st.insert(3, renamed)
5439 5460 st.insert(4, copied)
5440 5461
5441 5462 ms = mergemod.mergestate(repo)
5442 5463 st.append([f for f in ms if ms[f] == 'u'])
5443 5464
5444 5465 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5445 5466 st.append(subs)
5446 5467
5447 5468 labels = [ui.label(_('%d modified'), 'status.modified'),
5448 5469 ui.label(_('%d added'), 'status.added'),
5449 5470 ui.label(_('%d removed'), 'status.removed'),
5450 5471 ui.label(_('%d renamed'), 'status.copied'),
5451 5472 ui.label(_('%d copied'), 'status.copied'),
5452 5473 ui.label(_('%d deleted'), 'status.deleted'),
5453 5474 ui.label(_('%d unknown'), 'status.unknown'),
5454 5475 ui.label(_('%d ignored'), 'status.ignored'),
5455 5476 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5456 5477 ui.label(_('%d subrepos'), 'status.modified')]
5457 5478 t = []
5458 5479 for s, l in zip(st, labels):
5459 5480 if s:
5460 5481 t.append(l % len(s))
5461 5482
5462 5483 t = ', '.join(t)
5463 5484 cleanworkdir = False
5464 5485
5465 5486 if repo.vfs.exists('updatestate'):
5466 5487 t += _(' (interrupted update)')
5467 5488 elif len(parents) > 1:
5468 5489 t += _(' (merge)')
5469 5490 elif branch != parents[0].branch():
5470 5491 t += _(' (new branch)')
5471 5492 elif (parents[0].closesbranch() and
5472 5493 pnode in repo.branchheads(branch, closed=True)):
5473 5494 t += _(' (head closed)')
5474 5495 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5475 5496 t += _(' (clean)')
5476 5497 cleanworkdir = True
5477 5498 elif pnode not in bheads:
5478 5499 t += _(' (new branch head)')
5479 5500
5480 5501 if cleanworkdir:
5481 5502 # i18n: column positioning for "hg summary"
5482 5503 ui.status(_('commit: %s\n') % t.strip())
5483 5504 else:
5484 5505 # i18n: column positioning for "hg summary"
5485 5506 ui.write(_('commit: %s\n') % t.strip())
5486 5507
5487 5508 # all ancestors of branch heads - all ancestors of parent = new csets
5488 5509 new = len(repo.changelog.findmissing([ctx.node() for ctx in parents],
5489 5510 bheads))
5490 5511
5491 5512 if new == 0:
5492 5513 # i18n: column positioning for "hg summary"
5493 5514 ui.status(_('update: (current)\n'))
5494 5515 elif pnode not in bheads:
5495 5516 # i18n: column positioning for "hg summary"
5496 5517 ui.write(_('update: %d new changesets (update)\n') % new)
5497 5518 else:
5498 5519 # i18n: column positioning for "hg summary"
5499 5520 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5500 5521 (new, len(bheads)))
5501 5522
5502 5523 cmdutil.summaryhooks(ui, repo)
5503 5524
5504 5525 if opts.get('remote'):
5505 5526 needsincoming, needsoutgoing = True, True
5506 5527 else:
5507 5528 needsincoming, needsoutgoing = False, False
5508 5529 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5509 5530 if i:
5510 5531 needsincoming = True
5511 5532 if o:
5512 5533 needsoutgoing = True
5513 5534 if not needsincoming and not needsoutgoing:
5514 5535 return
5515 5536
5516 5537 def getincoming():
5517 5538 source, branches = hg.parseurl(ui.expandpath('default'))
5518 5539 sbranch = branches[0]
5519 5540 try:
5520 5541 other = hg.peer(repo, {}, source)
5521 5542 except error.RepoError:
5522 5543 if opts.get('remote'):
5523 5544 raise
5524 5545 return source, sbranch, None, None, None
5525 5546 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5526 5547 if revs:
5527 5548 revs = [other.lookup(rev) for rev in revs]
5528 5549 ui.debug('comparing with %s\n' % util.hidepassword(source))
5529 5550 repo.ui.pushbuffer()
5530 5551 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5531 5552 repo.ui.popbuffer()
5532 5553 return source, sbranch, other, commoninc, commoninc[1]
5533 5554
5534 5555 if needsincoming:
5535 5556 source, sbranch, sother, commoninc, incoming = getincoming()
5536 5557 else:
5537 5558 source = sbranch = sother = commoninc = incoming = None
5538 5559
5539 5560 def getoutgoing():
5540 5561 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5541 5562 dbranch = branches[0]
5542 5563 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5543 5564 if source != dest:
5544 5565 try:
5545 5566 dother = hg.peer(repo, {}, dest)
5546 5567 except error.RepoError:
5547 5568 if opts.get('remote'):
5548 5569 raise
5549 5570 return dest, dbranch, None, None
5550 5571 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5551 5572 elif sother is None:
5552 5573 # there is no explicit destination peer, but source one is invalid
5553 5574 return dest, dbranch, None, None
5554 5575 else:
5555 5576 dother = sother
5556 5577 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5557 5578 common = None
5558 5579 else:
5559 5580 common = commoninc
5560 5581 if revs:
5561 5582 revs = [repo.lookup(rev) for rev in revs]
5562 5583 repo.ui.pushbuffer()
5563 5584 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5564 5585 commoninc=common)
5565 5586 repo.ui.popbuffer()
5566 5587 return dest, dbranch, dother, outgoing
5567 5588
5568 5589 if needsoutgoing:
5569 5590 dest, dbranch, dother, outgoing = getoutgoing()
5570 5591 else:
5571 5592 dest = dbranch = dother = outgoing = None
5572 5593
5573 5594 if opts.get('remote'):
5574 5595 t = []
5575 5596 if incoming:
5576 5597 t.append(_('1 or more incoming'))
5577 5598 o = outgoing.missing
5578 5599 if o:
5579 5600 t.append(_('%d outgoing') % len(o))
5580 5601 other = dother or sother
5581 5602 if 'bookmarks' in other.listkeys('namespaces'):
5582 5603 lmarks = repo.listkeys('bookmarks')
5583 5604 rmarks = other.listkeys('bookmarks')
5584 5605 diff = set(rmarks) - set(lmarks)
5585 5606 if len(diff) > 0:
5586 5607 t.append(_('%d incoming bookmarks') % len(diff))
5587 5608 diff = set(lmarks) - set(rmarks)
5588 5609 if len(diff) > 0:
5589 5610 t.append(_('%d outgoing bookmarks') % len(diff))
5590 5611
5591 5612 if t:
5592 5613 # i18n: column positioning for "hg summary"
5593 5614 ui.write(_('remote: %s\n') % (', '.join(t)))
5594 5615 else:
5595 5616 # i18n: column positioning for "hg summary"
5596 5617 ui.status(_('remote: (synced)\n'))
5597 5618
5598 5619 cmdutil.summaryremotehooks(ui, repo, opts,
5599 5620 ((source, sbranch, sother, commoninc),
5600 5621 (dest, dbranch, dother, outgoing)))
5601 5622
5602 5623 @command('tag',
5603 5624 [('f', 'force', None, _('force tag')),
5604 5625 ('l', 'local', None, _('make the tag local')),
5605 5626 ('r', 'rev', '', _('revision to tag'), _('REV')),
5606 5627 ('', 'remove', None, _('remove a tag')),
5607 5628 # -l/--local is already there, commitopts cannot be used
5608 5629 ('e', 'edit', None, _('edit commit message')),
5609 5630 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5610 5631 ] + commitopts2,
5611 5632 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5612 5633 def tag(ui, repo, name1, *names, **opts):
5613 5634 """add one or more tags for the current or given revision
5614 5635
5615 5636 Name a particular revision using <name>.
5616 5637
5617 5638 Tags are used to name particular revisions of the repository and are
5618 5639 very useful to compare different revisions, to go back to significant
5619 5640 earlier versions or to mark branch points as releases, etc. Changing
5620 5641 an existing tag is normally disallowed; use -f/--force to override.
5621 5642
5622 5643 If no revision is given, the parent of the working directory is
5623 5644 used.
5624 5645
5625 5646 To facilitate version control, distribution, and merging of tags,
5626 5647 they are stored as a file named ".hgtags" which is managed similarly
5627 5648 to other project files and can be hand-edited if necessary. This
5628 5649 also means that tagging creates a new commit. The file
5629 5650 ".hg/localtags" is used for local tags (not shared among
5630 5651 repositories).
5631 5652
5632 5653 Tag commits are usually made at the head of a branch. If the parent
5633 5654 of the working directory is not a branch head, :hg:`tag` aborts; use
5634 5655 -f/--force to force the tag commit to be based on a non-head
5635 5656 changeset.
5636 5657
5637 5658 See :hg:`help dates` for a list of formats valid for -d/--date.
5638 5659
5639 5660 Since tag names have priority over branch names during revision
5640 5661 lookup, using an existing branch name as a tag name is discouraged.
5641 5662
5642 5663 Returns 0 on success.
5643 5664 """
5644 5665 wlock = lock = None
5645 5666 try:
5646 5667 wlock = repo.wlock()
5647 5668 lock = repo.lock()
5648 5669 rev_ = "."
5649 5670 names = [t.strip() for t in (name1,) + names]
5650 5671 if len(names) != len(set(names)):
5651 5672 raise util.Abort(_('tag names must be unique'))
5652 5673 for n in names:
5653 5674 scmutil.checknewlabel(repo, n, 'tag')
5654 5675 if not n:
5655 5676 raise util.Abort(_('tag names cannot consist entirely of '
5656 5677 'whitespace'))
5657 5678 if opts.get('rev') and opts.get('remove'):
5658 5679 raise util.Abort(_("--rev and --remove are incompatible"))
5659 5680 if opts.get('rev'):
5660 5681 rev_ = opts['rev']
5661 5682 message = opts.get('message')
5662 5683 if opts.get('remove'):
5663 5684 expectedtype = opts.get('local') and 'local' or 'global'
5664 5685 for n in names:
5665 5686 if not repo.tagtype(n):
5666 5687 raise util.Abort(_("tag '%s' does not exist") % n)
5667 5688 if repo.tagtype(n) != expectedtype:
5668 5689 if expectedtype == 'global':
5669 5690 raise util.Abort(_("tag '%s' is not a global tag") % n)
5670 5691 else:
5671 5692 raise util.Abort(_("tag '%s' is not a local tag") % n)
5672 5693 rev_ = nullid
5673 5694 if not message:
5674 5695 # we don't translate commit messages
5675 5696 message = 'Removed tag %s' % ', '.join(names)
5676 5697 elif not opts.get('force'):
5677 5698 for n in names:
5678 5699 if n in repo.tags():
5679 5700 raise util.Abort(_("tag '%s' already exists "
5680 5701 "(use -f to force)") % n)
5681 5702 if not opts.get('local'):
5682 5703 p1, p2 = repo.dirstate.parents()
5683 5704 if p2 != nullid:
5684 5705 raise util.Abort(_('uncommitted merge'))
5685 5706 bheads = repo.branchheads()
5686 5707 if not opts.get('force') and bheads and p1 not in bheads:
5687 5708 raise util.Abort(_('not at a branch head (use -f to force)'))
5688 5709 r = scmutil.revsingle(repo, rev_).node()
5689 5710
5690 5711 if not message:
5691 5712 # we don't translate commit messages
5692 5713 message = ('Added tag %s for changeset %s' %
5693 5714 (', '.join(names), short(r)))
5694 5715
5695 5716 date = opts.get('date')
5696 5717 if date:
5697 5718 date = util.parsedate(date)
5698 5719
5699 5720 editor = cmdutil.getcommiteditor(**opts)
5700 5721
5701 5722 # don't allow tagging the null rev
5702 5723 if (not opts.get('remove') and
5703 5724 scmutil.revsingle(repo, rev_).rev() == nullrev):
5704 5725 raise util.Abort(_("cannot tag null revision"))
5705 5726
5706 5727 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
5707 5728 editor=editor)
5708 5729 finally:
5709 5730 release(lock, wlock)
5710 5731
5711 5732 @command('tags', [], '')
5712 5733 def tags(ui, repo, **opts):
5713 5734 """list repository tags
5714 5735
5715 5736 This lists both regular and local tags. When the -v/--verbose
5716 5737 switch is used, a third column "local" is printed for local tags.
5717 5738
5718 5739 Returns 0 on success.
5719 5740 """
5720 5741
5721 5742 fm = ui.formatter('tags', opts)
5722 5743 hexfunc = ui.debugflag and hex or short
5723 5744 tagtype = ""
5724 5745
5725 5746 for t, n in reversed(repo.tagslist()):
5726 5747 hn = hexfunc(n)
5727 5748 label = 'tags.normal'
5728 5749 tagtype = ''
5729 5750 if repo.tagtype(t) == 'local':
5730 5751 label = 'tags.local'
5731 5752 tagtype = 'local'
5732 5753
5733 5754 fm.startitem()
5734 5755 fm.write('tag', '%s', t, label=label)
5735 5756 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5736 5757 fm.condwrite(not ui.quiet, 'rev id', fmt,
5737 5758 repo.changelog.rev(n), hn, label=label)
5738 5759 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5739 5760 tagtype, label=label)
5740 5761 fm.plain('\n')
5741 5762 fm.end()
5742 5763
5743 5764 @command('tip',
5744 5765 [('p', 'patch', None, _('show patch')),
5745 5766 ('g', 'git', None, _('use git extended diff format')),
5746 5767 ] + templateopts,
5747 5768 _('[-p] [-g]'))
5748 5769 def tip(ui, repo, **opts):
5749 5770 """show the tip revision (DEPRECATED)
5750 5771
5751 5772 The tip revision (usually just called the tip) is the changeset
5752 5773 most recently added to the repository (and therefore the most
5753 5774 recently changed head).
5754 5775
5755 5776 If you have just made a commit, that commit will be the tip. If
5756 5777 you have just pulled changes from another repository, the tip of
5757 5778 that repository becomes the current tip. The "tip" tag is special
5758 5779 and cannot be renamed or assigned to a different changeset.
5759 5780
5760 5781 This command is deprecated, please use :hg:`heads` instead.
5761 5782
5762 5783 Returns 0 on success.
5763 5784 """
5764 5785 displayer = cmdutil.show_changeset(ui, repo, opts)
5765 5786 displayer.show(repo['tip'])
5766 5787 displayer.close()
5767 5788
5768 5789 @command('unbundle',
5769 5790 [('u', 'update', None,
5770 5791 _('update to new branch head if changesets were unbundled'))],
5771 5792 _('[-u] FILE...'))
5772 5793 def unbundle(ui, repo, fname1, *fnames, **opts):
5773 5794 """apply one or more changegroup files
5774 5795
5775 5796 Apply one or more compressed changegroup files generated by the
5776 5797 bundle command.
5777 5798
5778 5799 Returns 0 on success, 1 if an update has unresolved files.
5779 5800 """
5780 5801 fnames = (fname1,) + fnames
5781 5802
5782 5803 lock = repo.lock()
5783 5804 wc = repo['.']
5784 5805 try:
5785 5806 for fname in fnames:
5786 5807 f = hg.openpath(ui, fname)
5787 5808 gen = exchange.readbundle(ui, f, fname)
5788 5809 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
5789 5810 'bundle:' + fname)
5790 5811 finally:
5791 5812 lock.release()
5792 5813 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5793 5814 return postincoming(ui, repo, modheads, opts.get('update'), None)
5794 5815
5795 5816 @command('^update|up|checkout|co',
5796 5817 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5797 5818 ('c', 'check', None,
5798 5819 _('update across branches if no uncommitted changes')),
5799 5820 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5800 5821 ('r', 'rev', '', _('revision'), _('REV'))
5801 5822 ] + mergetoolopts,
5802 5823 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5803 5824 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5804 5825 tool=None):
5805 5826 """update working directory (or switch revisions)
5806 5827
5807 5828 Update the repository's working directory to the specified
5808 5829 changeset. If no changeset is specified, update to the tip of the
5809 5830 current named branch and move the current bookmark (see :hg:`help
5810 5831 bookmarks`).
5811 5832
5812 5833 Update sets the working directory's parent revision to the specified
5813 5834 changeset (see :hg:`help parents`).
5814 5835
5815 5836 If the changeset is not a descendant or ancestor of the working
5816 5837 directory's parent, the update is aborted. With the -c/--check
5817 5838 option, the working directory is checked for uncommitted changes; if
5818 5839 none are found, the working directory is updated to the specified
5819 5840 changeset.
5820 5841
5821 5842 .. container:: verbose
5822 5843
5823 5844 The following rules apply when the working directory contains
5824 5845 uncommitted changes:
5825 5846
5826 5847 1. If neither -c/--check nor -C/--clean is specified, and if
5827 5848 the requested changeset is an ancestor or descendant of
5828 5849 the working directory's parent, the uncommitted changes
5829 5850 are merged into the requested changeset and the merged
5830 5851 result is left uncommitted. If the requested changeset is
5831 5852 not an ancestor or descendant (that is, it is on another
5832 5853 branch), the update is aborted and the uncommitted changes
5833 5854 are preserved.
5834 5855
5835 5856 2. With the -c/--check option, the update is aborted and the
5836 5857 uncommitted changes are preserved.
5837 5858
5838 5859 3. With the -C/--clean option, uncommitted changes are discarded and
5839 5860 the working directory is updated to the requested changeset.
5840 5861
5841 5862 To cancel an uncommitted merge (and lose your changes), use
5842 5863 :hg:`update --clean .`.
5843 5864
5844 5865 Use null as the changeset to remove the working directory (like
5845 5866 :hg:`clone -U`).
5846 5867
5847 5868 If you want to revert just one file to an older revision, use
5848 5869 :hg:`revert [-r REV] NAME`.
5849 5870
5850 5871 See :hg:`help dates` for a list of formats valid for -d/--date.
5851 5872
5852 5873 Returns 0 on success, 1 if there are unresolved files.
5853 5874 """
5854 5875 if rev and node:
5855 5876 raise util.Abort(_("please specify just one revision"))
5856 5877
5857 5878 if rev is None or rev == '':
5858 5879 rev = node
5859 5880
5860 5881 cmdutil.clearunfinished(repo)
5861 5882
5862 5883 # with no argument, we also move the current bookmark, if any
5863 5884 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
5864 5885
5865 5886 # if we defined a bookmark, we have to remember the original bookmark name
5866 5887 brev = rev
5867 5888 rev = scmutil.revsingle(repo, rev, rev).rev()
5868 5889
5869 5890 if check and clean:
5870 5891 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5871 5892
5872 5893 if date:
5873 5894 if rev is not None:
5874 5895 raise util.Abort(_("you can't specify a revision and a date"))
5875 5896 rev = cmdutil.finddate(ui, repo, date)
5876 5897
5877 5898 if check:
5878 5899 c = repo[None]
5879 5900 if c.dirty(merge=False, branch=False, missing=True):
5880 5901 raise util.Abort(_("uncommitted changes"))
5881 5902 if rev is None:
5882 5903 rev = repo[repo[None].branch()].rev()
5883 5904 mergemod._checkunknown(repo, repo[None], repo[rev])
5884 5905
5885 5906 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5886 5907
5887 5908 if clean:
5888 5909 ret = hg.clean(repo, rev)
5889 5910 else:
5890 5911 ret = hg.update(repo, rev)
5891 5912
5892 5913 if not ret and movemarkfrom:
5893 5914 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5894 5915 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5895 5916 elif brev in repo._bookmarks:
5896 5917 bookmarks.setcurrent(repo, brev)
5897 5918 ui.status(_("(activating bookmark %s)\n") % brev)
5898 5919 elif brev:
5899 5920 if repo._bookmarkcurrent:
5900 5921 ui.status(_("(leaving bookmark %s)\n") %
5901 5922 repo._bookmarkcurrent)
5902 5923 bookmarks.unsetcurrent(repo)
5903 5924
5904 5925 return ret
5905 5926
5906 5927 @command('verify', [])
5907 5928 def verify(ui, repo):
5908 5929 """verify the integrity of the repository
5909 5930
5910 5931 Verify the integrity of the current repository.
5911 5932
5912 5933 This will perform an extensive check of the repository's
5913 5934 integrity, validating the hashes and checksums of each entry in
5914 5935 the changelog, manifest, and tracked files, as well as the
5915 5936 integrity of their crosslinks and indices.
5916 5937
5917 5938 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
5918 5939 for more information about recovery from corruption of the
5919 5940 repository.
5920 5941
5921 5942 Returns 0 on success, 1 if errors are encountered.
5922 5943 """
5923 5944 return hg.verify(repo)
5924 5945
5925 5946 @command('version', [])
5926 5947 def version_(ui):
5927 5948 """output version and copyright information"""
5928 5949 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5929 5950 % util.version())
5930 5951 ui.status(_(
5931 5952 "(see http://mercurial.selenic.com for more information)\n"
5932 5953 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
5933 5954 "This is free software; see the source for copying conditions. "
5934 5955 "There is NO\nwarranty; "
5935 5956 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5936 5957 ))
5937 5958
5938 5959 norepo = ("clone init version help debugcommands debugcomplete"
5939 5960 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5940 5961 " debugknown debuggetbundle debugbundle")
5941 5962 optionalrepo = ("identify paths serve config showconfig debugancestor debugdag"
5942 5963 " debugdata debugindex debugindexdot debugrevlog")
5943 5964 inferrepo = ("add addremove annotate cat commit diff grep forget log parents"
5944 5965 " remove resolve status debugwalk")
@@ -1,1931 +1,1928 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email, os, errno, re, posixpath
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
13 13 import email.Generator
14 14 import email.Parser
15 15
16 16 from i18n import _
17 17 from node import hex, short
18 18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 19
20 20 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 21
22 22 class PatchError(Exception):
23 23 pass
24 24
25 25
26 26 # public functions
27 27
28 28 def split(stream):
29 29 '''return an iterator of individual patches from a stream'''
30 30 def isheader(line, inheader):
31 31 if inheader and line[0] in (' ', '\t'):
32 32 # continuation
33 33 return True
34 34 if line[0] in (' ', '-', '+'):
35 35 # diff line - don't check for header pattern in there
36 36 return False
37 37 l = line.split(': ', 1)
38 38 return len(l) == 2 and ' ' not in l[0]
39 39
40 40 def chunk(lines):
41 41 return cStringIO.StringIO(''.join(lines))
42 42
43 43 def hgsplit(stream, cur):
44 44 inheader = True
45 45
46 46 for line in stream:
47 47 if not line.strip():
48 48 inheader = False
49 49 if not inheader and line.startswith('# HG changeset patch'):
50 50 yield chunk(cur)
51 51 cur = []
52 52 inheader = True
53 53
54 54 cur.append(line)
55 55
56 56 if cur:
57 57 yield chunk(cur)
58 58
59 59 def mboxsplit(stream, cur):
60 60 for line in stream:
61 61 if line.startswith('From '):
62 62 for c in split(chunk(cur[1:])):
63 63 yield c
64 64 cur = []
65 65
66 66 cur.append(line)
67 67
68 68 if cur:
69 69 for c in split(chunk(cur[1:])):
70 70 yield c
71 71
72 72 def mimesplit(stream, cur):
73 73 def msgfp(m):
74 74 fp = cStringIO.StringIO()
75 75 g = email.Generator.Generator(fp, mangle_from_=False)
76 76 g.flatten(m)
77 77 fp.seek(0)
78 78 return fp
79 79
80 80 for line in stream:
81 81 cur.append(line)
82 82 c = chunk(cur)
83 83
84 84 m = email.Parser.Parser().parse(c)
85 85 if not m.is_multipart():
86 86 yield msgfp(m)
87 87 else:
88 88 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
89 89 for part in m.walk():
90 90 ct = part.get_content_type()
91 91 if ct not in ok_types:
92 92 continue
93 93 yield msgfp(part)
94 94
95 95 def headersplit(stream, cur):
96 96 inheader = False
97 97
98 98 for line in stream:
99 99 if not inheader and isheader(line, inheader):
100 100 yield chunk(cur)
101 101 cur = []
102 102 inheader = True
103 103 if inheader and not isheader(line, inheader):
104 104 inheader = False
105 105
106 106 cur.append(line)
107 107
108 108 if cur:
109 109 yield chunk(cur)
110 110
111 111 def remainder(cur):
112 112 yield chunk(cur)
113 113
114 114 class fiter(object):
115 115 def __init__(self, fp):
116 116 self.fp = fp
117 117
118 118 def __iter__(self):
119 119 return self
120 120
121 121 def next(self):
122 122 l = self.fp.readline()
123 123 if not l:
124 124 raise StopIteration
125 125 return l
126 126
127 127 inheader = False
128 128 cur = []
129 129
130 130 mimeheaders = ['content-type']
131 131
132 132 if not util.safehasattr(stream, 'next'):
133 133 # http responses, for example, have readline but not next
134 134 stream = fiter(stream)
135 135
136 136 for line in stream:
137 137 cur.append(line)
138 138 if line.startswith('# HG changeset patch'):
139 139 return hgsplit(stream, cur)
140 140 elif line.startswith('From '):
141 141 return mboxsplit(stream, cur)
142 142 elif isheader(line, inheader):
143 143 inheader = True
144 144 if line.split(':', 1)[0].lower() in mimeheaders:
145 145 # let email parser handle this
146 146 return mimesplit(stream, cur)
147 147 elif line.startswith('--- ') and inheader:
148 148 # No evil headers seen by diff start, split by hand
149 149 return headersplit(stream, cur)
150 150 # Not enough info, keep reading
151 151
152 152 # if we are here, we have a very plain patch
153 153 return remainder(cur)
154 154
155 155 def extract(ui, fileobj):
156 156 '''extract patch from data read from fileobj.
157 157
158 158 patch can be a normal patch or contained in an email message.
159 159
160 160 return tuple (filename, message, user, date, branch, node, p1, p2).
161 161 Any item in the returned tuple can be None. If filename is None,
162 162 fileobj did not contain a patch. Caller must unlink filename when done.'''
163 163
164 164 # attempt to detect the start of a patch
165 165 # (this heuristic is borrowed from quilt)
166 166 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
167 167 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
168 168 r'---[ \t].*?^\+\+\+[ \t]|'
169 169 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
170 170
171 171 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
172 172 tmpfp = os.fdopen(fd, 'w')
173 173 try:
174 174 msg = email.Parser.Parser().parse(fileobj)
175 175
176 176 subject = msg['Subject']
177 177 user = msg['From']
178 178 if not subject and not user:
179 179 # Not an email, restore parsed headers if any
180 180 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
181 181
182 182 # should try to parse msg['Date']
183 183 date = None
184 184 nodeid = None
185 185 branch = None
186 186 parents = []
187 187
188 188 if subject:
189 189 if subject.startswith('[PATCH'):
190 190 pend = subject.find(']')
191 191 if pend >= 0:
192 192 subject = subject[pend + 1:].lstrip()
193 193 subject = re.sub(r'\n[ \t]+', ' ', subject)
194 194 ui.debug('Subject: %s\n' % subject)
195 195 if user:
196 196 ui.debug('From: %s\n' % user)
197 197 diffs_seen = 0
198 198 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
199 199 message = ''
200 200 for part in msg.walk():
201 201 content_type = part.get_content_type()
202 202 ui.debug('Content-Type: %s\n' % content_type)
203 203 if content_type not in ok_types:
204 204 continue
205 205 payload = part.get_payload(decode=True)
206 206 m = diffre.search(payload)
207 207 if m:
208 208 hgpatch = False
209 209 hgpatchheader = False
210 210 ignoretext = False
211 211
212 212 ui.debug('found patch at byte %d\n' % m.start(0))
213 213 diffs_seen += 1
214 214 cfp = cStringIO.StringIO()
215 215 for line in payload[:m.start(0)].splitlines():
216 216 if line.startswith('# HG changeset patch') and not hgpatch:
217 217 ui.debug('patch generated by hg export\n')
218 218 hgpatch = True
219 219 hgpatchheader = True
220 220 # drop earlier commit message content
221 221 cfp.seek(0)
222 222 cfp.truncate()
223 223 subject = None
224 224 elif hgpatchheader:
225 225 if line.startswith('# User '):
226 226 user = line[7:]
227 227 ui.debug('From: %s\n' % user)
228 228 elif line.startswith("# Date "):
229 229 date = line[7:]
230 230 elif line.startswith("# Branch "):
231 231 branch = line[9:]
232 232 elif line.startswith("# Node ID "):
233 233 nodeid = line[10:]
234 234 elif line.startswith("# Parent "):
235 235 parents.append(line[9:].lstrip())
236 236 elif not line.startswith("# "):
237 237 hgpatchheader = False
238 238 elif line == '---':
239 239 ignoretext = True
240 240 if not hgpatchheader and not ignoretext:
241 241 cfp.write(line)
242 242 cfp.write('\n')
243 243 message = cfp.getvalue()
244 244 if tmpfp:
245 245 tmpfp.write(payload)
246 246 if not payload.endswith('\n'):
247 247 tmpfp.write('\n')
248 248 elif not diffs_seen and message and content_type == 'text/plain':
249 249 message += '\n' + payload
250 250 except: # re-raises
251 251 tmpfp.close()
252 252 os.unlink(tmpname)
253 253 raise
254 254
255 255 if subject and not message.startswith(subject):
256 256 message = '%s\n%s' % (subject, message)
257 257 tmpfp.close()
258 258 if not diffs_seen:
259 259 os.unlink(tmpname)
260 260 return None, message, user, date, branch, None, None, None
261 261 p1 = parents and parents.pop(0) or None
262 262 p2 = parents and parents.pop(0) or None
263 263 return tmpname, message, user, date, branch, nodeid, p1, p2
264 264
265 265 class patchmeta(object):
266 266 """Patched file metadata
267 267
268 268 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
269 269 or COPY. 'path' is patched file path. 'oldpath' is set to the
270 270 origin file when 'op' is either COPY or RENAME, None otherwise. If
271 271 file mode is changed, 'mode' is a tuple (islink, isexec) where
272 272 'islink' is True if the file is a symlink and 'isexec' is True if
273 273 the file is executable. Otherwise, 'mode' is None.
274 274 """
275 275 def __init__(self, path):
276 276 self.path = path
277 277 self.oldpath = None
278 278 self.mode = None
279 279 self.op = 'MODIFY'
280 280 self.binary = False
281 281
282 282 def setmode(self, mode):
283 283 islink = mode & 020000
284 284 isexec = mode & 0100
285 285 self.mode = (islink, isexec)
286 286
287 287 def copy(self):
288 288 other = patchmeta(self.path)
289 289 other.oldpath = self.oldpath
290 290 other.mode = self.mode
291 291 other.op = self.op
292 292 other.binary = self.binary
293 293 return other
294 294
295 295 def _ispatchinga(self, afile):
296 296 if afile == '/dev/null':
297 297 return self.op == 'ADD'
298 298 return afile == 'a/' + (self.oldpath or self.path)
299 299
300 300 def _ispatchingb(self, bfile):
301 301 if bfile == '/dev/null':
302 302 return self.op == 'DELETE'
303 303 return bfile == 'b/' + self.path
304 304
305 305 def ispatching(self, afile, bfile):
306 306 return self._ispatchinga(afile) and self._ispatchingb(bfile)
307 307
308 308 def __repr__(self):
309 309 return "<patchmeta %s %r>" % (self.op, self.path)
310 310
311 311 def readgitpatch(lr):
312 312 """extract git-style metadata about patches from <patchname>"""
313 313
314 314 # Filter patch for git information
315 315 gp = None
316 316 gitpatches = []
317 317 for line in lr:
318 318 line = line.rstrip(' \r\n')
319 319 if line.startswith('diff --git a/'):
320 320 m = gitre.match(line)
321 321 if m:
322 322 if gp:
323 323 gitpatches.append(gp)
324 324 dst = m.group(2)
325 325 gp = patchmeta(dst)
326 326 elif gp:
327 327 if line.startswith('--- '):
328 328 gitpatches.append(gp)
329 329 gp = None
330 330 continue
331 331 if line.startswith('rename from '):
332 332 gp.op = 'RENAME'
333 333 gp.oldpath = line[12:]
334 334 elif line.startswith('rename to '):
335 335 gp.path = line[10:]
336 336 elif line.startswith('copy from '):
337 337 gp.op = 'COPY'
338 338 gp.oldpath = line[10:]
339 339 elif line.startswith('copy to '):
340 340 gp.path = line[8:]
341 341 elif line.startswith('deleted file'):
342 342 gp.op = 'DELETE'
343 343 elif line.startswith('new file mode '):
344 344 gp.op = 'ADD'
345 345 gp.setmode(int(line[-6:], 8))
346 346 elif line.startswith('new mode '):
347 347 gp.setmode(int(line[-6:], 8))
348 348 elif line.startswith('GIT binary patch'):
349 349 gp.binary = True
350 350 if gp:
351 351 gitpatches.append(gp)
352 352
353 353 return gitpatches
354 354
355 355 class linereader(object):
356 356 # simple class to allow pushing lines back into the input stream
357 357 def __init__(self, fp):
358 358 self.fp = fp
359 359 self.buf = []
360 360
361 361 def push(self, line):
362 362 if line is not None:
363 363 self.buf.append(line)
364 364
365 365 def readline(self):
366 366 if self.buf:
367 367 l = self.buf[0]
368 368 del self.buf[0]
369 369 return l
370 370 return self.fp.readline()
371 371
372 372 def __iter__(self):
373 373 while True:
374 374 l = self.readline()
375 375 if not l:
376 376 break
377 377 yield l
378 378
379 379 class abstractbackend(object):
380 380 def __init__(self, ui):
381 381 self.ui = ui
382 382
383 383 def getfile(self, fname):
384 384 """Return target file data and flags as a (data, (islink,
385 385 isexec)) tuple.
386 386 """
387 387 raise NotImplementedError
388 388
389 389 def setfile(self, fname, data, mode, copysource):
390 390 """Write data to target file fname and set its mode. mode is a
391 391 (islink, isexec) tuple. If data is None, the file content should
392 392 be left unchanged. If the file is modified after being copied,
393 393 copysource is set to the original file name.
394 394 """
395 395 raise NotImplementedError
396 396
397 397 def unlink(self, fname):
398 398 """Unlink target file."""
399 399 raise NotImplementedError
400 400
401 401 def writerej(self, fname, failed, total, lines):
402 402 """Write rejected lines for fname. total is the number of hunks
403 403 which failed to apply and total the total number of hunks for this
404 404 files.
405 405 """
406 406 pass
407 407
408 408 def exists(self, fname):
409 409 raise NotImplementedError
410 410
411 411 class fsbackend(abstractbackend):
412 412 def __init__(self, ui, basedir):
413 413 super(fsbackend, self).__init__(ui)
414 414 self.opener = scmutil.opener(basedir)
415 415
416 416 def _join(self, f):
417 417 return os.path.join(self.opener.base, f)
418 418
419 419 def getfile(self, fname):
420 420 path = self._join(fname)
421 421 if os.path.islink(path):
422 422 return (os.readlink(path), (True, False))
423 423 isexec = False
424 424 try:
425 425 isexec = os.lstat(path).st_mode & 0100 != 0
426 426 except OSError, e:
427 427 if e.errno != errno.ENOENT:
428 428 raise
429 429 return (self.opener.read(fname), (False, isexec))
430 430
431 431 def setfile(self, fname, data, mode, copysource):
432 432 islink, isexec = mode
433 433 if data is None:
434 434 util.setflags(self._join(fname), islink, isexec)
435 435 return
436 436 if islink:
437 437 self.opener.symlink(data, fname)
438 438 else:
439 439 self.opener.write(fname, data)
440 440 if isexec:
441 441 util.setflags(self._join(fname), False, True)
442 442
443 443 def unlink(self, fname):
444 444 util.unlinkpath(self._join(fname), ignoremissing=True)
445 445
446 446 def writerej(self, fname, failed, total, lines):
447 447 fname = fname + ".rej"
448 448 self.ui.warn(
449 449 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
450 450 (failed, total, fname))
451 451 fp = self.opener(fname, 'w')
452 452 fp.writelines(lines)
453 453 fp.close()
454 454
455 455 def exists(self, fname):
456 456 return os.path.lexists(self._join(fname))
457 457
458 458 class workingbackend(fsbackend):
459 459 def __init__(self, ui, repo, similarity):
460 460 super(workingbackend, self).__init__(ui, repo.root)
461 461 self.repo = repo
462 462 self.similarity = similarity
463 463 self.removed = set()
464 464 self.changed = set()
465 465 self.copied = []
466 466
467 467 def _checkknown(self, fname):
468 468 if self.repo.dirstate[fname] == '?' and self.exists(fname):
469 469 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
470 470
471 471 def setfile(self, fname, data, mode, copysource):
472 472 self._checkknown(fname)
473 473 super(workingbackend, self).setfile(fname, data, mode, copysource)
474 474 if copysource is not None:
475 475 self.copied.append((copysource, fname))
476 476 self.changed.add(fname)
477 477
478 478 def unlink(self, fname):
479 479 self._checkknown(fname)
480 480 super(workingbackend, self).unlink(fname)
481 481 self.removed.add(fname)
482 482 self.changed.add(fname)
483 483
484 484 def close(self):
485 485 wctx = self.repo[None]
486 486 changed = set(self.changed)
487 487 for src, dst in self.copied:
488 488 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
489 489 if self.removed:
490 490 wctx.forget(sorted(self.removed))
491 491 for f in self.removed:
492 492 if f not in self.repo.dirstate:
493 493 # File was deleted and no longer belongs to the
494 494 # dirstate, it was probably marked added then
495 495 # deleted, and should not be considered by
496 496 # marktouched().
497 497 changed.discard(f)
498 498 if changed:
499 499 scmutil.marktouched(self.repo, changed, self.similarity)
500 500 return sorted(self.changed)
501 501
502 502 class filestore(object):
503 503 def __init__(self, maxsize=None):
504 504 self.opener = None
505 505 self.files = {}
506 506 self.created = 0
507 507 self.maxsize = maxsize
508 508 if self.maxsize is None:
509 509 self.maxsize = 4*(2**20)
510 510 self.size = 0
511 511 self.data = {}
512 512
513 513 def setfile(self, fname, data, mode, copied=None):
514 514 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
515 515 self.data[fname] = (data, mode, copied)
516 516 self.size += len(data)
517 517 else:
518 518 if self.opener is None:
519 519 root = tempfile.mkdtemp(prefix='hg-patch-')
520 520 self.opener = scmutil.opener(root)
521 521 # Avoid filename issues with these simple names
522 522 fn = str(self.created)
523 523 self.opener.write(fn, data)
524 524 self.created += 1
525 525 self.files[fname] = (fn, mode, copied)
526 526
527 527 def getfile(self, fname):
528 528 if fname in self.data:
529 529 return self.data[fname]
530 530 if not self.opener or fname not in self.files:
531 531 raise IOError
532 532 fn, mode, copied = self.files[fname]
533 533 return self.opener.read(fn), mode, copied
534 534
535 535 def close(self):
536 536 if self.opener:
537 537 shutil.rmtree(self.opener.base)
538 538
539 539 class repobackend(abstractbackend):
540 540 def __init__(self, ui, repo, ctx, store):
541 541 super(repobackend, self).__init__(ui)
542 542 self.repo = repo
543 543 self.ctx = ctx
544 544 self.store = store
545 545 self.changed = set()
546 546 self.removed = set()
547 547 self.copied = {}
548 548
549 549 def _checkknown(self, fname):
550 550 if fname not in self.ctx:
551 551 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
552 552
553 553 def getfile(self, fname):
554 554 try:
555 555 fctx = self.ctx[fname]
556 556 except error.LookupError:
557 557 raise IOError
558 558 flags = fctx.flags()
559 559 return fctx.data(), ('l' in flags, 'x' in flags)
560 560
561 561 def setfile(self, fname, data, mode, copysource):
562 562 if copysource:
563 563 self._checkknown(copysource)
564 564 if data is None:
565 565 data = self.ctx[fname].data()
566 566 self.store.setfile(fname, data, mode, copysource)
567 567 self.changed.add(fname)
568 568 if copysource:
569 569 self.copied[fname] = copysource
570 570
571 571 def unlink(self, fname):
572 572 self._checkknown(fname)
573 573 self.removed.add(fname)
574 574
575 575 def exists(self, fname):
576 576 return fname in self.ctx
577 577
578 578 def close(self):
579 579 return self.changed | self.removed
580 580
581 581 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
582 582 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
583 583 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
584 584 eolmodes = ['strict', 'crlf', 'lf', 'auto']
585 585
586 586 class patchfile(object):
587 587 def __init__(self, ui, gp, backend, store, eolmode='strict'):
588 588 self.fname = gp.path
589 589 self.eolmode = eolmode
590 590 self.eol = None
591 591 self.backend = backend
592 592 self.ui = ui
593 593 self.lines = []
594 594 self.exists = False
595 595 self.missing = True
596 596 self.mode = gp.mode
597 597 self.copysource = gp.oldpath
598 598 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
599 599 self.remove = gp.op == 'DELETE'
600 600 try:
601 601 if self.copysource is None:
602 602 data, mode = backend.getfile(self.fname)
603 603 self.exists = True
604 604 else:
605 605 data, mode = store.getfile(self.copysource)[:2]
606 606 self.exists = backend.exists(self.fname)
607 607 self.missing = False
608 608 if data:
609 609 self.lines = mdiff.splitnewlines(data)
610 610 if self.mode is None:
611 611 self.mode = mode
612 612 if self.lines:
613 613 # Normalize line endings
614 614 if self.lines[0].endswith('\r\n'):
615 615 self.eol = '\r\n'
616 616 elif self.lines[0].endswith('\n'):
617 617 self.eol = '\n'
618 618 if eolmode != 'strict':
619 619 nlines = []
620 620 for l in self.lines:
621 621 if l.endswith('\r\n'):
622 622 l = l[:-2] + '\n'
623 623 nlines.append(l)
624 624 self.lines = nlines
625 625 except IOError:
626 626 if self.create:
627 627 self.missing = False
628 628 if self.mode is None:
629 629 self.mode = (False, False)
630 630 if self.missing:
631 631 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
632 632
633 633 self.hash = {}
634 634 self.dirty = 0
635 635 self.offset = 0
636 636 self.skew = 0
637 637 self.rej = []
638 638 self.fileprinted = False
639 639 self.printfile(False)
640 640 self.hunks = 0
641 641
642 642 def writelines(self, fname, lines, mode):
643 643 if self.eolmode == 'auto':
644 644 eol = self.eol
645 645 elif self.eolmode == 'crlf':
646 646 eol = '\r\n'
647 647 else:
648 648 eol = '\n'
649 649
650 650 if self.eolmode != 'strict' and eol and eol != '\n':
651 651 rawlines = []
652 652 for l in lines:
653 653 if l and l[-1] == '\n':
654 654 l = l[:-1] + eol
655 655 rawlines.append(l)
656 656 lines = rawlines
657 657
658 658 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
659 659
660 660 def printfile(self, warn):
661 661 if self.fileprinted:
662 662 return
663 663 if warn or self.ui.verbose:
664 664 self.fileprinted = True
665 665 s = _("patching file %s\n") % self.fname
666 666 if warn:
667 667 self.ui.warn(s)
668 668 else:
669 669 self.ui.note(s)
670 670
671 671
672 672 def findlines(self, l, linenum):
673 673 # looks through the hash and finds candidate lines. The
674 674 # result is a list of line numbers sorted based on distance
675 675 # from linenum
676 676
677 677 cand = self.hash.get(l, [])
678 678 if len(cand) > 1:
679 679 # resort our list of potentials forward then back.
680 680 cand.sort(key=lambda x: abs(x - linenum))
681 681 return cand
682 682
683 683 def write_rej(self):
684 684 # our rejects are a little different from patch(1). This always
685 685 # creates rejects in the same form as the original patch. A file
686 686 # header is inserted so that you can run the reject through patch again
687 687 # without having to type the filename.
688 688 if not self.rej:
689 689 return
690 690 base = os.path.basename(self.fname)
691 691 lines = ["--- %s\n+++ %s\n" % (base, base)]
692 692 for x in self.rej:
693 693 for l in x.hunk:
694 694 lines.append(l)
695 695 if l[-1] != '\n':
696 696 lines.append("\n\ No newline at end of file\n")
697 697 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
698 698
699 699 def apply(self, h):
700 700 if not h.complete():
701 701 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
702 702 (h.number, h.desc, len(h.a), h.lena, len(h.b),
703 703 h.lenb))
704 704
705 705 self.hunks += 1
706 706
707 707 if self.missing:
708 708 self.rej.append(h)
709 709 return -1
710 710
711 711 if self.exists and self.create:
712 712 if self.copysource:
713 713 self.ui.warn(_("cannot create %s: destination already "
714 714 "exists\n") % self.fname)
715 715 else:
716 716 self.ui.warn(_("file %s already exists\n") % self.fname)
717 717 self.rej.append(h)
718 718 return -1
719 719
720 720 if isinstance(h, binhunk):
721 721 if self.remove:
722 722 self.backend.unlink(self.fname)
723 723 else:
724 724 l = h.new(self.lines)
725 725 self.lines[:] = l
726 726 self.offset += len(l)
727 727 self.dirty = True
728 728 return 0
729 729
730 730 horig = h
731 731 if (self.eolmode in ('crlf', 'lf')
732 732 or self.eolmode == 'auto' and self.eol):
733 733 # If new eols are going to be normalized, then normalize
734 734 # hunk data before patching. Otherwise, preserve input
735 735 # line-endings.
736 736 h = h.getnormalized()
737 737
738 738 # fast case first, no offsets, no fuzz
739 739 old, oldstart, new, newstart = h.fuzzit(0, False)
740 740 oldstart += self.offset
741 741 orig_start = oldstart
742 742 # if there's skew we want to emit the "(offset %d lines)" even
743 743 # when the hunk cleanly applies at start + skew, so skip the
744 744 # fast case code
745 745 if (self.skew == 0 and
746 746 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
747 747 if self.remove:
748 748 self.backend.unlink(self.fname)
749 749 else:
750 750 self.lines[oldstart:oldstart + len(old)] = new
751 751 self.offset += len(new) - len(old)
752 752 self.dirty = True
753 753 return 0
754 754
755 755 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
756 756 self.hash = {}
757 757 for x, s in enumerate(self.lines):
758 758 self.hash.setdefault(s, []).append(x)
759 759
760 760 for fuzzlen in xrange(3):
761 761 for toponly in [True, False]:
762 762 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
763 763 oldstart = oldstart + self.offset + self.skew
764 764 oldstart = min(oldstart, len(self.lines))
765 765 if old:
766 766 cand = self.findlines(old[0][1:], oldstart)
767 767 else:
768 768 # Only adding lines with no or fuzzed context, just
769 769 # take the skew in account
770 770 cand = [oldstart]
771 771
772 772 for l in cand:
773 773 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
774 774 self.lines[l : l + len(old)] = new
775 775 self.offset += len(new) - len(old)
776 776 self.skew = l - orig_start
777 777 self.dirty = True
778 778 offset = l - orig_start - fuzzlen
779 779 if fuzzlen:
780 780 msg = _("Hunk #%d succeeded at %d "
781 781 "with fuzz %d "
782 782 "(offset %d lines).\n")
783 783 self.printfile(True)
784 784 self.ui.warn(msg %
785 785 (h.number, l + 1, fuzzlen, offset))
786 786 else:
787 787 msg = _("Hunk #%d succeeded at %d "
788 788 "(offset %d lines).\n")
789 789 self.ui.note(msg % (h.number, l + 1, offset))
790 790 return fuzzlen
791 791 self.printfile(True)
792 792 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
793 793 self.rej.append(horig)
794 794 return -1
795 795
796 796 def close(self):
797 797 if self.dirty:
798 798 self.writelines(self.fname, self.lines, self.mode)
799 799 self.write_rej()
800 800 return len(self.rej)
801 801
802 802 class hunk(object):
803 803 def __init__(self, desc, num, lr, context):
804 804 self.number = num
805 805 self.desc = desc
806 806 self.hunk = [desc]
807 807 self.a = []
808 808 self.b = []
809 809 self.starta = self.lena = None
810 810 self.startb = self.lenb = None
811 811 if lr is not None:
812 812 if context:
813 813 self.read_context_hunk(lr)
814 814 else:
815 815 self.read_unified_hunk(lr)
816 816
817 817 def getnormalized(self):
818 818 """Return a copy with line endings normalized to LF."""
819 819
820 820 def normalize(lines):
821 821 nlines = []
822 822 for line in lines:
823 823 if line.endswith('\r\n'):
824 824 line = line[:-2] + '\n'
825 825 nlines.append(line)
826 826 return nlines
827 827
828 828 # Dummy object, it is rebuilt manually
829 829 nh = hunk(self.desc, self.number, None, None)
830 830 nh.number = self.number
831 831 nh.desc = self.desc
832 832 nh.hunk = self.hunk
833 833 nh.a = normalize(self.a)
834 834 nh.b = normalize(self.b)
835 835 nh.starta = self.starta
836 836 nh.startb = self.startb
837 837 nh.lena = self.lena
838 838 nh.lenb = self.lenb
839 839 return nh
840 840
841 841 def read_unified_hunk(self, lr):
842 842 m = unidesc.match(self.desc)
843 843 if not m:
844 844 raise PatchError(_("bad hunk #%d") % self.number)
845 845 self.starta, self.lena, self.startb, self.lenb = m.groups()
846 846 if self.lena is None:
847 847 self.lena = 1
848 848 else:
849 849 self.lena = int(self.lena)
850 850 if self.lenb is None:
851 851 self.lenb = 1
852 852 else:
853 853 self.lenb = int(self.lenb)
854 854 self.starta = int(self.starta)
855 855 self.startb = int(self.startb)
856 856 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
857 857 self.b)
858 858 # if we hit eof before finishing out the hunk, the last line will
859 859 # be zero length. Lets try to fix it up.
860 860 while len(self.hunk[-1]) == 0:
861 861 del self.hunk[-1]
862 862 del self.a[-1]
863 863 del self.b[-1]
864 864 self.lena -= 1
865 865 self.lenb -= 1
866 866 self._fixnewline(lr)
867 867
868 868 def read_context_hunk(self, lr):
869 869 self.desc = lr.readline()
870 870 m = contextdesc.match(self.desc)
871 871 if not m:
872 872 raise PatchError(_("bad hunk #%d") % self.number)
873 873 self.starta, aend = m.groups()
874 874 self.starta = int(self.starta)
875 875 if aend is None:
876 876 aend = self.starta
877 877 self.lena = int(aend) - self.starta
878 878 if self.starta:
879 879 self.lena += 1
880 880 for x in xrange(self.lena):
881 881 l = lr.readline()
882 882 if l.startswith('---'):
883 883 # lines addition, old block is empty
884 884 lr.push(l)
885 885 break
886 886 s = l[2:]
887 887 if l.startswith('- ') or l.startswith('! '):
888 888 u = '-' + s
889 889 elif l.startswith(' '):
890 890 u = ' ' + s
891 891 else:
892 892 raise PatchError(_("bad hunk #%d old text line %d") %
893 893 (self.number, x))
894 894 self.a.append(u)
895 895 self.hunk.append(u)
896 896
897 897 l = lr.readline()
898 898 if l.startswith('\ '):
899 899 s = self.a[-1][:-1]
900 900 self.a[-1] = s
901 901 self.hunk[-1] = s
902 902 l = lr.readline()
903 903 m = contextdesc.match(l)
904 904 if not m:
905 905 raise PatchError(_("bad hunk #%d") % self.number)
906 906 self.startb, bend = m.groups()
907 907 self.startb = int(self.startb)
908 908 if bend is None:
909 909 bend = self.startb
910 910 self.lenb = int(bend) - self.startb
911 911 if self.startb:
912 912 self.lenb += 1
913 913 hunki = 1
914 914 for x in xrange(self.lenb):
915 915 l = lr.readline()
916 916 if l.startswith('\ '):
917 917 # XXX: the only way to hit this is with an invalid line range.
918 918 # The no-eol marker is not counted in the line range, but I
919 919 # guess there are diff(1) out there which behave differently.
920 920 s = self.b[-1][:-1]
921 921 self.b[-1] = s
922 922 self.hunk[hunki - 1] = s
923 923 continue
924 924 if not l:
925 925 # line deletions, new block is empty and we hit EOF
926 926 lr.push(l)
927 927 break
928 928 s = l[2:]
929 929 if l.startswith('+ ') or l.startswith('! '):
930 930 u = '+' + s
931 931 elif l.startswith(' '):
932 932 u = ' ' + s
933 933 elif len(self.b) == 0:
934 934 # line deletions, new block is empty
935 935 lr.push(l)
936 936 break
937 937 else:
938 938 raise PatchError(_("bad hunk #%d old text line %d") %
939 939 (self.number, x))
940 940 self.b.append(s)
941 941 while True:
942 942 if hunki >= len(self.hunk):
943 943 h = ""
944 944 else:
945 945 h = self.hunk[hunki]
946 946 hunki += 1
947 947 if h == u:
948 948 break
949 949 elif h.startswith('-'):
950 950 continue
951 951 else:
952 952 self.hunk.insert(hunki - 1, u)
953 953 break
954 954
955 955 if not self.a:
956 956 # this happens when lines were only added to the hunk
957 957 for x in self.hunk:
958 958 if x.startswith('-') or x.startswith(' '):
959 959 self.a.append(x)
960 960 if not self.b:
961 961 # this happens when lines were only deleted from the hunk
962 962 for x in self.hunk:
963 963 if x.startswith('+') or x.startswith(' '):
964 964 self.b.append(x[1:])
965 965 # @@ -start,len +start,len @@
966 966 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
967 967 self.startb, self.lenb)
968 968 self.hunk[0] = self.desc
969 969 self._fixnewline(lr)
970 970
971 971 def _fixnewline(self, lr):
972 972 l = lr.readline()
973 973 if l.startswith('\ '):
974 974 diffhelpers.fix_newline(self.hunk, self.a, self.b)
975 975 else:
976 976 lr.push(l)
977 977
978 978 def complete(self):
979 979 return len(self.a) == self.lena and len(self.b) == self.lenb
980 980
981 981 def _fuzzit(self, old, new, fuzz, toponly):
982 982 # this removes context lines from the top and bottom of list 'l'. It
983 983 # checks the hunk to make sure only context lines are removed, and then
984 984 # returns a new shortened list of lines.
985 985 fuzz = min(fuzz, len(old))
986 986 if fuzz:
987 987 top = 0
988 988 bot = 0
989 989 hlen = len(self.hunk)
990 990 for x in xrange(hlen - 1):
991 991 # the hunk starts with the @@ line, so use x+1
992 992 if self.hunk[x + 1][0] == ' ':
993 993 top += 1
994 994 else:
995 995 break
996 996 if not toponly:
997 997 for x in xrange(hlen - 1):
998 998 if self.hunk[hlen - bot - 1][0] == ' ':
999 999 bot += 1
1000 1000 else:
1001 1001 break
1002 1002
1003 1003 bot = min(fuzz, bot)
1004 1004 top = min(fuzz, top)
1005 1005 return old[top:len(old) - bot], new[top:len(new) - bot], top
1006 1006 return old, new, 0
1007 1007
1008 1008 def fuzzit(self, fuzz, toponly):
1009 1009 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1010 1010 oldstart = self.starta + top
1011 1011 newstart = self.startb + top
1012 1012 # zero length hunk ranges already have their start decremented
1013 1013 if self.lena and oldstart > 0:
1014 1014 oldstart -= 1
1015 1015 if self.lenb and newstart > 0:
1016 1016 newstart -= 1
1017 1017 return old, oldstart, new, newstart
1018 1018
1019 1019 class binhunk(object):
1020 1020 'A binary patch file.'
1021 1021 def __init__(self, lr, fname):
1022 1022 self.text = None
1023 1023 self.delta = False
1024 1024 self.hunk = ['GIT binary patch\n']
1025 1025 self._fname = fname
1026 1026 self._read(lr)
1027 1027
1028 1028 def complete(self):
1029 1029 return self.text is not None
1030 1030
1031 1031 def new(self, lines):
1032 1032 if self.delta:
1033 1033 return [applybindelta(self.text, ''.join(lines))]
1034 1034 return [self.text]
1035 1035
1036 1036 def _read(self, lr):
1037 1037 def getline(lr, hunk):
1038 1038 l = lr.readline()
1039 1039 hunk.append(l)
1040 1040 return l.rstrip('\r\n')
1041 1041
1042 1042 size = 0
1043 1043 while True:
1044 1044 line = getline(lr, self.hunk)
1045 1045 if not line:
1046 1046 raise PatchError(_('could not extract "%s" binary data')
1047 1047 % self._fname)
1048 1048 if line.startswith('literal '):
1049 1049 size = int(line[8:].rstrip())
1050 1050 break
1051 1051 if line.startswith('delta '):
1052 1052 size = int(line[6:].rstrip())
1053 1053 self.delta = True
1054 1054 break
1055 1055 dec = []
1056 1056 line = getline(lr, self.hunk)
1057 1057 while len(line) > 1:
1058 1058 l = line[0]
1059 1059 if l <= 'Z' and l >= 'A':
1060 1060 l = ord(l) - ord('A') + 1
1061 1061 else:
1062 1062 l = ord(l) - ord('a') + 27
1063 1063 try:
1064 1064 dec.append(base85.b85decode(line[1:])[:l])
1065 1065 except ValueError, e:
1066 1066 raise PatchError(_('could not decode "%s" binary patch: %s')
1067 1067 % (self._fname, str(e)))
1068 1068 line = getline(lr, self.hunk)
1069 1069 text = zlib.decompress(''.join(dec))
1070 1070 if len(text) != size:
1071 1071 raise PatchError(_('"%s" length is %d bytes, should be %d')
1072 1072 % (self._fname, len(text), size))
1073 1073 self.text = text
1074 1074
1075 1075 def parsefilename(str):
1076 1076 # --- filename \t|space stuff
1077 1077 s = str[4:].rstrip('\r\n')
1078 1078 i = s.find('\t')
1079 1079 if i < 0:
1080 1080 i = s.find(' ')
1081 1081 if i < 0:
1082 1082 return s
1083 1083 return s[:i]
1084 1084
1085 1085 def pathstrip(path, strip):
1086 1086 pathlen = len(path)
1087 1087 i = 0
1088 1088 if strip == 0:
1089 1089 return '', path.rstrip()
1090 1090 count = strip
1091 1091 while count > 0:
1092 1092 i = path.find('/', i)
1093 1093 if i == -1:
1094 1094 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1095 1095 (count, strip, path))
1096 1096 i += 1
1097 1097 # consume '//' in the path
1098 1098 while i < pathlen - 1 and path[i] == '/':
1099 1099 i += 1
1100 1100 count -= 1
1101 1101 return path[:i].lstrip(), path[i:].rstrip()
1102 1102
1103 1103 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1104 1104 nulla = afile_orig == "/dev/null"
1105 1105 nullb = bfile_orig == "/dev/null"
1106 1106 create = nulla and hunk.starta == 0 and hunk.lena == 0
1107 1107 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1108 1108 abase, afile = pathstrip(afile_orig, strip)
1109 1109 gooda = not nulla and backend.exists(afile)
1110 1110 bbase, bfile = pathstrip(bfile_orig, strip)
1111 1111 if afile == bfile:
1112 1112 goodb = gooda
1113 1113 else:
1114 1114 goodb = not nullb and backend.exists(bfile)
1115 1115 missing = not goodb and not gooda and not create
1116 1116
1117 1117 # some diff programs apparently produce patches where the afile is
1118 1118 # not /dev/null, but afile starts with bfile
1119 1119 abasedir = afile[:afile.rfind('/') + 1]
1120 1120 bbasedir = bfile[:bfile.rfind('/') + 1]
1121 1121 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1122 1122 and hunk.starta == 0 and hunk.lena == 0):
1123 1123 create = True
1124 1124 missing = False
1125 1125
1126 1126 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1127 1127 # diff is between a file and its backup. In this case, the original
1128 1128 # file should be patched (see original mpatch code).
1129 1129 isbackup = (abase == bbase and bfile.startswith(afile))
1130 1130 fname = None
1131 1131 if not missing:
1132 1132 if gooda and goodb:
1133 1133 fname = isbackup and afile or bfile
1134 1134 elif gooda:
1135 1135 fname = afile
1136 1136
1137 1137 if not fname:
1138 1138 if not nullb:
1139 1139 fname = isbackup and afile or bfile
1140 1140 elif not nulla:
1141 1141 fname = afile
1142 1142 else:
1143 1143 raise PatchError(_("undefined source and destination files"))
1144 1144
1145 1145 gp = patchmeta(fname)
1146 1146 if create:
1147 1147 gp.op = 'ADD'
1148 1148 elif remove:
1149 1149 gp.op = 'DELETE'
1150 1150 return gp
1151 1151
1152 1152 def scangitpatch(lr, firstline):
1153 1153 """
1154 1154 Git patches can emit:
1155 1155 - rename a to b
1156 1156 - change b
1157 1157 - copy a to c
1158 1158 - change c
1159 1159
1160 1160 We cannot apply this sequence as-is, the renamed 'a' could not be
1161 1161 found for it would have been renamed already. And we cannot copy
1162 1162 from 'b' instead because 'b' would have been changed already. So
1163 1163 we scan the git patch for copy and rename commands so we can
1164 1164 perform the copies ahead of time.
1165 1165 """
1166 1166 pos = 0
1167 1167 try:
1168 1168 pos = lr.fp.tell()
1169 1169 fp = lr.fp
1170 1170 except IOError:
1171 1171 fp = cStringIO.StringIO(lr.fp.read())
1172 1172 gitlr = linereader(fp)
1173 1173 gitlr.push(firstline)
1174 1174 gitpatches = readgitpatch(gitlr)
1175 1175 fp.seek(pos)
1176 1176 return gitpatches
1177 1177
1178 1178 def iterhunks(fp):
1179 1179 """Read a patch and yield the following events:
1180 1180 - ("file", afile, bfile, firsthunk): select a new target file.
1181 1181 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1182 1182 "file" event.
1183 1183 - ("git", gitchanges): current diff is in git format, gitchanges
1184 1184 maps filenames to gitpatch records. Unique event.
1185 1185 """
1186 1186 afile = ""
1187 1187 bfile = ""
1188 1188 state = None
1189 1189 hunknum = 0
1190 1190 emitfile = newfile = False
1191 1191 gitpatches = None
1192 1192
1193 1193 # our states
1194 1194 BFILE = 1
1195 1195 context = None
1196 1196 lr = linereader(fp)
1197 1197
1198 1198 while True:
1199 1199 x = lr.readline()
1200 1200 if not x:
1201 1201 break
1202 1202 if state == BFILE and (
1203 1203 (not context and x[0] == '@')
1204 1204 or (context is not False and x.startswith('***************'))
1205 1205 or x.startswith('GIT binary patch')):
1206 1206 gp = None
1207 1207 if (gitpatches and
1208 1208 gitpatches[-1].ispatching(afile, bfile)):
1209 1209 gp = gitpatches.pop()
1210 1210 if x.startswith('GIT binary patch'):
1211 1211 h = binhunk(lr, gp.path)
1212 1212 else:
1213 1213 if context is None and x.startswith('***************'):
1214 1214 context = True
1215 1215 h = hunk(x, hunknum + 1, lr, context)
1216 1216 hunknum += 1
1217 1217 if emitfile:
1218 1218 emitfile = False
1219 1219 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1220 1220 yield 'hunk', h
1221 1221 elif x.startswith('diff --git a/'):
1222 1222 m = gitre.match(x.rstrip(' \r\n'))
1223 1223 if not m:
1224 1224 continue
1225 1225 if gitpatches is None:
1226 1226 # scan whole input for git metadata
1227 1227 gitpatches = scangitpatch(lr, x)
1228 1228 yield 'git', [g.copy() for g in gitpatches
1229 1229 if g.op in ('COPY', 'RENAME')]
1230 1230 gitpatches.reverse()
1231 1231 afile = 'a/' + m.group(1)
1232 1232 bfile = 'b/' + m.group(2)
1233 1233 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1234 1234 gp = gitpatches.pop()
1235 1235 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1236 1236 if not gitpatches:
1237 1237 raise PatchError(_('failed to synchronize metadata for "%s"')
1238 1238 % afile[2:])
1239 1239 gp = gitpatches[-1]
1240 1240 newfile = True
1241 1241 elif x.startswith('---'):
1242 1242 # check for a unified diff
1243 1243 l2 = lr.readline()
1244 1244 if not l2.startswith('+++'):
1245 1245 lr.push(l2)
1246 1246 continue
1247 1247 newfile = True
1248 1248 context = False
1249 1249 afile = parsefilename(x)
1250 1250 bfile = parsefilename(l2)
1251 1251 elif x.startswith('***'):
1252 1252 # check for a context diff
1253 1253 l2 = lr.readline()
1254 1254 if not l2.startswith('---'):
1255 1255 lr.push(l2)
1256 1256 continue
1257 1257 l3 = lr.readline()
1258 1258 lr.push(l3)
1259 1259 if not l3.startswith("***************"):
1260 1260 lr.push(l2)
1261 1261 continue
1262 1262 newfile = True
1263 1263 context = True
1264 1264 afile = parsefilename(x)
1265 1265 bfile = parsefilename(l2)
1266 1266
1267 1267 if newfile:
1268 1268 newfile = False
1269 1269 emitfile = True
1270 1270 state = BFILE
1271 1271 hunknum = 0
1272 1272
1273 1273 while gitpatches:
1274 1274 gp = gitpatches.pop()
1275 1275 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1276 1276
1277 1277 def applybindelta(binchunk, data):
1278 1278 """Apply a binary delta hunk
1279 1279 The algorithm used is the algorithm from git's patch-delta.c
1280 1280 """
1281 1281 def deltahead(binchunk):
1282 1282 i = 0
1283 1283 for c in binchunk:
1284 1284 i += 1
1285 1285 if not (ord(c) & 0x80):
1286 1286 return i
1287 1287 return i
1288 1288 out = ""
1289 1289 s = deltahead(binchunk)
1290 1290 binchunk = binchunk[s:]
1291 1291 s = deltahead(binchunk)
1292 1292 binchunk = binchunk[s:]
1293 1293 i = 0
1294 1294 while i < len(binchunk):
1295 1295 cmd = ord(binchunk[i])
1296 1296 i += 1
1297 1297 if (cmd & 0x80):
1298 1298 offset = 0
1299 1299 size = 0
1300 1300 if (cmd & 0x01):
1301 1301 offset = ord(binchunk[i])
1302 1302 i += 1
1303 1303 if (cmd & 0x02):
1304 1304 offset |= ord(binchunk[i]) << 8
1305 1305 i += 1
1306 1306 if (cmd & 0x04):
1307 1307 offset |= ord(binchunk[i]) << 16
1308 1308 i += 1
1309 1309 if (cmd & 0x08):
1310 1310 offset |= ord(binchunk[i]) << 24
1311 1311 i += 1
1312 1312 if (cmd & 0x10):
1313 1313 size = ord(binchunk[i])
1314 1314 i += 1
1315 1315 if (cmd & 0x20):
1316 1316 size |= ord(binchunk[i]) << 8
1317 1317 i += 1
1318 1318 if (cmd & 0x40):
1319 1319 size |= ord(binchunk[i]) << 16
1320 1320 i += 1
1321 1321 if size == 0:
1322 1322 size = 0x10000
1323 1323 offset_end = offset + size
1324 1324 out += data[offset:offset_end]
1325 1325 elif cmd != 0:
1326 1326 offset_end = i + cmd
1327 1327 out += binchunk[i:offset_end]
1328 1328 i += cmd
1329 1329 else:
1330 1330 raise PatchError(_('unexpected delta opcode 0'))
1331 1331 return out
1332 1332
1333 1333 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1334 1334 """Reads a patch from fp and tries to apply it.
1335 1335
1336 1336 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1337 1337 there was any fuzz.
1338 1338
1339 1339 If 'eolmode' is 'strict', the patch content and patched file are
1340 1340 read in binary mode. Otherwise, line endings are ignored when
1341 1341 patching then normalized according to 'eolmode'.
1342 1342 """
1343 1343 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1344 1344 eolmode=eolmode)
1345 1345
1346 1346 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1347 1347 eolmode='strict'):
1348 1348
1349 1349 def pstrip(p):
1350 1350 return pathstrip(p, strip - 1)[1]
1351 1351
1352 1352 rejects = 0
1353 1353 err = 0
1354 1354 current_file = None
1355 1355
1356 1356 for state, values in iterhunks(fp):
1357 1357 if state == 'hunk':
1358 1358 if not current_file:
1359 1359 continue
1360 1360 ret = current_file.apply(values)
1361 1361 if ret > 0:
1362 1362 err = 1
1363 1363 elif state == 'file':
1364 1364 if current_file:
1365 1365 rejects += current_file.close()
1366 1366 current_file = None
1367 1367 afile, bfile, first_hunk, gp = values
1368 1368 if gp:
1369 1369 gp.path = pstrip(gp.path)
1370 1370 if gp.oldpath:
1371 1371 gp.oldpath = pstrip(gp.oldpath)
1372 1372 else:
1373 1373 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1374 1374 if gp.op == 'RENAME':
1375 1375 backend.unlink(gp.oldpath)
1376 1376 if not first_hunk:
1377 1377 if gp.op == 'DELETE':
1378 1378 backend.unlink(gp.path)
1379 1379 continue
1380 1380 data, mode = None, None
1381 1381 if gp.op in ('RENAME', 'COPY'):
1382 1382 data, mode = store.getfile(gp.oldpath)[:2]
1383 1383 if gp.mode:
1384 1384 mode = gp.mode
1385 1385 if gp.op == 'ADD':
1386 1386 # Added files without content have no hunk and
1387 1387 # must be created
1388 1388 data = ''
1389 1389 if data or mode:
1390 1390 if (gp.op in ('ADD', 'RENAME', 'COPY')
1391 1391 and backend.exists(gp.path)):
1392 1392 raise PatchError(_("cannot create %s: destination "
1393 1393 "already exists") % gp.path)
1394 1394 backend.setfile(gp.path, data, mode, gp.oldpath)
1395 1395 continue
1396 1396 try:
1397 1397 current_file = patcher(ui, gp, backend, store,
1398 1398 eolmode=eolmode)
1399 1399 except PatchError, inst:
1400 1400 ui.warn(str(inst) + '\n')
1401 1401 current_file = None
1402 1402 rejects += 1
1403 1403 continue
1404 1404 elif state == 'git':
1405 1405 for gp in values:
1406 1406 path = pstrip(gp.oldpath)
1407 1407 try:
1408 1408 data, mode = backend.getfile(path)
1409 1409 except IOError, e:
1410 1410 if e.errno != errno.ENOENT:
1411 1411 raise
1412 1412 # The error ignored here will trigger a getfile()
1413 1413 # error in a place more appropriate for error
1414 1414 # handling, and will not interrupt the patching
1415 1415 # process.
1416 1416 else:
1417 1417 store.setfile(path, data, mode)
1418 1418 else:
1419 1419 raise util.Abort(_('unsupported parser state: %s') % state)
1420 1420
1421 1421 if current_file:
1422 1422 rejects += current_file.close()
1423 1423
1424 1424 if rejects:
1425 1425 return -1
1426 1426 return err
1427 1427
1428 1428 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1429 1429 similarity):
1430 1430 """use <patcher> to apply <patchname> to the working directory.
1431 1431 returns whether patch was applied with fuzz factor."""
1432 1432
1433 1433 fuzz = False
1434 1434 args = []
1435 1435 cwd = repo.root
1436 1436 if cwd:
1437 1437 args.append('-d %s' % util.shellquote(cwd))
1438 1438 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1439 1439 util.shellquote(patchname)))
1440 1440 try:
1441 1441 for line in fp:
1442 1442 line = line.rstrip()
1443 1443 ui.note(line + '\n')
1444 1444 if line.startswith('patching file '):
1445 1445 pf = util.parsepatchoutput(line)
1446 1446 printed_file = False
1447 1447 files.add(pf)
1448 1448 elif line.find('with fuzz') >= 0:
1449 1449 fuzz = True
1450 1450 if not printed_file:
1451 1451 ui.warn(pf + '\n')
1452 1452 printed_file = True
1453 1453 ui.warn(line + '\n')
1454 1454 elif line.find('saving rejects to file') >= 0:
1455 1455 ui.warn(line + '\n')
1456 1456 elif line.find('FAILED') >= 0:
1457 1457 if not printed_file:
1458 1458 ui.warn(pf + '\n')
1459 1459 printed_file = True
1460 1460 ui.warn(line + '\n')
1461 1461 finally:
1462 1462 if files:
1463 1463 scmutil.marktouched(repo, files, similarity)
1464 1464 code = fp.close()
1465 1465 if code:
1466 1466 raise PatchError(_("patch command failed: %s") %
1467 1467 util.explainexit(code)[0])
1468 1468 return fuzz
1469 1469
1470 1470 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1471 1471 if files is None:
1472 1472 files = set()
1473 1473 if eolmode is None:
1474 1474 eolmode = ui.config('patch', 'eol', 'strict')
1475 1475 if eolmode.lower() not in eolmodes:
1476 1476 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1477 1477 eolmode = eolmode.lower()
1478 1478
1479 1479 store = filestore()
1480 1480 try:
1481 1481 fp = open(patchobj, 'rb')
1482 1482 except TypeError:
1483 1483 fp = patchobj
1484 1484 try:
1485 1485 ret = applydiff(ui, fp, backend, store, strip=strip,
1486 1486 eolmode=eolmode)
1487 1487 finally:
1488 1488 if fp != patchobj:
1489 1489 fp.close()
1490 1490 files.update(backend.close())
1491 1491 store.close()
1492 1492 if ret < 0:
1493 1493 raise PatchError(_('patch failed to apply'))
1494 1494 return ret > 0
1495 1495
1496 1496 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1497 1497 similarity=0):
1498 1498 """use builtin patch to apply <patchobj> to the working directory.
1499 1499 returns whether patch was applied with fuzz factor."""
1500 1500 backend = workingbackend(ui, repo, similarity)
1501 1501 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1502 1502
1503 1503 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1504 1504 eolmode='strict'):
1505 1505 backend = repobackend(ui, repo, ctx, store)
1506 1506 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1507 1507
1508 1508 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1509 1509 similarity=0):
1510 1510 """Apply <patchname> to the working directory.
1511 1511
1512 1512 'eolmode' specifies how end of lines should be handled. It can be:
1513 1513 - 'strict': inputs are read in binary mode, EOLs are preserved
1514 1514 - 'crlf': EOLs are ignored when patching and reset to CRLF
1515 1515 - 'lf': EOLs are ignored when patching and reset to LF
1516 1516 - None: get it from user settings, default to 'strict'
1517 1517 'eolmode' is ignored when using an external patcher program.
1518 1518
1519 1519 Returns whether patch was applied with fuzz factor.
1520 1520 """
1521 1521 patcher = ui.config('ui', 'patch')
1522 1522 if files is None:
1523 1523 files = set()
1524 try:
1525 if patcher:
1526 return _externalpatch(ui, repo, patcher, patchname, strip,
1527 files, similarity)
1528 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1529 similarity)
1530 except PatchError, err:
1531 raise util.Abort(str(err))
1524 if patcher:
1525 return _externalpatch(ui, repo, patcher, patchname, strip,
1526 files, similarity)
1527 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1528 similarity)
1532 1529
1533 1530 def changedfiles(ui, repo, patchpath, strip=1):
1534 1531 backend = fsbackend(ui, repo.root)
1535 1532 fp = open(patchpath, 'rb')
1536 1533 try:
1537 1534 changed = set()
1538 1535 for state, values in iterhunks(fp):
1539 1536 if state == 'file':
1540 1537 afile, bfile, first_hunk, gp = values
1541 1538 if gp:
1542 1539 gp.path = pathstrip(gp.path, strip - 1)[1]
1543 1540 if gp.oldpath:
1544 1541 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1545 1542 else:
1546 1543 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1547 1544 changed.add(gp.path)
1548 1545 if gp.op == 'RENAME':
1549 1546 changed.add(gp.oldpath)
1550 1547 elif state not in ('hunk', 'git'):
1551 1548 raise util.Abort(_('unsupported parser state: %s') % state)
1552 1549 return changed
1553 1550 finally:
1554 1551 fp.close()
1555 1552
1556 1553 class GitDiffRequired(Exception):
1557 1554 pass
1558 1555
1559 1556 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1560 1557 def get(key, name=None, getter=ui.configbool):
1561 1558 return ((opts and opts.get(key)) or
1562 1559 getter(section, name or key, None, untrusted=untrusted))
1563 1560 return mdiff.diffopts(
1564 1561 text=opts and opts.get('text'),
1565 1562 git=get('git'),
1566 1563 nodates=get('nodates'),
1567 1564 showfunc=get('show_function', 'showfunc'),
1568 1565 ignorews=get('ignore_all_space', 'ignorews'),
1569 1566 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1570 1567 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1571 1568 context=get('unified', getter=ui.config))
1572 1569
1573 1570 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1574 1571 losedatafn=None, prefix=''):
1575 1572 '''yields diff of changes to files between two nodes, or node and
1576 1573 working directory.
1577 1574
1578 1575 if node1 is None, use first dirstate parent instead.
1579 1576 if node2 is None, compare node1 with working directory.
1580 1577
1581 1578 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1582 1579 every time some change cannot be represented with the current
1583 1580 patch format. Return False to upgrade to git patch format, True to
1584 1581 accept the loss or raise an exception to abort the diff. It is
1585 1582 called with the name of current file being diffed as 'fn'. If set
1586 1583 to None, patches will always be upgraded to git format when
1587 1584 necessary.
1588 1585
1589 1586 prefix is a filename prefix that is prepended to all filenames on
1590 1587 display (used for subrepos).
1591 1588 '''
1592 1589
1593 1590 if opts is None:
1594 1591 opts = mdiff.defaultopts
1595 1592
1596 1593 if not node1 and not node2:
1597 1594 node1 = repo.dirstate.p1()
1598 1595
1599 1596 def lrugetfilectx():
1600 1597 cache = {}
1601 1598 order = util.deque()
1602 1599 def getfilectx(f, ctx):
1603 1600 fctx = ctx.filectx(f, filelog=cache.get(f))
1604 1601 if f not in cache:
1605 1602 if len(cache) > 20:
1606 1603 del cache[order.popleft()]
1607 1604 cache[f] = fctx.filelog()
1608 1605 else:
1609 1606 order.remove(f)
1610 1607 order.append(f)
1611 1608 return fctx
1612 1609 return getfilectx
1613 1610 getfilectx = lrugetfilectx()
1614 1611
1615 1612 ctx1 = repo[node1]
1616 1613 ctx2 = repo[node2]
1617 1614
1618 1615 if not changes:
1619 1616 changes = repo.status(ctx1, ctx2, match=match)
1620 1617 modified, added, removed = changes[:3]
1621 1618
1622 1619 if not modified and not added and not removed:
1623 1620 return []
1624 1621
1625 1622 revs = None
1626 1623 hexfunc = repo.ui.debugflag and hex or short
1627 1624 revs = [hexfunc(node) for node in [node1, node2] if node]
1628 1625
1629 1626 copy = {}
1630 1627 if opts.git or opts.upgrade:
1631 1628 copy = copies.pathcopies(ctx1, ctx2)
1632 1629
1633 1630 def difffn(opts, losedata):
1634 1631 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1635 1632 copy, getfilectx, opts, losedata, prefix)
1636 1633 if opts.upgrade and not opts.git:
1637 1634 try:
1638 1635 def losedata(fn):
1639 1636 if not losedatafn or not losedatafn(fn=fn):
1640 1637 raise GitDiffRequired
1641 1638 # Buffer the whole output until we are sure it can be generated
1642 1639 return list(difffn(opts.copy(git=False), losedata))
1643 1640 except GitDiffRequired:
1644 1641 return difffn(opts.copy(git=True), None)
1645 1642 else:
1646 1643 return difffn(opts, None)
1647 1644
1648 1645 def difflabel(func, *args, **kw):
1649 1646 '''yields 2-tuples of (output, label) based on the output of func()'''
1650 1647 headprefixes = [('diff', 'diff.diffline'),
1651 1648 ('copy', 'diff.extended'),
1652 1649 ('rename', 'diff.extended'),
1653 1650 ('old', 'diff.extended'),
1654 1651 ('new', 'diff.extended'),
1655 1652 ('deleted', 'diff.extended'),
1656 1653 ('---', 'diff.file_a'),
1657 1654 ('+++', 'diff.file_b')]
1658 1655 textprefixes = [('@', 'diff.hunk'),
1659 1656 ('-', 'diff.deleted'),
1660 1657 ('+', 'diff.inserted')]
1661 1658 head = False
1662 1659 for chunk in func(*args, **kw):
1663 1660 lines = chunk.split('\n')
1664 1661 for i, line in enumerate(lines):
1665 1662 if i != 0:
1666 1663 yield ('\n', '')
1667 1664 if head:
1668 1665 if line.startswith('@'):
1669 1666 head = False
1670 1667 else:
1671 1668 if line and line[0] not in ' +-@\\':
1672 1669 head = True
1673 1670 stripline = line
1674 1671 if not head and line and line[0] in '+-':
1675 1672 # highlight trailing whitespace, but only in changed lines
1676 1673 stripline = line.rstrip()
1677 1674 prefixes = textprefixes
1678 1675 if head:
1679 1676 prefixes = headprefixes
1680 1677 for prefix, label in prefixes:
1681 1678 if stripline.startswith(prefix):
1682 1679 yield (stripline, label)
1683 1680 break
1684 1681 else:
1685 1682 yield (line, '')
1686 1683 if line != stripline:
1687 1684 yield (line[len(stripline):], 'diff.trailingwhitespace')
1688 1685
1689 1686 def diffui(*args, **kw):
1690 1687 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1691 1688 return difflabel(diff, *args, **kw)
1692 1689
1693 1690 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1694 1691 copy, getfilectx, opts, losedatafn, prefix):
1695 1692
1696 1693 def join(f):
1697 1694 return posixpath.join(prefix, f)
1698 1695
1699 1696 def addmodehdr(header, omode, nmode):
1700 1697 if omode != nmode:
1701 1698 header.append('old mode %s\n' % omode)
1702 1699 header.append('new mode %s\n' % nmode)
1703 1700
1704 1701 def addindexmeta(meta, revs):
1705 1702 if opts.git:
1706 1703 i = len(revs)
1707 1704 if i==2:
1708 1705 meta.append('index %s..%s\n' % tuple(revs))
1709 1706 elif i==3:
1710 1707 meta.append('index %s,%s..%s\n' % tuple(revs))
1711 1708
1712 1709 def gitindex(text):
1713 1710 if not text:
1714 1711 text = ""
1715 1712 l = len(text)
1716 1713 s = util.sha1('blob %d\0' % l)
1717 1714 s.update(text)
1718 1715 return s.hexdigest()
1719 1716
1720 1717 def diffline(a, b, revs):
1721 1718 if opts.git:
1722 1719 line = 'diff --git a/%s b/%s\n' % (a, b)
1723 1720 elif not repo.ui.quiet:
1724 1721 if revs:
1725 1722 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1726 1723 line = 'diff %s %s\n' % (revinfo, a)
1727 1724 else:
1728 1725 line = 'diff %s\n' % a
1729 1726 else:
1730 1727 line = ''
1731 1728 return line
1732 1729
1733 1730 date1 = util.datestr(ctx1.date())
1734 1731 man1 = ctx1.manifest()
1735 1732
1736 1733 gone = set()
1737 1734 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1738 1735
1739 1736 copyto = dict([(v, k) for k, v in copy.items()])
1740 1737
1741 1738 if opts.git:
1742 1739 revs = None
1743 1740
1744 1741 for f in sorted(modified + added + removed):
1745 1742 to = None
1746 1743 tn = None
1747 1744 dodiff = True
1748 1745 header = []
1749 1746 if f in man1:
1750 1747 to = getfilectx(f, ctx1).data()
1751 1748 if f not in removed:
1752 1749 tn = getfilectx(f, ctx2).data()
1753 1750 a, b = f, f
1754 1751 if opts.git or losedatafn:
1755 1752 if f in added or (f in modified and to is None):
1756 1753 mode = gitmode[ctx2.flags(f)]
1757 1754 if f in copy or f in copyto:
1758 1755 if opts.git:
1759 1756 if f in copy:
1760 1757 a = copy[f]
1761 1758 else:
1762 1759 a = copyto[f]
1763 1760 omode = gitmode[man1.flags(a)]
1764 1761 addmodehdr(header, omode, mode)
1765 1762 if a in removed and a not in gone:
1766 1763 op = 'rename'
1767 1764 gone.add(a)
1768 1765 else:
1769 1766 op = 'copy'
1770 1767 header.append('%s from %s\n' % (op, join(a)))
1771 1768 header.append('%s to %s\n' % (op, join(f)))
1772 1769 to = getfilectx(a, ctx1).data()
1773 1770 else:
1774 1771 losedatafn(f)
1775 1772 else:
1776 1773 if opts.git:
1777 1774 header.append('new file mode %s\n' % mode)
1778 1775 elif ctx2.flags(f):
1779 1776 losedatafn(f)
1780 1777 # In theory, if tn was copied or renamed we should check
1781 1778 # if the source is binary too but the copy record already
1782 1779 # forces git mode.
1783 1780 if util.binary(tn):
1784 1781 if opts.git:
1785 1782 dodiff = 'binary'
1786 1783 else:
1787 1784 losedatafn(f)
1788 1785 if not opts.git and not tn:
1789 1786 # regular diffs cannot represent new empty file
1790 1787 losedatafn(f)
1791 1788 elif f in removed or (f in modified and tn is None):
1792 1789 if opts.git:
1793 1790 # have we already reported a copy above?
1794 1791 if ((f in copy and copy[f] in added
1795 1792 and copyto[copy[f]] == f) or
1796 1793 (f in copyto and copyto[f] in added
1797 1794 and copy[copyto[f]] == f)):
1798 1795 dodiff = False
1799 1796 else:
1800 1797 header.append('deleted file mode %s\n' %
1801 1798 gitmode[man1.flags(f)])
1802 1799 if util.binary(to):
1803 1800 dodiff = 'binary'
1804 1801 elif not to or util.binary(to):
1805 1802 # regular diffs cannot represent empty file deletion
1806 1803 losedatafn(f)
1807 1804 else:
1808 1805 oflag = man1.flags(f)
1809 1806 nflag = ctx2.flags(f)
1810 1807 binary = util.binary(to) or util.binary(tn)
1811 1808 if opts.git:
1812 1809 addmodehdr(header, gitmode[oflag], gitmode[nflag])
1813 1810 if binary:
1814 1811 dodiff = 'binary'
1815 1812 elif binary or nflag != oflag:
1816 1813 losedatafn(f)
1817 1814
1818 1815 if dodiff:
1819 1816 if opts.git or revs:
1820 1817 header.insert(0, diffline(join(a), join(b), revs))
1821 1818 if dodiff == 'binary':
1822 1819 text = mdiff.b85diff(to, tn)
1823 1820 if text:
1824 1821 addindexmeta(header, [gitindex(to), gitindex(tn)])
1825 1822 else:
1826 1823 text = mdiff.unidiff(to, date1,
1827 1824 # ctx2 date may be dynamic
1828 1825 tn, util.datestr(ctx2.date()),
1829 1826 join(a), join(b), opts=opts)
1830 1827 if header and (text or len(header) > 1):
1831 1828 yield ''.join(header)
1832 1829 if text:
1833 1830 yield text
1834 1831
1835 1832 def diffstatsum(stats):
1836 1833 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1837 1834 for f, a, r, b in stats:
1838 1835 maxfile = max(maxfile, encoding.colwidth(f))
1839 1836 maxtotal = max(maxtotal, a + r)
1840 1837 addtotal += a
1841 1838 removetotal += r
1842 1839 binary = binary or b
1843 1840
1844 1841 return maxfile, maxtotal, addtotal, removetotal, binary
1845 1842
1846 1843 def diffstatdata(lines):
1847 1844 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1848 1845
1849 1846 results = []
1850 1847 filename, adds, removes, isbinary = None, 0, 0, False
1851 1848
1852 1849 def addresult():
1853 1850 if filename:
1854 1851 results.append((filename, adds, removes, isbinary))
1855 1852
1856 1853 for line in lines:
1857 1854 if line.startswith('diff'):
1858 1855 addresult()
1859 1856 # set numbers to 0 anyway when starting new file
1860 1857 adds, removes, isbinary = 0, 0, False
1861 1858 if line.startswith('diff --git a/'):
1862 1859 filename = gitre.search(line).group(2)
1863 1860 elif line.startswith('diff -r'):
1864 1861 # format: "diff -r ... -r ... filename"
1865 1862 filename = diffre.search(line).group(1)
1866 1863 elif line.startswith('+') and not line.startswith('+++ '):
1867 1864 adds += 1
1868 1865 elif line.startswith('-') and not line.startswith('--- '):
1869 1866 removes += 1
1870 1867 elif (line.startswith('GIT binary patch') or
1871 1868 line.startswith('Binary file')):
1872 1869 isbinary = True
1873 1870 addresult()
1874 1871 return results
1875 1872
1876 1873 def diffstat(lines, width=80, git=False):
1877 1874 output = []
1878 1875 stats = diffstatdata(lines)
1879 1876 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1880 1877
1881 1878 countwidth = len(str(maxtotal))
1882 1879 if hasbinary and countwidth < 3:
1883 1880 countwidth = 3
1884 1881 graphwidth = width - countwidth - maxname - 6
1885 1882 if graphwidth < 10:
1886 1883 graphwidth = 10
1887 1884
1888 1885 def scale(i):
1889 1886 if maxtotal <= graphwidth:
1890 1887 return i
1891 1888 # If diffstat runs out of room it doesn't print anything,
1892 1889 # which isn't very useful, so always print at least one + or -
1893 1890 # if there were at least some changes.
1894 1891 return max(i * graphwidth // maxtotal, int(bool(i)))
1895 1892
1896 1893 for filename, adds, removes, isbinary in stats:
1897 1894 if isbinary:
1898 1895 count = 'Bin'
1899 1896 else:
1900 1897 count = adds + removes
1901 1898 pluses = '+' * scale(adds)
1902 1899 minuses = '-' * scale(removes)
1903 1900 output.append(' %s%s | %*s %s%s\n' %
1904 1901 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1905 1902 countwidth, count, pluses, minuses))
1906 1903
1907 1904 if stats:
1908 1905 output.append(_(' %d files changed, %d insertions(+), '
1909 1906 '%d deletions(-)\n')
1910 1907 % (len(stats), totaladds, totalremoves))
1911 1908
1912 1909 return ''.join(output)
1913 1910
1914 1911 def diffstatui(*args, **kw):
1915 1912 '''like diffstat(), but yields 2-tuples of (output, label) for
1916 1913 ui.write()
1917 1914 '''
1918 1915
1919 1916 for line in diffstat(*args, **kw).splitlines():
1920 1917 if line and line[-1] in '+-':
1921 1918 name, graph = line.rsplit(' ', 1)
1922 1919 yield (name + ' ', '')
1923 1920 m = re.search(r'\++', graph)
1924 1921 if m:
1925 1922 yield (m.group(0), 'diffstat.inserted')
1926 1923 m = re.search(r'-+', graph)
1927 1924 if m:
1928 1925 yield (m.group(0), 'diffstat.deleted')
1929 1926 else:
1930 1927 yield (line, '')
1931 1928 yield ('\n', '')
@@ -1,332 +1,332 b''
1 1 Show all commands except debug commands
2 2 $ hg debugcomplete
3 3 add
4 4 addremove
5 5 annotate
6 6 archive
7 7 backout
8 8 bisect
9 9 bookmarks
10 10 branch
11 11 branches
12 12 bundle
13 13 cat
14 14 clone
15 15 commit
16 16 config
17 17 copy
18 18 diff
19 19 export
20 20 forget
21 21 graft
22 22 grep
23 23 heads
24 24 help
25 25 identify
26 26 import
27 27 incoming
28 28 init
29 29 locate
30 30 log
31 31 manifest
32 32 merge
33 33 outgoing
34 34 parents
35 35 paths
36 36 phase
37 37 pull
38 38 push
39 39 recover
40 40 remove
41 41 rename
42 42 resolve
43 43 revert
44 44 rollback
45 45 root
46 46 serve
47 47 status
48 48 summary
49 49 tag
50 50 tags
51 51 tip
52 52 unbundle
53 53 update
54 54 verify
55 55 version
56 56
57 57 Show all commands that start with "a"
58 58 $ hg debugcomplete a
59 59 add
60 60 addremove
61 61 annotate
62 62 archive
63 63
64 64 Do not show debug commands if there are other candidates
65 65 $ hg debugcomplete d
66 66 diff
67 67
68 68 Show debug commands if there are no other candidates
69 69 $ hg debugcomplete debug
70 70 debugancestor
71 71 debugbuilddag
72 72 debugbundle
73 73 debugcheckstate
74 74 debugcommands
75 75 debugcomplete
76 76 debugconfig
77 77 debugdag
78 78 debugdata
79 79 debugdate
80 80 debugdirstate
81 81 debugdiscovery
82 82 debugfileset
83 83 debugfsinfo
84 84 debuggetbundle
85 85 debugignore
86 86 debugindex
87 87 debugindexdot
88 88 debuginstall
89 89 debugknown
90 90 debuglabelcomplete
91 91 debugobsolete
92 92 debugpathcomplete
93 93 debugpushkey
94 94 debugpvec
95 95 debugrebuilddirstate
96 96 debugrename
97 97 debugrevlog
98 98 debugrevspec
99 99 debugsetparents
100 100 debugsub
101 101 debugsuccessorssets
102 102 debugwalk
103 103 debugwireargs
104 104
105 105 Do not show the alias of a debug command if there are other candidates
106 106 (this should hide rawcommit)
107 107 $ hg debugcomplete r
108 108 recover
109 109 remove
110 110 rename
111 111 resolve
112 112 revert
113 113 rollback
114 114 root
115 115 Show the alias of a debug command if there are no other candidates
116 116 $ hg debugcomplete rawc
117 117
118 118
119 119 Show the global options
120 120 $ hg debugcomplete --options | sort
121 121 --config
122 122 --cwd
123 123 --debug
124 124 --debugger
125 125 --encoding
126 126 --encodingmode
127 127 --help
128 128 --hidden
129 129 --noninteractive
130 130 --profile
131 131 --quiet
132 132 --repository
133 133 --time
134 134 --traceback
135 135 --verbose
136 136 --version
137 137 -R
138 138 -h
139 139 -q
140 140 -v
141 141 -y
142 142
143 143 Show the options for the "serve" command
144 144 $ hg debugcomplete --options serve | sort
145 145 --accesslog
146 146 --address
147 147 --certificate
148 148 --cmdserver
149 149 --config
150 150 --cwd
151 151 --daemon
152 152 --daemon-pipefds
153 153 --debug
154 154 --debugger
155 155 --encoding
156 156 --encodingmode
157 157 --errorlog
158 158 --help
159 159 --hidden
160 160 --ipv6
161 161 --name
162 162 --noninteractive
163 163 --pid-file
164 164 --port
165 165 --prefix
166 166 --profile
167 167 --quiet
168 168 --repository
169 169 --stdio
170 170 --style
171 171 --templates
172 172 --time
173 173 --traceback
174 174 --verbose
175 175 --version
176 176 --web-conf
177 177 -6
178 178 -A
179 179 -E
180 180 -R
181 181 -a
182 182 -d
183 183 -h
184 184 -n
185 185 -p
186 186 -q
187 187 -t
188 188 -v
189 189 -y
190 190
191 191 Show an error if we use --options with an ambiguous abbreviation
192 192 $ hg debugcomplete --options s
193 193 hg: command 's' is ambiguous:
194 194 serve showconfig status summary
195 195 [255]
196 196
197 197 Show all commands + options
198 198 $ hg debugcommands
199 199 add: include, exclude, subrepos, dry-run
200 200 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude
201 201 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
202 202 commit: addremove, close-branch, amend, secret, edit, include, exclude, message, logfile, date, user, subrepos
203 203 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
204 204 export: output, switch-parent, rev, text, git, nodates
205 205 forget: include, exclude
206 206 init: ssh, remotecmd, insecure
207 207 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
208 208 merge: force, rev, preview, tool
209 209 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
210 210 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
211 211 remove: after, force, include, exclude
212 212 serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
213 213 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
214 214 summary: remote
215 215 update: clean, check, date, rev, tool
216 216 addremove: similarity, include, exclude, dry-run
217 217 archive: no-decode, prefix, rev, type, subrepos, include, exclude
218 218 backout: merge, parent, rev, tool, include, exclude, message, logfile, date, user
219 219 bisect: reset, good, bad, skip, extend, command, noupdate
220 220 bookmarks: force, rev, delete, rename, inactive
221 221 branch: force, clean
222 222 branches: active, closed
223 223 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
224 224 cat: output, rev, decode, include, exclude
225 225 config: untrusted, edit, local, global
226 226 copy: after, force, include, exclude, dry-run
227 227 debugancestor:
228 228 debugbuilddag: mergeable-file, overwritten-file, new-file
229 229 debugbundle: all
230 230 debugcheckstate:
231 231 debugcommands:
232 232 debugcomplete: options
233 233 debugdag: tags, branches, dots, spaces
234 234 debugdata: changelog, manifest
235 235 debugdate: extended
236 236 debugdirstate: nodates, datesort
237 237 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
238 238 debugfileset: rev
239 239 debugfsinfo:
240 240 debuggetbundle: head, common, type
241 241 debugignore:
242 242 debugindex: changelog, manifest, format
243 243 debugindexdot:
244 244 debuginstall:
245 245 debugknown:
246 246 debuglabelcomplete:
247 247 debugobsolete: flags, date, user
248 248 debugpathcomplete: full, normal, added, removed
249 249 debugpushkey:
250 250 debugpvec:
251 251 debugrebuilddirstate: rev
252 252 debugrename: rev
253 253 debugrevlog: changelog, manifest, dump
254 254 debugrevspec: optimize
255 255 debugsetparents:
256 256 debugsub: rev
257 257 debugsuccessorssets:
258 258 debugwalk: include, exclude
259 259 debugwireargs: three, four, five, ssh, remotecmd, insecure
260 260 graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
261 261 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
262 262 heads: rev, topo, active, closed, style, template
263 263 help: extension, command, keyword
264 264 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
265 import: strip, base, edit, force, no-commit, bypass, exact, import-branch, message, logfile, date, user, similarity
265 import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity
266 266 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
267 267 locate: rev, print0, fullpath, include, exclude
268 268 manifest: rev, all
269 269 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
270 270 parents: rev, style, template
271 271 paths:
272 272 phase: public, draft, secret, force, rev
273 273 recover:
274 274 rename: after, force, include, exclude, dry-run
275 275 resolve: all, list, mark, unmark, no-status, tool, include, exclude
276 276 revert: all, date, rev, no-backup, include, exclude, dry-run
277 277 rollback: dry-run, force
278 278 root:
279 279 tag: force, local, rev, remove, edit, message, date, user
280 280 tags:
281 281 tip: patch, git, style, template
282 282 unbundle: update
283 283 verify:
284 284 version:
285 285
286 286 $ hg init a
287 287 $ cd a
288 288 $ echo fee > fee
289 289 $ hg ci -q -Amfee
290 290 $ hg tag fee
291 291 $ mkdir fie
292 292 $ echo dead > fie/dead
293 293 $ echo live > fie/live
294 294 $ hg bookmark fo
295 295 $ hg branch -q fie
296 296 $ hg ci -q -Amfie
297 297 $ echo fo > fo
298 298 $ hg branch -qf default
299 299 $ hg ci -q -Amfo
300 300 $ echo Fum > Fum
301 301 $ hg ci -q -AmFum
302 302 $ hg bookmark Fum
303 303
304 304 Test debugpathcomplete
305 305
306 306 $ hg debugpathcomplete f
307 307 fee
308 308 fie
309 309 fo
310 310 $ hg debugpathcomplete -f f
311 311 fee
312 312 fie/dead
313 313 fie/live
314 314 fo
315 315
316 316 $ hg rm Fum
317 317 $ hg debugpathcomplete -r F
318 318 Fum
319 319
320 320 Test debuglabelcomplete
321 321
322 322 $ hg debuglabelcomplete
323 323 Fum
324 324 default
325 325 fee
326 326 fie
327 327 fo
328 328 tip
329 329 $ hg debuglabelcomplete f
330 330 fee
331 331 fie
332 332 fo
@@ -1,1185 +1,1452 b''
1 1 $ hg init a
2 2 $ mkdir a/d1
3 3 $ mkdir a/d1/d2
4 4 $ echo line 1 > a/a
5 5 $ echo line 1 > a/d1/d2/a
6 6 $ hg --cwd a ci -Ama
7 7 adding a
8 8 adding d1/d2/a
9 9
10 10 $ echo line 2 >> a/a
11 11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12 12
13 13 import with no args:
14 14
15 15 $ hg --cwd a import
16 16 abort: need at least one patch to import
17 17 [255]
18 18
19 19 generate patches for the test
20 20
21 21 $ hg --cwd a export tip > exported-tip.patch
22 22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23 23
24 24
25 25 import exported patch
26 26 (this also tests that editor is not invoked, if the patch contains the
27 27 commit message and '--edit' is not specified)
28 28
29 29 $ hg clone -r0 a b
30 30 adding changesets
31 31 adding manifests
32 32 adding file changes
33 33 added 1 changesets with 2 changes to 2 files
34 34 updating to branch default
35 35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 37 applying ../exported-tip.patch
38 38
39 39 message and committer and date should be same
40 40
41 41 $ hg --cwd b tip
42 42 changeset: 1:1d4bd90af0e4
43 43 tag: tip
44 44 user: someone
45 45 date: Thu Jan 01 00:00:01 1970 +0000
46 46 summary: second change
47 47
48 48 $ rm -r b
49 49
50 50
51 51 import exported patch with external patcher
52 52 (this also tests that editor is invoked, if the '--edit' is specified,
53 53 regardless of the commit message in the patch)
54 54
55 55 $ cat > dummypatch.py <<EOF
56 56 > print 'patching file a'
57 57 > file('a', 'wb').write('line2\n')
58 58 > EOF
59 59 $ hg clone -r0 a b
60 60 adding changesets
61 61 adding manifests
62 62 adding file changes
63 63 added 1 changesets with 2 changes to 2 files
64 64 updating to branch default
65 65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 67 applying ../exported-tip.patch
68 68 second change
69 69
70 70
71 71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 72 HG: Leave message empty to abort commit.
73 73 HG: --
74 74 HG: user: someone
75 75 HG: branch 'default'
76 76 HG: changed a
77 77 $ cat b/a
78 78 line2
79 79 $ rm -r b
80 80
81 81
82 82 import of plain diff should fail without message
83 83 (this also tests that editor is invoked, if the patch doesn't contain
84 84 the commit message, regardless of '--edit')
85 85
86 86 $ hg clone -r0 a b
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 2 changes to 2 files
91 91 updating to branch default
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 $ HGEDITOR=cat hg --cwd b import ../diffed-tip.patch
94 94 applying ../diffed-tip.patch
95 95
96 96
97 97 HG: Enter commit message. Lines beginning with 'HG:' are removed.
98 98 HG: Leave message empty to abort commit.
99 99 HG: --
100 100 HG: user: test
101 101 HG: branch 'default'
102 102 HG: changed a
103 103 abort: empty commit message
104 104 [255]
105 105 $ rm -r b
106 106
107 107
108 108 import of plain diff should be ok with message
109 109
110 110 $ hg clone -r0 a b
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 1 changesets with 2 changes to 2 files
115 115 updating to branch default
116 116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ hg --cwd b import -mpatch ../diffed-tip.patch
118 118 applying ../diffed-tip.patch
119 119 $ rm -r b
120 120
121 121
122 122 import of plain diff with specific date and user
123 123 (this also tests that editor is not invoked, if
124 124 '--message'/'--logfile' is specified and '--edit' is not)
125 125
126 126 $ hg clone -r0 a b
127 127 adding changesets
128 128 adding manifests
129 129 adding file changes
130 130 added 1 changesets with 2 changes to 2 files
131 131 updating to branch default
132 132 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
134 134 applying ../diffed-tip.patch
135 135 $ hg -R b tip -pv
136 136 changeset: 1:ca68f19f3a40
137 137 tag: tip
138 138 user: user@nowhere.net
139 139 date: Thu Jan 01 00:00:01 1970 +0000
140 140 files: a
141 141 description:
142 142 patch
143 143
144 144
145 145 diff -r 80971e65b431 -r ca68f19f3a40 a
146 146 --- a/a Thu Jan 01 00:00:00 1970 +0000
147 147 +++ b/a Thu Jan 01 00:00:01 1970 +0000
148 148 @@ -1,1 +1,2 @@
149 149 line 1
150 150 +line 2
151 151
152 152 $ rm -r b
153 153
154 154
155 155 import of plain diff should be ok with --no-commit
156 156 (this also tests that editor is not invoked, if '--no-commit' is
157 157 specified, regardless of '--edit')
158 158
159 159 $ hg clone -r0 a b
160 160 adding changesets
161 161 adding manifests
162 162 adding file changes
163 163 added 1 changesets with 2 changes to 2 files
164 164 updating to branch default
165 165 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 166 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
167 167 applying ../diffed-tip.patch
168 168 $ hg --cwd b diff --nodates
169 169 diff -r 80971e65b431 a
170 170 --- a/a
171 171 +++ b/a
172 172 @@ -1,1 +1,2 @@
173 173 line 1
174 174 +line 2
175 175 $ rm -r b
176 176
177 177
178 178 import of malformed plain diff should fail
179 179
180 180 $ hg clone -r0 a b
181 181 adding changesets
182 182 adding manifests
183 183 adding file changes
184 184 added 1 changesets with 2 changes to 2 files
185 185 updating to branch default
186 186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
188 188 $ hg --cwd b import -mpatch ../broken.patch
189 189 applying ../broken.patch
190 190 abort: bad hunk #1
191 191 [255]
192 192 $ rm -r b
193 193
194 194
195 195 hg -R repo import
196 196 put the clone in a subdir - having a directory named "a"
197 197 used to hide a bug.
198 198
199 199 $ mkdir dir
200 200 $ hg clone -r0 a dir/b
201 201 adding changesets
202 202 adding manifests
203 203 adding file changes
204 204 added 1 changesets with 2 changes to 2 files
205 205 updating to branch default
206 206 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 207 $ cd dir
208 208 $ hg -R b import ../exported-tip.patch
209 209 applying ../exported-tip.patch
210 210 $ cd ..
211 211 $ rm -r dir
212 212
213 213
214 214 import from stdin
215 215
216 216 $ hg clone -r0 a b
217 217 adding changesets
218 218 adding manifests
219 219 adding file changes
220 220 added 1 changesets with 2 changes to 2 files
221 221 updating to branch default
222 222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 223 $ hg --cwd b import - < exported-tip.patch
224 224 applying patch from stdin
225 225 $ rm -r b
226 226
227 227
228 228 import two patches in one stream
229 229
230 230 $ hg init b
231 231 $ hg --cwd a export 0:tip | hg --cwd b import -
232 232 applying patch from stdin
233 233 $ hg --cwd a id
234 234 1d4bd90af0e4 tip
235 235 $ hg --cwd b id
236 236 1d4bd90af0e4 tip
237 237 $ rm -r b
238 238
239 239
240 240 override commit message
241 241
242 242 $ hg clone -r0 a b
243 243 adding changesets
244 244 adding manifests
245 245 adding file changes
246 246 added 1 changesets with 2 changes to 2 files
247 247 updating to branch default
248 248 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 249 $ hg --cwd b import -m 'override' - < exported-tip.patch
250 250 applying patch from stdin
251 251 $ hg --cwd b tip | grep override
252 252 summary: override
253 253 $ rm -r b
254 254
255 255 $ cat > mkmsg.py <<EOF
256 256 > import email.Message, sys
257 257 > msg = email.Message.Message()
258 258 > patch = open(sys.argv[1], 'rb').read()
259 259 > msg.set_payload('email commit message\n' + patch)
260 260 > msg['Subject'] = 'email patch'
261 261 > msg['From'] = 'email patcher'
262 262 > file(sys.argv[2], 'wb').write(msg.as_string())
263 263 > EOF
264 264
265 265
266 266 plain diff in email, subject, message body
267 267
268 268 $ hg clone -r0 a b
269 269 adding changesets
270 270 adding manifests
271 271 adding file changes
272 272 added 1 changesets with 2 changes to 2 files
273 273 updating to branch default
274 274 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 275 $ python mkmsg.py diffed-tip.patch msg.patch
276 276 $ hg --cwd b import ../msg.patch
277 277 applying ../msg.patch
278 278 $ hg --cwd b tip | grep email
279 279 user: email patcher
280 280 summary: email patch
281 281 $ rm -r b
282 282
283 283
284 284 plain diff in email, no subject, message body
285 285
286 286 $ hg clone -r0 a b
287 287 adding changesets
288 288 adding manifests
289 289 adding file changes
290 290 added 1 changesets with 2 changes to 2 files
291 291 updating to branch default
292 292 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 293 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
294 294 applying patch from stdin
295 295 $ rm -r b
296 296
297 297
298 298 plain diff in email, subject, no message body
299 299
300 300 $ hg clone -r0 a b
301 301 adding changesets
302 302 adding manifests
303 303 adding file changes
304 304 added 1 changesets with 2 changes to 2 files
305 305 updating to branch default
306 306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 307 $ grep -v '^email ' msg.patch | hg --cwd b import -
308 308 applying patch from stdin
309 309 $ rm -r b
310 310
311 311
312 312 plain diff in email, no subject, no message body, should fail
313 313
314 314 $ hg clone -r0 a b
315 315 adding changesets
316 316 adding manifests
317 317 adding file changes
318 318 added 1 changesets with 2 changes to 2 files
319 319 updating to branch default
320 320 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 321 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
322 322 applying patch from stdin
323 323 abort: empty commit message
324 324 [255]
325 325 $ rm -r b
326 326
327 327
328 328 hg export in email, should use patch header
329 329
330 330 $ hg clone -r0 a b
331 331 adding changesets
332 332 adding manifests
333 333 adding file changes
334 334 added 1 changesets with 2 changes to 2 files
335 335 updating to branch default
336 336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 337 $ python mkmsg.py exported-tip.patch msg.patch
338 338 $ cat msg.patch | hg --cwd b import -
339 339 applying patch from stdin
340 340 $ hg --cwd b tip | grep second
341 341 summary: second change
342 342 $ rm -r b
343 343
344 344
345 345 subject: duplicate detection, removal of [PATCH]
346 346 The '---' tests the gitsendmail handling without proper mail headers
347 347
348 348 $ cat > mkmsg2.py <<EOF
349 349 > import email.Message, sys
350 350 > msg = email.Message.Message()
351 351 > patch = open(sys.argv[1], 'rb').read()
352 352 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
353 353 > msg['Subject'] = '[PATCH] email patch'
354 354 > msg['From'] = 'email patcher'
355 355 > file(sys.argv[2], 'wb').write(msg.as_string())
356 356 > EOF
357 357
358 358
359 359 plain diff in email, [PATCH] subject, message body with subject
360 360
361 361 $ hg clone -r0 a b
362 362 adding changesets
363 363 adding manifests
364 364 adding file changes
365 365 added 1 changesets with 2 changes to 2 files
366 366 updating to branch default
367 367 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 368 $ python mkmsg2.py diffed-tip.patch msg.patch
369 369 $ cat msg.patch | hg --cwd b import -
370 370 applying patch from stdin
371 371 $ hg --cwd b tip --template '{desc}\n'
372 372 email patch
373 373
374 374 next line
375 375 $ rm -r b
376 376
377 377
378 378 Issue963: Parent of working dir incorrect after import of multiple
379 379 patches and rollback
380 380
381 381 We weren't backing up the correct dirstate file when importing many
382 382 patches: import patch1 patch2; rollback
383 383
384 384 $ echo line 3 >> a/a
385 385 $ hg --cwd a ci -m'third change'
386 386 $ hg --cwd a export -o '../patch%R' 1 2
387 387 $ hg clone -qr0 a b
388 388 $ hg --cwd b parents --template 'parent: {rev}\n'
389 389 parent: 0
390 390 $ hg --cwd b import -v ../patch1 ../patch2
391 391 applying ../patch1
392 392 patching file a
393 393 a
394 394 created 1d4bd90af0e4
395 395 applying ../patch2
396 396 patching file a
397 397 a
398 398 created 6d019af21222
399 399 $ hg --cwd b rollback
400 400 repository tip rolled back to revision 0 (undo import)
401 401 working directory now based on revision 0
402 402 $ hg --cwd b parents --template 'parent: {rev}\n'
403 403 parent: 0
404 404 $ rm -r b
405 405
406 406
407 407 importing a patch in a subdirectory failed at the commit stage
408 408
409 409 $ echo line 2 >> a/d1/d2/a
410 410 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
411 411
412 412 hg import in a subdirectory
413 413
414 414 $ hg clone -r0 a b
415 415 adding changesets
416 416 adding manifests
417 417 adding file changes
418 418 added 1 changesets with 2 changes to 2 files
419 419 updating to branch default
420 420 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
421 421 $ hg --cwd a export tip > tmp
422 422 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
423 423 $ dir=`pwd`
424 424 $ cd b/d1/d2 2>&1 > /dev/null
425 425 $ hg import ../../../subdir-tip.patch
426 426 applying ../../../subdir-tip.patch
427 427 $ cd "$dir"
428 428
429 429 message should be 'subdir change'
430 430 committer should be 'someoneelse'
431 431
432 432 $ hg --cwd b tip
433 433 changeset: 1:3577f5aea227
434 434 tag: tip
435 435 user: someoneelse
436 436 date: Thu Jan 01 00:00:01 1970 +0000
437 437 summary: subdir change
438 438
439 439
440 440 should be empty
441 441
442 442 $ hg --cwd b status
443 443
444 444
445 445 Test fuzziness (ambiguous patch location, fuzz=2)
446 446
447 447 $ hg init fuzzy
448 448 $ cd fuzzy
449 449 $ echo line1 > a
450 450 $ echo line0 >> a
451 451 $ echo line3 >> a
452 452 $ hg ci -Am adda
453 453 adding a
454 454 $ echo line1 > a
455 455 $ echo line2 >> a
456 456 $ echo line0 >> a
457 457 $ echo line3 >> a
458 458 $ hg ci -m change a
459 459 $ hg export tip > fuzzy-tip.patch
460 460 $ hg up -C 0
461 461 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 462 $ echo line1 > a
463 463 $ echo line0 >> a
464 464 $ echo line1 >> a
465 465 $ echo line0 >> a
466 466 $ hg ci -m brancha
467 467 created new head
468 468 $ hg import --no-commit -v fuzzy-tip.patch
469 469 applying fuzzy-tip.patch
470 470 patching file a
471 471 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
472 472 applied to working directory
473 473 $ hg revert -a
474 474 reverting a
475 475
476 476
477 477 import with --no-commit should have written .hg/last-message.txt
478 478
479 479 $ cat .hg/last-message.txt
480 480 change (no-eol)
481 481
482 482
483 483 test fuzziness with eol=auto
484 484
485 485 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
486 486 applying fuzzy-tip.patch
487 487 patching file a
488 488 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
489 489 applied to working directory
490 490 $ cd ..
491 491
492 492
493 493 Test hunk touching empty files (issue906)
494 494
495 495 $ hg init empty
496 496 $ cd empty
497 497 $ touch a
498 498 $ touch b1
499 499 $ touch c1
500 500 $ echo d > d
501 501 $ hg ci -Am init
502 502 adding a
503 503 adding b1
504 504 adding c1
505 505 adding d
506 506 $ echo a > a
507 507 $ echo b > b1
508 508 $ hg mv b1 b2
509 509 $ echo c > c1
510 510 $ hg copy c1 c2
511 511 $ rm d
512 512 $ touch d
513 513 $ hg diff --git
514 514 diff --git a/a b/a
515 515 --- a/a
516 516 +++ b/a
517 517 @@ -0,0 +1,1 @@
518 518 +a
519 519 diff --git a/b1 b/b2
520 520 rename from b1
521 521 rename to b2
522 522 --- a/b1
523 523 +++ b/b2
524 524 @@ -0,0 +1,1 @@
525 525 +b
526 526 diff --git a/c1 b/c1
527 527 --- a/c1
528 528 +++ b/c1
529 529 @@ -0,0 +1,1 @@
530 530 +c
531 531 diff --git a/c1 b/c2
532 532 copy from c1
533 533 copy to c2
534 534 --- a/c1
535 535 +++ b/c2
536 536 @@ -0,0 +1,1 @@
537 537 +c
538 538 diff --git a/d b/d
539 539 --- a/d
540 540 +++ b/d
541 541 @@ -1,1 +0,0 @@
542 542 -d
543 543 $ hg ci -m empty
544 544 $ hg export --git tip > empty.diff
545 545 $ hg up -C 0
546 546 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
547 547 $ hg import empty.diff
548 548 applying empty.diff
549 549 $ for name in a b1 b2 c1 c2 d; do
550 550 > echo % $name file
551 551 > test -f $name && cat $name
552 552 > done
553 553 % a file
554 554 a
555 555 % b1 file
556 556 % b2 file
557 557 b
558 558 % c1 file
559 559 c
560 560 % c2 file
561 561 c
562 562 % d file
563 563 $ cd ..
564 564
565 565
566 566 Test importing a patch ending with a binary file removal
567 567
568 568 $ hg init binaryremoval
569 569 $ cd binaryremoval
570 570 $ echo a > a
571 571 $ python -c "file('b', 'wb').write('a\x00b')"
572 572 $ hg ci -Am addall
573 573 adding a
574 574 adding b
575 575 $ hg rm a
576 576 $ hg rm b
577 577 $ hg st
578 578 R a
579 579 R b
580 580 $ hg ci -m remove
581 581 $ hg export --git . > remove.diff
582 582 $ cat remove.diff | grep git
583 583 diff --git a/a b/a
584 584 diff --git a/b b/b
585 585 $ hg up -C 0
586 586 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 587 $ hg import remove.diff
588 588 applying remove.diff
589 589 $ hg manifest
590 590 $ cd ..
591 591
592 592
593 593 Issue927: test update+rename with common name
594 594
595 595 $ hg init t
596 596 $ cd t
597 597 $ touch a
598 598 $ hg ci -Am t
599 599 adding a
600 600 $ echo a > a
601 601
602 602 Here, bfile.startswith(afile)
603 603
604 604 $ hg copy a a2
605 605 $ hg ci -m copya
606 606 $ hg export --git tip > copy.diff
607 607 $ hg up -C 0
608 608 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
609 609 $ hg import copy.diff
610 610 applying copy.diff
611 611
612 612 a should contain an 'a'
613 613
614 614 $ cat a
615 615 a
616 616
617 617 and a2 should have duplicated it
618 618
619 619 $ cat a2
620 620 a
621 621 $ cd ..
622 622
623 623
624 624 test -p0
625 625
626 626 $ hg init p0
627 627 $ cd p0
628 628 $ echo a > a
629 629 $ hg ci -Am t
630 630 adding a
631 631 $ hg import -p foo
632 632 abort: invalid value 'foo' for option -p, expected int
633 633 [255]
634 634 $ hg import -p0 - << EOF
635 635 > foobar
636 636 > --- a Sat Apr 12 22:43:58 2008 -0400
637 637 > +++ a Sat Apr 12 22:44:05 2008 -0400
638 638 > @@ -1,1 +1,1 @@
639 639 > -a
640 640 > +bb
641 641 > EOF
642 642 applying patch from stdin
643 643 $ hg status
644 644 $ cat a
645 645 bb
646 646 $ cd ..
647 647
648 648
649 649 test paths outside repo root
650 650
651 651 $ mkdir outside
652 652 $ touch outside/foo
653 653 $ hg init inside
654 654 $ cd inside
655 655 $ hg import - <<EOF
656 656 > diff --git a/a b/b
657 657 > rename from ../outside/foo
658 658 > rename to bar
659 659 > EOF
660 660 applying patch from stdin
661 661 abort: path contains illegal component: ../outside/foo (glob)
662 662 [255]
663 663 $ cd ..
664 664
665 665
666 666 test import with similarity and git and strip (issue295 et al.)
667 667
668 668 $ hg init sim
669 669 $ cd sim
670 670 $ echo 'this is a test' > a
671 671 $ hg ci -Ama
672 672 adding a
673 673 $ cat > ../rename.diff <<EOF
674 674 > diff --git a/foo/a b/foo/a
675 675 > deleted file mode 100644
676 676 > --- a/foo/a
677 677 > +++ /dev/null
678 678 > @@ -1,1 +0,0 @@
679 679 > -this is a test
680 680 > diff --git a/foo/b b/foo/b
681 681 > new file mode 100644
682 682 > --- /dev/null
683 683 > +++ b/foo/b
684 684 > @@ -0,0 +1,2 @@
685 685 > +this is a test
686 686 > +foo
687 687 > EOF
688 688 $ hg import --no-commit -v -s 1 ../rename.diff -p2
689 689 applying ../rename.diff
690 690 patching file a
691 691 patching file b
692 692 adding b
693 693 recording removal of a as rename to b (88% similar)
694 694 applied to working directory
695 695 $ hg st -C
696 696 A b
697 697 a
698 698 R a
699 699 $ hg revert -a
700 700 undeleting a
701 701 forgetting b
702 702 $ rm b
703 703 $ hg import --no-commit -v -s 100 ../rename.diff -p2
704 704 applying ../rename.diff
705 705 patching file a
706 706 patching file b
707 707 adding b
708 708 applied to working directory
709 709 $ hg st -C
710 710 A b
711 711 R a
712 712 $ cd ..
713 713
714 714
715 715 Issue1495: add empty file from the end of patch
716 716
717 717 $ hg init addemptyend
718 718 $ cd addemptyend
719 719 $ touch a
720 720 $ hg addremove
721 721 adding a
722 722 $ hg ci -m "commit"
723 723 $ cat > a.patch <<EOF
724 724 > add a, b
725 725 > diff --git a/a b/a
726 726 > --- a/a
727 727 > +++ b/a
728 728 > @@ -0,0 +1,1 @@
729 729 > +a
730 730 > diff --git a/b b/b
731 731 > new file mode 100644
732 732 > EOF
733 733 $ hg import --no-commit a.patch
734 734 applying a.patch
735 735
736 736 apply a good patch followed by an empty patch (mainly to ensure
737 737 that dirstate is *not* updated when import crashes)
738 738 $ hg update -q -C .
739 739 $ rm b
740 740 $ touch empty.patch
741 741 $ hg import a.patch empty.patch
742 742 applying a.patch
743 743 applying empty.patch
744 744 transaction abort!
745 745 rollback completed
746 746 abort: empty.patch: no diffs found
747 747 [255]
748 748 $ hg tip --template '{rev} {desc|firstline}\n'
749 749 0 commit
750 750 $ hg -q status
751 751 M a
752 752 $ cd ..
753 753
754 754 create file when source is not /dev/null
755 755
756 756 $ cat > create.patch <<EOF
757 757 > diff -Naur proj-orig/foo proj-new/foo
758 758 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
759 759 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
760 760 > @@ -0,0 +1,1 @@
761 761 > +a
762 762 > EOF
763 763
764 764 some people have patches like the following too
765 765
766 766 $ cat > create2.patch <<EOF
767 767 > diff -Naur proj-orig/foo proj-new/foo
768 768 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
769 769 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
770 770 > @@ -0,0 +1,1 @@
771 771 > +a
772 772 > EOF
773 773 $ hg init oddcreate
774 774 $ cd oddcreate
775 775 $ hg import --no-commit ../create.patch
776 776 applying ../create.patch
777 777 $ cat foo
778 778 a
779 779 $ rm foo
780 780 $ hg revert foo
781 781 $ hg import --no-commit ../create2.patch
782 782 applying ../create2.patch
783 783 $ cat foo
784 784 a
785 785
786 786 $ cd ..
787 787
788 788 Issue1859: first line mistaken for email headers
789 789
790 790 $ hg init emailconfusion
791 791 $ cd emailconfusion
792 792 $ cat > a.patch <<EOF
793 793 > module: summary
794 794 >
795 795 > description
796 796 >
797 797 >
798 798 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
799 799 > --- /dev/null
800 800 > +++ b/a
801 801 > @@ -0,0 +1,1 @@
802 802 > +a
803 803 > EOF
804 804 $ hg import -d '0 0' a.patch
805 805 applying a.patch
806 806 $ hg parents -v
807 807 changeset: 0:5a681217c0ad
808 808 tag: tip
809 809 user: test
810 810 date: Thu Jan 01 00:00:00 1970 +0000
811 811 files: a
812 812 description:
813 813 module: summary
814 814
815 815 description
816 816
817 817
818 818 $ cd ..
819 819
820 820
821 821 in commit message
822 822
823 823 $ hg init commitconfusion
824 824 $ cd commitconfusion
825 825 $ cat > a.patch <<EOF
826 826 > module: summary
827 827 >
828 828 > --- description
829 829 >
830 830 > diff --git a/a b/a
831 831 > new file mode 100644
832 832 > --- /dev/null
833 833 > +++ b/a
834 834 > @@ -0,0 +1,1 @@
835 835 > +a
836 836 > EOF
837 837 > hg import -d '0 0' a.patch
838 838 > hg parents -v
839 839 > cd ..
840 840 >
841 841 > echo '% tricky header splitting'
842 842 > cat > trickyheaders.patch <<EOF
843 843 > From: User A <user@a>
844 844 > Subject: [PATCH] from: tricky!
845 845 >
846 846 > # HG changeset patch
847 847 > # User User B
848 848 > # Date 1266264441 18000
849 849 > # Branch stable
850 850 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
851 851 > # Parent 0000000000000000000000000000000000000000
852 852 > from: tricky!
853 853 >
854 854 > That is not a header.
855 855 >
856 856 > diff -r 000000000000 -r f2be6a1170ac foo
857 857 > --- /dev/null
858 858 > +++ b/foo
859 859 > @@ -0,0 +1,1 @@
860 860 > +foo
861 861 > EOF
862 862 applying a.patch
863 863 changeset: 0:f34d9187897d
864 864 tag: tip
865 865 user: test
866 866 date: Thu Jan 01 00:00:00 1970 +0000
867 867 files: a
868 868 description:
869 869 module: summary
870 870
871 871
872 872 % tricky header splitting
873 873
874 874 $ hg init trickyheaders
875 875 $ cd trickyheaders
876 876 $ hg import -d '0 0' ../trickyheaders.patch
877 877 applying ../trickyheaders.patch
878 878 $ hg export --git tip
879 879 # HG changeset patch
880 880 # User User B
881 881 # Date 0 0
882 882 # Thu Jan 01 00:00:00 1970 +0000
883 883 # Node ID eb56ab91903632294ac504838508cb370c0901d2
884 884 # Parent 0000000000000000000000000000000000000000
885 885 from: tricky!
886 886
887 887 That is not a header.
888 888
889 889 diff --git a/foo b/foo
890 890 new file mode 100644
891 891 --- /dev/null
892 892 +++ b/foo
893 893 @@ -0,0 +1,1 @@
894 894 +foo
895 895 $ cd ..
896 896
897 897
898 898 Issue2102: hg export and hg import speak different languages
899 899
900 900 $ hg init issue2102
901 901 $ cd issue2102
902 902 $ mkdir -p src/cmd/gc
903 903 $ touch src/cmd/gc/mksys.bash
904 904 $ hg ci -Am init
905 905 adding src/cmd/gc/mksys.bash
906 906 $ hg import - <<EOF
907 907 > # HG changeset patch
908 908 > # User Rob Pike
909 909 > # Date 1216685449 25200
910 910 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
911 911 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
912 912 > help management of empty pkg and lib directories in perforce
913 913 >
914 914 > R=gri
915 915 > DELTA=4 (4 added, 0 deleted, 0 changed)
916 916 > OCL=13328
917 917 > CL=13328
918 918 >
919 919 > diff --git a/lib/place-holder b/lib/place-holder
920 920 > new file mode 100644
921 921 > --- /dev/null
922 922 > +++ b/lib/place-holder
923 923 > @@ -0,0 +1,2 @@
924 924 > +perforce does not maintain empty directories.
925 925 > +this file helps.
926 926 > diff --git a/pkg/place-holder b/pkg/place-holder
927 927 > new file mode 100644
928 928 > --- /dev/null
929 929 > +++ b/pkg/place-holder
930 930 > @@ -0,0 +1,2 @@
931 931 > +perforce does not maintain empty directories.
932 932 > +this file helps.
933 933 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
934 934 > old mode 100644
935 935 > new mode 100755
936 936 > EOF
937 937 applying patch from stdin
938 938
939 939 #if execbit
940 940
941 941 $ hg sum
942 942 parent: 1:d59915696727 tip
943 943 help management of empty pkg and lib directories in perforce
944 944 branch: default
945 945 commit: (clean)
946 946 update: (current)
947 947
948 948 $ hg diff --git -c tip
949 949 diff --git a/lib/place-holder b/lib/place-holder
950 950 new file mode 100644
951 951 --- /dev/null
952 952 +++ b/lib/place-holder
953 953 @@ -0,0 +1,2 @@
954 954 +perforce does not maintain empty directories.
955 955 +this file helps.
956 956 diff --git a/pkg/place-holder b/pkg/place-holder
957 957 new file mode 100644
958 958 --- /dev/null
959 959 +++ b/pkg/place-holder
960 960 @@ -0,0 +1,2 @@
961 961 +perforce does not maintain empty directories.
962 962 +this file helps.
963 963 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
964 964 old mode 100644
965 965 new mode 100755
966 966
967 967 #else
968 968
969 969 $ hg sum
970 970 parent: 1:28f089cc9ccc tip
971 971 help management of empty pkg and lib directories in perforce
972 972 branch: default
973 973 commit: (clean)
974 974 update: (current)
975 975
976 976 $ hg diff --git -c tip
977 977 diff --git a/lib/place-holder b/lib/place-holder
978 978 new file mode 100644
979 979 --- /dev/null
980 980 +++ b/lib/place-holder
981 981 @@ -0,0 +1,2 @@
982 982 +perforce does not maintain empty directories.
983 983 +this file helps.
984 984 diff --git a/pkg/place-holder b/pkg/place-holder
985 985 new file mode 100644
986 986 --- /dev/null
987 987 +++ b/pkg/place-holder
988 988 @@ -0,0 +1,2 @@
989 989 +perforce does not maintain empty directories.
990 990 +this file helps.
991 991
992 992 /* The mode change for mksys.bash is missing here, because on platforms */
993 993 /* that don't support execbits, mode changes in patches are ignored when */
994 994 /* they are imported. This is obviously also the reason for why the hash */
995 995 /* in the created changeset is different to the one you see above the */
996 996 /* #else clause */
997 997
998 998 #endif
999 999 $ cd ..
1000 1000
1001 1001
1002 1002 diff lines looking like headers
1003 1003
1004 1004 $ hg init difflineslikeheaders
1005 1005 $ cd difflineslikeheaders
1006 1006 $ echo a >a
1007 1007 $ echo b >b
1008 1008 $ echo c >c
1009 1009 $ hg ci -Am1
1010 1010 adding a
1011 1011 adding b
1012 1012 adding c
1013 1013
1014 1014 $ echo "key: value" >>a
1015 1015 $ echo "key: value" >>b
1016 1016 $ echo "foo" >>c
1017 1017 $ hg ci -m2
1018 1018
1019 1019 $ hg up -C 0
1020 1020 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1021 1021 $ hg diff --git -c1 >want
1022 1022 $ hg diff -c1 | hg import --no-commit -
1023 1023 applying patch from stdin
1024 1024 $ hg diff --git >have
1025 1025 $ diff want have
1026 1026 $ cd ..
1027 1027
1028 1028 import a unified diff with no lines of context (diff -U0)
1029 1029
1030 1030 $ hg init diffzero
1031 1031 $ cd diffzero
1032 1032 $ cat > f << EOF
1033 1033 > c2
1034 1034 > c4
1035 1035 > c5
1036 1036 > EOF
1037 1037 $ hg commit -Am0
1038 1038 adding f
1039 1039
1040 1040 $ hg import --no-commit - << EOF
1041 1041 > # HG changeset patch
1042 1042 > # User test
1043 1043 > # Date 0 0
1044 1044 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1045 1045 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1046 1046 > 1
1047 1047 > diff -r 8679a12a975b -r f4974ab632f3 f
1048 1048 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1049 1049 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1050 1050 > @@ -0,0 +1,1 @@
1051 1051 > +c1
1052 1052 > @@ -1,0 +3,1 @@
1053 1053 > +c3
1054 1054 > @@ -3,1 +4,0 @@
1055 1055 > -c5
1056 1056 > EOF
1057 1057 applying patch from stdin
1058 1058
1059 1059 $ cat f
1060 1060 c1
1061 1061 c2
1062 1062 c3
1063 1063 c4
1064 1064
1065 1065 $ cd ..
1066 1066
1067 1067 no segfault while importing a unified diff which start line is zero but chunk
1068 1068 size is non-zero
1069 1069
1070 1070 $ hg init startlinezero
1071 1071 $ cd startlinezero
1072 1072 $ echo foo > foo
1073 1073 $ hg commit -Amfoo
1074 1074 adding foo
1075 1075
1076 1076 $ hg import --no-commit - << EOF
1077 1077 > diff a/foo b/foo
1078 1078 > --- a/foo
1079 1079 > +++ b/foo
1080 1080 > @@ -0,1 +0,1 @@
1081 1081 > foo
1082 1082 > EOF
1083 1083 applying patch from stdin
1084 1084
1085 1085 $ cd ..
1086 1086
1087 1087 Test corner case involving fuzz and skew
1088 1088
1089 1089 $ hg init morecornercases
1090 1090 $ cd morecornercases
1091 1091
1092 1092 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1093 1093 > diff --git a/a b/a
1094 1094 > --- a/a
1095 1095 > +++ b/a
1096 1096 > @@ -1,0 +1,1 @@
1097 1097 > +line
1098 1098 > EOF
1099 1099
1100 1100 $ cat > 02-no-context-middle-of-file.diff <<EOF
1101 1101 > diff --git a/a b/a
1102 1102 > --- a/a
1103 1103 > +++ b/a
1104 1104 > @@ -1,1 +1,1 @@
1105 1105 > -2
1106 1106 > +add some skew
1107 1107 > @@ -2,0 +2,1 @@
1108 1108 > +line
1109 1109 > EOF
1110 1110
1111 1111 $ cat > 03-no-context-end-of-file.diff <<EOF
1112 1112 > diff --git a/a b/a
1113 1113 > --- a/a
1114 1114 > +++ b/a
1115 1115 > @@ -10,0 +10,1 @@
1116 1116 > +line
1117 1117 > EOF
1118 1118
1119 1119 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1120 1120 > diff --git a/a b/a
1121 1121 > --- a/a
1122 1122 > +++ b/a
1123 1123 > @@ -1,1 +1,1 @@
1124 1124 > -2
1125 1125 > +add some skew
1126 1126 > @@ -2,2 +2,3 @@
1127 1127 > not matching, should fuzz
1128 1128 > ... a bit
1129 1129 > +line
1130 1130 > EOF
1131 1131
1132 1132 $ cat > a <<EOF
1133 1133 > 1
1134 1134 > 2
1135 1135 > 3
1136 1136 > 4
1137 1137 > EOF
1138 1138 $ hg ci -Am adda a
1139 1139 $ for p in *.diff; do
1140 1140 > hg import -v --no-commit $p
1141 1141 > cat a
1142 1142 > hg revert -aqC a
1143 1143 > # patch -p1 < $p
1144 1144 > # cat a
1145 1145 > # hg revert -aC a
1146 1146 > done
1147 1147 applying 01-no-context-beginning-of-file.diff
1148 1148 patching file a
1149 1149 applied to working directory
1150 1150 1
1151 1151 line
1152 1152 2
1153 1153 3
1154 1154 4
1155 1155 applying 02-no-context-middle-of-file.diff
1156 1156 patching file a
1157 1157 Hunk #1 succeeded at 2 (offset 1 lines).
1158 1158 Hunk #2 succeeded at 4 (offset 1 lines).
1159 1159 applied to working directory
1160 1160 1
1161 1161 add some skew
1162 1162 3
1163 1163 line
1164 1164 4
1165 1165 applying 03-no-context-end-of-file.diff
1166 1166 patching file a
1167 1167 Hunk #1 succeeded at 5 (offset -6 lines).
1168 1168 applied to working directory
1169 1169 1
1170 1170 2
1171 1171 3
1172 1172 4
1173 1173 line
1174 1174 applying 04-middle-of-file-completely-fuzzed.diff
1175 1175 patching file a
1176 1176 Hunk #1 succeeded at 2 (offset 1 lines).
1177 1177 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1178 1178 applied to working directory
1179 1179 1
1180 1180 add some skew
1181 1181 3
1182 1182 4
1183 1183 line
1184 $ cd ..
1184 1185
1185 $ cd ..
1186 Test partial application
1187 ------------------------
1188
1189 prepare a stack of patches depending on each other
1190
1191 $ hg init partial
1192 $ cd partial
1193 $ cat << EOF > a
1194 > one
1195 > two
1196 > three
1197 > four
1198 > five
1199 > six
1200 > seven
1201 > EOF
1202 $ hg add a
1203 $ echo 'b' > b
1204 $ hg add b
1205 $ hg commit -m 'initial' -u Babar
1206 $ cat << EOF > a
1207 > one
1208 > two
1209 > 3
1210 > four
1211 > five
1212 > six
1213 > seven
1214 > EOF
1215 $ hg commit -m 'three' -u Celeste
1216 $ cat << EOF > a
1217 > one
1218 > two
1219 > 3
1220 > 4
1221 > five
1222 > six
1223 > seven
1224 > EOF
1225 $ hg commit -m 'four' -u Rataxes
1226 $ cat << EOF > a
1227 > one
1228 > two
1229 > 3
1230 > 4
1231 > 5
1232 > six
1233 > seven
1234 > EOF
1235 $ echo bb >> b
1236 $ hg commit -m 'five' -u Arthur
1237 $ echo 'Babar' > jungle
1238 $ hg add jungle
1239 $ hg ci -m 'jungle' -u Zephir
1240 $ echo 'Celeste' >> jungle
1241 $ hg ci -m 'extended jungle' -u Cornelius
1242 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1243 @ extended jungle [Cornelius] 1: +1/-0
1244 |
1245 o jungle [Zephir] 1: +1/-0
1246 |
1247 o five [Arthur] 2: +2/-1
1248 |
1249 o four [Rataxes] 1: +1/-1
1250 |
1251 o three [Celeste] 1: +1/-1
1252 |
1253 o initial [Babar] 2: +8/-0
1254
1255
1256 Importing with some success and some errors:
1257
1258 $ hg update --rev 'desc(initial)'
1259 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1260 $ hg export --rev 'desc(five)' | hg import --partial -
1261 applying patch from stdin
1262 patching file a
1263 Hunk #1 FAILED at 1
1264 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1265 patch applied partially
1266 (fix the .rej files and run `hg commit --amend`)
1267 [1]
1268
1269 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1270 @ five [Arthur] 1: +1/-0
1271 |
1272 | o extended jungle [Cornelius] 1: +1/-0
1273 | |
1274 | o jungle [Zephir] 1: +1/-0
1275 | |
1276 | o five [Arthur] 2: +2/-1
1277 | |
1278 | o four [Rataxes] 1: +1/-1
1279 | |
1280 | o three [Celeste] 1: +1/-1
1281 |/
1282 o initial [Babar] 2: +8/-0
1283
1284 $ hg export
1285 # HG changeset patch
1286 # User Arthur
1287 # Date 0 0
1288 # Thu Jan 01 00:00:00 1970 +0000
1289 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1290 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1291 five
1292
1293 diff -r 8e4f0351909e -r 26e6446bb252 b
1294 --- a/b Thu Jan 01 00:00:00 1970 +0000
1295 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1296 @@ -1,1 +1,2 @@
1297 b
1298 +bb
1299 $ hg status -c .
1300 C a
1301 C b
1302 $ ls
1303 a
1304 a.rej
1305 b
1306
1307 Importing with zero success:
1308
1309 $ hg update --rev 'desc(initial)'
1310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1311 $ hg export --rev 'desc(four)' | hg import --partial -
1312 applying patch from stdin
1313 patching file a
1314 Hunk #1 FAILED at 0
1315 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1316 patch applied partially
1317 (fix the .rej files and run `hg commit --amend`)
1318 [1]
1319
1320 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1321 @ four [Rataxes] 0: +0/-0
1322 |
1323 | o five [Arthur] 1: +1/-0
1324 |/
1325 | o extended jungle [Cornelius] 1: +1/-0
1326 | |
1327 | o jungle [Zephir] 1: +1/-0
1328 | |
1329 | o five [Arthur] 2: +2/-1
1330 | |
1331 | o four [Rataxes] 1: +1/-1
1332 | |
1333 | o three [Celeste] 1: +1/-1
1334 |/
1335 o initial [Babar] 2: +8/-0
1336
1337 $ hg export
1338 # HG changeset patch
1339 # User Rataxes
1340 # Date 0 0
1341 # Thu Jan 01 00:00:00 1970 +0000
1342 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1343 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1344 four
1345
1346 $ hg status -c .
1347 C a
1348 C b
1349 $ ls
1350 a
1351 a.rej
1352 b
1353
1354 Importing with unknown file:
1355
1356 $ hg update --rev 'desc(initial)'
1357 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1358 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1359 applying patch from stdin
1360 unable to find 'jungle' for patching
1361 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1362 patch applied partially
1363 (fix the .rej files and run `hg commit --amend`)
1364 [1]
1365
1366 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1367 @ extended jungle [Cornelius] 0: +0/-0
1368 |
1369 | o four [Rataxes] 0: +0/-0
1370 |/
1371 | o five [Arthur] 1: +1/-0
1372 |/
1373 | o extended jungle [Cornelius] 1: +1/-0
1374 | |
1375 | o jungle [Zephir] 1: +1/-0
1376 | |
1377 | o five [Arthur] 2: +2/-1
1378 | |
1379 | o four [Rataxes] 1: +1/-1
1380 | |
1381 | o three [Celeste] 1: +1/-1
1382 |/
1383 o initial [Babar] 2: +8/-0
1384
1385 $ hg export
1386 # HG changeset patch
1387 # User Cornelius
1388 # Date 0 0
1389 # Thu Jan 01 00:00:00 1970 +0000
1390 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1391 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1392 extended jungle
1393
1394 $ hg status -c .
1395 C a
1396 C b
1397 $ ls
1398 a
1399 a.rej
1400 b
1401 jungle.rej
1402
1403 Importing multiple failing patches:
1404
1405 $ hg update --rev 'desc(initial)'
1406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1407 $ echo 'B' > b # just to make another commit
1408 $ hg commit -m "a new base"
1409 created new head
1410 $ hg export --rev 'desc("extended jungle") + desc("four")' | hg import --partial -
1411 applying patch from stdin
1412 patching file a
1413 Hunk #1 FAILED at 0
1414 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1415 patch applied partially
1416 (fix the .rej files and run `hg commit --amend`)
1417 [1]
1418 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1419 @ four [Rataxes] 0: +0/-0
1420 |
1421 o a new base [test] 1: +1/-1
1422 |
1423 | o extended jungle [Cornelius] 0: +0/-0
1424 |/
1425 | o four [Rataxes] 0: +0/-0
1426 |/
1427 | o five [Arthur] 1: +1/-0
1428 |/
1429 | o extended jungle [Cornelius] 1: +1/-0
1430 | |
1431 | o jungle [Zephir] 1: +1/-0
1432 | |
1433 | o five [Arthur] 2: +2/-1
1434 | |
1435 | o four [Rataxes] 1: +1/-1
1436 | |
1437 | o three [Celeste] 1: +1/-1
1438 |/
1439 o initial [Babar] 2: +8/-0
1440
1441 $ hg export
1442 # HG changeset patch
1443 # User Rataxes
1444 # Date 0 0
1445 # Thu Jan 01 00:00:00 1970 +0000
1446 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1447 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1448 four
1449
1450 $ hg status -c .
1451 C a
1452 C b
General Comments 0
You need to be logged in to leave comments. Login now