##// END OF EJS Templates
import: show the warning message for failure of merging...
FUJIWARA Katsunori -
r22303:0c838e74 stable
parent child Browse files
Show More
@@ -1,2629 +1,2632
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 594 return (None, None, False)
595 595 msg = _('applied to working directory')
596 596
597 597 rejects = False
598 598
599 599 try:
600 600 cmdline_message = logmessage(ui, opts)
601 601 if cmdline_message:
602 602 # pickup the cmdline msg
603 603 message = cmdline_message
604 604 elif message:
605 605 # pickup the patch msg
606 606 message = message.strip()
607 607 else:
608 608 # launch the editor
609 609 message = None
610 610 ui.debug('message:\n%s\n' % message)
611 611
612 612 if len(parents) == 1:
613 613 parents.append(repo[nullid])
614 614 if opts.get('exact'):
615 615 if not nodeid or not p1:
616 616 raise util.Abort(_('not a Mercurial patch'))
617 617 p1 = repo[p1]
618 618 p2 = repo[p2 or nullid]
619 619 elif p2:
620 620 try:
621 621 p1 = repo[p1]
622 622 p2 = repo[p2]
623 623 # Without any options, consider p2 only if the
624 624 # patch is being applied on top of the recorded
625 625 # first parent.
626 626 if p1 != parents[0]:
627 627 p1 = parents[0]
628 628 p2 = repo[nullid]
629 629 except error.RepoError:
630 630 p1, p2 = parents
631 if p2.node() == nullid:
632 ui.warn(_("warning: import the patch as a normal revision\n"
633 "(use --exact to import the patch as a merge)\n"))
631 634 else:
632 635 p1, p2 = parents
633 636
634 637 n = None
635 638 if update:
636 639 if p1 != parents[0]:
637 640 updatefunc(repo, p1.node())
638 641 if p2 != parents[1]:
639 642 repo.setparents(p1.node(), p2.node())
640 643
641 644 if opts.get('exact') or opts.get('import_branch'):
642 645 repo.dirstate.setbranch(branch or 'default')
643 646
644 647 partial = opts.get('partial', False)
645 648 files = set()
646 649 try:
647 650 patch.patch(ui, repo, tmpname, strip=strip, files=files,
648 651 eolmode=None, similarity=sim / 100.0)
649 652 except patch.PatchError, e:
650 653 if not partial:
651 654 raise util.Abort(str(e))
652 655 if partial:
653 656 rejects = True
654 657
655 658 files = list(files)
656 659 if opts.get('no_commit'):
657 660 if message:
658 661 msgs.append(message)
659 662 else:
660 663 if opts.get('exact') or p2:
661 664 # If you got here, you either use --force and know what
662 665 # you are doing or used --exact or a merge patch while
663 666 # being updated to its first parent.
664 667 m = None
665 668 else:
666 669 m = scmutil.matchfiles(repo, files or [])
667 670 n = repo.commit(message, opts.get('user') or user,
668 671 opts.get('date') or date, match=m,
669 672 editor=editor, force=partial)
670 673 else:
671 674 if opts.get('exact') or opts.get('import_branch'):
672 675 branch = branch or 'default'
673 676 else:
674 677 branch = p1.branch()
675 678 store = patch.filestore()
676 679 try:
677 680 files = set()
678 681 try:
679 682 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
680 683 files, eolmode=None)
681 684 except patch.PatchError, e:
682 685 raise util.Abort(str(e))
683 686 memctx = context.makememctx(repo, (p1.node(), p2.node()),
684 687 message,
685 688 opts.get('user') or user,
686 689 opts.get('date') or date,
687 690 branch, files, store,
688 691 editor=getcommiteditor())
689 692 n = memctx.commit()
690 693 finally:
691 694 store.close()
692 695 if opts.get('exact') and hex(n) != nodeid:
693 696 raise util.Abort(_('patch is damaged or loses information'))
694 697 if n:
695 698 # i18n: refers to a short changeset id
696 699 msg = _('created %s') % short(n)
697 700 return (msg, n, rejects)
698 701 finally:
699 702 os.unlink(tmpname)
700 703
701 704 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
702 705 opts=None):
703 706 '''export changesets as hg patches.'''
704 707
705 708 total = len(revs)
706 709 revwidth = max([len(str(rev)) for rev in revs])
707 710 filemode = {}
708 711
709 712 def single(rev, seqno, fp):
710 713 ctx = repo[rev]
711 714 node = ctx.node()
712 715 parents = [p.node() for p in ctx.parents() if p]
713 716 branch = ctx.branch()
714 717 if switch_parent:
715 718 parents.reverse()
716 719 prev = (parents and parents[0]) or nullid
717 720
718 721 shouldclose = False
719 722 if not fp and len(template) > 0:
720 723 desc_lines = ctx.description().rstrip().split('\n')
721 724 desc = desc_lines[0] #Commit always has a first line.
722 725 fp = makefileobj(repo, template, node, desc=desc, total=total,
723 726 seqno=seqno, revwidth=revwidth, mode='wb',
724 727 modemap=filemode)
725 728 if fp != template:
726 729 shouldclose = True
727 730 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
728 731 repo.ui.note("%s\n" % fp.name)
729 732
730 733 if not fp:
731 734 write = repo.ui.write
732 735 else:
733 736 def write(s, **kw):
734 737 fp.write(s)
735 738
736 739
737 740 write("# HG changeset patch\n")
738 741 write("# User %s\n" % ctx.user())
739 742 write("# Date %d %d\n" % ctx.date())
740 743 write("# %s\n" % util.datestr(ctx.date()))
741 744 if branch and branch != 'default':
742 745 write("# Branch %s\n" % branch)
743 746 write("# Node ID %s\n" % hex(node))
744 747 write("# Parent %s\n" % hex(prev))
745 748 if len(parents) > 1:
746 749 write("# Parent %s\n" % hex(parents[1]))
747 750 write(ctx.description().rstrip())
748 751 write("\n\n")
749 752
750 753 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
751 754 write(chunk, label=label)
752 755
753 756 if shouldclose:
754 757 fp.close()
755 758
756 759 for seqno, rev in enumerate(revs):
757 760 single(rev, seqno + 1, fp)
758 761
759 762 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
760 763 changes=None, stat=False, fp=None, prefix='',
761 764 listsubrepos=False):
762 765 '''show diff or diffstat.'''
763 766 if fp is None:
764 767 write = ui.write
765 768 else:
766 769 def write(s, **kw):
767 770 fp.write(s)
768 771
769 772 if stat:
770 773 diffopts = diffopts.copy(context=0)
771 774 width = 80
772 775 if not ui.plain():
773 776 width = ui.termwidth()
774 777 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
775 778 prefix=prefix)
776 779 for chunk, label in patch.diffstatui(util.iterlines(chunks),
777 780 width=width,
778 781 git=diffopts.git):
779 782 write(chunk, label=label)
780 783 else:
781 784 for chunk, label in patch.diffui(repo, node1, node2, match,
782 785 changes, diffopts, prefix=prefix):
783 786 write(chunk, label=label)
784 787
785 788 if listsubrepos:
786 789 ctx1 = repo[node1]
787 790 ctx2 = repo[node2]
788 791 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
789 792 tempnode2 = node2
790 793 try:
791 794 if node2 is not None:
792 795 tempnode2 = ctx2.substate[subpath][1]
793 796 except KeyError:
794 797 # A subrepo that existed in node1 was deleted between node1 and
795 798 # node2 (inclusive). Thus, ctx2's substate won't contain that
796 799 # subpath. The best we can do is to ignore it.
797 800 tempnode2 = None
798 801 submatch = matchmod.narrowmatcher(subpath, match)
799 802 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
800 803 stat=stat, fp=fp, prefix=prefix)
801 804
802 805 class changeset_printer(object):
803 806 '''show changeset information when templating not requested.'''
804 807
805 808 def __init__(self, ui, repo, patch, diffopts, buffered):
806 809 self.ui = ui
807 810 self.repo = repo
808 811 self.buffered = buffered
809 812 self.patch = patch
810 813 self.diffopts = diffopts
811 814 self.header = {}
812 815 self.hunk = {}
813 816 self.lastheader = None
814 817 self.footer = None
815 818
816 819 def flush(self, rev):
817 820 if rev in self.header:
818 821 h = self.header[rev]
819 822 if h != self.lastheader:
820 823 self.lastheader = h
821 824 self.ui.write(h)
822 825 del self.header[rev]
823 826 if rev in self.hunk:
824 827 self.ui.write(self.hunk[rev])
825 828 del self.hunk[rev]
826 829 return 1
827 830 return 0
828 831
829 832 def close(self):
830 833 if self.footer:
831 834 self.ui.write(self.footer)
832 835
833 836 def show(self, ctx, copies=None, matchfn=None, **props):
834 837 if self.buffered:
835 838 self.ui.pushbuffer()
836 839 self._show(ctx, copies, matchfn, props)
837 840 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
838 841 else:
839 842 self._show(ctx, copies, matchfn, props)
840 843
841 844 def _show(self, ctx, copies, matchfn, props):
842 845 '''show a single changeset or file revision'''
843 846 changenode = ctx.node()
844 847 rev = ctx.rev()
845 848
846 849 if self.ui.quiet:
847 850 self.ui.write("%d:%s\n" % (rev, short(changenode)),
848 851 label='log.node')
849 852 return
850 853
851 854 log = self.repo.changelog
852 855 date = util.datestr(ctx.date())
853 856
854 857 hexfunc = self.ui.debugflag and hex or short
855 858
856 859 parents = [(p, hexfunc(log.node(p)))
857 860 for p in self._meaningful_parentrevs(log, rev)]
858 861
859 862 # i18n: column positioning for "hg log"
860 863 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
861 864 label='log.changeset changeset.%s' % ctx.phasestr())
862 865
863 866 branch = ctx.branch()
864 867 # don't show the default branch name
865 868 if branch != 'default':
866 869 # i18n: column positioning for "hg log"
867 870 self.ui.write(_("branch: %s\n") % branch,
868 871 label='log.branch')
869 872 for bookmark in self.repo.nodebookmarks(changenode):
870 873 # i18n: column positioning for "hg log"
871 874 self.ui.write(_("bookmark: %s\n") % bookmark,
872 875 label='log.bookmark')
873 876 for tag in self.repo.nodetags(changenode):
874 877 # i18n: column positioning for "hg log"
875 878 self.ui.write(_("tag: %s\n") % tag,
876 879 label='log.tag')
877 880 if self.ui.debugflag and ctx.phase():
878 881 # i18n: column positioning for "hg log"
879 882 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
880 883 label='log.phase')
881 884 for parent in parents:
882 885 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
883 886 # i18n: column positioning for "hg log"
884 887 self.ui.write(_("parent: %d:%s\n") % parent,
885 888 label=label)
886 889
887 890 if self.ui.debugflag:
888 891 mnode = ctx.manifestnode()
889 892 # i18n: column positioning for "hg log"
890 893 self.ui.write(_("manifest: %d:%s\n") %
891 894 (self.repo.manifest.rev(mnode), hex(mnode)),
892 895 label='ui.debug log.manifest')
893 896 # i18n: column positioning for "hg log"
894 897 self.ui.write(_("user: %s\n") % ctx.user(),
895 898 label='log.user')
896 899 # i18n: column positioning for "hg log"
897 900 self.ui.write(_("date: %s\n") % date,
898 901 label='log.date')
899 902
900 903 if self.ui.debugflag:
901 904 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
902 905 for key, value in zip([# i18n: column positioning for "hg log"
903 906 _("files:"),
904 907 # i18n: column positioning for "hg log"
905 908 _("files+:"),
906 909 # i18n: column positioning for "hg log"
907 910 _("files-:")], files):
908 911 if value:
909 912 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
910 913 label='ui.debug log.files')
911 914 elif ctx.files() and self.ui.verbose:
912 915 # i18n: column positioning for "hg log"
913 916 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
914 917 label='ui.note log.files')
915 918 if copies and self.ui.verbose:
916 919 copies = ['%s (%s)' % c for c in copies]
917 920 # i18n: column positioning for "hg log"
918 921 self.ui.write(_("copies: %s\n") % ' '.join(copies),
919 922 label='ui.note log.copies')
920 923
921 924 extra = ctx.extra()
922 925 if extra and self.ui.debugflag:
923 926 for key, value in sorted(extra.items()):
924 927 # i18n: column positioning for "hg log"
925 928 self.ui.write(_("extra: %s=%s\n")
926 929 % (key, value.encode('string_escape')),
927 930 label='ui.debug log.extra')
928 931
929 932 description = ctx.description().strip()
930 933 if description:
931 934 if self.ui.verbose:
932 935 self.ui.write(_("description:\n"),
933 936 label='ui.note log.description')
934 937 self.ui.write(description,
935 938 label='ui.note log.description')
936 939 self.ui.write("\n\n")
937 940 else:
938 941 # i18n: column positioning for "hg log"
939 942 self.ui.write(_("summary: %s\n") %
940 943 description.splitlines()[0],
941 944 label='log.summary')
942 945 self.ui.write("\n")
943 946
944 947 self.showpatch(changenode, matchfn)
945 948
946 949 def showpatch(self, node, matchfn):
947 950 if not matchfn:
948 951 matchfn = self.patch
949 952 if matchfn:
950 953 stat = self.diffopts.get('stat')
951 954 diff = self.diffopts.get('patch')
952 955 diffopts = patch.diffopts(self.ui, self.diffopts)
953 956 prev = self.repo.changelog.parents(node)[0]
954 957 if stat:
955 958 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
956 959 match=matchfn, stat=True)
957 960 if diff:
958 961 if stat:
959 962 self.ui.write("\n")
960 963 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
961 964 match=matchfn, stat=False)
962 965 self.ui.write("\n")
963 966
964 967 def _meaningful_parentrevs(self, log, rev):
965 968 """Return list of meaningful (or all if debug) parentrevs for rev.
966 969
967 970 For merges (two non-nullrev revisions) both parents are meaningful.
968 971 Otherwise the first parent revision is considered meaningful if it
969 972 is not the preceding revision.
970 973 """
971 974 parents = log.parentrevs(rev)
972 975 if not self.ui.debugflag and parents[1] == nullrev:
973 976 if parents[0] >= rev - 1:
974 977 parents = []
975 978 else:
976 979 parents = [parents[0]]
977 980 return parents
978 981
979 982
980 983 class changeset_templater(changeset_printer):
981 984 '''format changeset information.'''
982 985
983 986 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
984 987 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
985 988 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
986 989 defaulttempl = {
987 990 'parent': '{rev}:{node|formatnode} ',
988 991 'manifest': '{rev}:{node|formatnode}',
989 992 'file_copy': '{name} ({source})',
990 993 'extra': '{key}={value|stringescape}'
991 994 }
992 995 # filecopy is preserved for compatibility reasons
993 996 defaulttempl['filecopy'] = defaulttempl['file_copy']
994 997 self.t = templater.templater(mapfile, {'formatnode': formatnode},
995 998 cache=defaulttempl)
996 999 if tmpl:
997 1000 self.t.cache['changeset'] = tmpl
998 1001
999 1002 self.cache = {}
1000 1003
1001 1004 def _meaningful_parentrevs(self, ctx):
1002 1005 """Return list of meaningful (or all if debug) parentrevs for rev.
1003 1006 """
1004 1007 parents = ctx.parents()
1005 1008 if len(parents) > 1:
1006 1009 return parents
1007 1010 if self.ui.debugflag:
1008 1011 return [parents[0], self.repo['null']]
1009 1012 if parents[0].rev() >= ctx.rev() - 1:
1010 1013 return []
1011 1014 return parents
1012 1015
1013 1016 def _show(self, ctx, copies, matchfn, props):
1014 1017 '''show a single changeset or file revision'''
1015 1018
1016 1019 showlist = templatekw.showlist
1017 1020
1018 1021 # showparents() behaviour depends on ui trace level which
1019 1022 # causes unexpected behaviours at templating level and makes
1020 1023 # it harder to extract it in a standalone function. Its
1021 1024 # behaviour cannot be changed so leave it here for now.
1022 1025 def showparents(**args):
1023 1026 ctx = args['ctx']
1024 1027 parents = [[('rev', p.rev()), ('node', p.hex())]
1025 1028 for p in self._meaningful_parentrevs(ctx)]
1026 1029 return showlist('parent', parents, **args)
1027 1030
1028 1031 props = props.copy()
1029 1032 props.update(templatekw.keywords)
1030 1033 props['parents'] = showparents
1031 1034 props['templ'] = self.t
1032 1035 props['ctx'] = ctx
1033 1036 props['repo'] = self.repo
1034 1037 props['revcache'] = {'copies': copies}
1035 1038 props['cache'] = self.cache
1036 1039
1037 1040 # find correct templates for current mode
1038 1041
1039 1042 tmplmodes = [
1040 1043 (True, None),
1041 1044 (self.ui.verbose, 'verbose'),
1042 1045 (self.ui.quiet, 'quiet'),
1043 1046 (self.ui.debugflag, 'debug'),
1044 1047 ]
1045 1048
1046 1049 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1047 1050 for mode, postfix in tmplmodes:
1048 1051 for type in types:
1049 1052 cur = postfix and ('%s_%s' % (type, postfix)) or type
1050 1053 if mode and cur in self.t:
1051 1054 types[type] = cur
1052 1055
1053 1056 try:
1054 1057
1055 1058 # write header
1056 1059 if types['header']:
1057 1060 h = templater.stringify(self.t(types['header'], **props))
1058 1061 if self.buffered:
1059 1062 self.header[ctx.rev()] = h
1060 1063 else:
1061 1064 if self.lastheader != h:
1062 1065 self.lastheader = h
1063 1066 self.ui.write(h)
1064 1067
1065 1068 # write changeset metadata, then patch if requested
1066 1069 key = types['changeset']
1067 1070 self.ui.write(templater.stringify(self.t(key, **props)))
1068 1071 self.showpatch(ctx.node(), matchfn)
1069 1072
1070 1073 if types['footer']:
1071 1074 if not self.footer:
1072 1075 self.footer = templater.stringify(self.t(types['footer'],
1073 1076 **props))
1074 1077
1075 1078 except KeyError, inst:
1076 1079 msg = _("%s: no key named '%s'")
1077 1080 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1078 1081 except SyntaxError, inst:
1079 1082 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1080 1083
1081 1084 def gettemplate(ui, tmpl, style):
1082 1085 """
1083 1086 Find the template matching the given template spec or style.
1084 1087 """
1085 1088
1086 1089 # ui settings
1087 1090 if not tmpl and not style:
1088 1091 tmpl = ui.config('ui', 'logtemplate')
1089 1092 if tmpl:
1090 1093 try:
1091 1094 tmpl = templater.parsestring(tmpl)
1092 1095 except SyntaxError:
1093 1096 tmpl = templater.parsestring(tmpl, quoted=False)
1094 1097 return tmpl, None
1095 1098 else:
1096 1099 style = util.expandpath(ui.config('ui', 'style', ''))
1097 1100
1098 1101 if style:
1099 1102 mapfile = style
1100 1103 if not os.path.split(mapfile)[0]:
1101 1104 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1102 1105 or templater.templatepath(mapfile))
1103 1106 if mapname:
1104 1107 mapfile = mapname
1105 1108 return None, mapfile
1106 1109
1107 1110 if not tmpl:
1108 1111 return None, None
1109 1112
1110 1113 # looks like a literal template?
1111 1114 if '{' in tmpl:
1112 1115 return tmpl, None
1113 1116
1114 1117 # perhaps a stock style?
1115 1118 if not os.path.split(tmpl)[0]:
1116 1119 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1117 1120 or templater.templatepath(tmpl))
1118 1121 if mapname and os.path.isfile(mapname):
1119 1122 return None, mapname
1120 1123
1121 1124 # perhaps it's a reference to [templates]
1122 1125 t = ui.config('templates', tmpl)
1123 1126 if t:
1124 1127 try:
1125 1128 tmpl = templater.parsestring(t)
1126 1129 except SyntaxError:
1127 1130 tmpl = templater.parsestring(t, quoted=False)
1128 1131 return tmpl, None
1129 1132
1130 1133 if tmpl == 'list':
1131 1134 ui.write(_("available styles: %s\n") % templater.stylelist())
1132 1135 raise util.Abort(_("specify a template"))
1133 1136
1134 1137 # perhaps it's a path to a map or a template
1135 1138 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1136 1139 # is it a mapfile for a style?
1137 1140 if os.path.basename(tmpl).startswith("map-"):
1138 1141 return None, os.path.realpath(tmpl)
1139 1142 tmpl = open(tmpl).read()
1140 1143 return tmpl, None
1141 1144
1142 1145 # constant string?
1143 1146 return tmpl, None
1144 1147
1145 1148 def show_changeset(ui, repo, opts, buffered=False):
1146 1149 """show one changeset using template or regular display.
1147 1150
1148 1151 Display format will be the first non-empty hit of:
1149 1152 1. option 'template'
1150 1153 2. option 'style'
1151 1154 3. [ui] setting 'logtemplate'
1152 1155 4. [ui] setting 'style'
1153 1156 If all of these values are either the unset or the empty string,
1154 1157 regular display via changeset_printer() is done.
1155 1158 """
1156 1159 # options
1157 1160 patch = None
1158 1161 if opts.get('patch') or opts.get('stat'):
1159 1162 patch = scmutil.matchall(repo)
1160 1163
1161 1164 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1162 1165
1163 1166 if not tmpl and not mapfile:
1164 1167 return changeset_printer(ui, repo, patch, opts, buffered)
1165 1168
1166 1169 try:
1167 1170 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1168 1171 except SyntaxError, inst:
1169 1172 raise util.Abort(inst.args[0])
1170 1173 return t
1171 1174
1172 1175 def showmarker(ui, marker):
1173 1176 """utility function to display obsolescence marker in a readable way
1174 1177
1175 1178 To be used by debug function."""
1176 1179 ui.write(hex(marker.precnode()))
1177 1180 for repl in marker.succnodes():
1178 1181 ui.write(' ')
1179 1182 ui.write(hex(repl))
1180 1183 ui.write(' %X ' % marker._data[2])
1181 1184 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1182 1185 sorted(marker.metadata().items()))))
1183 1186 ui.write('\n')
1184 1187
1185 1188 def finddate(ui, repo, date):
1186 1189 """Find the tipmost changeset that matches the given date spec"""
1187 1190
1188 1191 df = util.matchdate(date)
1189 1192 m = scmutil.matchall(repo)
1190 1193 results = {}
1191 1194
1192 1195 def prep(ctx, fns):
1193 1196 d = ctx.date()
1194 1197 if df(d[0]):
1195 1198 results[ctx.rev()] = d
1196 1199
1197 1200 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1198 1201 rev = ctx.rev()
1199 1202 if rev in results:
1200 1203 ui.status(_("found revision %s from %s\n") %
1201 1204 (rev, util.datestr(results[rev])))
1202 1205 return str(rev)
1203 1206
1204 1207 raise util.Abort(_("revision matching date not found"))
1205 1208
1206 1209 def increasingwindows(windowsize=8, sizelimit=512):
1207 1210 while True:
1208 1211 yield windowsize
1209 1212 if windowsize < sizelimit:
1210 1213 windowsize *= 2
1211 1214
1212 1215 class FileWalkError(Exception):
1213 1216 pass
1214 1217
1215 1218 def walkfilerevs(repo, match, follow, revs, fncache):
1216 1219 '''Walks the file history for the matched files.
1217 1220
1218 1221 Returns the changeset revs that are involved in the file history.
1219 1222
1220 1223 Throws FileWalkError if the file history can't be walked using
1221 1224 filelogs alone.
1222 1225 '''
1223 1226 wanted = set()
1224 1227 copies = []
1225 1228 minrev, maxrev = min(revs), max(revs)
1226 1229 def filerevgen(filelog, last):
1227 1230 """
1228 1231 Only files, no patterns. Check the history of each file.
1229 1232
1230 1233 Examines filelog entries within minrev, maxrev linkrev range
1231 1234 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1232 1235 tuples in backwards order
1233 1236 """
1234 1237 cl_count = len(repo)
1235 1238 revs = []
1236 1239 for j in xrange(0, last + 1):
1237 1240 linkrev = filelog.linkrev(j)
1238 1241 if linkrev < minrev:
1239 1242 continue
1240 1243 # only yield rev for which we have the changelog, it can
1241 1244 # happen while doing "hg log" during a pull or commit
1242 1245 if linkrev >= cl_count:
1243 1246 break
1244 1247
1245 1248 parentlinkrevs = []
1246 1249 for p in filelog.parentrevs(j):
1247 1250 if p != nullrev:
1248 1251 parentlinkrevs.append(filelog.linkrev(p))
1249 1252 n = filelog.node(j)
1250 1253 revs.append((linkrev, parentlinkrevs,
1251 1254 follow and filelog.renamed(n)))
1252 1255
1253 1256 return reversed(revs)
1254 1257 def iterfiles():
1255 1258 pctx = repo['.']
1256 1259 for filename in match.files():
1257 1260 if follow:
1258 1261 if filename not in pctx:
1259 1262 raise util.Abort(_('cannot follow file not in parent '
1260 1263 'revision: "%s"') % filename)
1261 1264 yield filename, pctx[filename].filenode()
1262 1265 else:
1263 1266 yield filename, None
1264 1267 for filename_node in copies:
1265 1268 yield filename_node
1266 1269
1267 1270 for file_, node in iterfiles():
1268 1271 filelog = repo.file(file_)
1269 1272 if not len(filelog):
1270 1273 if node is None:
1271 1274 # A zero count may be a directory or deleted file, so
1272 1275 # try to find matching entries on the slow path.
1273 1276 if follow:
1274 1277 raise util.Abort(
1275 1278 _('cannot follow nonexistent file: "%s"') % file_)
1276 1279 raise FileWalkError("Cannot walk via filelog")
1277 1280 else:
1278 1281 continue
1279 1282
1280 1283 if node is None:
1281 1284 last = len(filelog) - 1
1282 1285 else:
1283 1286 last = filelog.rev(node)
1284 1287
1285 1288
1286 1289 # keep track of all ancestors of the file
1287 1290 ancestors = set([filelog.linkrev(last)])
1288 1291
1289 1292 # iterate from latest to oldest revision
1290 1293 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1291 1294 if not follow:
1292 1295 if rev > maxrev:
1293 1296 continue
1294 1297 else:
1295 1298 # Note that last might not be the first interesting
1296 1299 # rev to us:
1297 1300 # if the file has been changed after maxrev, we'll
1298 1301 # have linkrev(last) > maxrev, and we still need
1299 1302 # to explore the file graph
1300 1303 if rev not in ancestors:
1301 1304 continue
1302 1305 # XXX insert 1327 fix here
1303 1306 if flparentlinkrevs:
1304 1307 ancestors.update(flparentlinkrevs)
1305 1308
1306 1309 fncache.setdefault(rev, []).append(file_)
1307 1310 wanted.add(rev)
1308 1311 if copied:
1309 1312 copies.append(copied)
1310 1313
1311 1314 return wanted
1312 1315
1313 1316 def walkchangerevs(repo, match, opts, prepare):
1314 1317 '''Iterate over files and the revs in which they changed.
1315 1318
1316 1319 Callers most commonly need to iterate backwards over the history
1317 1320 in which they are interested. Doing so has awful (quadratic-looking)
1318 1321 performance, so we use iterators in a "windowed" way.
1319 1322
1320 1323 We walk a window of revisions in the desired order. Within the
1321 1324 window, we first walk forwards to gather data, then in the desired
1322 1325 order (usually backwards) to display it.
1323 1326
1324 1327 This function returns an iterator yielding contexts. Before
1325 1328 yielding each context, the iterator will first call the prepare
1326 1329 function on each context in the window in forward order.'''
1327 1330
1328 1331 follow = opts.get('follow') or opts.get('follow_first')
1329 1332
1330 1333 if opts.get('rev'):
1331 1334 revs = scmutil.revrange(repo, opts.get('rev'))
1332 1335 elif follow:
1333 1336 revs = repo.revs('reverse(:.)')
1334 1337 else:
1335 1338 revs = revset.spanset(repo)
1336 1339 revs.reverse()
1337 1340 if not revs:
1338 1341 return []
1339 1342 wanted = set()
1340 1343 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1341 1344 fncache = {}
1342 1345 change = repo.changectx
1343 1346
1344 1347 # First step is to fill wanted, the set of revisions that we want to yield.
1345 1348 # When it does not induce extra cost, we also fill fncache for revisions in
1346 1349 # wanted: a cache of filenames that were changed (ctx.files()) and that
1347 1350 # match the file filtering conditions.
1348 1351
1349 1352 if not slowpath and not match.files():
1350 1353 # No files, no patterns. Display all revs.
1351 1354 wanted = revs
1352 1355
1353 1356 if not slowpath and match.files():
1354 1357 # We only have to read through the filelog to find wanted revisions
1355 1358
1356 1359 try:
1357 1360 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1358 1361 except FileWalkError:
1359 1362 slowpath = True
1360 1363
1361 1364 # We decided to fall back to the slowpath because at least one
1362 1365 # of the paths was not a file. Check to see if at least one of them
1363 1366 # existed in history, otherwise simply return
1364 1367 for path in match.files():
1365 1368 if path == '.' or path in repo.store:
1366 1369 break
1367 1370 else:
1368 1371 return []
1369 1372
1370 1373 if slowpath:
1371 1374 # We have to read the changelog to match filenames against
1372 1375 # changed files
1373 1376
1374 1377 if follow:
1375 1378 raise util.Abort(_('can only follow copies/renames for explicit '
1376 1379 'filenames'))
1377 1380
1378 1381 # The slow path checks files modified in every changeset.
1379 1382 # This is really slow on large repos, so compute the set lazily.
1380 1383 class lazywantedset(object):
1381 1384 def __init__(self):
1382 1385 self.set = set()
1383 1386 self.revs = set(revs)
1384 1387
1385 1388 # No need to worry about locality here because it will be accessed
1386 1389 # in the same order as the increasing window below.
1387 1390 def __contains__(self, value):
1388 1391 if value in self.set:
1389 1392 return True
1390 1393 elif not value in self.revs:
1391 1394 return False
1392 1395 else:
1393 1396 self.revs.discard(value)
1394 1397 ctx = change(value)
1395 1398 matches = filter(match, ctx.files())
1396 1399 if matches:
1397 1400 fncache[value] = matches
1398 1401 self.set.add(value)
1399 1402 return True
1400 1403 return False
1401 1404
1402 1405 def discard(self, value):
1403 1406 self.revs.discard(value)
1404 1407 self.set.discard(value)
1405 1408
1406 1409 wanted = lazywantedset()
1407 1410
1408 1411 class followfilter(object):
1409 1412 def __init__(self, onlyfirst=False):
1410 1413 self.startrev = nullrev
1411 1414 self.roots = set()
1412 1415 self.onlyfirst = onlyfirst
1413 1416
1414 1417 def match(self, rev):
1415 1418 def realparents(rev):
1416 1419 if self.onlyfirst:
1417 1420 return repo.changelog.parentrevs(rev)[0:1]
1418 1421 else:
1419 1422 return filter(lambda x: x != nullrev,
1420 1423 repo.changelog.parentrevs(rev))
1421 1424
1422 1425 if self.startrev == nullrev:
1423 1426 self.startrev = rev
1424 1427 return True
1425 1428
1426 1429 if rev > self.startrev:
1427 1430 # forward: all descendants
1428 1431 if not self.roots:
1429 1432 self.roots.add(self.startrev)
1430 1433 for parent in realparents(rev):
1431 1434 if parent in self.roots:
1432 1435 self.roots.add(rev)
1433 1436 return True
1434 1437 else:
1435 1438 # backwards: all parents
1436 1439 if not self.roots:
1437 1440 self.roots.update(realparents(self.startrev))
1438 1441 if rev in self.roots:
1439 1442 self.roots.remove(rev)
1440 1443 self.roots.update(realparents(rev))
1441 1444 return True
1442 1445
1443 1446 return False
1444 1447
1445 1448 # it might be worthwhile to do this in the iterator if the rev range
1446 1449 # is descending and the prune args are all within that range
1447 1450 for rev in opts.get('prune', ()):
1448 1451 rev = repo[rev].rev()
1449 1452 ff = followfilter()
1450 1453 stop = min(revs[0], revs[-1])
1451 1454 for x in xrange(rev, stop - 1, -1):
1452 1455 if ff.match(x):
1453 1456 wanted = wanted - [x]
1454 1457
1455 1458 # Now that wanted is correctly initialized, we can iterate over the
1456 1459 # revision range, yielding only revisions in wanted.
1457 1460 def iterate():
1458 1461 if follow and not match.files():
1459 1462 ff = followfilter(onlyfirst=opts.get('follow_first'))
1460 1463 def want(rev):
1461 1464 return ff.match(rev) and rev in wanted
1462 1465 else:
1463 1466 def want(rev):
1464 1467 return rev in wanted
1465 1468
1466 1469 it = iter(revs)
1467 1470 stopiteration = False
1468 1471 for windowsize in increasingwindows():
1469 1472 nrevs = []
1470 1473 for i in xrange(windowsize):
1471 1474 try:
1472 1475 rev = it.next()
1473 1476 if want(rev):
1474 1477 nrevs.append(rev)
1475 1478 except (StopIteration):
1476 1479 stopiteration = True
1477 1480 break
1478 1481 for rev in sorted(nrevs):
1479 1482 fns = fncache.get(rev)
1480 1483 ctx = change(rev)
1481 1484 if not fns:
1482 1485 def fns_generator():
1483 1486 for f in ctx.files():
1484 1487 if match(f):
1485 1488 yield f
1486 1489 fns = fns_generator()
1487 1490 prepare(ctx, fns)
1488 1491 for rev in nrevs:
1489 1492 yield change(rev)
1490 1493
1491 1494 if stopiteration:
1492 1495 break
1493 1496
1494 1497 return iterate()
1495 1498
1496 1499 def _makefollowlogfilematcher(repo, files, followfirst):
1497 1500 # When displaying a revision with --patch --follow FILE, we have
1498 1501 # to know which file of the revision must be diffed. With
1499 1502 # --follow, we want the names of the ancestors of FILE in the
1500 1503 # revision, stored in "fcache". "fcache" is populated by
1501 1504 # reproducing the graph traversal already done by --follow revset
1502 1505 # and relating linkrevs to file names (which is not "correct" but
1503 1506 # good enough).
1504 1507 fcache = {}
1505 1508 fcacheready = [False]
1506 1509 pctx = repo['.']
1507 1510
1508 1511 def populate():
1509 1512 for fn in files:
1510 1513 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1511 1514 for c in i:
1512 1515 fcache.setdefault(c.linkrev(), set()).add(c.path())
1513 1516
1514 1517 def filematcher(rev):
1515 1518 if not fcacheready[0]:
1516 1519 # Lazy initialization
1517 1520 fcacheready[0] = True
1518 1521 populate()
1519 1522 return scmutil.matchfiles(repo, fcache.get(rev, []))
1520 1523
1521 1524 return filematcher
1522 1525
1523 1526 def _makenofollowlogfilematcher(repo, pats, opts):
1524 1527 '''hook for extensions to override the filematcher for non-follow cases'''
1525 1528 return None
1526 1529
1527 1530 def _makelogrevset(repo, pats, opts, revs):
1528 1531 """Return (expr, filematcher) where expr is a revset string built
1529 1532 from log options and file patterns or None. If --stat or --patch
1530 1533 are not passed filematcher is None. Otherwise it is a callable
1531 1534 taking a revision number and returning a match objects filtering
1532 1535 the files to be detailed when displaying the revision.
1533 1536 """
1534 1537 opt2revset = {
1535 1538 'no_merges': ('not merge()', None),
1536 1539 'only_merges': ('merge()', None),
1537 1540 '_ancestors': ('ancestors(%(val)s)', None),
1538 1541 '_fancestors': ('_firstancestors(%(val)s)', None),
1539 1542 '_descendants': ('descendants(%(val)s)', None),
1540 1543 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1541 1544 '_matchfiles': ('_matchfiles(%(val)s)', None),
1542 1545 'date': ('date(%(val)r)', None),
1543 1546 'branch': ('branch(%(val)r)', ' or '),
1544 1547 '_patslog': ('filelog(%(val)r)', ' or '),
1545 1548 '_patsfollow': ('follow(%(val)r)', ' or '),
1546 1549 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1547 1550 'keyword': ('keyword(%(val)r)', ' or '),
1548 1551 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1549 1552 'user': ('user(%(val)r)', ' or '),
1550 1553 }
1551 1554
1552 1555 opts = dict(opts)
1553 1556 # follow or not follow?
1554 1557 follow = opts.get('follow') or opts.get('follow_first')
1555 1558 followfirst = opts.get('follow_first') and 1 or 0
1556 1559 # --follow with FILE behaviour depends on revs...
1557 1560 it = iter(revs)
1558 1561 startrev = it.next()
1559 1562 try:
1560 1563 followdescendants = startrev < it.next()
1561 1564 except (StopIteration):
1562 1565 followdescendants = False
1563 1566
1564 1567 # branch and only_branch are really aliases and must be handled at
1565 1568 # the same time
1566 1569 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1567 1570 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1568 1571 # pats/include/exclude are passed to match.match() directly in
1569 1572 # _matchfiles() revset but walkchangerevs() builds its matcher with
1570 1573 # scmutil.match(). The difference is input pats are globbed on
1571 1574 # platforms without shell expansion (windows).
1572 1575 pctx = repo[None]
1573 1576 match, pats = scmutil.matchandpats(pctx, pats, opts)
1574 1577 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1575 1578 if not slowpath:
1576 1579 for f in match.files():
1577 1580 if follow and f not in pctx:
1578 1581 raise util.Abort(_('cannot follow file not in parent '
1579 1582 'revision: "%s"') % f)
1580 1583 filelog = repo.file(f)
1581 1584 if not filelog:
1582 1585 # A zero count may be a directory or deleted file, so
1583 1586 # try to find matching entries on the slow path.
1584 1587 if follow:
1585 1588 raise util.Abort(
1586 1589 _('cannot follow nonexistent file: "%s"') % f)
1587 1590 slowpath = True
1588 1591
1589 1592 # We decided to fall back to the slowpath because at least one
1590 1593 # of the paths was not a file. Check to see if at least one of them
1591 1594 # existed in history - in that case, we'll continue down the
1592 1595 # slowpath; otherwise, we can turn off the slowpath
1593 1596 if slowpath:
1594 1597 for path in match.files():
1595 1598 if path == '.' or path in repo.store:
1596 1599 break
1597 1600 else:
1598 1601 slowpath = False
1599 1602
1600 1603 if slowpath:
1601 1604 # See walkchangerevs() slow path.
1602 1605 #
1603 1606 if follow:
1604 1607 raise util.Abort(_('can only follow copies/renames for explicit '
1605 1608 'filenames'))
1606 1609 # pats/include/exclude cannot be represented as separate
1607 1610 # revset expressions as their filtering logic applies at file
1608 1611 # level. For instance "-I a -X a" matches a revision touching
1609 1612 # "a" and "b" while "file(a) and not file(b)" does
1610 1613 # not. Besides, filesets are evaluated against the working
1611 1614 # directory.
1612 1615 matchargs = ['r:', 'd:relpath']
1613 1616 for p in pats:
1614 1617 matchargs.append('p:' + p)
1615 1618 for p in opts.get('include', []):
1616 1619 matchargs.append('i:' + p)
1617 1620 for p in opts.get('exclude', []):
1618 1621 matchargs.append('x:' + p)
1619 1622 matchargs = ','.join(('%r' % p) for p in matchargs)
1620 1623 opts['_matchfiles'] = matchargs
1621 1624 else:
1622 1625 if follow:
1623 1626 fpats = ('_patsfollow', '_patsfollowfirst')
1624 1627 fnopats = (('_ancestors', '_fancestors'),
1625 1628 ('_descendants', '_fdescendants'))
1626 1629 if pats:
1627 1630 # follow() revset interprets its file argument as a
1628 1631 # manifest entry, so use match.files(), not pats.
1629 1632 opts[fpats[followfirst]] = list(match.files())
1630 1633 else:
1631 1634 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1632 1635 else:
1633 1636 opts['_patslog'] = list(pats)
1634 1637
1635 1638 filematcher = None
1636 1639 if opts.get('patch') or opts.get('stat'):
1637 1640 if follow and not match.always():
1638 1641 # _makelogfilematcher expects its files argument to be relative to
1639 1642 # the repo root, so use match.files(), not pats.
1640 1643 filematcher = _makefollowlogfilematcher(repo, match.files(),
1641 1644 followfirst)
1642 1645 else:
1643 1646 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1644 1647 if filematcher is None:
1645 1648 filematcher = lambda rev: match
1646 1649
1647 1650 expr = []
1648 1651 for op, val in opts.iteritems():
1649 1652 if not val:
1650 1653 continue
1651 1654 if op not in opt2revset:
1652 1655 continue
1653 1656 revop, andor = opt2revset[op]
1654 1657 if '%(val)' not in revop:
1655 1658 expr.append(revop)
1656 1659 else:
1657 1660 if not isinstance(val, list):
1658 1661 e = revop % {'val': val}
1659 1662 else:
1660 1663 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1661 1664 expr.append(e)
1662 1665
1663 1666 if expr:
1664 1667 expr = '(' + ' and '.join(expr) + ')'
1665 1668 else:
1666 1669 expr = None
1667 1670 return expr, filematcher
1668 1671
1669 1672 def getgraphlogrevs(repo, pats, opts):
1670 1673 """Return (revs, expr, filematcher) where revs is an iterable of
1671 1674 revision numbers, expr is a revset string built from log options
1672 1675 and file patterns or None, and used to filter 'revs'. If --stat or
1673 1676 --patch are not passed filematcher is None. Otherwise it is a
1674 1677 callable taking a revision number and returning a match objects
1675 1678 filtering the files to be detailed when displaying the revision.
1676 1679 """
1677 1680 if not len(repo):
1678 1681 return [], None, None
1679 1682 limit = loglimit(opts)
1680 1683 # Default --rev value depends on --follow but --follow behaviour
1681 1684 # depends on revisions resolved from --rev...
1682 1685 follow = opts.get('follow') or opts.get('follow_first')
1683 1686 possiblyunsorted = False # whether revs might need sorting
1684 1687 if opts.get('rev'):
1685 1688 revs = scmutil.revrange(repo, opts['rev'])
1686 1689 # Don't sort here because _makelogrevset might depend on the
1687 1690 # order of revs
1688 1691 possiblyunsorted = True
1689 1692 else:
1690 1693 if follow and len(repo) > 0:
1691 1694 revs = repo.revs('reverse(:.)')
1692 1695 else:
1693 1696 revs = revset.spanset(repo)
1694 1697 revs.reverse()
1695 1698 if not revs:
1696 1699 return revset.baseset(), None, None
1697 1700 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1698 1701 if possiblyunsorted:
1699 1702 revs.sort(reverse=True)
1700 1703 if expr:
1701 1704 # Revset matchers often operate faster on revisions in changelog
1702 1705 # order, because most filters deal with the changelog.
1703 1706 revs.reverse()
1704 1707 matcher = revset.match(repo.ui, expr)
1705 1708 # Revset matches can reorder revisions. "A or B" typically returns
1706 1709 # returns the revision matching A then the revision matching B. Sort
1707 1710 # again to fix that.
1708 1711 revs = matcher(repo, revs)
1709 1712 revs.sort(reverse=True)
1710 1713 if limit is not None:
1711 1714 limitedrevs = revset.baseset()
1712 1715 for idx, rev in enumerate(revs):
1713 1716 if idx >= limit:
1714 1717 break
1715 1718 limitedrevs.append(rev)
1716 1719 revs = limitedrevs
1717 1720
1718 1721 return revs, expr, filematcher
1719 1722
1720 1723 def getlogrevs(repo, pats, opts):
1721 1724 """Return (revs, expr, filematcher) where revs is an iterable of
1722 1725 revision numbers, expr is a revset string built from log options
1723 1726 and file patterns or None, and used to filter 'revs'. If --stat or
1724 1727 --patch are not passed filematcher is None. Otherwise it is a
1725 1728 callable taking a revision number and returning a match objects
1726 1729 filtering the files to be detailed when displaying the revision.
1727 1730 """
1728 1731 limit = loglimit(opts)
1729 1732 # Default --rev value depends on --follow but --follow behaviour
1730 1733 # depends on revisions resolved from --rev...
1731 1734 follow = opts.get('follow') or opts.get('follow_first')
1732 1735 if opts.get('rev'):
1733 1736 revs = scmutil.revrange(repo, opts['rev'])
1734 1737 elif follow:
1735 1738 revs = repo.revs('reverse(:.)')
1736 1739 else:
1737 1740 revs = revset.spanset(repo)
1738 1741 revs.reverse()
1739 1742 if not revs:
1740 1743 return revset.baseset([]), None, None
1741 1744 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1742 1745 if expr:
1743 1746 # Revset matchers often operate faster on revisions in changelog
1744 1747 # order, because most filters deal with the changelog.
1745 1748 if not opts.get('rev'):
1746 1749 revs.reverse()
1747 1750 matcher = revset.match(repo.ui, expr)
1748 1751 # Revset matches can reorder revisions. "A or B" typically returns
1749 1752 # returns the revision matching A then the revision matching B. Sort
1750 1753 # again to fix that.
1751 1754 revs = matcher(repo, revs)
1752 1755 if not opts.get('rev'):
1753 1756 revs.sort(reverse=True)
1754 1757 if limit is not None:
1755 1758 count = 0
1756 1759 limitedrevs = revset.baseset([])
1757 1760 it = iter(revs)
1758 1761 while count < limit:
1759 1762 try:
1760 1763 limitedrevs.append(it.next())
1761 1764 except (StopIteration):
1762 1765 break
1763 1766 count += 1
1764 1767 revs = limitedrevs
1765 1768
1766 1769 return revs, expr, filematcher
1767 1770
1768 1771 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1769 1772 filematcher=None):
1770 1773 seen, state = [], graphmod.asciistate()
1771 1774 for rev, type, ctx, parents in dag:
1772 1775 char = 'o'
1773 1776 if ctx.node() in showparents:
1774 1777 char = '@'
1775 1778 elif ctx.obsolete():
1776 1779 char = 'x'
1777 1780 copies = None
1778 1781 if getrenamed and ctx.rev():
1779 1782 copies = []
1780 1783 for fn in ctx.files():
1781 1784 rename = getrenamed(fn, ctx.rev())
1782 1785 if rename:
1783 1786 copies.append((fn, rename[0]))
1784 1787 revmatchfn = None
1785 1788 if filematcher is not None:
1786 1789 revmatchfn = filematcher(ctx.rev())
1787 1790 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1788 1791 lines = displayer.hunk.pop(rev).split('\n')
1789 1792 if not lines[-1]:
1790 1793 del lines[-1]
1791 1794 displayer.flush(rev)
1792 1795 edges = edgefn(type, char, lines, seen, rev, parents)
1793 1796 for type, char, lines, coldata in edges:
1794 1797 graphmod.ascii(ui, state, type, char, lines, coldata)
1795 1798 displayer.close()
1796 1799
1797 1800 def graphlog(ui, repo, *pats, **opts):
1798 1801 # Parameters are identical to log command ones
1799 1802 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1800 1803 revdag = graphmod.dagwalker(repo, revs)
1801 1804
1802 1805 getrenamed = None
1803 1806 if opts.get('copies'):
1804 1807 endrev = None
1805 1808 if opts.get('rev'):
1806 1809 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1807 1810 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1808 1811 displayer = show_changeset(ui, repo, opts, buffered=True)
1809 1812 showparents = [ctx.node() for ctx in repo[None].parents()]
1810 1813 displaygraph(ui, revdag, displayer, showparents,
1811 1814 graphmod.asciiedges, getrenamed, filematcher)
1812 1815
1813 1816 def checkunsupportedgraphflags(pats, opts):
1814 1817 for op in ["newest_first"]:
1815 1818 if op in opts and opts[op]:
1816 1819 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1817 1820 % op.replace("_", "-"))
1818 1821
1819 1822 def graphrevs(repo, nodes, opts):
1820 1823 limit = loglimit(opts)
1821 1824 nodes.reverse()
1822 1825 if limit is not None:
1823 1826 nodes = nodes[:limit]
1824 1827 return graphmod.nodes(repo, nodes)
1825 1828
1826 1829 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1827 1830 join = lambda f: os.path.join(prefix, f)
1828 1831 bad = []
1829 1832 oldbad = match.bad
1830 1833 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1831 1834 names = []
1832 1835 wctx = repo[None]
1833 1836 cca = None
1834 1837 abort, warn = scmutil.checkportabilityalert(ui)
1835 1838 if abort or warn:
1836 1839 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1837 1840 for f in repo.walk(match):
1838 1841 exact = match.exact(f)
1839 1842 if exact or not explicitonly and f not in repo.dirstate:
1840 1843 if cca:
1841 1844 cca(f)
1842 1845 names.append(f)
1843 1846 if ui.verbose or not exact:
1844 1847 ui.status(_('adding %s\n') % match.rel(join(f)))
1845 1848
1846 1849 for subpath in sorted(wctx.substate):
1847 1850 sub = wctx.sub(subpath)
1848 1851 try:
1849 1852 submatch = matchmod.narrowmatcher(subpath, match)
1850 1853 if listsubrepos:
1851 1854 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1852 1855 False))
1853 1856 else:
1854 1857 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1855 1858 True))
1856 1859 except error.LookupError:
1857 1860 ui.status(_("skipping missing subrepository: %s\n")
1858 1861 % join(subpath))
1859 1862
1860 1863 if not dryrun:
1861 1864 rejected = wctx.add(names, prefix)
1862 1865 bad.extend(f for f in rejected if f in match.files())
1863 1866 return bad
1864 1867
1865 1868 def forget(ui, repo, match, prefix, explicitonly):
1866 1869 join = lambda f: os.path.join(prefix, f)
1867 1870 bad = []
1868 1871 oldbad = match.bad
1869 1872 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1870 1873 wctx = repo[None]
1871 1874 forgot = []
1872 1875 s = repo.status(match=match, clean=True)
1873 1876 forget = sorted(s[0] + s[1] + s[3] + s[6])
1874 1877 if explicitonly:
1875 1878 forget = [f for f in forget if match.exact(f)]
1876 1879
1877 1880 for subpath in sorted(wctx.substate):
1878 1881 sub = wctx.sub(subpath)
1879 1882 try:
1880 1883 submatch = matchmod.narrowmatcher(subpath, match)
1881 1884 subbad, subforgot = sub.forget(ui, submatch, prefix)
1882 1885 bad.extend([subpath + '/' + f for f in subbad])
1883 1886 forgot.extend([subpath + '/' + f for f in subforgot])
1884 1887 except error.LookupError:
1885 1888 ui.status(_("skipping missing subrepository: %s\n")
1886 1889 % join(subpath))
1887 1890
1888 1891 if not explicitonly:
1889 1892 for f in match.files():
1890 1893 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1891 1894 if f not in forgot:
1892 1895 if os.path.exists(match.rel(join(f))):
1893 1896 ui.warn(_('not removing %s: '
1894 1897 'file is already untracked\n')
1895 1898 % match.rel(join(f)))
1896 1899 bad.append(f)
1897 1900
1898 1901 for f in forget:
1899 1902 if ui.verbose or not match.exact(f):
1900 1903 ui.status(_('removing %s\n') % match.rel(join(f)))
1901 1904
1902 1905 rejected = wctx.forget(forget, prefix)
1903 1906 bad.extend(f for f in rejected if f in match.files())
1904 1907 forgot.extend(forget)
1905 1908 return bad, forgot
1906 1909
1907 1910 def cat(ui, repo, ctx, matcher, prefix, **opts):
1908 1911 err = 1
1909 1912
1910 1913 def write(path):
1911 1914 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1912 1915 pathname=os.path.join(prefix, path))
1913 1916 data = ctx[path].data()
1914 1917 if opts.get('decode'):
1915 1918 data = repo.wwritedata(path, data)
1916 1919 fp.write(data)
1917 1920 fp.close()
1918 1921
1919 1922 # Automation often uses hg cat on single files, so special case it
1920 1923 # for performance to avoid the cost of parsing the manifest.
1921 1924 if len(matcher.files()) == 1 and not matcher.anypats():
1922 1925 file = matcher.files()[0]
1923 1926 mf = repo.manifest
1924 1927 mfnode = ctx._changeset[0]
1925 1928 if mf.find(mfnode, file)[0]:
1926 1929 write(file)
1927 1930 return 0
1928 1931
1929 1932 # Don't warn about "missing" files that are really in subrepos
1930 1933 bad = matcher.bad
1931 1934
1932 1935 def badfn(path, msg):
1933 1936 for subpath in ctx.substate:
1934 1937 if path.startswith(subpath):
1935 1938 return
1936 1939 bad(path, msg)
1937 1940
1938 1941 matcher.bad = badfn
1939 1942
1940 1943 for abs in ctx.walk(matcher):
1941 1944 write(abs)
1942 1945 err = 0
1943 1946
1944 1947 matcher.bad = bad
1945 1948
1946 1949 for subpath in sorted(ctx.substate):
1947 1950 sub = ctx.sub(subpath)
1948 1951 try:
1949 1952 submatch = matchmod.narrowmatcher(subpath, matcher)
1950 1953
1951 1954 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1952 1955 **opts):
1953 1956 err = 0
1954 1957 except error.RepoLookupError:
1955 1958 ui.status(_("skipping missing subrepository: %s\n")
1956 1959 % os.path.join(prefix, subpath))
1957 1960
1958 1961 return err
1959 1962
1960 1963 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1961 1964 '''reproduce copies from fromrev to rev in the dirstate
1962 1965
1963 1966 If skiprev is specified, it's a revision that should be used to
1964 1967 filter copy records. Any copies that occur between fromrev and
1965 1968 skiprev will not be duplicated, even if they appear in the set of
1966 1969 copies between fromrev and rev.
1967 1970 '''
1968 1971 exclude = {}
1969 1972 if skiprev is not None:
1970 1973 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1971 1974 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1972 1975 # copies.pathcopies returns backward renames, so dst might not
1973 1976 # actually be in the dirstate
1974 1977 if dst in exclude:
1975 1978 continue
1976 1979 if repo.dirstate[dst] in "nma":
1977 1980 repo.dirstate.copy(src, dst)
1978 1981
1979 1982 def commit(ui, repo, commitfunc, pats, opts):
1980 1983 '''commit the specified files or all outstanding changes'''
1981 1984 date = opts.get('date')
1982 1985 if date:
1983 1986 opts['date'] = util.parsedate(date)
1984 1987 message = logmessage(ui, opts)
1985 1988
1986 1989 # extract addremove carefully -- this function can be called from a command
1987 1990 # that doesn't support addremove
1988 1991 if opts.get('addremove'):
1989 1992 scmutil.addremove(repo, pats, opts)
1990 1993
1991 1994 return commitfunc(ui, repo, message,
1992 1995 scmutil.match(repo[None], pats, opts), opts)
1993 1996
1994 1997 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1995 1998 ui.note(_('amending changeset %s\n') % old)
1996 1999 base = old.p1()
1997 2000
1998 2001 wlock = lock = newid = None
1999 2002 try:
2000 2003 wlock = repo.wlock()
2001 2004 lock = repo.lock()
2002 2005 tr = repo.transaction('amend')
2003 2006 try:
2004 2007 # See if we got a message from -m or -l, if not, open the editor
2005 2008 # with the message of the changeset to amend
2006 2009 message = logmessage(ui, opts)
2007 2010 # ensure logfile does not conflict with later enforcement of the
2008 2011 # message. potential logfile content has been processed by
2009 2012 # `logmessage` anyway.
2010 2013 opts.pop('logfile')
2011 2014 # First, do a regular commit to record all changes in the working
2012 2015 # directory (if there are any)
2013 2016 ui.callhooks = False
2014 2017 currentbookmark = repo._bookmarkcurrent
2015 2018 try:
2016 2019 repo._bookmarkcurrent = None
2017 2020 opts['message'] = 'temporary amend commit for %s' % old
2018 2021 node = commit(ui, repo, commitfunc, pats, opts)
2019 2022 finally:
2020 2023 repo._bookmarkcurrent = currentbookmark
2021 2024 ui.callhooks = True
2022 2025 ctx = repo[node]
2023 2026
2024 2027 # Participating changesets:
2025 2028 #
2026 2029 # node/ctx o - new (intermediate) commit that contains changes
2027 2030 # | from working dir to go into amending commit
2028 2031 # | (or a workingctx if there were no changes)
2029 2032 # |
2030 2033 # old o - changeset to amend
2031 2034 # |
2032 2035 # base o - parent of amending changeset
2033 2036
2034 2037 # Update extra dict from amended commit (e.g. to preserve graft
2035 2038 # source)
2036 2039 extra.update(old.extra())
2037 2040
2038 2041 # Also update it from the intermediate commit or from the wctx
2039 2042 extra.update(ctx.extra())
2040 2043
2041 2044 if len(old.parents()) > 1:
2042 2045 # ctx.files() isn't reliable for merges, so fall back to the
2043 2046 # slower repo.status() method
2044 2047 files = set([fn for st in repo.status(base, old)[:3]
2045 2048 for fn in st])
2046 2049 else:
2047 2050 files = set(old.files())
2048 2051
2049 2052 # Second, we use either the commit we just did, or if there were no
2050 2053 # changes the parent of the working directory as the version of the
2051 2054 # files in the final amend commit
2052 2055 if node:
2053 2056 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2054 2057
2055 2058 user = ctx.user()
2056 2059 date = ctx.date()
2057 2060 # Recompute copies (avoid recording a -> b -> a)
2058 2061 copied = copies.pathcopies(base, ctx)
2059 2062
2060 2063 # Prune files which were reverted by the updates: if old
2061 2064 # introduced file X and our intermediate commit, node,
2062 2065 # renamed that file, then those two files are the same and
2063 2066 # we can discard X from our list of files. Likewise if X
2064 2067 # was deleted, it's no longer relevant
2065 2068 files.update(ctx.files())
2066 2069
2067 2070 def samefile(f):
2068 2071 if f in ctx.manifest():
2069 2072 a = ctx.filectx(f)
2070 2073 if f in base.manifest():
2071 2074 b = base.filectx(f)
2072 2075 return (not a.cmp(b)
2073 2076 and a.flags() == b.flags())
2074 2077 else:
2075 2078 return False
2076 2079 else:
2077 2080 return f not in base.manifest()
2078 2081 files = [f for f in files if not samefile(f)]
2079 2082
2080 2083 def filectxfn(repo, ctx_, path):
2081 2084 try:
2082 2085 fctx = ctx[path]
2083 2086 flags = fctx.flags()
2084 2087 mctx = context.memfilectx(repo,
2085 2088 fctx.path(), fctx.data(),
2086 2089 islink='l' in flags,
2087 2090 isexec='x' in flags,
2088 2091 copied=copied.get(path))
2089 2092 return mctx
2090 2093 except KeyError:
2091 2094 raise IOError
2092 2095 else:
2093 2096 ui.note(_('copying changeset %s to %s\n') % (old, base))
2094 2097
2095 2098 # Use version of files as in the old cset
2096 2099 def filectxfn(repo, ctx_, path):
2097 2100 try:
2098 2101 return old.filectx(path)
2099 2102 except KeyError:
2100 2103 raise IOError
2101 2104
2102 2105 user = opts.get('user') or old.user()
2103 2106 date = opts.get('date') or old.date()
2104 2107 editor = getcommiteditor(**opts)
2105 2108 if not message:
2106 2109 editor = getcommiteditor(edit=True)
2107 2110 message = old.description()
2108 2111
2109 2112 pureextra = extra.copy()
2110 2113 extra['amend_source'] = old.hex()
2111 2114
2112 2115 new = context.memctx(repo,
2113 2116 parents=[base.node(), old.p2().node()],
2114 2117 text=message,
2115 2118 files=files,
2116 2119 filectxfn=filectxfn,
2117 2120 user=user,
2118 2121 date=date,
2119 2122 extra=extra,
2120 2123 editor=editor)
2121 2124
2122 2125 newdesc = changelog.stripdesc(new.description())
2123 2126 if ((not node)
2124 2127 and newdesc == old.description()
2125 2128 and user == old.user()
2126 2129 and date == old.date()
2127 2130 and pureextra == old.extra()):
2128 2131 # nothing changed. continuing here would create a new node
2129 2132 # anyway because of the amend_source noise.
2130 2133 #
2131 2134 # This not what we expect from amend.
2132 2135 return old.node()
2133 2136
2134 2137 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2135 2138 try:
2136 2139 if opts.get('secret'):
2137 2140 commitphase = 'secret'
2138 2141 else:
2139 2142 commitphase = old.phase()
2140 2143 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2141 2144 newid = repo.commitctx(new)
2142 2145 finally:
2143 2146 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2144 2147 if newid != old.node():
2145 2148 # Reroute the working copy parent to the new changeset
2146 2149 repo.setparents(newid, nullid)
2147 2150
2148 2151 # Move bookmarks from old parent to amend commit
2149 2152 bms = repo.nodebookmarks(old.node())
2150 2153 if bms:
2151 2154 marks = repo._bookmarks
2152 2155 for bm in bms:
2153 2156 marks[bm] = newid
2154 2157 marks.write()
2155 2158 #commit the whole amend process
2156 2159 if obsolete._enabled and newid != old.node():
2157 2160 # mark the new changeset as successor of the rewritten one
2158 2161 new = repo[newid]
2159 2162 obs = [(old, (new,))]
2160 2163 if node:
2161 2164 obs.append((ctx, ()))
2162 2165
2163 2166 obsolete.createmarkers(repo, obs)
2164 2167 tr.close()
2165 2168 finally:
2166 2169 tr.release()
2167 2170 if (not obsolete._enabled) and newid != old.node():
2168 2171 # Strip the intermediate commit (if there was one) and the amended
2169 2172 # commit
2170 2173 if node:
2171 2174 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2172 2175 ui.note(_('stripping amended changeset %s\n') % old)
2173 2176 repair.strip(ui, repo, old.node(), topic='amend-backup')
2174 2177 finally:
2175 2178 if newid is None:
2176 2179 repo.dirstate.invalidate()
2177 2180 lockmod.release(lock, wlock)
2178 2181 return newid
2179 2182
2180 2183 def commiteditor(repo, ctx, subs):
2181 2184 if ctx.description():
2182 2185 return ctx.description()
2183 2186 return commitforceeditor(repo, ctx, subs)
2184 2187
2185 2188 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
2186 2189 if not extramsg:
2187 2190 extramsg = _("Leave message empty to abort commit.")
2188 2191 tmpl = repo.ui.config('committemplate', 'changeset', '').strip()
2189 2192 if tmpl:
2190 2193 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2191 2194 else:
2192 2195 committext = buildcommittext(repo, ctx, subs, extramsg)
2193 2196
2194 2197 # run editor in the repository root
2195 2198 olddir = os.getcwd()
2196 2199 os.chdir(repo.root)
2197 2200 text = repo.ui.edit(committext, ctx.user(), ctx.extra())
2198 2201 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2199 2202 os.chdir(olddir)
2200 2203
2201 2204 if finishdesc:
2202 2205 text = finishdesc(text)
2203 2206 if not text.strip():
2204 2207 raise util.Abort(_("empty commit message"))
2205 2208
2206 2209 return text
2207 2210
2208 2211 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2209 2212 ui = repo.ui
2210 2213 tmpl, mapfile = gettemplate(ui, tmpl, None)
2211 2214
2212 2215 try:
2213 2216 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2214 2217 except SyntaxError, inst:
2215 2218 raise util.Abort(inst.args[0])
2216 2219
2217 2220 if not extramsg:
2218 2221 extramsg = '' # ensure that extramsg is string
2219 2222
2220 2223 ui.pushbuffer()
2221 2224 t.show(ctx, extramsg=extramsg)
2222 2225 return ui.popbuffer()
2223 2226
2224 2227 def buildcommittext(repo, ctx, subs, extramsg):
2225 2228 edittext = []
2226 2229 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2227 2230 if ctx.description():
2228 2231 edittext.append(ctx.description())
2229 2232 edittext.append("")
2230 2233 edittext.append("") # Empty line between message and comments.
2231 2234 edittext.append(_("HG: Enter commit message."
2232 2235 " Lines beginning with 'HG:' are removed."))
2233 2236 edittext.append("HG: %s" % extramsg)
2234 2237 edittext.append("HG: --")
2235 2238 edittext.append(_("HG: user: %s") % ctx.user())
2236 2239 if ctx.p2():
2237 2240 edittext.append(_("HG: branch merge"))
2238 2241 if ctx.branch():
2239 2242 edittext.append(_("HG: branch '%s'") % ctx.branch())
2240 2243 if bookmarks.iscurrent(repo):
2241 2244 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2242 2245 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2243 2246 edittext.extend([_("HG: added %s") % f for f in added])
2244 2247 edittext.extend([_("HG: changed %s") % f for f in modified])
2245 2248 edittext.extend([_("HG: removed %s") % f for f in removed])
2246 2249 if not added and not modified and not removed:
2247 2250 edittext.append(_("HG: no files changed"))
2248 2251 edittext.append("")
2249 2252
2250 2253 return "\n".join(edittext)
2251 2254
2252 2255 def commitstatus(repo, node, branch, bheads=None, opts={}):
2253 2256 ctx = repo[node]
2254 2257 parents = ctx.parents()
2255 2258
2256 2259 if (not opts.get('amend') and bheads and node not in bheads and not
2257 2260 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2258 2261 repo.ui.status(_('created new head\n'))
2259 2262 # The message is not printed for initial roots. For the other
2260 2263 # changesets, it is printed in the following situations:
2261 2264 #
2262 2265 # Par column: for the 2 parents with ...
2263 2266 # N: null or no parent
2264 2267 # B: parent is on another named branch
2265 2268 # C: parent is a regular non head changeset
2266 2269 # H: parent was a branch head of the current branch
2267 2270 # Msg column: whether we print "created new head" message
2268 2271 # In the following, it is assumed that there already exists some
2269 2272 # initial branch heads of the current branch, otherwise nothing is
2270 2273 # printed anyway.
2271 2274 #
2272 2275 # Par Msg Comment
2273 2276 # N N y additional topo root
2274 2277 #
2275 2278 # B N y additional branch root
2276 2279 # C N y additional topo head
2277 2280 # H N n usual case
2278 2281 #
2279 2282 # B B y weird additional branch root
2280 2283 # C B y branch merge
2281 2284 # H B n merge with named branch
2282 2285 #
2283 2286 # C C y additional head from merge
2284 2287 # C H n merge with a head
2285 2288 #
2286 2289 # H H n head merge: head count decreases
2287 2290
2288 2291 if not opts.get('close_branch'):
2289 2292 for r in parents:
2290 2293 if r.closesbranch() and r.branch() == branch:
2291 2294 repo.ui.status(_('reopening closed branch head %d\n') % r)
2292 2295
2293 2296 if repo.ui.debugflag:
2294 2297 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2295 2298 elif repo.ui.verbose:
2296 2299 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2297 2300
2298 2301 def revert(ui, repo, ctx, parents, *pats, **opts):
2299 2302 parent, p2 = parents
2300 2303 node = ctx.node()
2301 2304
2302 2305 mf = ctx.manifest()
2303 2306 if node == p2:
2304 2307 parent = p2
2305 2308 if node == parent:
2306 2309 pmf = mf
2307 2310 else:
2308 2311 pmf = None
2309 2312
2310 2313 # need all matching names in dirstate and manifest of target rev,
2311 2314 # so have to walk both. do not print errors if files exist in one
2312 2315 # but not other.
2313 2316
2314 2317 # `names` is a mapping for all elements in working copy and target revision
2315 2318 # The mapping is in the form:
2316 2319 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2317 2320 names = {}
2318 2321
2319 2322 wlock = repo.wlock()
2320 2323 try:
2321 2324 ## filling of the `names` mapping
2322 2325 # walk dirstate to fill `names`
2323 2326
2324 2327 m = scmutil.match(repo[None], pats, opts)
2325 2328 m.bad = lambda x, y: False
2326 2329 for abs in repo.walk(m):
2327 2330 names[abs] = m.rel(abs), m.exact(abs)
2328 2331
2329 2332 # walk target manifest to fill `names`
2330 2333
2331 2334 def badfn(path, msg):
2332 2335 if path in names:
2333 2336 return
2334 2337 if path in ctx.substate:
2335 2338 return
2336 2339 path_ = path + '/'
2337 2340 for f in names:
2338 2341 if f.startswith(path_):
2339 2342 return
2340 2343 ui.warn("%s: %s\n" % (m.rel(path), msg))
2341 2344
2342 2345 m = scmutil.match(ctx, pats, opts)
2343 2346 m.bad = badfn
2344 2347 for abs in ctx.walk(m):
2345 2348 if abs not in names:
2346 2349 names[abs] = m.rel(abs), m.exact(abs)
2347 2350
2348 2351 # get the list of subrepos that must be reverted
2349 2352 targetsubs = sorted(s for s in ctx.substate if m(s))
2350 2353
2351 2354 # Find status of all file in `names`. (Against working directory parent)
2352 2355 m = scmutil.matchfiles(repo, names)
2353 2356 changes = repo.status(node1=parent, match=m)[:4]
2354 2357 modified, added, removed, deleted = map(set, changes)
2355 2358
2356 2359 # if f is a rename, update `names` to also revert the source
2357 2360 cwd = repo.getcwd()
2358 2361 for f in added:
2359 2362 src = repo.dirstate.copied(f)
2360 2363 if src and src not in names and repo.dirstate[src] == 'r':
2361 2364 removed.add(src)
2362 2365 names[src] = (repo.pathto(src, cwd), True)
2363 2366
2364 2367 ## computation of the action to performs on `names` content.
2365 2368
2366 2369 def removeforget(abs):
2367 2370 if repo.dirstate[abs] == 'a':
2368 2371 return _('forgetting %s\n')
2369 2372 return _('removing %s\n')
2370 2373
2371 2374 # action to be actually performed by revert
2372 2375 # (<list of file>, message>) tuple
2373 2376 actions = {'revert': ([], _('reverting %s\n')),
2374 2377 'add': ([], _('adding %s\n')),
2375 2378 'remove': ([], removeforget),
2376 2379 'undelete': ([], _('undeleting %s\n'))}
2377 2380
2378 2381 disptable = (
2379 2382 # dispatch table:
2380 2383 # file state
2381 2384 # action if in target manifest
2382 2385 # action if not in target manifest
2383 2386 # make backup if in target manifest
2384 2387 # make backup if not in target manifest
2385 2388 (modified, (actions['revert'], True),
2386 2389 (actions['remove'], True)),
2387 2390 (added, (actions['revert'], True),
2388 2391 (actions['remove'], False)),
2389 2392 (removed, (actions['undelete'], True),
2390 2393 (None, False)),
2391 2394 (deleted, (actions['revert'], False),
2392 2395 (actions['remove'], False)),
2393 2396 )
2394 2397
2395 2398 for abs, (rel, exact) in sorted(names.items()):
2396 2399 # hash on file in target manifest (or None if missing from target)
2397 2400 mfentry = mf.get(abs)
2398 2401 # target file to be touch on disk (relative to cwd)
2399 2402 target = repo.wjoin(abs)
2400 2403 def handle(xlist, dobackup):
2401 2404 xlist[0].append(abs)
2402 2405 if (dobackup and not opts.get('no_backup') and
2403 2406 os.path.lexists(target) and
2404 2407 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2405 2408 bakname = "%s.orig" % rel
2406 2409 ui.note(_('saving current version of %s as %s\n') %
2407 2410 (rel, bakname))
2408 2411 if not opts.get('dry_run'):
2409 2412 util.rename(target, bakname)
2410 2413 if ui.verbose or not exact:
2411 2414 msg = xlist[1]
2412 2415 if not isinstance(msg, basestring):
2413 2416 msg = msg(abs)
2414 2417 ui.status(msg % rel)
2415 2418 # search the entry in the dispatch table.
2416 2419 # if the file is in any of this sets, it was touched in the working
2417 2420 # directory parent and we are sure it needs to be reverted.
2418 2421 for table, hit, miss in disptable:
2419 2422 if abs not in table:
2420 2423 continue
2421 2424 # file has changed in dirstate
2422 2425 if mfentry:
2423 2426 handle(*hit)
2424 2427 elif miss[0] is not None:
2425 2428 handle(*miss)
2426 2429 break
2427 2430 else:
2428 2431 # Not touched in current dirstate.
2429 2432
2430 2433 # file is unknown in parent, restore older version or ignore.
2431 2434 if abs not in repo.dirstate:
2432 2435 if mfentry:
2433 2436 handle(actions['add'], True)
2434 2437 elif exact:
2435 2438 ui.warn(_('file not managed: %s\n') % rel)
2436 2439 continue
2437 2440
2438 2441 # parent is target, no changes mean no changes
2439 2442 if node == parent:
2440 2443 if exact:
2441 2444 ui.warn(_('no changes needed to %s\n') % rel)
2442 2445 continue
2443 2446 # no change in dirstate but parent and target may differ
2444 2447 if pmf is None:
2445 2448 # only need parent manifest in this unlikely case,
2446 2449 # so do not read by default
2447 2450 pmf = repo[parent].manifest()
2448 2451 if abs in pmf and mfentry:
2449 2452 # if version of file is same in parent and target
2450 2453 # manifests, do nothing
2451 2454 if (pmf[abs] != mfentry or
2452 2455 pmf.flags(abs) != mf.flags(abs)):
2453 2456 handle(actions['revert'], False)
2454 2457 else:
2455 2458 handle(actions['remove'], False)
2456 2459
2457 2460 if not opts.get('dry_run'):
2458 2461 _performrevert(repo, parents, ctx, actions)
2459 2462
2460 2463 if targetsubs:
2461 2464 # Revert the subrepos on the revert list
2462 2465 for sub in targetsubs:
2463 2466 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2464 2467 finally:
2465 2468 wlock.release()
2466 2469
2467 2470 def _performrevert(repo, parents, ctx, actions):
2468 2471 """function that actually perform all the actions computed for revert
2469 2472
2470 2473 This is an independent function to let extension to plug in and react to
2471 2474 the imminent revert.
2472 2475
2473 2476 Make sure you have the working directory locked when calling this function.
2474 2477 """
2475 2478 parent, p2 = parents
2476 2479 node = ctx.node()
2477 2480 def checkout(f):
2478 2481 fc = ctx[f]
2479 2482 repo.wwrite(f, fc.data(), fc.flags())
2480 2483
2481 2484 audit_path = pathutil.pathauditor(repo.root)
2482 2485 for f in actions['remove'][0]:
2483 2486 if repo.dirstate[f] == 'a':
2484 2487 repo.dirstate.drop(f)
2485 2488 continue
2486 2489 audit_path(f)
2487 2490 try:
2488 2491 util.unlinkpath(repo.wjoin(f))
2489 2492 except OSError:
2490 2493 pass
2491 2494 repo.dirstate.remove(f)
2492 2495
2493 2496 normal = None
2494 2497 if node == parent:
2495 2498 # We're reverting to our parent. If possible, we'd like status
2496 2499 # to report the file as clean. We have to use normallookup for
2497 2500 # merges to avoid losing information about merged/dirty files.
2498 2501 if p2 != nullid:
2499 2502 normal = repo.dirstate.normallookup
2500 2503 else:
2501 2504 normal = repo.dirstate.normal
2502 2505 for f in actions['revert'][0]:
2503 2506 checkout(f)
2504 2507 if normal:
2505 2508 normal(f)
2506 2509
2507 2510 for f in actions['add'][0]:
2508 2511 checkout(f)
2509 2512 repo.dirstate.add(f)
2510 2513
2511 2514 normal = repo.dirstate.normallookup
2512 2515 if node == parent and p2 == nullid:
2513 2516 normal = repo.dirstate.normal
2514 2517 for f in actions['undelete'][0]:
2515 2518 checkout(f)
2516 2519 normal(f)
2517 2520
2518 2521 copied = copies.pathcopies(repo[parent], ctx)
2519 2522
2520 2523 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2521 2524 if f in copied:
2522 2525 repo.dirstate.copy(copied[f], f)
2523 2526
2524 2527 def command(table):
2525 2528 """Returns a function object to be used as a decorator for making commands.
2526 2529
2527 2530 This function receives a command table as its argument. The table should
2528 2531 be a dict.
2529 2532
2530 2533 The returned function can be used as a decorator for adding commands
2531 2534 to that command table. This function accepts multiple arguments to define
2532 2535 a command.
2533 2536
2534 2537 The first argument is the command name.
2535 2538
2536 2539 The options argument is an iterable of tuples defining command arguments.
2537 2540 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2538 2541
2539 2542 The synopsis argument defines a short, one line summary of how to use the
2540 2543 command. This shows up in the help output.
2541 2544
2542 2545 The norepo argument defines whether the command does not require a
2543 2546 local repository. Most commands operate against a repository, thus the
2544 2547 default is False.
2545 2548
2546 2549 The optionalrepo argument defines whether the command optionally requires
2547 2550 a local repository.
2548 2551
2549 2552 The inferrepo argument defines whether to try to find a repository from the
2550 2553 command line arguments. If True, arguments will be examined for potential
2551 2554 repository locations. See ``findrepo()``. If a repository is found, it
2552 2555 will be used.
2553 2556 """
2554 2557 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2555 2558 inferrepo=False):
2556 2559 def decorator(func):
2557 2560 if synopsis:
2558 2561 table[name] = func, list(options), synopsis
2559 2562 else:
2560 2563 table[name] = func, list(options)
2561 2564
2562 2565 if norepo:
2563 2566 # Avoid import cycle.
2564 2567 import commands
2565 2568 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2566 2569
2567 2570 if optionalrepo:
2568 2571 import commands
2569 2572 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2570 2573
2571 2574 if inferrepo:
2572 2575 import commands
2573 2576 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2574 2577
2575 2578 return func
2576 2579 return decorator
2577 2580
2578 2581 return cmd
2579 2582
2580 2583 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2581 2584 # commands.outgoing. "missing" is "missing" of the result of
2582 2585 # "findcommonoutgoing()"
2583 2586 outgoinghooks = util.hooks()
2584 2587
2585 2588 # a list of (ui, repo) functions called by commands.summary
2586 2589 summaryhooks = util.hooks()
2587 2590
2588 2591 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2589 2592 #
2590 2593 # functions should return tuple of booleans below, if 'changes' is None:
2591 2594 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2592 2595 #
2593 2596 # otherwise, 'changes' is a tuple of tuples below:
2594 2597 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2595 2598 # - (desturl, destbranch, destpeer, outgoing)
2596 2599 summaryremotehooks = util.hooks()
2597 2600
2598 2601 # A list of state files kept by multistep operations like graft.
2599 2602 # Since graft cannot be aborted, it is considered 'clearable' by update.
2600 2603 # note: bisect is intentionally excluded
2601 2604 # (state file, clearable, allowcommit, error, hint)
2602 2605 unfinishedstates = [
2603 2606 ('graftstate', True, False, _('graft in progress'),
2604 2607 _("use 'hg graft --continue' or 'hg update' to abort")),
2605 2608 ('updatestate', True, False, _('last update was interrupted'),
2606 2609 _("use 'hg update' to get a consistent checkout"))
2607 2610 ]
2608 2611
2609 2612 def checkunfinished(repo, commit=False):
2610 2613 '''Look for an unfinished multistep operation, like graft, and abort
2611 2614 if found. It's probably good to check this right before
2612 2615 bailifchanged().
2613 2616 '''
2614 2617 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2615 2618 if commit and allowcommit:
2616 2619 continue
2617 2620 if repo.vfs.exists(f):
2618 2621 raise util.Abort(msg, hint=hint)
2619 2622
2620 2623 def clearunfinished(repo):
2621 2624 '''Check for unfinished operations (as above), and clear the ones
2622 2625 that are clearable.
2623 2626 '''
2624 2627 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2625 2628 if not clearable and repo.vfs.exists(f):
2626 2629 raise util.Abort(msg, hint=hint)
2627 2630 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2628 2631 if clearable and repo.vfs.exists(f):
2629 2632 util.unlink(repo.join(f))
@@ -1,155 +1,159
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "mq=" >> $HGRCPATH
3 3
4 4 $ tipparents() {
5 5 > hg parents --template "{rev}:{node|short} {desc|firstline}\n" -r tip
6 6 > }
7 7
8 8 Test import and merge diffs
9 9
10 10 $ hg init repo
11 11 $ cd repo
12 12 $ echo a > a
13 13 $ hg ci -Am adda
14 14 adding a
15 15 $ echo a >> a
16 16 $ hg ci -m changea
17 17 $ echo c > c
18 18 $ hg ci -Am addc
19 19 adding c
20 20 $ hg up 0
21 21 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 22 $ echo b > b
23 23 $ hg ci -Am addb
24 24 adding b
25 25 created new head
26 26 $ hg up 1
27 27 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
28 28 $ hg merge 3
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 (branch merge, don't forget to commit)
31 31 $ hg ci -m merge
32 32 $ hg export . > ../merge.diff
33 33 $ cd ..
34 34 $ hg clone -r2 repo repo2
35 35 adding changesets
36 36 adding manifests
37 37 adding file changes
38 38 added 3 changesets with 3 changes to 2 files
39 39 updating to branch default
40 40 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 41 $ cd repo2
42 42 $ hg pull -r3 ../repo
43 43 pulling from ../repo
44 44 searching for changes
45 45 adding changesets
46 46 adding manifests
47 47 adding file changes
48 48 added 1 changesets with 1 changes to 1 files (+1 heads)
49 49 (run 'hg heads' to see heads, 'hg merge' to merge)
50 50
51 51 Test without --exact and diff.p1 == workingdir.p1
52 52
53 53 $ hg up 1
54 54 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
55 55 $ hg import ../merge.diff
56 56 applying ../merge.diff
57 57 $ tipparents
58 58 1:540395c44225 changea
59 59 3:102a90ea7b4a addb
60 60 $ hg strip --no-backup tip
61 61 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
62 62
63 63 Test without --exact and diff.p1 != workingdir.p1
64 64
65 65 $ hg up 2
66 66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 67 $ hg import ../merge.diff
68 68 applying ../merge.diff
69 warning: import the patch as a normal revision
70 (use --exact to import the patch as a merge)
69 71 $ tipparents
70 72 2:890ecaa90481 addc
71 73 $ hg strip --no-backup tip
72 74 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 75
74 76 Test with --exact
75 77
76 78 $ hg import --exact ../merge.diff
77 79 applying ../merge.diff
78 80 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
79 81 $ tipparents
80 82 1:540395c44225 changea
81 83 3:102a90ea7b4a addb
82 84 $ hg strip --no-backup tip
83 85 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
84 86
85 87 Test with --bypass and diff.p1 == workingdir.p1
86 88
87 89 $ hg up 1
88 90 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 91 $ hg import --bypass ../merge.diff
90 92 applying ../merge.diff
91 93 $ tipparents
92 94 1:540395c44225 changea
93 95 3:102a90ea7b4a addb
94 96 $ hg strip --no-backup tip
95 97
96 98 Test with --bypass and diff.p1 != workingdir.p1
97 99
98 100 $ hg up 2
99 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 102 $ hg import --bypass ../merge.diff
101 103 applying ../merge.diff
104 warning: import the patch as a normal revision
105 (use --exact to import the patch as a merge)
102 106 $ tipparents
103 107 2:890ecaa90481 addc
104 108 $ hg strip --no-backup tip
105 109
106 110 Test with --bypass and --exact
107 111
108 112 $ hg import --bypass --exact ../merge.diff
109 113 applying ../merge.diff
110 114 $ tipparents
111 115 1:540395c44225 changea
112 116 3:102a90ea7b4a addb
113 117 $ hg strip --no-backup tip
114 118
115 119 $ cd ..
116 120
117 121 Test that --exact on a bad header doesn't corrupt the repo (issue3616)
118 122
119 123 $ hg init repo3
120 124 $ cd repo3
121 125 $ echo a>a
122 126 $ hg ci -Aqm0
123 127 $ echo a>>a
124 128 $ hg ci -m1
125 129 $ echo a>>a
126 130 $ hg ci -m2
127 131 $ echo a>a
128 132 $ echo b>>a
129 133 $ echo a>>a
130 134 $ hg ci -m3
131 135 $ hg export 2 | head -7 > ../a.patch
132 136 $ hg export tip > out
133 137 >>> apatch = open("../a.patch", "ab")
134 138 >>> apatch.write("".join(open("out").readlines()[7:]))
135 139
136 140 $ cd ..
137 141 $ hg clone -qr0 repo3 repo3-clone
138 142 $ cd repo3-clone
139 143 $ hg pull -qr1 ../repo3
140 144
141 145 $ hg import --exact ../a.patch
142 146 applying ../a.patch
143 147 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 148 patching file a
145 149 Hunk #1 succeeded at 1 with fuzz 1 (offset -1 lines).
146 150 transaction abort!
147 151 rollback completed
148 152 abort: patch is damaged or loses information
149 153 [255]
150 154 $ hg verify
151 155 checking changesets
152 156 checking manifests
153 157 crosschecking files in changesets and manifests
154 158 checking files
155 159 1 files, 2 changesets, 2 total revisions
General Comments 0
You need to be logged in to leave comments. Login now