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