##// END OF EJS Templates
cmdutil: enhance "getcommiteditor()" for specific usages in MQ...
FUJIWARA Katsunori -
r21419:27278548 default
parent child Browse files
Show More
@@ -1,2479 +1,2501 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_('uncommitted changes'))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 def getcommiteditor(edit=False, **opts):
113 """get appropriate commit message editor according to '--edit' option"""
114 if edit:
115 return commitforceeditor
112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
113 """get appropriate commit message editor according to '--edit' option
114
115 'finishdesc' is a function to be called with edited commit message
116 (= 'description' of the new changeset) just after editing, but
117 before checking empty-ness. It should return actual text to be
118 stored into history. This allows to change description before
119 storing.
120
121 'extramsg' is a extra message to be shown in the editor instead of
122 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
123 is automatically added.
124
125 'getcommiteditor' returns 'commitforceeditor' regardless of
126 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
127 they are specific for usage in MQ.
128 """
129 if edit or finishdesc or extramsg:
130 return lambda r, c, s: commitforceeditor(r, c, s,
131 finishdesc=finishdesc,
132 extramsg=extramsg)
116 133 else:
117 134 return commiteditor
118 135
119 136 def loglimit(opts):
120 137 """get the log limit according to option -l/--limit"""
121 138 limit = opts.get('limit')
122 139 if limit:
123 140 try:
124 141 limit = int(limit)
125 142 except ValueError:
126 143 raise util.Abort(_('limit must be a positive integer'))
127 144 if limit <= 0:
128 145 raise util.Abort(_('limit must be positive'))
129 146 else:
130 147 limit = None
131 148 return limit
132 149
133 150 def makefilename(repo, pat, node, desc=None,
134 151 total=None, seqno=None, revwidth=None, pathname=None):
135 152 node_expander = {
136 153 'H': lambda: hex(node),
137 154 'R': lambda: str(repo.changelog.rev(node)),
138 155 'h': lambda: short(node),
139 156 'm': lambda: re.sub('[^\w]', '_', str(desc))
140 157 }
141 158 expander = {
142 159 '%': lambda: '%',
143 160 'b': lambda: os.path.basename(repo.root),
144 161 }
145 162
146 163 try:
147 164 if node:
148 165 expander.update(node_expander)
149 166 if node:
150 167 expander['r'] = (lambda:
151 168 str(repo.changelog.rev(node)).zfill(revwidth or 0))
152 169 if total is not None:
153 170 expander['N'] = lambda: str(total)
154 171 if seqno is not None:
155 172 expander['n'] = lambda: str(seqno)
156 173 if total is not None and seqno is not None:
157 174 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
158 175 if pathname is not None:
159 176 expander['s'] = lambda: os.path.basename(pathname)
160 177 expander['d'] = lambda: os.path.dirname(pathname) or '.'
161 178 expander['p'] = lambda: pathname
162 179
163 180 newname = []
164 181 patlen = len(pat)
165 182 i = 0
166 183 while i < patlen:
167 184 c = pat[i]
168 185 if c == '%':
169 186 i += 1
170 187 c = pat[i]
171 188 c = expander[c]()
172 189 newname.append(c)
173 190 i += 1
174 191 return ''.join(newname)
175 192 except KeyError, inst:
176 193 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
177 194 inst.args[0])
178 195
179 196 def makefileobj(repo, pat, node=None, desc=None, total=None,
180 197 seqno=None, revwidth=None, mode='wb', modemap=None,
181 198 pathname=None):
182 199
183 200 writable = mode not in ('r', 'rb')
184 201
185 202 if not pat or pat == '-':
186 203 fp = writable and repo.ui.fout or repo.ui.fin
187 204 if util.safehasattr(fp, 'fileno'):
188 205 return os.fdopen(os.dup(fp.fileno()), mode)
189 206 else:
190 207 # if this fp can't be duped properly, return
191 208 # a dummy object that can be closed
192 209 class wrappedfileobj(object):
193 210 noop = lambda x: None
194 211 def __init__(self, f):
195 212 self.f = f
196 213 def __getattr__(self, attr):
197 214 if attr == 'close':
198 215 return self.noop
199 216 else:
200 217 return getattr(self.f, attr)
201 218
202 219 return wrappedfileobj(fp)
203 220 if util.safehasattr(pat, 'write') and writable:
204 221 return pat
205 222 if util.safehasattr(pat, 'read') and 'r' in mode:
206 223 return pat
207 224 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
208 225 if modemap is not None:
209 226 mode = modemap.get(fn, mode)
210 227 if mode == 'wb':
211 228 modemap[fn] = 'ab'
212 229 return open(fn, mode)
213 230
214 231 def openrevlog(repo, cmd, file_, opts):
215 232 """opens the changelog, manifest, a filelog or a given revlog"""
216 233 cl = opts['changelog']
217 234 mf = opts['manifest']
218 235 msg = None
219 236 if cl and mf:
220 237 msg = _('cannot specify --changelog and --manifest at the same time')
221 238 elif cl or mf:
222 239 if file_:
223 240 msg = _('cannot specify filename with --changelog or --manifest')
224 241 elif not repo:
225 242 msg = _('cannot specify --changelog or --manifest '
226 243 'without a repository')
227 244 if msg:
228 245 raise util.Abort(msg)
229 246
230 247 r = None
231 248 if repo:
232 249 if cl:
233 250 r = repo.unfiltered().changelog
234 251 elif mf:
235 252 r = repo.manifest
236 253 elif file_:
237 254 filelog = repo.file(file_)
238 255 if len(filelog):
239 256 r = filelog
240 257 if not r:
241 258 if not file_:
242 259 raise error.CommandError(cmd, _('invalid arguments'))
243 260 if not os.path.isfile(file_):
244 261 raise util.Abort(_("revlog '%s' not found") % file_)
245 262 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
246 263 file_[:-2] + ".i")
247 264 return r
248 265
249 266 def copy(ui, repo, pats, opts, rename=False):
250 267 # called with the repo lock held
251 268 #
252 269 # hgsep => pathname that uses "/" to separate directories
253 270 # ossep => pathname that uses os.sep to separate directories
254 271 cwd = repo.getcwd()
255 272 targets = {}
256 273 after = opts.get("after")
257 274 dryrun = opts.get("dry_run")
258 275 wctx = repo[None]
259 276
260 277 def walkpat(pat):
261 278 srcs = []
262 279 badstates = after and '?' or '?r'
263 280 m = scmutil.match(repo[None], [pat], opts, globbed=True)
264 281 for abs in repo.walk(m):
265 282 state = repo.dirstate[abs]
266 283 rel = m.rel(abs)
267 284 exact = m.exact(abs)
268 285 if state in badstates:
269 286 if exact and state == '?':
270 287 ui.warn(_('%s: not copying - file is not managed\n') % rel)
271 288 if exact and state == 'r':
272 289 ui.warn(_('%s: not copying - file has been marked for'
273 290 ' remove\n') % rel)
274 291 continue
275 292 # abs: hgsep
276 293 # rel: ossep
277 294 srcs.append((abs, rel, exact))
278 295 return srcs
279 296
280 297 # abssrc: hgsep
281 298 # relsrc: ossep
282 299 # otarget: ossep
283 300 def copyfile(abssrc, relsrc, otarget, exact):
284 301 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
285 302 if '/' in abstarget:
286 303 # We cannot normalize abstarget itself, this would prevent
287 304 # case only renames, like a => A.
288 305 abspath, absname = abstarget.rsplit('/', 1)
289 306 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
290 307 reltarget = repo.pathto(abstarget, cwd)
291 308 target = repo.wjoin(abstarget)
292 309 src = repo.wjoin(abssrc)
293 310 state = repo.dirstate[abstarget]
294 311
295 312 scmutil.checkportable(ui, abstarget)
296 313
297 314 # check for collisions
298 315 prevsrc = targets.get(abstarget)
299 316 if prevsrc is not None:
300 317 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
301 318 (reltarget, repo.pathto(abssrc, cwd),
302 319 repo.pathto(prevsrc, cwd)))
303 320 return
304 321
305 322 # check for overwrites
306 323 exists = os.path.lexists(target)
307 324 samefile = False
308 325 if exists and abssrc != abstarget:
309 326 if (repo.dirstate.normalize(abssrc) ==
310 327 repo.dirstate.normalize(abstarget)):
311 328 if not rename:
312 329 ui.warn(_("%s: can't copy - same file\n") % reltarget)
313 330 return
314 331 exists = False
315 332 samefile = True
316 333
317 334 if not after and exists or after and state in 'mn':
318 335 if not opts['force']:
319 336 ui.warn(_('%s: not overwriting - file exists\n') %
320 337 reltarget)
321 338 return
322 339
323 340 if after:
324 341 if not exists:
325 342 if rename:
326 343 ui.warn(_('%s: not recording move - %s does not exist\n') %
327 344 (relsrc, reltarget))
328 345 else:
329 346 ui.warn(_('%s: not recording copy - %s does not exist\n') %
330 347 (relsrc, reltarget))
331 348 return
332 349 elif not dryrun:
333 350 try:
334 351 if exists:
335 352 os.unlink(target)
336 353 targetdir = os.path.dirname(target) or '.'
337 354 if not os.path.isdir(targetdir):
338 355 os.makedirs(targetdir)
339 356 if samefile:
340 357 tmp = target + "~hgrename"
341 358 os.rename(src, tmp)
342 359 os.rename(tmp, target)
343 360 else:
344 361 util.copyfile(src, target)
345 362 srcexists = True
346 363 except IOError, inst:
347 364 if inst.errno == errno.ENOENT:
348 365 ui.warn(_('%s: deleted in working copy\n') % relsrc)
349 366 srcexists = False
350 367 else:
351 368 ui.warn(_('%s: cannot copy - %s\n') %
352 369 (relsrc, inst.strerror))
353 370 return True # report a failure
354 371
355 372 if ui.verbose or not exact:
356 373 if rename:
357 374 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
358 375 else:
359 376 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
360 377
361 378 targets[abstarget] = abssrc
362 379
363 380 # fix up dirstate
364 381 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
365 382 dryrun=dryrun, cwd=cwd)
366 383 if rename and not dryrun:
367 384 if not after and srcexists and not samefile:
368 385 util.unlinkpath(repo.wjoin(abssrc))
369 386 wctx.forget([abssrc])
370 387
371 388 # pat: ossep
372 389 # dest ossep
373 390 # srcs: list of (hgsep, hgsep, ossep, bool)
374 391 # return: function that takes hgsep and returns ossep
375 392 def targetpathfn(pat, dest, srcs):
376 393 if os.path.isdir(pat):
377 394 abspfx = pathutil.canonpath(repo.root, cwd, pat)
378 395 abspfx = util.localpath(abspfx)
379 396 if destdirexists:
380 397 striplen = len(os.path.split(abspfx)[0])
381 398 else:
382 399 striplen = len(abspfx)
383 400 if striplen:
384 401 striplen += len(os.sep)
385 402 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
386 403 elif destdirexists:
387 404 res = lambda p: os.path.join(dest,
388 405 os.path.basename(util.localpath(p)))
389 406 else:
390 407 res = lambda p: dest
391 408 return res
392 409
393 410 # pat: ossep
394 411 # dest ossep
395 412 # srcs: list of (hgsep, hgsep, ossep, bool)
396 413 # return: function that takes hgsep and returns ossep
397 414 def targetpathafterfn(pat, dest, srcs):
398 415 if matchmod.patkind(pat):
399 416 # a mercurial pattern
400 417 res = lambda p: os.path.join(dest,
401 418 os.path.basename(util.localpath(p)))
402 419 else:
403 420 abspfx = pathutil.canonpath(repo.root, cwd, pat)
404 421 if len(abspfx) < len(srcs[0][0]):
405 422 # A directory. Either the target path contains the last
406 423 # component of the source path or it does not.
407 424 def evalpath(striplen):
408 425 score = 0
409 426 for s in srcs:
410 427 t = os.path.join(dest, util.localpath(s[0])[striplen:])
411 428 if os.path.lexists(t):
412 429 score += 1
413 430 return score
414 431
415 432 abspfx = util.localpath(abspfx)
416 433 striplen = len(abspfx)
417 434 if striplen:
418 435 striplen += len(os.sep)
419 436 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
420 437 score = evalpath(striplen)
421 438 striplen1 = len(os.path.split(abspfx)[0])
422 439 if striplen1:
423 440 striplen1 += len(os.sep)
424 441 if evalpath(striplen1) > score:
425 442 striplen = striplen1
426 443 res = lambda p: os.path.join(dest,
427 444 util.localpath(p)[striplen:])
428 445 else:
429 446 # a file
430 447 if destdirexists:
431 448 res = lambda p: os.path.join(dest,
432 449 os.path.basename(util.localpath(p)))
433 450 else:
434 451 res = lambda p: dest
435 452 return res
436 453
437 454
438 455 pats = scmutil.expandpats(pats)
439 456 if not pats:
440 457 raise util.Abort(_('no source or destination specified'))
441 458 if len(pats) == 1:
442 459 raise util.Abort(_('no destination specified'))
443 460 dest = pats.pop()
444 461 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
445 462 if not destdirexists:
446 463 if len(pats) > 1 or matchmod.patkind(pats[0]):
447 464 raise util.Abort(_('with multiple sources, destination must be an '
448 465 'existing directory'))
449 466 if util.endswithsep(dest):
450 467 raise util.Abort(_('destination %s is not a directory') % dest)
451 468
452 469 tfn = targetpathfn
453 470 if after:
454 471 tfn = targetpathafterfn
455 472 copylist = []
456 473 for pat in pats:
457 474 srcs = walkpat(pat)
458 475 if not srcs:
459 476 continue
460 477 copylist.append((tfn(pat, dest, srcs), srcs))
461 478 if not copylist:
462 479 raise util.Abort(_('no files to copy'))
463 480
464 481 errors = 0
465 482 for targetpath, srcs in copylist:
466 483 for abssrc, relsrc, exact in srcs:
467 484 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
468 485 errors += 1
469 486
470 487 if errors:
471 488 ui.warn(_('(consider using --after)\n'))
472 489
473 490 return errors != 0
474 491
475 492 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
476 493 runargs=None, appendpid=False):
477 494 '''Run a command as a service.'''
478 495
479 496 def writepid(pid):
480 497 if opts['pid_file']:
481 498 mode = appendpid and 'a' or 'w'
482 499 fp = open(opts['pid_file'], mode)
483 500 fp.write(str(pid) + '\n')
484 501 fp.close()
485 502
486 503 if opts['daemon'] and not opts['daemon_pipefds']:
487 504 # Signal child process startup with file removal
488 505 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
489 506 os.close(lockfd)
490 507 try:
491 508 if not runargs:
492 509 runargs = util.hgcmd() + sys.argv[1:]
493 510 runargs.append('--daemon-pipefds=%s' % lockpath)
494 511 # Don't pass --cwd to the child process, because we've already
495 512 # changed directory.
496 513 for i in xrange(1, len(runargs)):
497 514 if runargs[i].startswith('--cwd='):
498 515 del runargs[i]
499 516 break
500 517 elif runargs[i].startswith('--cwd'):
501 518 del runargs[i:i + 2]
502 519 break
503 520 def condfn():
504 521 return not os.path.exists(lockpath)
505 522 pid = util.rundetached(runargs, condfn)
506 523 if pid < 0:
507 524 raise util.Abort(_('child process failed to start'))
508 525 writepid(pid)
509 526 finally:
510 527 try:
511 528 os.unlink(lockpath)
512 529 except OSError, e:
513 530 if e.errno != errno.ENOENT:
514 531 raise
515 532 if parentfn:
516 533 return parentfn(pid)
517 534 else:
518 535 return
519 536
520 537 if initfn:
521 538 initfn()
522 539
523 540 if not opts['daemon']:
524 541 writepid(os.getpid())
525 542
526 543 if opts['daemon_pipefds']:
527 544 lockpath = opts['daemon_pipefds']
528 545 try:
529 546 os.setsid()
530 547 except AttributeError:
531 548 pass
532 549 os.unlink(lockpath)
533 550 util.hidewindow()
534 551 sys.stdout.flush()
535 552 sys.stderr.flush()
536 553
537 554 nullfd = os.open(os.devnull, os.O_RDWR)
538 555 logfilefd = nullfd
539 556 if logfile:
540 557 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
541 558 os.dup2(nullfd, 0)
542 559 os.dup2(logfilefd, 1)
543 560 os.dup2(logfilefd, 2)
544 561 if nullfd not in (0, 1, 2):
545 562 os.close(nullfd)
546 563 if logfile and logfilefd not in (0, 1, 2):
547 564 os.close(logfilefd)
548 565
549 566 if runfn:
550 567 return runfn()
551 568
552 569 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
553 570 """Utility function used by commands.import to import a single patch
554 571
555 572 This function is explicitly defined here to help the evolve extension to
556 573 wrap this part of the import logic.
557 574
558 575 The API is currently a bit ugly because it a simple code translation from
559 576 the import command. Feel free to make it better.
560 577
561 578 :hunk: a patch (as a binary string)
562 579 :parents: nodes that will be parent of the created commit
563 580 :opts: the full dict of option passed to the import command
564 581 :msgs: list to save commit message to.
565 582 (used in case we need to save it when failing)
566 583 :updatefunc: a function that update a repo to a given node
567 584 updatefunc(<repo>, <node>)
568 585 """
569 586 tmpname, message, user, date, branch, nodeid, p1, p2 = \
570 587 patch.extract(ui, hunk)
571 588
572 589 editor = getcommiteditor(**opts)
573 590 update = not opts.get('bypass')
574 591 strip = opts["strip"]
575 592 sim = float(opts.get('similarity') or 0)
576 593 if not tmpname:
577 594 return (None, None)
578 595 msg = _('applied to working directory')
579 596
580 597 try:
581 598 cmdline_message = logmessage(ui, opts)
582 599 if cmdline_message:
583 600 # pickup the cmdline msg
584 601 message = cmdline_message
585 602 elif message:
586 603 # pickup the patch msg
587 604 message = message.strip()
588 605 else:
589 606 # launch the editor
590 607 message = None
591 608 ui.debug('message:\n%s\n' % message)
592 609
593 610 if len(parents) == 1:
594 611 parents.append(repo[nullid])
595 612 if opts.get('exact'):
596 613 if not nodeid or not p1:
597 614 raise util.Abort(_('not a Mercurial patch'))
598 615 p1 = repo[p1]
599 616 p2 = repo[p2 or nullid]
600 617 elif p2:
601 618 try:
602 619 p1 = repo[p1]
603 620 p2 = repo[p2]
604 621 # Without any options, consider p2 only if the
605 622 # patch is being applied on top of the recorded
606 623 # first parent.
607 624 if p1 != parents[0]:
608 625 p1 = parents[0]
609 626 p2 = repo[nullid]
610 627 except error.RepoError:
611 628 p1, p2 = parents
612 629 else:
613 630 p1, p2 = parents
614 631
615 632 n = None
616 633 if update:
617 634 if p1 != parents[0]:
618 635 updatefunc(repo, p1.node())
619 636 if p2 != parents[1]:
620 637 repo.setparents(p1.node(), p2.node())
621 638
622 639 if opts.get('exact') or opts.get('import_branch'):
623 640 repo.dirstate.setbranch(branch or 'default')
624 641
625 642 files = set()
626 643 patch.patch(ui, repo, tmpname, strip=strip, files=files,
627 644 eolmode=None, similarity=sim / 100.0)
628 645 files = list(files)
629 646 if opts.get('no_commit'):
630 647 if message:
631 648 msgs.append(message)
632 649 else:
633 650 if opts.get('exact') or p2:
634 651 # If you got here, you either use --force and know what
635 652 # you are doing or used --exact or a merge patch while
636 653 # being updated to its first parent.
637 654 m = None
638 655 else:
639 656 m = scmutil.matchfiles(repo, files or [])
640 657 n = repo.commit(message, opts.get('user') or user,
641 658 opts.get('date') or date, match=m,
642 659 editor=editor)
643 660 else:
644 661 if opts.get('exact') or opts.get('import_branch'):
645 662 branch = branch or 'default'
646 663 else:
647 664 branch = p1.branch()
648 665 store = patch.filestore()
649 666 try:
650 667 files = set()
651 668 try:
652 669 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
653 670 files, eolmode=None)
654 671 except patch.PatchError, e:
655 672 raise util.Abort(str(e))
656 673 memctx = context.makememctx(repo, (p1.node(), p2.node()),
657 674 message,
658 675 opts.get('user') or user,
659 676 opts.get('date') or date,
660 677 branch, files, store,
661 678 editor=getcommiteditor())
662 679 n = memctx.commit()
663 680 finally:
664 681 store.close()
665 682 if opts.get('exact') and hex(n) != nodeid:
666 683 raise util.Abort(_('patch is damaged or loses information'))
667 684 if n:
668 685 # i18n: refers to a short changeset id
669 686 msg = _('created %s') % short(n)
670 687 return (msg, n)
671 688 finally:
672 689 os.unlink(tmpname)
673 690
674 691 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
675 692 opts=None):
676 693 '''export changesets as hg patches.'''
677 694
678 695 total = len(revs)
679 696 revwidth = max([len(str(rev)) for rev in revs])
680 697 filemode = {}
681 698
682 699 def single(rev, seqno, fp):
683 700 ctx = repo[rev]
684 701 node = ctx.node()
685 702 parents = [p.node() for p in ctx.parents() if p]
686 703 branch = ctx.branch()
687 704 if switch_parent:
688 705 parents.reverse()
689 706 prev = (parents and parents[0]) or nullid
690 707
691 708 shouldclose = False
692 709 if not fp and len(template) > 0:
693 710 desc_lines = ctx.description().rstrip().split('\n')
694 711 desc = desc_lines[0] #Commit always has a first line.
695 712 fp = makefileobj(repo, template, node, desc=desc, total=total,
696 713 seqno=seqno, revwidth=revwidth, mode='wb',
697 714 modemap=filemode)
698 715 if fp != template:
699 716 shouldclose = True
700 717 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
701 718 repo.ui.note("%s\n" % fp.name)
702 719
703 720 if not fp:
704 721 write = repo.ui.write
705 722 else:
706 723 def write(s, **kw):
707 724 fp.write(s)
708 725
709 726
710 727 write("# HG changeset patch\n")
711 728 write("# User %s\n" % ctx.user())
712 729 write("# Date %d %d\n" % ctx.date())
713 730 write("# %s\n" % util.datestr(ctx.date()))
714 731 if branch and branch != 'default':
715 732 write("# Branch %s\n" % branch)
716 733 write("# Node ID %s\n" % hex(node))
717 734 write("# Parent %s\n" % hex(prev))
718 735 if len(parents) > 1:
719 736 write("# Parent %s\n" % hex(parents[1]))
720 737 write(ctx.description().rstrip())
721 738 write("\n\n")
722 739
723 740 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
724 741 write(chunk, label=label)
725 742
726 743 if shouldclose:
727 744 fp.close()
728 745
729 746 for seqno, rev in enumerate(revs):
730 747 single(rev, seqno + 1, fp)
731 748
732 749 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
733 750 changes=None, stat=False, fp=None, prefix='',
734 751 listsubrepos=False):
735 752 '''show diff or diffstat.'''
736 753 if fp is None:
737 754 write = ui.write
738 755 else:
739 756 def write(s, **kw):
740 757 fp.write(s)
741 758
742 759 if stat:
743 760 diffopts = diffopts.copy(context=0)
744 761 width = 80
745 762 if not ui.plain():
746 763 width = ui.termwidth()
747 764 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
748 765 prefix=prefix)
749 766 for chunk, label in patch.diffstatui(util.iterlines(chunks),
750 767 width=width,
751 768 git=diffopts.git):
752 769 write(chunk, label=label)
753 770 else:
754 771 for chunk, label in patch.diffui(repo, node1, node2, match,
755 772 changes, diffopts, prefix=prefix):
756 773 write(chunk, label=label)
757 774
758 775 if listsubrepos:
759 776 ctx1 = repo[node1]
760 777 ctx2 = repo[node2]
761 778 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
762 779 tempnode2 = node2
763 780 try:
764 781 if node2 is not None:
765 782 tempnode2 = ctx2.substate[subpath][1]
766 783 except KeyError:
767 784 # A subrepo that existed in node1 was deleted between node1 and
768 785 # node2 (inclusive). Thus, ctx2's substate won't contain that
769 786 # subpath. The best we can do is to ignore it.
770 787 tempnode2 = None
771 788 submatch = matchmod.narrowmatcher(subpath, match)
772 789 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
773 790 stat=stat, fp=fp, prefix=prefix)
774 791
775 792 class changeset_printer(object):
776 793 '''show changeset information when templating not requested.'''
777 794
778 795 def __init__(self, ui, repo, patch, diffopts, buffered):
779 796 self.ui = ui
780 797 self.repo = repo
781 798 self.buffered = buffered
782 799 self.patch = patch
783 800 self.diffopts = diffopts
784 801 self.header = {}
785 802 self.hunk = {}
786 803 self.lastheader = None
787 804 self.footer = None
788 805
789 806 def flush(self, rev):
790 807 if rev in self.header:
791 808 h = self.header[rev]
792 809 if h != self.lastheader:
793 810 self.lastheader = h
794 811 self.ui.write(h)
795 812 del self.header[rev]
796 813 if rev in self.hunk:
797 814 self.ui.write(self.hunk[rev])
798 815 del self.hunk[rev]
799 816 return 1
800 817 return 0
801 818
802 819 def close(self):
803 820 if self.footer:
804 821 self.ui.write(self.footer)
805 822
806 823 def show(self, ctx, copies=None, matchfn=None, **props):
807 824 if self.buffered:
808 825 self.ui.pushbuffer()
809 826 self._show(ctx, copies, matchfn, props)
810 827 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
811 828 else:
812 829 self._show(ctx, copies, matchfn, props)
813 830
814 831 def _show(self, ctx, copies, matchfn, props):
815 832 '''show a single changeset or file revision'''
816 833 changenode = ctx.node()
817 834 rev = ctx.rev()
818 835
819 836 if self.ui.quiet:
820 837 self.ui.write("%d:%s\n" % (rev, short(changenode)),
821 838 label='log.node')
822 839 return
823 840
824 841 log = self.repo.changelog
825 842 date = util.datestr(ctx.date())
826 843
827 844 hexfunc = self.ui.debugflag and hex or short
828 845
829 846 parents = [(p, hexfunc(log.node(p)))
830 847 for p in self._meaningful_parentrevs(log, rev)]
831 848
832 849 # i18n: column positioning for "hg log"
833 850 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
834 851 label='log.changeset changeset.%s' % ctx.phasestr())
835 852
836 853 branch = ctx.branch()
837 854 # don't show the default branch name
838 855 if branch != 'default':
839 856 # i18n: column positioning for "hg log"
840 857 self.ui.write(_("branch: %s\n") % branch,
841 858 label='log.branch')
842 859 for bookmark in self.repo.nodebookmarks(changenode):
843 860 # i18n: column positioning for "hg log"
844 861 self.ui.write(_("bookmark: %s\n") % bookmark,
845 862 label='log.bookmark')
846 863 for tag in self.repo.nodetags(changenode):
847 864 # i18n: column positioning for "hg log"
848 865 self.ui.write(_("tag: %s\n") % tag,
849 866 label='log.tag')
850 867 if self.ui.debugflag and ctx.phase():
851 868 # i18n: column positioning for "hg log"
852 869 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
853 870 label='log.phase')
854 871 for parent in parents:
855 872 # i18n: column positioning for "hg log"
856 873 self.ui.write(_("parent: %d:%s\n") % parent,
857 874 label='log.parent changeset.%s' % ctx.phasestr())
858 875
859 876 if self.ui.debugflag:
860 877 mnode = ctx.manifestnode()
861 878 # i18n: column positioning for "hg log"
862 879 self.ui.write(_("manifest: %d:%s\n") %
863 880 (self.repo.manifest.rev(mnode), hex(mnode)),
864 881 label='ui.debug log.manifest')
865 882 # i18n: column positioning for "hg log"
866 883 self.ui.write(_("user: %s\n") % ctx.user(),
867 884 label='log.user')
868 885 # i18n: column positioning for "hg log"
869 886 self.ui.write(_("date: %s\n") % date,
870 887 label='log.date')
871 888
872 889 if self.ui.debugflag:
873 890 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
874 891 for key, value in zip([# i18n: column positioning for "hg log"
875 892 _("files:"),
876 893 # i18n: column positioning for "hg log"
877 894 _("files+:"),
878 895 # i18n: column positioning for "hg log"
879 896 _("files-:")], files):
880 897 if value:
881 898 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
882 899 label='ui.debug log.files')
883 900 elif ctx.files() and self.ui.verbose:
884 901 # i18n: column positioning for "hg log"
885 902 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
886 903 label='ui.note log.files')
887 904 if copies and self.ui.verbose:
888 905 copies = ['%s (%s)' % c for c in copies]
889 906 # i18n: column positioning for "hg log"
890 907 self.ui.write(_("copies: %s\n") % ' '.join(copies),
891 908 label='ui.note log.copies')
892 909
893 910 extra = ctx.extra()
894 911 if extra and self.ui.debugflag:
895 912 for key, value in sorted(extra.items()):
896 913 # i18n: column positioning for "hg log"
897 914 self.ui.write(_("extra: %s=%s\n")
898 915 % (key, value.encode('string_escape')),
899 916 label='ui.debug log.extra')
900 917
901 918 description = ctx.description().strip()
902 919 if description:
903 920 if self.ui.verbose:
904 921 self.ui.write(_("description:\n"),
905 922 label='ui.note log.description')
906 923 self.ui.write(description,
907 924 label='ui.note log.description')
908 925 self.ui.write("\n\n")
909 926 else:
910 927 # i18n: column positioning for "hg log"
911 928 self.ui.write(_("summary: %s\n") %
912 929 description.splitlines()[0],
913 930 label='log.summary')
914 931 self.ui.write("\n")
915 932
916 933 self.showpatch(changenode, matchfn)
917 934
918 935 def showpatch(self, node, matchfn):
919 936 if not matchfn:
920 937 matchfn = self.patch
921 938 if matchfn:
922 939 stat = self.diffopts.get('stat')
923 940 diff = self.diffopts.get('patch')
924 941 diffopts = patch.diffopts(self.ui, self.diffopts)
925 942 prev = self.repo.changelog.parents(node)[0]
926 943 if stat:
927 944 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
928 945 match=matchfn, stat=True)
929 946 if diff:
930 947 if stat:
931 948 self.ui.write("\n")
932 949 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
933 950 match=matchfn, stat=False)
934 951 self.ui.write("\n")
935 952
936 953 def _meaningful_parentrevs(self, log, rev):
937 954 """Return list of meaningful (or all if debug) parentrevs for rev.
938 955
939 956 For merges (two non-nullrev revisions) both parents are meaningful.
940 957 Otherwise the first parent revision is considered meaningful if it
941 958 is not the preceding revision.
942 959 """
943 960 parents = log.parentrevs(rev)
944 961 if not self.ui.debugflag and parents[1] == nullrev:
945 962 if parents[0] >= rev - 1:
946 963 parents = []
947 964 else:
948 965 parents = [parents[0]]
949 966 return parents
950 967
951 968
952 969 class changeset_templater(changeset_printer):
953 970 '''format changeset information.'''
954 971
955 972 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
956 973 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
957 974 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
958 975 defaulttempl = {
959 976 'parent': '{rev}:{node|formatnode} ',
960 977 'manifest': '{rev}:{node|formatnode}',
961 978 'file_copy': '{name} ({source})',
962 979 'extra': '{key}={value|stringescape}'
963 980 }
964 981 # filecopy is preserved for compatibility reasons
965 982 defaulttempl['filecopy'] = defaulttempl['file_copy']
966 983 self.t = templater.templater(mapfile, {'formatnode': formatnode},
967 984 cache=defaulttempl)
968 985 if tmpl:
969 986 self.t.cache['changeset'] = tmpl
970 987
971 988 self.cache = {}
972 989
973 990 def _meaningful_parentrevs(self, ctx):
974 991 """Return list of meaningful (or all if debug) parentrevs for rev.
975 992 """
976 993 parents = ctx.parents()
977 994 if len(parents) > 1:
978 995 return parents
979 996 if self.ui.debugflag:
980 997 return [parents[0], self.repo['null']]
981 998 if parents[0].rev() >= ctx.rev() - 1:
982 999 return []
983 1000 return parents
984 1001
985 1002 def _show(self, ctx, copies, matchfn, props):
986 1003 '''show a single changeset or file revision'''
987 1004
988 1005 showlist = templatekw.showlist
989 1006
990 1007 # showparents() behaviour depends on ui trace level which
991 1008 # causes unexpected behaviours at templating level and makes
992 1009 # it harder to extract it in a standalone function. Its
993 1010 # behaviour cannot be changed so leave it here for now.
994 1011 def showparents(**args):
995 1012 ctx = args['ctx']
996 1013 parents = [[('rev', p.rev()), ('node', p.hex())]
997 1014 for p in self._meaningful_parentrevs(ctx)]
998 1015 return showlist('parent', parents, **args)
999 1016
1000 1017 props = props.copy()
1001 1018 props.update(templatekw.keywords)
1002 1019 props['parents'] = showparents
1003 1020 props['templ'] = self.t
1004 1021 props['ctx'] = ctx
1005 1022 props['repo'] = self.repo
1006 1023 props['revcache'] = {'copies': copies}
1007 1024 props['cache'] = self.cache
1008 1025
1009 1026 # find correct templates for current mode
1010 1027
1011 1028 tmplmodes = [
1012 1029 (True, None),
1013 1030 (self.ui.verbose, 'verbose'),
1014 1031 (self.ui.quiet, 'quiet'),
1015 1032 (self.ui.debugflag, 'debug'),
1016 1033 ]
1017 1034
1018 1035 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1019 1036 for mode, postfix in tmplmodes:
1020 1037 for type in types:
1021 1038 cur = postfix and ('%s_%s' % (type, postfix)) or type
1022 1039 if mode and cur in self.t:
1023 1040 types[type] = cur
1024 1041
1025 1042 try:
1026 1043
1027 1044 # write header
1028 1045 if types['header']:
1029 1046 h = templater.stringify(self.t(types['header'], **props))
1030 1047 if self.buffered:
1031 1048 self.header[ctx.rev()] = h
1032 1049 else:
1033 1050 if self.lastheader != h:
1034 1051 self.lastheader = h
1035 1052 self.ui.write(h)
1036 1053
1037 1054 # write changeset metadata, then patch if requested
1038 1055 key = types['changeset']
1039 1056 self.ui.write(templater.stringify(self.t(key, **props)))
1040 1057 self.showpatch(ctx.node(), matchfn)
1041 1058
1042 1059 if types['footer']:
1043 1060 if not self.footer:
1044 1061 self.footer = templater.stringify(self.t(types['footer'],
1045 1062 **props))
1046 1063
1047 1064 except KeyError, inst:
1048 1065 msg = _("%s: no key named '%s'")
1049 1066 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1050 1067 except SyntaxError, inst:
1051 1068 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1052 1069
1053 1070 def gettemplate(ui, tmpl, style):
1054 1071 """
1055 1072 Find the template matching the given template spec or style.
1056 1073 """
1057 1074
1058 1075 # ui settings
1059 1076 if not tmpl and not style:
1060 1077 tmpl = ui.config('ui', 'logtemplate')
1061 1078 if tmpl:
1062 1079 try:
1063 1080 tmpl = templater.parsestring(tmpl)
1064 1081 except SyntaxError:
1065 1082 tmpl = templater.parsestring(tmpl, quoted=False)
1066 1083 return tmpl, None
1067 1084 else:
1068 1085 style = util.expandpath(ui.config('ui', 'style', ''))
1069 1086
1070 1087 if style:
1071 1088 mapfile = style
1072 1089 if not os.path.split(mapfile)[0]:
1073 1090 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1074 1091 or templater.templatepath(mapfile))
1075 1092 if mapname:
1076 1093 mapfile = mapname
1077 1094 return None, mapfile
1078 1095
1079 1096 if not tmpl:
1080 1097 return None, None
1081 1098
1082 1099 # looks like a literal template?
1083 1100 if '{' in tmpl:
1084 1101 return tmpl, None
1085 1102
1086 1103 # perhaps a stock style?
1087 1104 if not os.path.split(tmpl)[0]:
1088 1105 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1089 1106 or templater.templatepath(tmpl))
1090 1107 if mapname and os.path.isfile(mapname):
1091 1108 return None, mapname
1092 1109
1093 1110 # perhaps it's a reference to [templates]
1094 1111 t = ui.config('templates', tmpl)
1095 1112 if t:
1096 1113 try:
1097 1114 tmpl = templater.parsestring(t)
1098 1115 except SyntaxError:
1099 1116 tmpl = templater.parsestring(t, quoted=False)
1100 1117 return tmpl, None
1101 1118
1102 1119 # perhaps it's a path to a map or a template
1103 1120 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1104 1121 # is it a mapfile for a style?
1105 1122 if os.path.basename(tmpl).startswith("map-"):
1106 1123 return None, os.path.realpath(tmpl)
1107 1124 tmpl = open(tmpl).read()
1108 1125 return tmpl, None
1109 1126
1110 1127 # constant string?
1111 1128 return tmpl, None
1112 1129
1113 1130 def show_changeset(ui, repo, opts, buffered=False):
1114 1131 """show one changeset using template or regular display.
1115 1132
1116 1133 Display format will be the first non-empty hit of:
1117 1134 1. option 'template'
1118 1135 2. option 'style'
1119 1136 3. [ui] setting 'logtemplate'
1120 1137 4. [ui] setting 'style'
1121 1138 If all of these values are either the unset or the empty string,
1122 1139 regular display via changeset_printer() is done.
1123 1140 """
1124 1141 # options
1125 1142 patch = None
1126 1143 if opts.get('patch') or opts.get('stat'):
1127 1144 patch = scmutil.matchall(repo)
1128 1145
1129 1146 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1130 1147
1131 1148 if not tmpl and not mapfile:
1132 1149 return changeset_printer(ui, repo, patch, opts, buffered)
1133 1150
1134 1151 try:
1135 1152 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1136 1153 except SyntaxError, inst:
1137 1154 raise util.Abort(inst.args[0])
1138 1155 return t
1139 1156
1140 1157 def showmarker(ui, marker):
1141 1158 """utility function to display obsolescence marker in a readable way
1142 1159
1143 1160 To be used by debug function."""
1144 1161 ui.write(hex(marker.precnode()))
1145 1162 for repl in marker.succnodes():
1146 1163 ui.write(' ')
1147 1164 ui.write(hex(repl))
1148 1165 ui.write(' %X ' % marker._data[2])
1149 1166 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1150 1167 sorted(marker.metadata().items()))))
1151 1168 ui.write('\n')
1152 1169
1153 1170 def finddate(ui, repo, date):
1154 1171 """Find the tipmost changeset that matches the given date spec"""
1155 1172
1156 1173 df = util.matchdate(date)
1157 1174 m = scmutil.matchall(repo)
1158 1175 results = {}
1159 1176
1160 1177 def prep(ctx, fns):
1161 1178 d = ctx.date()
1162 1179 if df(d[0]):
1163 1180 results[ctx.rev()] = d
1164 1181
1165 1182 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1166 1183 rev = ctx.rev()
1167 1184 if rev in results:
1168 1185 ui.status(_("found revision %s from %s\n") %
1169 1186 (rev, util.datestr(results[rev])))
1170 1187 return str(rev)
1171 1188
1172 1189 raise util.Abort(_("revision matching date not found"))
1173 1190
1174 1191 def increasingwindows(windowsize=8, sizelimit=512):
1175 1192 while True:
1176 1193 yield windowsize
1177 1194 if windowsize < sizelimit:
1178 1195 windowsize *= 2
1179 1196
1180 1197 class FileWalkError(Exception):
1181 1198 pass
1182 1199
1183 1200 def walkfilerevs(repo, match, follow, revs, fncache):
1184 1201 '''Walks the file history for the matched files.
1185 1202
1186 1203 Returns the changeset revs that are involved in the file history.
1187 1204
1188 1205 Throws FileWalkError if the file history can't be walked using
1189 1206 filelogs alone.
1190 1207 '''
1191 1208 wanted = set()
1192 1209 copies = []
1193 1210 minrev, maxrev = min(revs), max(revs)
1194 1211 def filerevgen(filelog, last):
1195 1212 """
1196 1213 Only files, no patterns. Check the history of each file.
1197 1214
1198 1215 Examines filelog entries within minrev, maxrev linkrev range
1199 1216 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1200 1217 tuples in backwards order
1201 1218 """
1202 1219 cl_count = len(repo)
1203 1220 revs = []
1204 1221 for j in xrange(0, last + 1):
1205 1222 linkrev = filelog.linkrev(j)
1206 1223 if linkrev < minrev:
1207 1224 continue
1208 1225 # only yield rev for which we have the changelog, it can
1209 1226 # happen while doing "hg log" during a pull or commit
1210 1227 if linkrev >= cl_count:
1211 1228 break
1212 1229
1213 1230 parentlinkrevs = []
1214 1231 for p in filelog.parentrevs(j):
1215 1232 if p != nullrev:
1216 1233 parentlinkrevs.append(filelog.linkrev(p))
1217 1234 n = filelog.node(j)
1218 1235 revs.append((linkrev, parentlinkrevs,
1219 1236 follow and filelog.renamed(n)))
1220 1237
1221 1238 return reversed(revs)
1222 1239 def iterfiles():
1223 1240 pctx = repo['.']
1224 1241 for filename in match.files():
1225 1242 if follow:
1226 1243 if filename not in pctx:
1227 1244 raise util.Abort(_('cannot follow file not in parent '
1228 1245 'revision: "%s"') % filename)
1229 1246 yield filename, pctx[filename].filenode()
1230 1247 else:
1231 1248 yield filename, None
1232 1249 for filename_node in copies:
1233 1250 yield filename_node
1234 1251
1235 1252 for file_, node in iterfiles():
1236 1253 filelog = repo.file(file_)
1237 1254 if not len(filelog):
1238 1255 if node is None:
1239 1256 # A zero count may be a directory or deleted file, so
1240 1257 # try to find matching entries on the slow path.
1241 1258 if follow:
1242 1259 raise util.Abort(
1243 1260 _('cannot follow nonexistent file: "%s"') % file_)
1244 1261 raise FileWalkError("Cannot walk via filelog")
1245 1262 else:
1246 1263 continue
1247 1264
1248 1265 if node is None:
1249 1266 last = len(filelog) - 1
1250 1267 else:
1251 1268 last = filelog.rev(node)
1252 1269
1253 1270
1254 1271 # keep track of all ancestors of the file
1255 1272 ancestors = set([filelog.linkrev(last)])
1256 1273
1257 1274 # iterate from latest to oldest revision
1258 1275 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1259 1276 if not follow:
1260 1277 if rev > maxrev:
1261 1278 continue
1262 1279 else:
1263 1280 # Note that last might not be the first interesting
1264 1281 # rev to us:
1265 1282 # if the file has been changed after maxrev, we'll
1266 1283 # have linkrev(last) > maxrev, and we still need
1267 1284 # to explore the file graph
1268 1285 if rev not in ancestors:
1269 1286 continue
1270 1287 # XXX insert 1327 fix here
1271 1288 if flparentlinkrevs:
1272 1289 ancestors.update(flparentlinkrevs)
1273 1290
1274 1291 fncache.setdefault(rev, []).append(file_)
1275 1292 wanted.add(rev)
1276 1293 if copied:
1277 1294 copies.append(copied)
1278 1295
1279 1296 return wanted
1280 1297
1281 1298 def walkchangerevs(repo, match, opts, prepare):
1282 1299 '''Iterate over files and the revs in which they changed.
1283 1300
1284 1301 Callers most commonly need to iterate backwards over the history
1285 1302 in which they are interested. Doing so has awful (quadratic-looking)
1286 1303 performance, so we use iterators in a "windowed" way.
1287 1304
1288 1305 We walk a window of revisions in the desired order. Within the
1289 1306 window, we first walk forwards to gather data, then in the desired
1290 1307 order (usually backwards) to display it.
1291 1308
1292 1309 This function returns an iterator yielding contexts. Before
1293 1310 yielding each context, the iterator will first call the prepare
1294 1311 function on each context in the window in forward order.'''
1295 1312
1296 1313 follow = opts.get('follow') or opts.get('follow_first')
1297 1314
1298 1315 if opts.get('rev'):
1299 1316 revs = scmutil.revrange(repo, opts.get('rev'))
1300 1317 elif follow:
1301 1318 revs = repo.revs('reverse(:.)')
1302 1319 else:
1303 1320 revs = revset.spanset(repo)
1304 1321 revs.reverse()
1305 1322 if not revs:
1306 1323 return []
1307 1324 wanted = set()
1308 1325 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1309 1326 fncache = {}
1310 1327 change = repo.changectx
1311 1328
1312 1329 # First step is to fill wanted, the set of revisions that we want to yield.
1313 1330 # When it does not induce extra cost, we also fill fncache for revisions in
1314 1331 # wanted: a cache of filenames that were changed (ctx.files()) and that
1315 1332 # match the file filtering conditions.
1316 1333
1317 1334 if not slowpath and not match.files():
1318 1335 # No files, no patterns. Display all revs.
1319 1336 wanted = revs
1320 1337
1321 1338 if not slowpath and match.files():
1322 1339 # We only have to read through the filelog to find wanted revisions
1323 1340
1324 1341 try:
1325 1342 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1326 1343 except FileWalkError:
1327 1344 slowpath = True
1328 1345
1329 1346 # We decided to fall back to the slowpath because at least one
1330 1347 # of the paths was not a file. Check to see if at least one of them
1331 1348 # existed in history, otherwise simply return
1332 1349 for path in match.files():
1333 1350 if path == '.' or path in repo.store:
1334 1351 break
1335 1352 else:
1336 1353 return []
1337 1354
1338 1355 if slowpath:
1339 1356 # We have to read the changelog to match filenames against
1340 1357 # changed files
1341 1358
1342 1359 if follow:
1343 1360 raise util.Abort(_('can only follow copies/renames for explicit '
1344 1361 'filenames'))
1345 1362
1346 1363 # The slow path checks files modified in every changeset.
1347 1364 # This is really slow on large repos, so compute the set lazily.
1348 1365 class lazywantedset(object):
1349 1366 def __init__(self):
1350 1367 self.set = set()
1351 1368 self.revs = set(revs)
1352 1369
1353 1370 # No need to worry about locality here because it will be accessed
1354 1371 # in the same order as the increasing window below.
1355 1372 def __contains__(self, value):
1356 1373 if value in self.set:
1357 1374 return True
1358 1375 elif not value in self.revs:
1359 1376 return False
1360 1377 else:
1361 1378 self.revs.discard(value)
1362 1379 ctx = change(value)
1363 1380 matches = filter(match, ctx.files())
1364 1381 if matches:
1365 1382 fncache[value] = matches
1366 1383 self.set.add(value)
1367 1384 return True
1368 1385 return False
1369 1386
1370 1387 def discard(self, value):
1371 1388 self.revs.discard(value)
1372 1389 self.set.discard(value)
1373 1390
1374 1391 wanted = lazywantedset()
1375 1392
1376 1393 class followfilter(object):
1377 1394 def __init__(self, onlyfirst=False):
1378 1395 self.startrev = nullrev
1379 1396 self.roots = set()
1380 1397 self.onlyfirst = onlyfirst
1381 1398
1382 1399 def match(self, rev):
1383 1400 def realparents(rev):
1384 1401 if self.onlyfirst:
1385 1402 return repo.changelog.parentrevs(rev)[0:1]
1386 1403 else:
1387 1404 return filter(lambda x: x != nullrev,
1388 1405 repo.changelog.parentrevs(rev))
1389 1406
1390 1407 if self.startrev == nullrev:
1391 1408 self.startrev = rev
1392 1409 return True
1393 1410
1394 1411 if rev > self.startrev:
1395 1412 # forward: all descendants
1396 1413 if not self.roots:
1397 1414 self.roots.add(self.startrev)
1398 1415 for parent in realparents(rev):
1399 1416 if parent in self.roots:
1400 1417 self.roots.add(rev)
1401 1418 return True
1402 1419 else:
1403 1420 # backwards: all parents
1404 1421 if not self.roots:
1405 1422 self.roots.update(realparents(self.startrev))
1406 1423 if rev in self.roots:
1407 1424 self.roots.remove(rev)
1408 1425 self.roots.update(realparents(rev))
1409 1426 return True
1410 1427
1411 1428 return False
1412 1429
1413 1430 # it might be worthwhile to do this in the iterator if the rev range
1414 1431 # is descending and the prune args are all within that range
1415 1432 for rev in opts.get('prune', ()):
1416 1433 rev = repo[rev].rev()
1417 1434 ff = followfilter()
1418 1435 stop = min(revs[0], revs[-1])
1419 1436 for x in xrange(rev, stop - 1, -1):
1420 1437 if ff.match(x):
1421 1438 wanted = wanted - [x]
1422 1439
1423 1440 # Now that wanted is correctly initialized, we can iterate over the
1424 1441 # revision range, yielding only revisions in wanted.
1425 1442 def iterate():
1426 1443 if follow and not match.files():
1427 1444 ff = followfilter(onlyfirst=opts.get('follow_first'))
1428 1445 def want(rev):
1429 1446 return ff.match(rev) and rev in wanted
1430 1447 else:
1431 1448 def want(rev):
1432 1449 return rev in wanted
1433 1450
1434 1451 it = iter(revs)
1435 1452 stopiteration = False
1436 1453 for windowsize in increasingwindows():
1437 1454 nrevs = []
1438 1455 for i in xrange(windowsize):
1439 1456 try:
1440 1457 rev = it.next()
1441 1458 if want(rev):
1442 1459 nrevs.append(rev)
1443 1460 except (StopIteration):
1444 1461 stopiteration = True
1445 1462 break
1446 1463 for rev in sorted(nrevs):
1447 1464 fns = fncache.get(rev)
1448 1465 ctx = change(rev)
1449 1466 if not fns:
1450 1467 def fns_generator():
1451 1468 for f in ctx.files():
1452 1469 if match(f):
1453 1470 yield f
1454 1471 fns = fns_generator()
1455 1472 prepare(ctx, fns)
1456 1473 for rev in nrevs:
1457 1474 yield change(rev)
1458 1475
1459 1476 if stopiteration:
1460 1477 break
1461 1478
1462 1479 return iterate()
1463 1480
1464 1481 def _makelogfilematcher(repo, pats, followfirst):
1465 1482 # When displaying a revision with --patch --follow FILE, we have
1466 1483 # to know which file of the revision must be diffed. With
1467 1484 # --follow, we want the names of the ancestors of FILE in the
1468 1485 # revision, stored in "fcache". "fcache" is populated by
1469 1486 # reproducing the graph traversal already done by --follow revset
1470 1487 # and relating linkrevs to file names (which is not "correct" but
1471 1488 # good enough).
1472 1489 fcache = {}
1473 1490 fcacheready = [False]
1474 1491 pctx = repo['.']
1475 1492 wctx = repo[None]
1476 1493
1477 1494 def populate():
1478 1495 for fn in pats:
1479 1496 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1480 1497 for c in i:
1481 1498 fcache.setdefault(c.linkrev(), set()).add(c.path())
1482 1499
1483 1500 def filematcher(rev):
1484 1501 if not fcacheready[0]:
1485 1502 # Lazy initialization
1486 1503 fcacheready[0] = True
1487 1504 populate()
1488 1505 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1489 1506
1490 1507 return filematcher
1491 1508
1492 1509 def _makelogrevset(repo, pats, opts, revs):
1493 1510 """Return (expr, filematcher) where expr is a revset string built
1494 1511 from log options and file patterns or None. If --stat or --patch
1495 1512 are not passed filematcher is None. Otherwise it is a callable
1496 1513 taking a revision number and returning a match objects filtering
1497 1514 the files to be detailed when displaying the revision.
1498 1515 """
1499 1516 opt2revset = {
1500 1517 'no_merges': ('not merge()', None),
1501 1518 'only_merges': ('merge()', None),
1502 1519 '_ancestors': ('ancestors(%(val)s)', None),
1503 1520 '_fancestors': ('_firstancestors(%(val)s)', None),
1504 1521 '_descendants': ('descendants(%(val)s)', None),
1505 1522 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1506 1523 '_matchfiles': ('_matchfiles(%(val)s)', None),
1507 1524 'date': ('date(%(val)r)', None),
1508 1525 'branch': ('branch(%(val)r)', ' or '),
1509 1526 '_patslog': ('filelog(%(val)r)', ' or '),
1510 1527 '_patsfollow': ('follow(%(val)r)', ' or '),
1511 1528 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1512 1529 'keyword': ('keyword(%(val)r)', ' or '),
1513 1530 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1514 1531 'user': ('user(%(val)r)', ' or '),
1515 1532 }
1516 1533
1517 1534 opts = dict(opts)
1518 1535 # follow or not follow?
1519 1536 follow = opts.get('follow') or opts.get('follow_first')
1520 1537 followfirst = opts.get('follow_first') and 1 or 0
1521 1538 # --follow with FILE behaviour depends on revs...
1522 1539 it = iter(revs)
1523 1540 startrev = it.next()
1524 1541 try:
1525 1542 followdescendants = startrev < it.next()
1526 1543 except (StopIteration):
1527 1544 followdescendants = False
1528 1545
1529 1546 # branch and only_branch are really aliases and must be handled at
1530 1547 # the same time
1531 1548 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1532 1549 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1533 1550 # pats/include/exclude are passed to match.match() directly in
1534 1551 # _matchfiles() revset but walkchangerevs() builds its matcher with
1535 1552 # scmutil.match(). The difference is input pats are globbed on
1536 1553 # platforms without shell expansion (windows).
1537 1554 pctx = repo[None]
1538 1555 match, pats = scmutil.matchandpats(pctx, pats, opts)
1539 1556 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1540 1557 if not slowpath:
1541 1558 for f in match.files():
1542 1559 if follow and f not in pctx:
1543 1560 raise util.Abort(_('cannot follow file not in parent '
1544 1561 'revision: "%s"') % f)
1545 1562 filelog = repo.file(f)
1546 1563 if not filelog:
1547 1564 # A zero count may be a directory or deleted file, so
1548 1565 # try to find matching entries on the slow path.
1549 1566 if follow:
1550 1567 raise util.Abort(
1551 1568 _('cannot follow nonexistent file: "%s"') % f)
1552 1569 slowpath = True
1553 1570
1554 1571 # We decided to fall back to the slowpath because at least one
1555 1572 # of the paths was not a file. Check to see if at least one of them
1556 1573 # existed in history - in that case, we'll continue down the
1557 1574 # slowpath; otherwise, we can turn off the slowpath
1558 1575 if slowpath:
1559 1576 for path in match.files():
1560 1577 if path == '.' or path in repo.store:
1561 1578 break
1562 1579 else:
1563 1580 slowpath = False
1564 1581
1565 1582 if slowpath:
1566 1583 # See walkchangerevs() slow path.
1567 1584 #
1568 1585 if follow:
1569 1586 raise util.Abort(_('can only follow copies/renames for explicit '
1570 1587 'filenames'))
1571 1588 # pats/include/exclude cannot be represented as separate
1572 1589 # revset expressions as their filtering logic applies at file
1573 1590 # level. For instance "-I a -X a" matches a revision touching
1574 1591 # "a" and "b" while "file(a) and not file(b)" does
1575 1592 # not. Besides, filesets are evaluated against the working
1576 1593 # directory.
1577 1594 matchargs = ['r:', 'd:relpath']
1578 1595 for p in pats:
1579 1596 matchargs.append('p:' + p)
1580 1597 for p in opts.get('include', []):
1581 1598 matchargs.append('i:' + p)
1582 1599 for p in opts.get('exclude', []):
1583 1600 matchargs.append('x:' + p)
1584 1601 matchargs = ','.join(('%r' % p) for p in matchargs)
1585 1602 opts['_matchfiles'] = matchargs
1586 1603 else:
1587 1604 if follow:
1588 1605 fpats = ('_patsfollow', '_patsfollowfirst')
1589 1606 fnopats = (('_ancestors', '_fancestors'),
1590 1607 ('_descendants', '_fdescendants'))
1591 1608 if pats:
1592 1609 # follow() revset interprets its file argument as a
1593 1610 # manifest entry, so use match.files(), not pats.
1594 1611 opts[fpats[followfirst]] = list(match.files())
1595 1612 else:
1596 1613 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1597 1614 else:
1598 1615 opts['_patslog'] = list(pats)
1599 1616
1600 1617 filematcher = None
1601 1618 if opts.get('patch') or opts.get('stat'):
1602 1619 if follow:
1603 1620 filematcher = _makelogfilematcher(repo, pats, followfirst)
1604 1621 else:
1605 1622 filematcher = lambda rev: match
1606 1623
1607 1624 expr = []
1608 1625 for op, val in opts.iteritems():
1609 1626 if not val:
1610 1627 continue
1611 1628 if op not in opt2revset:
1612 1629 continue
1613 1630 revop, andor = opt2revset[op]
1614 1631 if '%(val)' not in revop:
1615 1632 expr.append(revop)
1616 1633 else:
1617 1634 if not isinstance(val, list):
1618 1635 e = revop % {'val': val}
1619 1636 else:
1620 1637 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1621 1638 expr.append(e)
1622 1639
1623 1640 if expr:
1624 1641 expr = '(' + ' and '.join(expr) + ')'
1625 1642 else:
1626 1643 expr = None
1627 1644 return expr, filematcher
1628 1645
1629 1646 def getgraphlogrevs(repo, pats, opts):
1630 1647 """Return (revs, expr, filematcher) where revs is an iterable of
1631 1648 revision numbers, expr is a revset string built from log options
1632 1649 and file patterns or None, and used to filter 'revs'. If --stat or
1633 1650 --patch are not passed filematcher is None. Otherwise it is a
1634 1651 callable taking a revision number and returning a match objects
1635 1652 filtering the files to be detailed when displaying the revision.
1636 1653 """
1637 1654 if not len(repo):
1638 1655 return [], None, None
1639 1656 limit = loglimit(opts)
1640 1657 # Default --rev value depends on --follow but --follow behaviour
1641 1658 # depends on revisions resolved from --rev...
1642 1659 follow = opts.get('follow') or opts.get('follow_first')
1643 1660 possiblyunsorted = False # whether revs might need sorting
1644 1661 if opts.get('rev'):
1645 1662 revs = scmutil.revrange(repo, opts['rev'])
1646 1663 # Don't sort here because _makelogrevset might depend on the
1647 1664 # order of revs
1648 1665 possiblyunsorted = True
1649 1666 else:
1650 1667 if follow and len(repo) > 0:
1651 1668 revs = repo.revs('reverse(:.)')
1652 1669 else:
1653 1670 revs = revset.spanset(repo)
1654 1671 revs.reverse()
1655 1672 if not revs:
1656 1673 return revset.baseset(), None, None
1657 1674 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1658 1675 if possiblyunsorted:
1659 1676 revs.sort(reverse=True)
1660 1677 if expr:
1661 1678 # Revset matchers often operate faster on revisions in changelog
1662 1679 # order, because most filters deal with the changelog.
1663 1680 revs.reverse()
1664 1681 matcher = revset.match(repo.ui, expr)
1665 1682 # Revset matches can reorder revisions. "A or B" typically returns
1666 1683 # returns the revision matching A then the revision matching B. Sort
1667 1684 # again to fix that.
1668 1685 revs = matcher(repo, revs)
1669 1686 revs.sort(reverse=True)
1670 1687 if limit is not None:
1671 1688 limitedrevs = revset.baseset()
1672 1689 for idx, rev in enumerate(revs):
1673 1690 if idx >= limit:
1674 1691 break
1675 1692 limitedrevs.append(rev)
1676 1693 revs = limitedrevs
1677 1694
1678 1695 return revs, expr, filematcher
1679 1696
1680 1697 def getlogrevs(repo, pats, opts):
1681 1698 """Return (revs, expr, filematcher) where revs is an iterable of
1682 1699 revision numbers, expr is a revset string built from log options
1683 1700 and file patterns or None, and used to filter 'revs'. If --stat or
1684 1701 --patch are not passed filematcher is None. Otherwise it is a
1685 1702 callable taking a revision number and returning a match objects
1686 1703 filtering the files to be detailed when displaying the revision.
1687 1704 """
1688 1705 limit = loglimit(opts)
1689 1706 # Default --rev value depends on --follow but --follow behaviour
1690 1707 # depends on revisions resolved from --rev...
1691 1708 follow = opts.get('follow') or opts.get('follow_first')
1692 1709 if opts.get('rev'):
1693 1710 revs = scmutil.revrange(repo, opts['rev'])
1694 1711 elif follow:
1695 1712 revs = revset.baseset(repo.revs('reverse(:.)'))
1696 1713 else:
1697 1714 revs = revset.spanset(repo)
1698 1715 revs.reverse()
1699 1716 if not revs:
1700 1717 return revset.baseset([]), None, None
1701 1718 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1702 1719 if expr:
1703 1720 # Revset matchers often operate faster on revisions in changelog
1704 1721 # order, because most filters deal with the changelog.
1705 1722 if not opts.get('rev'):
1706 1723 revs.reverse()
1707 1724 matcher = revset.match(repo.ui, expr)
1708 1725 # Revset matches can reorder revisions. "A or B" typically returns
1709 1726 # returns the revision matching A then the revision matching B. Sort
1710 1727 # again to fix that.
1711 1728 revs = matcher(repo, revs)
1712 1729 if not opts.get('rev'):
1713 1730 revs.sort(reverse=True)
1714 1731 if limit is not None:
1715 1732 count = 0
1716 1733 limitedrevs = revset.baseset([])
1717 1734 it = iter(revs)
1718 1735 while count < limit:
1719 1736 try:
1720 1737 limitedrevs.append(it.next())
1721 1738 except (StopIteration):
1722 1739 break
1723 1740 count += 1
1724 1741 revs = limitedrevs
1725 1742
1726 1743 return revs, expr, filematcher
1727 1744
1728 1745 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1729 1746 filematcher=None):
1730 1747 seen, state = [], graphmod.asciistate()
1731 1748 for rev, type, ctx, parents in dag:
1732 1749 char = 'o'
1733 1750 if ctx.node() in showparents:
1734 1751 char = '@'
1735 1752 elif ctx.obsolete():
1736 1753 char = 'x'
1737 1754 copies = None
1738 1755 if getrenamed and ctx.rev():
1739 1756 copies = []
1740 1757 for fn in ctx.files():
1741 1758 rename = getrenamed(fn, ctx.rev())
1742 1759 if rename:
1743 1760 copies.append((fn, rename[0]))
1744 1761 revmatchfn = None
1745 1762 if filematcher is not None:
1746 1763 revmatchfn = filematcher(ctx.rev())
1747 1764 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1748 1765 lines = displayer.hunk.pop(rev).split('\n')
1749 1766 if not lines[-1]:
1750 1767 del lines[-1]
1751 1768 displayer.flush(rev)
1752 1769 edges = edgefn(type, char, lines, seen, rev, parents)
1753 1770 for type, char, lines, coldata in edges:
1754 1771 graphmod.ascii(ui, state, type, char, lines, coldata)
1755 1772 displayer.close()
1756 1773
1757 1774 def graphlog(ui, repo, *pats, **opts):
1758 1775 # Parameters are identical to log command ones
1759 1776 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1760 1777 revdag = graphmod.dagwalker(repo, revs)
1761 1778
1762 1779 getrenamed = None
1763 1780 if opts.get('copies'):
1764 1781 endrev = None
1765 1782 if opts.get('rev'):
1766 1783 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1767 1784 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1768 1785 displayer = show_changeset(ui, repo, opts, buffered=True)
1769 1786 showparents = [ctx.node() for ctx in repo[None].parents()]
1770 1787 displaygraph(ui, revdag, displayer, showparents,
1771 1788 graphmod.asciiedges, getrenamed, filematcher)
1772 1789
1773 1790 def checkunsupportedgraphflags(pats, opts):
1774 1791 for op in ["newest_first"]:
1775 1792 if op in opts and opts[op]:
1776 1793 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1777 1794 % op.replace("_", "-"))
1778 1795
1779 1796 def graphrevs(repo, nodes, opts):
1780 1797 limit = loglimit(opts)
1781 1798 nodes.reverse()
1782 1799 if limit is not None:
1783 1800 nodes = nodes[:limit]
1784 1801 return graphmod.nodes(repo, nodes)
1785 1802
1786 1803 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1787 1804 join = lambda f: os.path.join(prefix, f)
1788 1805 bad = []
1789 1806 oldbad = match.bad
1790 1807 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1791 1808 names = []
1792 1809 wctx = repo[None]
1793 1810 cca = None
1794 1811 abort, warn = scmutil.checkportabilityalert(ui)
1795 1812 if abort or warn:
1796 1813 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1797 1814 for f in repo.walk(match):
1798 1815 exact = match.exact(f)
1799 1816 if exact or not explicitonly and f not in repo.dirstate:
1800 1817 if cca:
1801 1818 cca(f)
1802 1819 names.append(f)
1803 1820 if ui.verbose or not exact:
1804 1821 ui.status(_('adding %s\n') % match.rel(join(f)))
1805 1822
1806 1823 for subpath in sorted(wctx.substate):
1807 1824 sub = wctx.sub(subpath)
1808 1825 try:
1809 1826 submatch = matchmod.narrowmatcher(subpath, match)
1810 1827 if listsubrepos:
1811 1828 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1812 1829 False))
1813 1830 else:
1814 1831 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1815 1832 True))
1816 1833 except error.LookupError:
1817 1834 ui.status(_("skipping missing subrepository: %s\n")
1818 1835 % join(subpath))
1819 1836
1820 1837 if not dryrun:
1821 1838 rejected = wctx.add(names, prefix)
1822 1839 bad.extend(f for f in rejected if f in match.files())
1823 1840 return bad
1824 1841
1825 1842 def forget(ui, repo, match, prefix, explicitonly):
1826 1843 join = lambda f: os.path.join(prefix, f)
1827 1844 bad = []
1828 1845 oldbad = match.bad
1829 1846 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1830 1847 wctx = repo[None]
1831 1848 forgot = []
1832 1849 s = repo.status(match=match, clean=True)
1833 1850 forget = sorted(s[0] + s[1] + s[3] + s[6])
1834 1851 if explicitonly:
1835 1852 forget = [f for f in forget if match.exact(f)]
1836 1853
1837 1854 for subpath in sorted(wctx.substate):
1838 1855 sub = wctx.sub(subpath)
1839 1856 try:
1840 1857 submatch = matchmod.narrowmatcher(subpath, match)
1841 1858 subbad, subforgot = sub.forget(ui, submatch, prefix)
1842 1859 bad.extend([subpath + '/' + f for f in subbad])
1843 1860 forgot.extend([subpath + '/' + f for f in subforgot])
1844 1861 except error.LookupError:
1845 1862 ui.status(_("skipping missing subrepository: %s\n")
1846 1863 % join(subpath))
1847 1864
1848 1865 if not explicitonly:
1849 1866 for f in match.files():
1850 1867 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1851 1868 if f not in forgot:
1852 1869 if os.path.exists(match.rel(join(f))):
1853 1870 ui.warn(_('not removing %s: '
1854 1871 'file is already untracked\n')
1855 1872 % match.rel(join(f)))
1856 1873 bad.append(f)
1857 1874
1858 1875 for f in forget:
1859 1876 if ui.verbose or not match.exact(f):
1860 1877 ui.status(_('removing %s\n') % match.rel(join(f)))
1861 1878
1862 1879 rejected = wctx.forget(forget, prefix)
1863 1880 bad.extend(f for f in rejected if f in match.files())
1864 1881 forgot.extend(forget)
1865 1882 return bad, forgot
1866 1883
1867 1884 def cat(ui, repo, ctx, matcher, prefix, **opts):
1868 1885 err = 1
1869 1886
1870 1887 def write(path):
1871 1888 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1872 1889 pathname=os.path.join(prefix, path))
1873 1890 data = ctx[path].data()
1874 1891 if opts.get('decode'):
1875 1892 data = repo.wwritedata(path, data)
1876 1893 fp.write(data)
1877 1894 fp.close()
1878 1895
1879 1896 # Automation often uses hg cat on single files, so special case it
1880 1897 # for performance to avoid the cost of parsing the manifest.
1881 1898 if len(matcher.files()) == 1 and not matcher.anypats():
1882 1899 file = matcher.files()[0]
1883 1900 mf = repo.manifest
1884 1901 mfnode = ctx._changeset[0]
1885 1902 if mf.find(mfnode, file)[0]:
1886 1903 write(file)
1887 1904 return 0
1888 1905
1889 1906 # Don't warn about "missing" files that are really in subrepos
1890 1907 bad = matcher.bad
1891 1908
1892 1909 def badfn(path, msg):
1893 1910 for subpath in ctx.substate:
1894 1911 if path.startswith(subpath):
1895 1912 return
1896 1913 bad(path, msg)
1897 1914
1898 1915 matcher.bad = badfn
1899 1916
1900 1917 for abs in ctx.walk(matcher):
1901 1918 write(abs)
1902 1919 err = 0
1903 1920
1904 1921 matcher.bad = bad
1905 1922
1906 1923 for subpath in sorted(ctx.substate):
1907 1924 sub = ctx.sub(subpath)
1908 1925 try:
1909 1926 submatch = matchmod.narrowmatcher(subpath, matcher)
1910 1927
1911 1928 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1912 1929 **opts):
1913 1930 err = 0
1914 1931 except error.RepoLookupError:
1915 1932 ui.status(_("skipping missing subrepository: %s\n")
1916 1933 % os.path.join(prefix, subpath))
1917 1934
1918 1935 return err
1919 1936
1920 1937 def duplicatecopies(repo, rev, fromrev):
1921 1938 '''reproduce copies from fromrev to rev in the dirstate'''
1922 1939 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1923 1940 # copies.pathcopies returns backward renames, so dst might not
1924 1941 # actually be in the dirstate
1925 1942 if repo.dirstate[dst] in "nma":
1926 1943 repo.dirstate.copy(src, dst)
1927 1944
1928 1945 def commit(ui, repo, commitfunc, pats, opts):
1929 1946 '''commit the specified files or all outstanding changes'''
1930 1947 date = opts.get('date')
1931 1948 if date:
1932 1949 opts['date'] = util.parsedate(date)
1933 1950 message = logmessage(ui, opts)
1934 1951
1935 1952 # extract addremove carefully -- this function can be called from a command
1936 1953 # that doesn't support addremove
1937 1954 if opts.get('addremove'):
1938 1955 scmutil.addremove(repo, pats, opts)
1939 1956
1940 1957 return commitfunc(ui, repo, message,
1941 1958 scmutil.match(repo[None], pats, opts), opts)
1942 1959
1943 1960 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1944 1961 ui.note(_('amending changeset %s\n') % old)
1945 1962 base = old.p1()
1946 1963
1947 1964 wlock = lock = newid = None
1948 1965 try:
1949 1966 wlock = repo.wlock()
1950 1967 lock = repo.lock()
1951 1968 tr = repo.transaction('amend')
1952 1969 try:
1953 1970 # See if we got a message from -m or -l, if not, open the editor
1954 1971 # with the message of the changeset to amend
1955 1972 message = logmessage(ui, opts)
1956 1973 # ensure logfile does not conflict with later enforcement of the
1957 1974 # message. potential logfile content has been processed by
1958 1975 # `logmessage` anyway.
1959 1976 opts.pop('logfile')
1960 1977 # First, do a regular commit to record all changes in the working
1961 1978 # directory (if there are any)
1962 1979 ui.callhooks = False
1963 1980 currentbookmark = repo._bookmarkcurrent
1964 1981 try:
1965 1982 repo._bookmarkcurrent = None
1966 1983 opts['message'] = 'temporary amend commit for %s' % old
1967 1984 node = commit(ui, repo, commitfunc, pats, opts)
1968 1985 finally:
1969 1986 repo._bookmarkcurrent = currentbookmark
1970 1987 ui.callhooks = True
1971 1988 ctx = repo[node]
1972 1989
1973 1990 # Participating changesets:
1974 1991 #
1975 1992 # node/ctx o - new (intermediate) commit that contains changes
1976 1993 # | from working dir to go into amending commit
1977 1994 # | (or a workingctx if there were no changes)
1978 1995 # |
1979 1996 # old o - changeset to amend
1980 1997 # |
1981 1998 # base o - parent of amending changeset
1982 1999
1983 2000 # Update extra dict from amended commit (e.g. to preserve graft
1984 2001 # source)
1985 2002 extra.update(old.extra())
1986 2003
1987 2004 # Also update it from the intermediate commit or from the wctx
1988 2005 extra.update(ctx.extra())
1989 2006
1990 2007 if len(old.parents()) > 1:
1991 2008 # ctx.files() isn't reliable for merges, so fall back to the
1992 2009 # slower repo.status() method
1993 2010 files = set([fn for st in repo.status(base, old)[:3]
1994 2011 for fn in st])
1995 2012 else:
1996 2013 files = set(old.files())
1997 2014
1998 2015 # Second, we use either the commit we just did, or if there were no
1999 2016 # changes the parent of the working directory as the version of the
2000 2017 # files in the final amend commit
2001 2018 if node:
2002 2019 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2003 2020
2004 2021 user = ctx.user()
2005 2022 date = ctx.date()
2006 2023 # Recompute copies (avoid recording a -> b -> a)
2007 2024 copied = copies.pathcopies(base, ctx)
2008 2025
2009 2026 # Prune files which were reverted by the updates: if old
2010 2027 # introduced file X and our intermediate commit, node,
2011 2028 # renamed that file, then those two files are the same and
2012 2029 # we can discard X from our list of files. Likewise if X
2013 2030 # was deleted, it's no longer relevant
2014 2031 files.update(ctx.files())
2015 2032
2016 2033 def samefile(f):
2017 2034 if f in ctx.manifest():
2018 2035 a = ctx.filectx(f)
2019 2036 if f in base.manifest():
2020 2037 b = base.filectx(f)
2021 2038 return (not a.cmp(b)
2022 2039 and a.flags() == b.flags())
2023 2040 else:
2024 2041 return False
2025 2042 else:
2026 2043 return f not in base.manifest()
2027 2044 files = [f for f in files if not samefile(f)]
2028 2045
2029 2046 def filectxfn(repo, ctx_, path):
2030 2047 try:
2031 2048 fctx = ctx[path]
2032 2049 flags = fctx.flags()
2033 2050 mctx = context.memfilectx(fctx.path(), fctx.data(),
2034 2051 islink='l' in flags,
2035 2052 isexec='x' in flags,
2036 2053 copied=copied.get(path))
2037 2054 return mctx
2038 2055 except KeyError:
2039 2056 raise IOError
2040 2057 else:
2041 2058 ui.note(_('copying changeset %s to %s\n') % (old, base))
2042 2059
2043 2060 # Use version of files as in the old cset
2044 2061 def filectxfn(repo, ctx_, path):
2045 2062 try:
2046 2063 return old.filectx(path)
2047 2064 except KeyError:
2048 2065 raise IOError
2049 2066
2050 2067 user = opts.get('user') or old.user()
2051 2068 date = opts.get('date') or old.date()
2052 2069 editor = getcommiteditor(**opts)
2053 2070 if not message:
2054 2071 editor = getcommiteditor(edit=True)
2055 2072 message = old.description()
2056 2073
2057 2074 pureextra = extra.copy()
2058 2075 extra['amend_source'] = old.hex()
2059 2076
2060 2077 new = context.memctx(repo,
2061 2078 parents=[base.node(), old.p2().node()],
2062 2079 text=message,
2063 2080 files=files,
2064 2081 filectxfn=filectxfn,
2065 2082 user=user,
2066 2083 date=date,
2067 2084 extra=extra,
2068 2085 editor=editor)
2069 2086
2070 2087 newdesc = changelog.stripdesc(new.description())
2071 2088 if ((not node)
2072 2089 and newdesc == old.description()
2073 2090 and user == old.user()
2074 2091 and date == old.date()
2075 2092 and pureextra == old.extra()):
2076 2093 # nothing changed. continuing here would create a new node
2077 2094 # anyway because of the amend_source noise.
2078 2095 #
2079 2096 # This not what we expect from amend.
2080 2097 return old.node()
2081 2098
2082 2099 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2083 2100 try:
2084 2101 if opts.get('secret'):
2085 2102 commitphase = 'secret'
2086 2103 else:
2087 2104 commitphase = old.phase()
2088 2105 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2089 2106 newid = repo.commitctx(new)
2090 2107 finally:
2091 2108 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2092 2109 if newid != old.node():
2093 2110 # Reroute the working copy parent to the new changeset
2094 2111 repo.setparents(newid, nullid)
2095 2112
2096 2113 # Move bookmarks from old parent to amend commit
2097 2114 bms = repo.nodebookmarks(old.node())
2098 2115 if bms:
2099 2116 marks = repo._bookmarks
2100 2117 for bm in bms:
2101 2118 marks[bm] = newid
2102 2119 marks.write()
2103 2120 #commit the whole amend process
2104 2121 if obsolete._enabled and newid != old.node():
2105 2122 # mark the new changeset as successor of the rewritten one
2106 2123 new = repo[newid]
2107 2124 obs = [(old, (new,))]
2108 2125 if node:
2109 2126 obs.append((ctx, ()))
2110 2127
2111 2128 obsolete.createmarkers(repo, obs)
2112 2129 tr.close()
2113 2130 finally:
2114 2131 tr.release()
2115 2132 if (not obsolete._enabled) and newid != old.node():
2116 2133 # Strip the intermediate commit (if there was one) and the amended
2117 2134 # commit
2118 2135 if node:
2119 2136 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2120 2137 ui.note(_('stripping amended changeset %s\n') % old)
2121 2138 repair.strip(ui, repo, old.node(), topic='amend-backup')
2122 2139 finally:
2123 2140 if newid is None:
2124 2141 repo.dirstate.invalidate()
2125 2142 lockmod.release(lock, wlock)
2126 2143 return newid
2127 2144
2128 2145 def commiteditor(repo, ctx, subs):
2129 2146 if ctx.description():
2130 2147 return ctx.description()
2131 2148 return commitforceeditor(repo, ctx, subs)
2132 2149
2133 def commitforceeditor(repo, ctx, subs):
2150 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
2134 2151 edittext = []
2135 2152 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2136 2153 if ctx.description():
2137 2154 edittext.append(ctx.description())
2138 2155 edittext.append("")
2139 2156 edittext.append("") # Empty line between message and comments.
2140 2157 edittext.append(_("HG: Enter commit message."
2141 2158 " Lines beginning with 'HG:' are removed."))
2142 edittext.append(_("HG: Leave message empty to abort commit."))
2159 if extramsg:
2160 edittext.append("HG: %s" % extramsg)
2161 else:
2162 edittext.append(_("HG: Leave message empty to abort commit."))
2143 2163 edittext.append("HG: --")
2144 2164 edittext.append(_("HG: user: %s") % ctx.user())
2145 2165 if ctx.p2():
2146 2166 edittext.append(_("HG: branch merge"))
2147 2167 if ctx.branch():
2148 2168 edittext.append(_("HG: branch '%s'") % ctx.branch())
2149 2169 if bookmarks.iscurrent(repo):
2150 2170 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2151 2171 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2152 2172 edittext.extend([_("HG: added %s") % f for f in added])
2153 2173 edittext.extend([_("HG: changed %s") % f for f in modified])
2154 2174 edittext.extend([_("HG: removed %s") % f for f in removed])
2155 2175 if not added and not modified and not removed:
2156 2176 edittext.append(_("HG: no files changed"))
2157 2177 edittext.append("")
2158 2178 # run editor in the repository root
2159 2179 olddir = os.getcwd()
2160 2180 os.chdir(repo.root)
2161 2181 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2162 2182 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2163 2183 os.chdir(olddir)
2164 2184
2185 if finishdesc:
2186 text = finishdesc(text)
2165 2187 if not text.strip():
2166 2188 raise util.Abort(_("empty commit message"))
2167 2189
2168 2190 return text
2169 2191
2170 2192 def commitstatus(repo, node, branch, bheads=None, opts={}):
2171 2193 ctx = repo[node]
2172 2194 parents = ctx.parents()
2173 2195
2174 2196 if (not opts.get('amend') and bheads and node not in bheads and not
2175 2197 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2176 2198 repo.ui.status(_('created new head\n'))
2177 2199 # The message is not printed for initial roots. For the other
2178 2200 # changesets, it is printed in the following situations:
2179 2201 #
2180 2202 # Par column: for the 2 parents with ...
2181 2203 # N: null or no parent
2182 2204 # B: parent is on another named branch
2183 2205 # C: parent is a regular non head changeset
2184 2206 # H: parent was a branch head of the current branch
2185 2207 # Msg column: whether we print "created new head" message
2186 2208 # In the following, it is assumed that there already exists some
2187 2209 # initial branch heads of the current branch, otherwise nothing is
2188 2210 # printed anyway.
2189 2211 #
2190 2212 # Par Msg Comment
2191 2213 # N N y additional topo root
2192 2214 #
2193 2215 # B N y additional branch root
2194 2216 # C N y additional topo head
2195 2217 # H N n usual case
2196 2218 #
2197 2219 # B B y weird additional branch root
2198 2220 # C B y branch merge
2199 2221 # H B n merge with named branch
2200 2222 #
2201 2223 # C C y additional head from merge
2202 2224 # C H n merge with a head
2203 2225 #
2204 2226 # H H n head merge: head count decreases
2205 2227
2206 2228 if not opts.get('close_branch'):
2207 2229 for r in parents:
2208 2230 if r.closesbranch() and r.branch() == branch:
2209 2231 repo.ui.status(_('reopening closed branch head %d\n') % r)
2210 2232
2211 2233 if repo.ui.debugflag:
2212 2234 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2213 2235 elif repo.ui.verbose:
2214 2236 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2215 2237
2216 2238 def revert(ui, repo, ctx, parents, *pats, **opts):
2217 2239 parent, p2 = parents
2218 2240 node = ctx.node()
2219 2241
2220 2242 mf = ctx.manifest()
2221 2243 if node == parent:
2222 2244 pmf = mf
2223 2245 else:
2224 2246 pmf = None
2225 2247
2226 2248 # need all matching names in dirstate and manifest of target rev,
2227 2249 # so have to walk both. do not print errors if files exist in one
2228 2250 # but not other.
2229 2251
2230 2252 names = {}
2231 2253
2232 2254 wlock = repo.wlock()
2233 2255 try:
2234 2256 # walk dirstate.
2235 2257
2236 2258 m = scmutil.match(repo[None], pats, opts)
2237 2259 m.bad = lambda x, y: False
2238 2260 for abs in repo.walk(m):
2239 2261 names[abs] = m.rel(abs), m.exact(abs)
2240 2262
2241 2263 # walk target manifest.
2242 2264
2243 2265 def badfn(path, msg):
2244 2266 if path in names:
2245 2267 return
2246 2268 if path in ctx.substate:
2247 2269 return
2248 2270 path_ = path + '/'
2249 2271 for f in names:
2250 2272 if f.startswith(path_):
2251 2273 return
2252 2274 ui.warn("%s: %s\n" % (m.rel(path), msg))
2253 2275
2254 2276 m = scmutil.match(ctx, pats, opts)
2255 2277 m.bad = badfn
2256 2278 for abs in ctx.walk(m):
2257 2279 if abs not in names:
2258 2280 names[abs] = m.rel(abs), m.exact(abs)
2259 2281
2260 2282 # get the list of subrepos that must be reverted
2261 2283 targetsubs = sorted(s for s in ctx.substate if m(s))
2262 2284 m = scmutil.matchfiles(repo, names)
2263 2285 changes = repo.status(match=m)[:4]
2264 2286 modified, added, removed, deleted = map(set, changes)
2265 2287
2266 2288 # if f is a rename, also revert the source
2267 2289 cwd = repo.getcwd()
2268 2290 for f in added:
2269 2291 src = repo.dirstate.copied(f)
2270 2292 if src and src not in names and repo.dirstate[src] == 'r':
2271 2293 removed.add(src)
2272 2294 names[src] = (repo.pathto(src, cwd), True)
2273 2295
2274 2296 def removeforget(abs):
2275 2297 if repo.dirstate[abs] == 'a':
2276 2298 return _('forgetting %s\n')
2277 2299 return _('removing %s\n')
2278 2300
2279 2301 revert = ([], _('reverting %s\n'))
2280 2302 add = ([], _('adding %s\n'))
2281 2303 remove = ([], removeforget)
2282 2304 undelete = ([], _('undeleting %s\n'))
2283 2305
2284 2306 disptable = (
2285 2307 # dispatch table:
2286 2308 # file state
2287 2309 # action if in target manifest
2288 2310 # action if not in target manifest
2289 2311 # make backup if in target manifest
2290 2312 # make backup if not in target manifest
2291 2313 (modified, revert, remove, True, True),
2292 2314 (added, revert, remove, True, False),
2293 2315 (removed, undelete, None, True, False),
2294 2316 (deleted, revert, remove, False, False),
2295 2317 )
2296 2318
2297 2319 for abs, (rel, exact) in sorted(names.items()):
2298 2320 mfentry = mf.get(abs)
2299 2321 target = repo.wjoin(abs)
2300 2322 def handle(xlist, dobackup):
2301 2323 xlist[0].append(abs)
2302 2324 if (dobackup and not opts.get('no_backup') and
2303 2325 os.path.lexists(target) and
2304 2326 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2305 2327 bakname = "%s.orig" % rel
2306 2328 ui.note(_('saving current version of %s as %s\n') %
2307 2329 (rel, bakname))
2308 2330 if not opts.get('dry_run'):
2309 2331 util.rename(target, bakname)
2310 2332 if ui.verbose or not exact:
2311 2333 msg = xlist[1]
2312 2334 if not isinstance(msg, basestring):
2313 2335 msg = msg(abs)
2314 2336 ui.status(msg % rel)
2315 2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2316 2338 if abs not in table:
2317 2339 continue
2318 2340 # file has changed in dirstate
2319 2341 if mfentry:
2320 2342 handle(hitlist, backuphit)
2321 2343 elif misslist is not None:
2322 2344 handle(misslist, backupmiss)
2323 2345 break
2324 2346 else:
2325 2347 if abs not in repo.dirstate:
2326 2348 if mfentry:
2327 2349 handle(add, True)
2328 2350 elif exact:
2329 2351 ui.warn(_('file not managed: %s\n') % rel)
2330 2352 continue
2331 2353 # file has not changed in dirstate
2332 2354 if node == parent:
2333 2355 if exact:
2334 2356 ui.warn(_('no changes needed to %s\n') % rel)
2335 2357 continue
2336 2358 if pmf is None:
2337 2359 # only need parent manifest in this unlikely case,
2338 2360 # so do not read by default
2339 2361 pmf = repo[parent].manifest()
2340 2362 if abs in pmf and mfentry:
2341 2363 # if version of file is same in parent and target
2342 2364 # manifests, do nothing
2343 2365 if (pmf[abs] != mfentry or
2344 2366 pmf.flags(abs) != mf.flags(abs)):
2345 2367 handle(revert, False)
2346 2368 else:
2347 2369 handle(remove, False)
2348 2370 if not opts.get('dry_run'):
2349 2371 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2350 2372
2351 2373 if targetsubs:
2352 2374 # Revert the subrepos on the revert list
2353 2375 for sub in targetsubs:
2354 2376 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2355 2377 finally:
2356 2378 wlock.release()
2357 2379
2358 2380 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2359 2381 """function that actually perform all the action computed for revert
2360 2382
2361 2383 This is an independent function to let extension to plug in and react to
2362 2384 the imminent revert.
2363 2385
2364 2386 Make sure you have the working directory locked when calling this function.
2365 2387 """
2366 2388 parent, p2 = parents
2367 2389 node = ctx.node()
2368 2390 def checkout(f):
2369 2391 fc = ctx[f]
2370 2392 repo.wwrite(f, fc.data(), fc.flags())
2371 2393
2372 2394 audit_path = pathutil.pathauditor(repo.root)
2373 2395 for f in remove[0]:
2374 2396 if repo.dirstate[f] == 'a':
2375 2397 repo.dirstate.drop(f)
2376 2398 continue
2377 2399 audit_path(f)
2378 2400 try:
2379 2401 util.unlinkpath(repo.wjoin(f))
2380 2402 except OSError:
2381 2403 pass
2382 2404 repo.dirstate.remove(f)
2383 2405
2384 2406 normal = None
2385 2407 if node == parent:
2386 2408 # We're reverting to our parent. If possible, we'd like status
2387 2409 # to report the file as clean. We have to use normallookup for
2388 2410 # merges to avoid losing information about merged/dirty files.
2389 2411 if p2 != nullid:
2390 2412 normal = repo.dirstate.normallookup
2391 2413 else:
2392 2414 normal = repo.dirstate.normal
2393 2415 for f in revert[0]:
2394 2416 checkout(f)
2395 2417 if normal:
2396 2418 normal(f)
2397 2419
2398 2420 for f in add[0]:
2399 2421 checkout(f)
2400 2422 repo.dirstate.add(f)
2401 2423
2402 2424 normal = repo.dirstate.normallookup
2403 2425 if node == parent and p2 == nullid:
2404 2426 normal = repo.dirstate.normal
2405 2427 for f in undelete[0]:
2406 2428 checkout(f)
2407 2429 normal(f)
2408 2430
2409 2431 copied = copies.pathcopies(repo[parent], ctx)
2410 2432
2411 2433 for f in add[0] + undelete[0] + revert[0]:
2412 2434 if f in copied:
2413 2435 repo.dirstate.copy(copied[f], f)
2414 2436
2415 2437 def command(table):
2416 2438 '''returns a function object bound to table which can be used as
2417 2439 a decorator for populating table as a command table'''
2418 2440
2419 2441 def cmd(name, options=(), synopsis=None):
2420 2442 def decorator(func):
2421 2443 if synopsis:
2422 2444 table[name] = func, list(options), synopsis
2423 2445 else:
2424 2446 table[name] = func, list(options)
2425 2447 return func
2426 2448 return decorator
2427 2449
2428 2450 return cmd
2429 2451
2430 2452 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2431 2453 # commands.outgoing. "missing" is "missing" of the result of
2432 2454 # "findcommonoutgoing()"
2433 2455 outgoinghooks = util.hooks()
2434 2456
2435 2457 # a list of (ui, repo) functions called by commands.summary
2436 2458 summaryhooks = util.hooks()
2437 2459
2438 2460 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2439 2461 #
2440 2462 # functions should return tuple of booleans below, if 'changes' is None:
2441 2463 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2442 2464 #
2443 2465 # otherwise, 'changes' is a tuple of tuples below:
2444 2466 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2445 2467 # - (desturl, destbranch, destpeer, outgoing)
2446 2468 summaryremotehooks = util.hooks()
2447 2469
2448 2470 # A list of state files kept by multistep operations like graft.
2449 2471 # Since graft cannot be aborted, it is considered 'clearable' by update.
2450 2472 # note: bisect is intentionally excluded
2451 2473 # (state file, clearable, allowcommit, error, hint)
2452 2474 unfinishedstates = [
2453 2475 ('graftstate', True, False, _('graft in progress'),
2454 2476 _("use 'hg graft --continue' or 'hg update' to abort")),
2455 2477 ('updatestate', True, False, _('last update was interrupted'),
2456 2478 _("use 'hg update' to get a consistent checkout"))
2457 2479 ]
2458 2480
2459 2481 def checkunfinished(repo, commit=False):
2460 2482 '''Look for an unfinished multistep operation, like graft, and abort
2461 2483 if found. It's probably good to check this right before
2462 2484 bailifchanged().
2463 2485 '''
2464 2486 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2465 2487 if commit and allowcommit:
2466 2488 continue
2467 2489 if repo.vfs.exists(f):
2468 2490 raise util.Abort(msg, hint=hint)
2469 2491
2470 2492 def clearunfinished(repo):
2471 2493 '''Check for unfinished operations (as above), and clear the ones
2472 2494 that are clearable.
2473 2495 '''
2474 2496 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2475 2497 if not clearable and repo.vfs.exists(f):
2476 2498 raise util.Abort(msg, hint=hint)
2477 2499 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2478 2500 if clearable and repo.vfs.exists(f):
2479 2501 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now