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