##// END OF EJS Templates
cmdutil: cleanup imports
Alexander Solovyov -
r11231:1107888a default
parent child Browse files
Show More
@@ -1,1247 +1,1247 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, glob, tempfile
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 12 import match as _match
13 13 import similar
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.parents()[1] != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 94 message = open(logfile).read()
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def remoteui(src, opts):
115 115 'build a remote ui from ui or repo and opts'
116 116 if hasattr(src, 'baseui'): # looks like a repository
117 117 dst = src.baseui.copy() # drop repo-specific config
118 118 src = src.ui # copy target options from repo
119 119 else: # assume it's a global ui object
120 120 dst = src.copy() # keep all global options
121 121
122 122 # copy ssh-specific options
123 123 for o in 'ssh', 'remotecmd':
124 124 v = opts.get(o) or src.config('ui', o)
125 125 if v:
126 126 dst.setconfig("ui", o, v)
127 127
128 128 # copy bundle-specific options
129 129 r = src.config('bundle', 'mainreporoot')
130 130 if r:
131 131 dst.setconfig('bundle', 'mainreporoot', r)
132 132
133 133 # copy auth and http_proxy section settings
134 134 for sect in ('auth', 'http_proxy'):
135 135 for key, val in src.configitems(sect):
136 136 dst.setconfig(sect, key, val)
137 137
138 138 return dst
139 139
140 140 def revpair(repo, revs):
141 141 '''return pair of nodes, given list of revisions. second item can
142 142 be None, meaning use working dir.'''
143 143
144 144 def revfix(repo, val, defval):
145 145 if not val and val != 0 and defval is not None:
146 146 val = defval
147 147 return repo.lookup(val)
148 148
149 149 if not revs:
150 150 return repo.dirstate.parents()[0], None
151 151 end = None
152 152 if len(revs) == 1:
153 153 if revrangesep in revs[0]:
154 154 start, end = revs[0].split(revrangesep, 1)
155 155 start = revfix(repo, start, 0)
156 156 end = revfix(repo, end, len(repo) - 1)
157 157 else:
158 158 start = revfix(repo, revs[0], None)
159 159 elif len(revs) == 2:
160 160 if revrangesep in revs[0] or revrangesep in revs[1]:
161 161 raise util.Abort(_('too many revisions specified'))
162 162 start = revfix(repo, revs[0], None)
163 163 end = revfix(repo, revs[1], None)
164 164 else:
165 165 raise util.Abort(_('too many revisions specified'))
166 166 return start, end
167 167
168 168 def revrange(repo, revs):
169 169 """Yield revision as strings from a list of revision specifications."""
170 170
171 171 def revfix(repo, val, defval):
172 172 if not val and val != 0 and defval is not None:
173 173 return defval
174 174 return repo.changelog.rev(repo.lookup(val))
175 175
176 176 seen, l = set(), []
177 177 for spec in revs:
178 178 if revrangesep in spec:
179 179 start, end = spec.split(revrangesep, 1)
180 180 start = revfix(repo, start, 0)
181 181 end = revfix(repo, end, len(repo) - 1)
182 182 step = start > end and -1 or 1
183 183 for rev in xrange(start, end + step, step):
184 184 if rev in seen:
185 185 continue
186 186 seen.add(rev)
187 187 l.append(rev)
188 188 else:
189 189 rev = revfix(repo, spec, None)
190 190 if rev in seen:
191 191 continue
192 192 seen.add(rev)
193 193 l.append(rev)
194 194
195 195 return l
196 196
197 197 def make_filename(repo, pat, node,
198 198 total=None, seqno=None, revwidth=None, pathname=None):
199 199 node_expander = {
200 200 'H': lambda: hex(node),
201 201 'R': lambda: str(repo.changelog.rev(node)),
202 202 'h': lambda: short(node),
203 203 }
204 204 expander = {
205 205 '%': lambda: '%',
206 206 'b': lambda: os.path.basename(repo.root),
207 207 }
208 208
209 209 try:
210 210 if node:
211 211 expander.update(node_expander)
212 212 if node:
213 213 expander['r'] = (lambda:
214 214 str(repo.changelog.rev(node)).zfill(revwidth or 0))
215 215 if total is not None:
216 216 expander['N'] = lambda: str(total)
217 217 if seqno is not None:
218 218 expander['n'] = lambda: str(seqno)
219 219 if total is not None and seqno is not None:
220 220 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
221 221 if pathname is not None:
222 222 expander['s'] = lambda: os.path.basename(pathname)
223 223 expander['d'] = lambda: os.path.dirname(pathname) or '.'
224 224 expander['p'] = lambda: pathname
225 225
226 226 newname = []
227 227 patlen = len(pat)
228 228 i = 0
229 229 while i < patlen:
230 230 c = pat[i]
231 231 if c == '%':
232 232 i += 1
233 233 c = pat[i]
234 234 c = expander[c]()
235 235 newname.append(c)
236 236 i += 1
237 237 return ''.join(newname)
238 238 except KeyError, inst:
239 239 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
240 240 inst.args[0])
241 241
242 242 def make_file(repo, pat, node=None,
243 243 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
244 244
245 245 writable = 'w' in mode or 'a' in mode
246 246
247 247 if not pat or pat == '-':
248 248 return writable and sys.stdout or sys.stdin
249 249 if hasattr(pat, 'write') and writable:
250 250 return pat
251 251 if hasattr(pat, 'read') and 'r' in mode:
252 252 return pat
253 253 return open(make_filename(repo, pat, node, total, seqno, revwidth,
254 254 pathname),
255 255 mode)
256 256
257 257 def expandpats(pats):
258 258 if not util.expandglobs:
259 259 return list(pats)
260 260 ret = []
261 261 for p in pats:
262 262 kind, name = _match._patsplit(p, None)
263 263 if kind is None:
264 264 try:
265 265 globbed = glob.glob(name)
266 266 except re.error:
267 267 globbed = [name]
268 268 if globbed:
269 269 ret.extend(globbed)
270 270 continue
271 271 ret.append(p)
272 272 return ret
273 273
274 274 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
275 275 if not globbed and default == 'relpath':
276 276 pats = expandpats(pats or [])
277 277 m = _match.match(repo.root, repo.getcwd(), pats,
278 278 opts.get('include'), opts.get('exclude'), default)
279 279 def badfn(f, msg):
280 280 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
281 281 m.bad = badfn
282 282 return m
283 283
284 284 def matchall(repo):
285 285 return _match.always(repo.root, repo.getcwd())
286 286
287 287 def matchfiles(repo, files):
288 288 return _match.exact(repo.root, repo.getcwd(), files)
289 289
290 290 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
291 291 if dry_run is None:
292 292 dry_run = opts.get('dry_run')
293 293 if similarity is None:
294 294 similarity = float(opts.get('similarity') or 0)
295 295 # we'd use status here, except handling of symlinks and ignore is tricky
296 296 added, unknown, deleted, removed = [], [], [], []
297 297 audit_path = util.path_auditor(repo.root)
298 298 m = match(repo, pats, opts)
299 299 for abs in repo.walk(m):
300 300 target = repo.wjoin(abs)
301 301 good = True
302 302 try:
303 303 audit_path(abs)
304 304 except:
305 305 good = False
306 306 rel = m.rel(abs)
307 307 exact = m.exact(abs)
308 308 if good and abs not in repo.dirstate:
309 309 unknown.append(abs)
310 310 if repo.ui.verbose or not exact:
311 311 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
312 312 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
313 313 or (os.path.isdir(target) and not os.path.islink(target))):
314 314 deleted.append(abs)
315 315 if repo.ui.verbose or not exact:
316 316 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
317 317 # for finding renames
318 318 elif repo.dirstate[abs] == 'r':
319 319 removed.append(abs)
320 320 elif repo.dirstate[abs] == 'a':
321 321 added.append(abs)
322 322 copies = {}
323 323 if similarity > 0:
324 324 for old, new, score in similar.findrenames(repo,
325 325 added + unknown, removed + deleted, similarity):
326 326 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
327 327 repo.ui.status(_('recording removal of %s as rename to %s '
328 328 '(%d%% similar)\n') %
329 329 (m.rel(old), m.rel(new), score * 100))
330 330 copies[new] = old
331 331
332 332 if not dry_run:
333 333 wlock = repo.wlock()
334 334 try:
335 335 repo.remove(deleted)
336 336 repo.add(unknown)
337 337 for new, old in copies.iteritems():
338 338 repo.copy(old, new)
339 339 finally:
340 340 wlock.release()
341 341
342 342 def copy(ui, repo, pats, opts, rename=False):
343 343 # called with the repo lock held
344 344 #
345 345 # hgsep => pathname that uses "/" to separate directories
346 346 # ossep => pathname that uses os.sep to separate directories
347 347 cwd = repo.getcwd()
348 348 targets = {}
349 349 after = opts.get("after")
350 350 dryrun = opts.get("dry_run")
351 351
352 352 def walkpat(pat):
353 353 srcs = []
354 354 badstates = after and '?' or '?r'
355 355 m = match(repo, [pat], opts, globbed=True)
356 356 for abs in repo.walk(m):
357 357 state = repo.dirstate[abs]
358 358 rel = m.rel(abs)
359 359 exact = m.exact(abs)
360 360 if state in badstates:
361 361 if exact and state == '?':
362 362 ui.warn(_('%s: not copying - file is not managed\n') % rel)
363 363 if exact and state == 'r':
364 364 ui.warn(_('%s: not copying - file has been marked for'
365 365 ' remove\n') % rel)
366 366 continue
367 367 # abs: hgsep
368 368 # rel: ossep
369 369 srcs.append((abs, rel, exact))
370 370 return srcs
371 371
372 372 # abssrc: hgsep
373 373 # relsrc: ossep
374 374 # otarget: ossep
375 375 def copyfile(abssrc, relsrc, otarget, exact):
376 376 abstarget = util.canonpath(repo.root, cwd, otarget)
377 377 reltarget = repo.pathto(abstarget, cwd)
378 378 target = repo.wjoin(abstarget)
379 379 src = repo.wjoin(abssrc)
380 380 state = repo.dirstate[abstarget]
381 381
382 382 # check for collisions
383 383 prevsrc = targets.get(abstarget)
384 384 if prevsrc is not None:
385 385 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
386 386 (reltarget, repo.pathto(abssrc, cwd),
387 387 repo.pathto(prevsrc, cwd)))
388 388 return
389 389
390 390 # check for overwrites
391 391 exists = os.path.exists(target)
392 392 if not after and exists or after and state in 'mn':
393 393 if not opts['force']:
394 394 ui.warn(_('%s: not overwriting - file exists\n') %
395 395 reltarget)
396 396 return
397 397
398 398 if after:
399 399 if not exists:
400 400 if rename:
401 401 ui.warn(_('%s: not recording move - %s does not exist\n') %
402 402 (relsrc, reltarget))
403 403 else:
404 404 ui.warn(_('%s: not recording copy - %s does not exist\n') %
405 405 (relsrc, reltarget))
406 406 return
407 407 elif not dryrun:
408 408 try:
409 409 if exists:
410 410 os.unlink(target)
411 411 targetdir = os.path.dirname(target) or '.'
412 412 if not os.path.isdir(targetdir):
413 413 os.makedirs(targetdir)
414 414 util.copyfile(src, target)
415 415 except IOError, inst:
416 416 if inst.errno == errno.ENOENT:
417 417 ui.warn(_('%s: deleted in working copy\n') % relsrc)
418 418 else:
419 419 ui.warn(_('%s: cannot copy - %s\n') %
420 420 (relsrc, inst.strerror))
421 421 return True # report a failure
422 422
423 423 if ui.verbose or not exact:
424 424 if rename:
425 425 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
426 426 else:
427 427 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
428 428
429 429 targets[abstarget] = abssrc
430 430
431 431 # fix up dirstate
432 432 origsrc = repo.dirstate.copied(abssrc) or abssrc
433 433 if abstarget == origsrc: # copying back a copy?
434 434 if state not in 'mn' and not dryrun:
435 435 repo.dirstate.normallookup(abstarget)
436 436 else:
437 437 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
438 438 if not ui.quiet:
439 439 ui.warn(_("%s has not been committed yet, so no copy "
440 440 "data will be stored for %s.\n")
441 441 % (repo.pathto(origsrc, cwd), reltarget))
442 442 if repo.dirstate[abstarget] in '?r' and not dryrun:
443 443 repo.add([abstarget])
444 444 elif not dryrun:
445 445 repo.copy(origsrc, abstarget)
446 446
447 447 if rename and not dryrun:
448 448 repo.remove([abssrc], not after)
449 449
450 450 # pat: ossep
451 451 # dest ossep
452 452 # srcs: list of (hgsep, hgsep, ossep, bool)
453 453 # return: function that takes hgsep and returns ossep
454 454 def targetpathfn(pat, dest, srcs):
455 455 if os.path.isdir(pat):
456 456 abspfx = util.canonpath(repo.root, cwd, pat)
457 457 abspfx = util.localpath(abspfx)
458 458 if destdirexists:
459 459 striplen = len(os.path.split(abspfx)[0])
460 460 else:
461 461 striplen = len(abspfx)
462 462 if striplen:
463 463 striplen += len(os.sep)
464 464 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
465 465 elif destdirexists:
466 466 res = lambda p: os.path.join(dest,
467 467 os.path.basename(util.localpath(p)))
468 468 else:
469 469 res = lambda p: dest
470 470 return res
471 471
472 472 # pat: ossep
473 473 # dest ossep
474 474 # srcs: list of (hgsep, hgsep, ossep, bool)
475 475 # return: function that takes hgsep and returns ossep
476 476 def targetpathafterfn(pat, dest, srcs):
477 477 if _match.patkind(pat):
478 478 # a mercurial pattern
479 479 res = lambda p: os.path.join(dest,
480 480 os.path.basename(util.localpath(p)))
481 481 else:
482 482 abspfx = util.canonpath(repo.root, cwd, pat)
483 483 if len(abspfx) < len(srcs[0][0]):
484 484 # A directory. Either the target path contains the last
485 485 # component of the source path or it does not.
486 486 def evalpath(striplen):
487 487 score = 0
488 488 for s in srcs:
489 489 t = os.path.join(dest, util.localpath(s[0])[striplen:])
490 490 if os.path.exists(t):
491 491 score += 1
492 492 return score
493 493
494 494 abspfx = util.localpath(abspfx)
495 495 striplen = len(abspfx)
496 496 if striplen:
497 497 striplen += len(os.sep)
498 498 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
499 499 score = evalpath(striplen)
500 500 striplen1 = len(os.path.split(abspfx)[0])
501 501 if striplen1:
502 502 striplen1 += len(os.sep)
503 503 if evalpath(striplen1) > score:
504 504 striplen = striplen1
505 505 res = lambda p: os.path.join(dest,
506 506 util.localpath(p)[striplen:])
507 507 else:
508 508 # a file
509 509 if destdirexists:
510 510 res = lambda p: os.path.join(dest,
511 511 os.path.basename(util.localpath(p)))
512 512 else:
513 513 res = lambda p: dest
514 514 return res
515 515
516 516
517 517 pats = expandpats(pats)
518 518 if not pats:
519 519 raise util.Abort(_('no source or destination specified'))
520 520 if len(pats) == 1:
521 521 raise util.Abort(_('no destination specified'))
522 522 dest = pats.pop()
523 523 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
524 524 if not destdirexists:
525 525 if len(pats) > 1 or _match.patkind(pats[0]):
526 526 raise util.Abort(_('with multiple sources, destination must be an '
527 527 'existing directory'))
528 528 if util.endswithsep(dest):
529 529 raise util.Abort(_('destination %s is not a directory') % dest)
530 530
531 531 tfn = targetpathfn
532 532 if after:
533 533 tfn = targetpathafterfn
534 534 copylist = []
535 535 for pat in pats:
536 536 srcs = walkpat(pat)
537 537 if not srcs:
538 538 continue
539 539 copylist.append((tfn(pat, dest, srcs), srcs))
540 540 if not copylist:
541 541 raise util.Abort(_('no files to copy'))
542 542
543 543 errors = 0
544 544 for targetpath, srcs in copylist:
545 545 for abssrc, relsrc, exact in srcs:
546 546 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
547 547 errors += 1
548 548
549 549 if errors:
550 550 ui.warn(_('(consider using --after)\n'))
551 551
552 552 return errors != 0
553 553
554 554 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
555 555 runargs=None, appendpid=False):
556 556 '''Run a command as a service.'''
557 557
558 558 if opts['daemon'] and not opts['daemon_pipefds']:
559 559 # Signal child process startup with file removal
560 560 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
561 561 os.close(lockfd)
562 562 try:
563 563 if not runargs:
564 564 runargs = util.hgcmd() + sys.argv[1:]
565 565 runargs.append('--daemon-pipefds=%s' % lockpath)
566 566 # Don't pass --cwd to the child process, because we've already
567 567 # changed directory.
568 568 for i in xrange(1, len(runargs)):
569 569 if runargs[i].startswith('--cwd='):
570 570 del runargs[i]
571 571 break
572 572 elif runargs[i].startswith('--cwd'):
573 573 del runargs[i:i + 2]
574 574 break
575 575 def condfn():
576 576 return not os.path.exists(lockpath)
577 577 pid = util.rundetached(runargs, condfn)
578 578 if pid < 0:
579 579 raise util.Abort(_('child process failed to start'))
580 580 finally:
581 581 try:
582 582 os.unlink(lockpath)
583 583 except OSError, e:
584 584 if e.errno != errno.ENOENT:
585 585 raise
586 586 if parentfn:
587 587 return parentfn(pid)
588 588 else:
589 589 return
590 590
591 591 if initfn:
592 592 initfn()
593 593
594 594 if opts['pid_file']:
595 595 mode = appendpid and 'a' or 'w'
596 596 fp = open(opts['pid_file'], mode)
597 597 fp.write(str(os.getpid()) + '\n')
598 598 fp.close()
599 599
600 600 if opts['daemon_pipefds']:
601 601 lockpath = opts['daemon_pipefds']
602 602 try:
603 603 os.setsid()
604 604 except AttributeError:
605 605 pass
606 606 os.unlink(lockpath)
607 607 util.hidewindow()
608 608 sys.stdout.flush()
609 609 sys.stderr.flush()
610 610
611 611 nullfd = os.open(util.nulldev, os.O_RDWR)
612 612 logfilefd = nullfd
613 613 if logfile:
614 614 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
615 615 os.dup2(nullfd, 0)
616 616 os.dup2(logfilefd, 1)
617 617 os.dup2(logfilefd, 2)
618 618 if nullfd not in (0, 1, 2):
619 619 os.close(nullfd)
620 620 if logfile and logfilefd not in (0, 1, 2):
621 621 os.close(logfilefd)
622 622
623 623 if runfn:
624 624 return runfn()
625 625
626 626 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
627 627 opts=None):
628 628 '''export changesets as hg patches.'''
629 629
630 630 total = len(revs)
631 631 revwidth = max([len(str(rev)) for rev in revs])
632 632
633 633 def single(rev, seqno, fp):
634 634 ctx = repo[rev]
635 635 node = ctx.node()
636 636 parents = [p.node() for p in ctx.parents() if p]
637 637 branch = ctx.branch()
638 638 if switch_parent:
639 639 parents.reverse()
640 640 prev = (parents and parents[0]) or nullid
641 641
642 642 if not fp:
643 643 fp = make_file(repo, template, node, total=total, seqno=seqno,
644 644 revwidth=revwidth, mode='ab')
645 645 if fp != sys.stdout and hasattr(fp, 'name'):
646 646 repo.ui.note("%s\n" % fp.name)
647 647
648 648 fp.write("# HG changeset patch\n")
649 649 fp.write("# User %s\n" % ctx.user())
650 650 fp.write("# Date %d %d\n" % ctx.date())
651 651 if branch and (branch != 'default'):
652 652 fp.write("# Branch %s\n" % branch)
653 653 fp.write("# Node ID %s\n" % hex(node))
654 654 fp.write("# Parent %s\n" % hex(prev))
655 655 if len(parents) > 1:
656 656 fp.write("# Parent %s\n" % hex(parents[1]))
657 657 fp.write(ctx.description().rstrip())
658 658 fp.write("\n\n")
659 659
660 660 for chunk in patch.diff(repo, prev, node, opts=opts):
661 661 fp.write(chunk)
662 662
663 663 for seqno, rev in enumerate(revs):
664 664 single(rev, seqno + 1, fp)
665 665
666 666 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
667 667 changes=None, stat=False, fp=None):
668 668 '''show diff or diffstat.'''
669 669 if fp is None:
670 670 write = ui.write
671 671 else:
672 672 def write(s, **kw):
673 673 fp.write(s)
674 674
675 675 if stat:
676 676 diffopts.context = 0
677 677 width = 80
678 678 if not ui.plain():
679 679 width = util.termwidth()
680 680 chunks = patch.diff(repo, node1, node2, match, changes, diffopts)
681 681 for chunk, label in patch.diffstatui(util.iterlines(chunks),
682 682 width=width,
683 683 git=diffopts.git):
684 684 write(chunk, label=label)
685 685 else:
686 686 for chunk, label in patch.diffui(repo, node1, node2, match,
687 687 changes, diffopts):
688 688 write(chunk, label=label)
689 689
690 690 class changeset_printer(object):
691 691 '''show changeset information when templating not requested.'''
692 692
693 693 def __init__(self, ui, repo, patch, diffopts, buffered):
694 694 self.ui = ui
695 695 self.repo = repo
696 696 self.buffered = buffered
697 697 self.patch = patch
698 698 self.diffopts = diffopts
699 699 self.header = {}
700 700 self.hunk = {}
701 701 self.lastheader = None
702 702 self.footer = None
703 703
704 704 def flush(self, rev):
705 705 if rev in self.header:
706 706 h = self.header[rev]
707 707 if h != self.lastheader:
708 708 self.lastheader = h
709 709 self.ui.write(h)
710 710 del self.header[rev]
711 711 if rev in self.hunk:
712 712 self.ui.write(self.hunk[rev])
713 713 del self.hunk[rev]
714 714 return 1
715 715 return 0
716 716
717 717 def close(self):
718 718 if self.footer:
719 719 self.ui.write(self.footer)
720 720
721 721 def show(self, ctx, copies=None, **props):
722 722 if self.buffered:
723 723 self.ui.pushbuffer()
724 724 self._show(ctx, copies, props)
725 725 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
726 726 else:
727 727 self._show(ctx, copies, props)
728 728
729 729 def _show(self, ctx, copies, props):
730 730 '''show a single changeset or file revision'''
731 731 changenode = ctx.node()
732 732 rev = ctx.rev()
733 733
734 734 if self.ui.quiet:
735 735 self.ui.write("%d:%s\n" % (rev, short(changenode)),
736 736 label='log.node')
737 737 return
738 738
739 739 log = self.repo.changelog
740 740 date = util.datestr(ctx.date())
741 741
742 742 hexfunc = self.ui.debugflag and hex or short
743 743
744 744 parents = [(p, hexfunc(log.node(p)))
745 745 for p in self._meaningful_parentrevs(log, rev)]
746 746
747 747 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
748 748 label='log.changeset')
749 749
750 750 branch = ctx.branch()
751 751 # don't show the default branch name
752 752 if branch != 'default':
753 753 branch = encoding.tolocal(branch)
754 754 self.ui.write(_("branch: %s\n") % branch,
755 755 label='log.branch')
756 756 for tag in self.repo.nodetags(changenode):
757 757 self.ui.write(_("tag: %s\n") % tag,
758 758 label='log.tag')
759 759 for parent in parents:
760 760 self.ui.write(_("parent: %d:%s\n") % parent,
761 761 label='log.parent')
762 762
763 763 if self.ui.debugflag:
764 764 mnode = ctx.manifestnode()
765 765 self.ui.write(_("manifest: %d:%s\n") %
766 766 (self.repo.manifest.rev(mnode), hex(mnode)),
767 767 label='ui.debug log.manifest')
768 768 self.ui.write(_("user: %s\n") % ctx.user(),
769 769 label='log.user')
770 770 self.ui.write(_("date: %s\n") % date,
771 771 label='log.date')
772 772
773 773 if self.ui.debugflag:
774 774 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
775 775 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
776 776 files):
777 777 if value:
778 778 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
779 779 label='ui.debug log.files')
780 780 elif ctx.files() and self.ui.verbose:
781 781 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
782 782 label='ui.note log.files')
783 783 if copies and self.ui.verbose:
784 784 copies = ['%s (%s)' % c for c in copies]
785 785 self.ui.write(_("copies: %s\n") % ' '.join(copies),
786 786 label='ui.note log.copies')
787 787
788 788 extra = ctx.extra()
789 789 if extra and self.ui.debugflag:
790 790 for key, value in sorted(extra.items()):
791 791 self.ui.write(_("extra: %s=%s\n")
792 792 % (key, value.encode('string_escape')),
793 793 label='ui.debug log.extra')
794 794
795 795 description = ctx.description().strip()
796 796 if description:
797 797 if self.ui.verbose:
798 798 self.ui.write(_("description:\n"),
799 799 label='ui.note log.description')
800 800 self.ui.write(description,
801 801 label='ui.note log.description')
802 802 self.ui.write("\n\n")
803 803 else:
804 804 self.ui.write(_("summary: %s\n") %
805 805 description.splitlines()[0],
806 806 label='log.summary')
807 807 self.ui.write("\n")
808 808
809 809 self.showpatch(changenode)
810 810
811 811 def showpatch(self, node):
812 812 if self.patch:
813 813 stat = self.diffopts.get('stat')
814 814 diffopts = patch.diffopts(self.ui, self.diffopts)
815 815 prev = self.repo.changelog.parents(node)[0]
816 816 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
817 817 match=self.patch, stat=stat)
818 818 self.ui.write("\n")
819 819
820 820 def _meaningful_parentrevs(self, log, rev):
821 821 """Return list of meaningful (or all if debug) parentrevs for rev.
822 822
823 823 For merges (two non-nullrev revisions) both parents are meaningful.
824 824 Otherwise the first parent revision is considered meaningful if it
825 825 is not the preceding revision.
826 826 """
827 827 parents = log.parentrevs(rev)
828 828 if not self.ui.debugflag and parents[1] == nullrev:
829 829 if parents[0] >= rev - 1:
830 830 parents = []
831 831 else:
832 832 parents = [parents[0]]
833 833 return parents
834 834
835 835
836 836 class changeset_templater(changeset_printer):
837 837 '''format changeset information.'''
838 838
839 839 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
840 840 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
841 841 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
842 842 defaulttempl = {
843 843 'parent': '{rev}:{node|formatnode} ',
844 844 'manifest': '{rev}:{node|formatnode}',
845 845 'file_copy': '{name} ({source})',
846 846 'extra': '{key}={value|stringescape}'
847 847 }
848 848 # filecopy is preserved for compatibility reasons
849 849 defaulttempl['filecopy'] = defaulttempl['file_copy']
850 850 self.t = templater.templater(mapfile, {'formatnode': formatnode},
851 851 cache=defaulttempl)
852 852 self.cache = {}
853 853
854 854 def use_template(self, t):
855 855 '''set template string to use'''
856 856 self.t.cache['changeset'] = t
857 857
858 858 def _meaningful_parentrevs(self, ctx):
859 859 """Return list of meaningful (or all if debug) parentrevs for rev.
860 860 """
861 861 parents = ctx.parents()
862 862 if len(parents) > 1:
863 863 return parents
864 864 if self.ui.debugflag:
865 865 return [parents[0], self.repo['null']]
866 866 if parents[0].rev() >= ctx.rev() - 1:
867 867 return []
868 868 return parents
869 869
870 870 def _show(self, ctx, copies, props):
871 871 '''show a single changeset or file revision'''
872 872
873 873 showlist = templatekw.showlist
874 874
875 875 # showparents() behaviour depends on ui trace level which
876 876 # causes unexpected behaviours at templating level and makes
877 877 # it harder to extract it in a standalone function. Its
878 878 # behaviour cannot be changed so leave it here for now.
879 879 def showparents(**args):
880 880 ctx = args['ctx']
881 881 parents = [[('rev', p.rev()), ('node', p.hex())]
882 882 for p in self._meaningful_parentrevs(ctx)]
883 883 return showlist('parent', parents, **args)
884 884
885 885 props = props.copy()
886 886 props.update(templatekw.keywords)
887 887 props['parents'] = showparents
888 888 props['templ'] = self.t
889 889 props['ctx'] = ctx
890 890 props['repo'] = self.repo
891 891 props['revcache'] = {'copies': copies}
892 892 props['cache'] = self.cache
893 893
894 894 # find correct templates for current mode
895 895
896 896 tmplmodes = [
897 897 (True, None),
898 898 (self.ui.verbose, 'verbose'),
899 899 (self.ui.quiet, 'quiet'),
900 900 (self.ui.debugflag, 'debug'),
901 901 ]
902 902
903 903 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
904 904 for mode, postfix in tmplmodes:
905 905 for type in types:
906 906 cur = postfix and ('%s_%s' % (type, postfix)) or type
907 907 if mode and cur in self.t:
908 908 types[type] = cur
909 909
910 910 try:
911 911
912 912 # write header
913 913 if types['header']:
914 914 h = templater.stringify(self.t(types['header'], **props))
915 915 if self.buffered:
916 916 self.header[ctx.rev()] = h
917 917 else:
918 918 self.ui.write(h)
919 919
920 920 # write changeset metadata, then patch if requested
921 921 key = types['changeset']
922 922 self.ui.write(templater.stringify(self.t(key, **props)))
923 923 self.showpatch(ctx.node())
924 924
925 925 if types['footer']:
926 926 if not self.footer:
927 927 self.footer = templater.stringify(self.t(types['footer'],
928 928 **props))
929 929
930 930 except KeyError, inst:
931 931 msg = _("%s: no key named '%s'")
932 932 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
933 933 except SyntaxError, inst:
934 934 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
935 935
936 936 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
937 937 """show one changeset using template or regular display.
938 938
939 939 Display format will be the first non-empty hit of:
940 940 1. option 'template'
941 941 2. option 'style'
942 942 3. [ui] setting 'logtemplate'
943 943 4. [ui] setting 'style'
944 944 If all of these values are either the unset or the empty string,
945 945 regular display via changeset_printer() is done.
946 946 """
947 947 # options
948 948 patch = False
949 949 if opts.get('patch') or opts.get('stat'):
950 950 patch = matchfn or matchall(repo)
951 951
952 952 tmpl = opts.get('template')
953 953 style = None
954 954 if tmpl:
955 955 tmpl = templater.parsestring(tmpl, quoted=False)
956 956 else:
957 957 style = opts.get('style')
958 958
959 959 # ui settings
960 960 if not (tmpl or style):
961 961 tmpl = ui.config('ui', 'logtemplate')
962 962 if tmpl:
963 963 tmpl = templater.parsestring(tmpl)
964 964 else:
965 965 style = util.expandpath(ui.config('ui', 'style', ''))
966 966
967 967 if not (tmpl or style):
968 968 return changeset_printer(ui, repo, patch, opts, buffered)
969 969
970 970 mapfile = None
971 971 if style and not tmpl:
972 972 mapfile = style
973 973 if not os.path.split(mapfile)[0]:
974 974 mapname = (templater.templatepath('map-cmdline.' + mapfile)
975 975 or templater.templatepath(mapfile))
976 976 if mapname:
977 977 mapfile = mapname
978 978
979 979 try:
980 980 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
981 981 except SyntaxError, inst:
982 982 raise util.Abort(inst.args[0])
983 983 if tmpl:
984 984 t.use_template(tmpl)
985 985 return t
986 986
987 987 def finddate(ui, repo, date):
988 988 """Find the tipmost changeset that matches the given date spec"""
989 989
990 990 df = util.matchdate(date)
991 991 m = matchall(repo)
992 992 results = {}
993 993
994 994 def prep(ctx, fns):
995 995 d = ctx.date()
996 996 if df(d[0]):
997 997 results[ctx.rev()] = d
998 998
999 999 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1000 1000 rev = ctx.rev()
1001 1001 if rev in results:
1002 1002 ui.status(_("Found revision %s from %s\n") %
1003 1003 (rev, util.datestr(results[rev])))
1004 1004 return str(rev)
1005 1005
1006 1006 raise util.Abort(_("revision matching date not found"))
1007 1007
1008 1008 def walkchangerevs(repo, match, opts, prepare):
1009 1009 '''Iterate over files and the revs in which they changed.
1010 1010
1011 1011 Callers most commonly need to iterate backwards over the history
1012 1012 in which they are interested. Doing so has awful (quadratic-looking)
1013 1013 performance, so we use iterators in a "windowed" way.
1014 1014
1015 1015 We walk a window of revisions in the desired order. Within the
1016 1016 window, we first walk forwards to gather data, then in the desired
1017 1017 order (usually backwards) to display it.
1018 1018
1019 1019 This function returns an iterator yielding contexts. Before
1020 1020 yielding each context, the iterator will first call the prepare
1021 1021 function on each context in the window in forward order.'''
1022 1022
1023 1023 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1024 1024 if start < end:
1025 1025 while start < end:
1026 1026 yield start, min(windowsize, end - start)
1027 1027 start += windowsize
1028 1028 if windowsize < sizelimit:
1029 1029 windowsize *= 2
1030 1030 else:
1031 1031 while start > end:
1032 1032 yield start, min(windowsize, start - end - 1)
1033 1033 start -= windowsize
1034 1034 if windowsize < sizelimit:
1035 1035 windowsize *= 2
1036 1036
1037 1037 follow = opts.get('follow') or opts.get('follow_first')
1038 1038
1039 1039 if not len(repo):
1040 1040 return []
1041 1041
1042 1042 if follow:
1043 1043 defrange = '%s:0' % repo['.'].rev()
1044 1044 else:
1045 1045 defrange = '-1:0'
1046 1046 revs = revrange(repo, opts['rev'] or [defrange])
1047 1047 wanted = set()
1048 1048 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1049 1049 fncache = {}
1050 1050 change = util.cachefunc(repo.changectx)
1051 1051
1052 1052 if not slowpath and not match.files():
1053 1053 # No files, no patterns. Display all revs.
1054 1054 wanted = set(revs)
1055 1055 copies = []
1056 1056
1057 1057 if not slowpath:
1058 1058 # Only files, no patterns. Check the history of each file.
1059 1059 def filerevgen(filelog, node):
1060 1060 cl_count = len(repo)
1061 1061 if node is None:
1062 1062 last = len(filelog) - 1
1063 1063 else:
1064 1064 last = filelog.rev(node)
1065 1065 for i, window in increasing_windows(last, nullrev):
1066 1066 revs = []
1067 1067 for j in xrange(i - window, i + 1):
1068 1068 n = filelog.node(j)
1069 1069 revs.append((filelog.linkrev(j),
1070 1070 follow and filelog.renamed(n)))
1071 1071 for rev in reversed(revs):
1072 1072 # only yield rev for which we have the changelog, it can
1073 1073 # happen while doing "hg log" during a pull or commit
1074 1074 if rev[0] < cl_count:
1075 1075 yield rev
1076 1076 def iterfiles():
1077 1077 for filename in match.files():
1078 1078 yield filename, None
1079 1079 for filename_node in copies:
1080 1080 yield filename_node
1081 1081 minrev, maxrev = min(revs), max(revs)
1082 1082 for file_, node in iterfiles():
1083 1083 filelog = repo.file(file_)
1084 1084 if not len(filelog):
1085 1085 if node is None:
1086 1086 # A zero count may be a directory or deleted file, so
1087 1087 # try to find matching entries on the slow path.
1088 1088 if follow:
1089 1089 raise util.Abort(
1090 1090 _('cannot follow nonexistent file: "%s"') % file_)
1091 1091 slowpath = True
1092 1092 break
1093 1093 else:
1094 1094 continue
1095 1095 for rev, copied in filerevgen(filelog, node):
1096 1096 if rev <= maxrev:
1097 1097 if rev < minrev:
1098 1098 break
1099 1099 fncache.setdefault(rev, [])
1100 1100 fncache[rev].append(file_)
1101 1101 wanted.add(rev)
1102 1102 if copied:
1103 1103 copies.append(copied)
1104 1104 if slowpath:
1105 1105 if follow:
1106 1106 raise util.Abort(_('can only follow copies/renames for explicit '
1107 1107 'filenames'))
1108 1108
1109 1109 # The slow path checks files modified in every changeset.
1110 1110 def changerevgen():
1111 1111 for i, window in increasing_windows(len(repo) - 1, nullrev):
1112 1112 for j in xrange(i - window, i + 1):
1113 1113 yield change(j)
1114 1114
1115 1115 for ctx in changerevgen():
1116 1116 matches = filter(match, ctx.files())
1117 1117 if matches:
1118 1118 fncache[ctx.rev()] = matches
1119 1119 wanted.add(ctx.rev())
1120 1120
1121 1121 class followfilter(object):
1122 1122 def __init__(self, onlyfirst=False):
1123 1123 self.startrev = nullrev
1124 1124 self.roots = set()
1125 1125 self.onlyfirst = onlyfirst
1126 1126
1127 1127 def match(self, rev):
1128 1128 def realparents(rev):
1129 1129 if self.onlyfirst:
1130 1130 return repo.changelog.parentrevs(rev)[0:1]
1131 1131 else:
1132 1132 return filter(lambda x: x != nullrev,
1133 1133 repo.changelog.parentrevs(rev))
1134 1134
1135 1135 if self.startrev == nullrev:
1136 1136 self.startrev = rev
1137 1137 return True
1138 1138
1139 1139 if rev > self.startrev:
1140 1140 # forward: all descendants
1141 1141 if not self.roots:
1142 1142 self.roots.add(self.startrev)
1143 1143 for parent in realparents(rev):
1144 1144 if parent in self.roots:
1145 1145 self.roots.add(rev)
1146 1146 return True
1147 1147 else:
1148 1148 # backwards: all parents
1149 1149 if not self.roots:
1150 1150 self.roots.update(realparents(self.startrev))
1151 1151 if rev in self.roots:
1152 1152 self.roots.remove(rev)
1153 1153 self.roots.update(realparents(rev))
1154 1154 return True
1155 1155
1156 1156 return False
1157 1157
1158 1158 # it might be worthwhile to do this in the iterator if the rev range
1159 1159 # is descending and the prune args are all within that range
1160 1160 for rev in opts.get('prune', ()):
1161 1161 rev = repo.changelog.rev(repo.lookup(rev))
1162 1162 ff = followfilter()
1163 1163 stop = min(revs[0], revs[-1])
1164 1164 for x in xrange(rev, stop - 1, -1):
1165 1165 if ff.match(x):
1166 1166 wanted.discard(x)
1167 1167
1168 1168 def iterate():
1169 1169 if follow and not match.files():
1170 1170 ff = followfilter(onlyfirst=opts.get('follow_first'))
1171 1171 def want(rev):
1172 1172 return ff.match(rev) and rev in wanted
1173 1173 else:
1174 1174 def want(rev):
1175 1175 return rev in wanted
1176 1176
1177 1177 for i, window in increasing_windows(0, len(revs)):
1178 1178 change = util.cachefunc(repo.changectx)
1179 1179 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1180 1180 for rev in sorted(nrevs):
1181 1181 fns = fncache.get(rev)
1182 1182 ctx = change(rev)
1183 1183 if not fns:
1184 1184 def fns_generator():
1185 1185 for f in ctx.files():
1186 1186 if match(f):
1187 1187 yield f
1188 1188 fns = fns_generator()
1189 1189 prepare(ctx, fns)
1190 1190 for rev in nrevs:
1191 1191 yield change(rev)
1192 1192 return iterate()
1193 1193
1194 1194 def commit(ui, repo, commitfunc, pats, opts):
1195 1195 '''commit the specified files or all outstanding changes'''
1196 1196 date = opts.get('date')
1197 1197 if date:
1198 1198 opts['date'] = util.parsedate(date)
1199 1199 message = logmessage(opts)
1200 1200
1201 1201 # extract addremove carefully -- this function can be called from a command
1202 1202 # that doesn't support addremove
1203 1203 if opts.get('addremove'):
1204 1204 addremove(repo, pats, opts)
1205 1205
1206 1206 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1207 1207
1208 1208 def commiteditor(repo, ctx, subs):
1209 1209 if ctx.description():
1210 1210 return ctx.description()
1211 1211 return commitforceeditor(repo, ctx, subs)
1212 1212
1213 1213 def commitforceeditor(repo, ctx, subs):
1214 1214 edittext = []
1215 1215 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1216 1216 if ctx.description():
1217 1217 edittext.append(ctx.description())
1218 1218 edittext.append("")
1219 1219 edittext.append("") # Empty line between message and comments.
1220 1220 edittext.append(_("HG: Enter commit message."
1221 1221 " Lines beginning with 'HG:' are removed."))
1222 1222 edittext.append(_("HG: Leave message empty to abort commit."))
1223 1223 edittext.append("HG: --")
1224 1224 edittext.append(_("HG: user: %s") % ctx.user())
1225 1225 if ctx.p2():
1226 1226 edittext.append(_("HG: branch merge"))
1227 1227 if ctx.branch():
1228 1228 edittext.append(_("HG: branch '%s'")
1229 1229 % encoding.tolocal(ctx.branch()))
1230 1230 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1231 1231 edittext.extend([_("HG: added %s") % f for f in added])
1232 1232 edittext.extend([_("HG: changed %s") % f for f in modified])
1233 1233 edittext.extend([_("HG: removed %s") % f for f in removed])
1234 1234 if not added and not modified and not removed:
1235 1235 edittext.append(_("HG: no files changed"))
1236 1236 edittext.append("")
1237 1237 # run editor in the repository root
1238 1238 olddir = os.getcwd()
1239 1239 os.chdir(repo.root)
1240 1240 text = repo.ui.edit("\n".join(edittext), ctx.user())
1241 1241 text = re.sub("(?m)^HG:.*\n", "", text)
1242 1242 os.chdir(olddir)
1243 1243
1244 1244 if not text.strip():
1245 1245 raise util.Abort(_("empty commit message"))
1246 1246
1247 1247 return text
General Comments 0
You need to be logged in to leave comments. Login now