##// END OF EJS Templates
templater: ability to display diffstat for log-like commands
Alexander Solovyov <piranha at piranha.org.ua> -
r7879:5c4026a2 default
parent child Browse files
Show More
@@ -1,1194 +1,1204
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, bisect, stat
11 11 import mdiff, bdiff, util, templater, templatefilters, patch, errno, error
12 12 import match as _match
13 13
14 14 revrangesep = ':'
15 15
16 16 def findpossible(cmd, table, strict=False):
17 17 """
18 18 Return cmd -> (aliases, command table entry)
19 19 for each matching command.
20 20 Return debug commands (or their aliases) only if no normal command matches.
21 21 """
22 22 choice = {}
23 23 debugchoice = {}
24 24 for e in table.keys():
25 25 aliases = e.lstrip("^").split("|")
26 26 found = None
27 27 if cmd in aliases:
28 28 found = cmd
29 29 elif not strict:
30 30 for a in aliases:
31 31 if a.startswith(cmd):
32 32 found = a
33 33 break
34 34 if found is not None:
35 35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 36 debugchoice[found] = (aliases, table[e])
37 37 else:
38 38 choice[found] = (aliases, table[e])
39 39
40 40 if not choice and debugchoice:
41 41 choice = debugchoice
42 42
43 43 return choice
44 44
45 45 def findcmd(cmd, table, strict=True):
46 46 """Return (aliases, command table entry) for command string."""
47 47 choice = findpossible(cmd, table, strict)
48 48
49 49 if cmd in choice:
50 50 return choice[cmd]
51 51
52 52 if len(choice) > 1:
53 53 clist = choice.keys()
54 54 clist.sort()
55 55 raise error.AmbiguousCommand(cmd, clist)
56 56
57 57 if choice:
58 58 return choice.values()[0]
59 59
60 60 raise error.UnknownCommand(cmd)
61 61
62 62 def bail_if_changed(repo):
63 63 if repo.dirstate.parents()[1] != nullid:
64 64 raise util.Abort(_('outstanding uncommitted merge'))
65 65 modified, added, removed, deleted = repo.status()[:4]
66 66 if modified or added or removed or deleted:
67 67 raise util.Abort(_("outstanding uncommitted changes"))
68 68
69 69 def logmessage(opts):
70 70 """ get the log message according to -m and -l option """
71 71 message = opts.get('message')
72 72 logfile = opts.get('logfile')
73 73
74 74 if message and logfile:
75 75 raise util.Abort(_('options --message and --logfile are mutually '
76 76 'exclusive'))
77 77 if not message and logfile:
78 78 try:
79 79 if logfile == '-':
80 80 message = sys.stdin.read()
81 81 else:
82 82 message = open(logfile).read()
83 83 except IOError, inst:
84 84 raise util.Abort(_("can't read commit message '%s': %s") %
85 85 (logfile, inst.strerror))
86 86 return message
87 87
88 88 def loglimit(opts):
89 89 """get the log limit according to option -l/--limit"""
90 90 limit = opts.get('limit')
91 91 if limit:
92 92 try:
93 93 limit = int(limit)
94 94 except ValueError:
95 95 raise util.Abort(_('limit must be a positive integer'))
96 96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 97 else:
98 98 limit = sys.maxint
99 99 return limit
100 100
101 101 def setremoteconfig(ui, opts):
102 102 "copy remote options to ui tree"
103 103 if opts.get('ssh'):
104 104 ui.setconfig("ui", "ssh", opts['ssh'])
105 105 if opts.get('remotecmd'):
106 106 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
107 107
108 108 def revpair(repo, revs):
109 109 '''return pair of nodes, given list of revisions. second item can
110 110 be None, meaning use working dir.'''
111 111
112 112 def revfix(repo, val, defval):
113 113 if not val and val != 0 and defval is not None:
114 114 val = defval
115 115 return repo.lookup(val)
116 116
117 117 if not revs:
118 118 return repo.dirstate.parents()[0], None
119 119 end = None
120 120 if len(revs) == 1:
121 121 if revrangesep in revs[0]:
122 122 start, end = revs[0].split(revrangesep, 1)
123 123 start = revfix(repo, start, 0)
124 124 end = revfix(repo, end, len(repo) - 1)
125 125 else:
126 126 start = revfix(repo, revs[0], None)
127 127 elif len(revs) == 2:
128 128 if revrangesep in revs[0] or revrangesep in revs[1]:
129 129 raise util.Abort(_('too many revisions specified'))
130 130 start = revfix(repo, revs[0], None)
131 131 end = revfix(repo, revs[1], None)
132 132 else:
133 133 raise util.Abort(_('too many revisions specified'))
134 134 return start, end
135 135
136 136 def revrange(repo, revs):
137 137 """Yield revision as strings from a list of revision specifications."""
138 138
139 139 def revfix(repo, val, defval):
140 140 if not val and val != 0 and defval is not None:
141 141 return defval
142 142 return repo.changelog.rev(repo.lookup(val))
143 143
144 144 seen, l = {}, []
145 145 for spec in revs:
146 146 if revrangesep in spec:
147 147 start, end = spec.split(revrangesep, 1)
148 148 start = revfix(repo, start, 0)
149 149 end = revfix(repo, end, len(repo) - 1)
150 150 step = start > end and -1 or 1
151 151 for rev in xrange(start, end+step, step):
152 152 if rev in seen:
153 153 continue
154 154 seen[rev] = 1
155 155 l.append(rev)
156 156 else:
157 157 rev = revfix(repo, spec, None)
158 158 if rev in seen:
159 159 continue
160 160 seen[rev] = 1
161 161 l.append(rev)
162 162
163 163 return l
164 164
165 165 def make_filename(repo, pat, node,
166 166 total=None, seqno=None, revwidth=None, pathname=None):
167 167 node_expander = {
168 168 'H': lambda: hex(node),
169 169 'R': lambda: str(repo.changelog.rev(node)),
170 170 'h': lambda: short(node),
171 171 }
172 172 expander = {
173 173 '%': lambda: '%',
174 174 'b': lambda: os.path.basename(repo.root),
175 175 }
176 176
177 177 try:
178 178 if node:
179 179 expander.update(node_expander)
180 180 if node:
181 181 expander['r'] = (lambda:
182 182 str(repo.changelog.rev(node)).zfill(revwidth or 0))
183 183 if total is not None:
184 184 expander['N'] = lambda: str(total)
185 185 if seqno is not None:
186 186 expander['n'] = lambda: str(seqno)
187 187 if total is not None and seqno is not None:
188 188 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
189 189 if pathname is not None:
190 190 expander['s'] = lambda: os.path.basename(pathname)
191 191 expander['d'] = lambda: os.path.dirname(pathname) or '.'
192 192 expander['p'] = lambda: pathname
193 193
194 194 newname = []
195 195 patlen = len(pat)
196 196 i = 0
197 197 while i < patlen:
198 198 c = pat[i]
199 199 if c == '%':
200 200 i += 1
201 201 c = pat[i]
202 202 c = expander[c]()
203 203 newname.append(c)
204 204 i += 1
205 205 return ''.join(newname)
206 206 except KeyError, inst:
207 207 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
208 208 inst.args[0])
209 209
210 210 def make_file(repo, pat, node=None,
211 211 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
212 212
213 213 writable = 'w' in mode or 'a' in mode
214 214
215 215 if not pat or pat == '-':
216 216 return writable and sys.stdout or sys.stdin
217 217 if hasattr(pat, 'write') and writable:
218 218 return pat
219 219 if hasattr(pat, 'read') and 'r' in mode:
220 220 return pat
221 221 return open(make_filename(repo, pat, node, total, seqno, revwidth,
222 222 pathname),
223 223 mode)
224 224
225 225 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
226 226 if not globbed and default == 'relpath':
227 227 pats = util.expand_glob(pats or [])
228 228 m = _match.match(repo.root, repo.getcwd(), pats,
229 229 opts.get('include'), opts.get('exclude'), default)
230 230 def badfn(f, msg):
231 231 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
232 232 return False
233 233 m.bad = badfn
234 234 return m
235 235
236 236 def matchall(repo):
237 237 return _match.always(repo.root, repo.getcwd())
238 238
239 239 def matchfiles(repo, files):
240 240 return _match.exact(repo.root, repo.getcwd(), files)
241 241
242 242 def findrenames(repo, added=None, removed=None, threshold=0.5):
243 243 '''find renamed files -- yields (before, after, score) tuples'''
244 244 if added is None or removed is None:
245 245 added, removed = repo.status()[1:3]
246 246 ctx = repo['.']
247 247 for a in added:
248 248 aa = repo.wread(a)
249 249 bestname, bestscore = None, threshold
250 250 for r in removed:
251 251 rr = ctx.filectx(r).data()
252 252
253 253 # bdiff.blocks() returns blocks of matching lines
254 254 # count the number of bytes in each
255 255 equal = 0
256 256 alines = mdiff.splitnewlines(aa)
257 257 matches = bdiff.blocks(aa, rr)
258 258 for x1,x2,y1,y2 in matches:
259 259 for line in alines[x1:x2]:
260 260 equal += len(line)
261 261
262 262 lengths = len(aa) + len(rr)
263 263 if lengths:
264 264 myscore = equal*2.0 / lengths
265 265 if myscore >= bestscore:
266 266 bestname, bestscore = r, myscore
267 267 if bestname:
268 268 yield bestname, a, bestscore
269 269
270 270 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
271 271 if dry_run is None:
272 272 dry_run = opts.get('dry_run')
273 273 if similarity is None:
274 274 similarity = float(opts.get('similarity') or 0)
275 275 add, remove = [], []
276 276 mapping = {}
277 277 audit_path = util.path_auditor(repo.root)
278 278 m = match(repo, pats, opts)
279 279 for abs in repo.walk(m):
280 280 target = repo.wjoin(abs)
281 281 good = True
282 282 try:
283 283 audit_path(abs)
284 284 except:
285 285 good = False
286 286 rel = m.rel(abs)
287 287 exact = m.exact(abs)
288 288 if good and abs not in repo.dirstate:
289 289 add.append(abs)
290 290 mapping[abs] = rel, m.exact(abs)
291 291 if repo.ui.verbose or not exact:
292 292 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
293 293 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
294 294 or (os.path.isdir(target) and not os.path.islink(target))):
295 295 remove.append(abs)
296 296 mapping[abs] = rel, exact
297 297 if repo.ui.verbose or not exact:
298 298 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
299 299 if not dry_run:
300 300 repo.remove(remove)
301 301 repo.add(add)
302 302 if similarity > 0:
303 303 for old, new, score in findrenames(repo, add, remove, similarity):
304 304 oldrel, oldexact = mapping[old]
305 305 newrel, newexact = mapping[new]
306 306 if repo.ui.verbose or not oldexact or not newexact:
307 307 repo.ui.status(_('recording removal of %s as rename to %s '
308 308 '(%d%% similar)\n') %
309 309 (oldrel, newrel, score * 100))
310 310 if not dry_run:
311 311 repo.copy(old, new)
312 312
313 313 def copy(ui, repo, pats, opts, rename=False):
314 314 # called with the repo lock held
315 315 #
316 316 # hgsep => pathname that uses "/" to separate directories
317 317 # ossep => pathname that uses os.sep to separate directories
318 318 cwd = repo.getcwd()
319 319 targets = {}
320 320 after = opts.get("after")
321 321 dryrun = opts.get("dry_run")
322 322
323 323 def walkpat(pat):
324 324 srcs = []
325 325 m = match(repo, [pat], opts, globbed=True)
326 326 for abs in repo.walk(m):
327 327 state = repo.dirstate[abs]
328 328 rel = m.rel(abs)
329 329 exact = m.exact(abs)
330 330 if state in '?r':
331 331 if exact and state == '?':
332 332 ui.warn(_('%s: not copying - file is not managed\n') % rel)
333 333 if exact and state == 'r':
334 334 ui.warn(_('%s: not copying - file has been marked for'
335 335 ' remove\n') % rel)
336 336 continue
337 337 # abs: hgsep
338 338 # rel: ossep
339 339 srcs.append((abs, rel, exact))
340 340 return srcs
341 341
342 342 # abssrc: hgsep
343 343 # relsrc: ossep
344 344 # otarget: ossep
345 345 def copyfile(abssrc, relsrc, otarget, exact):
346 346 abstarget = util.canonpath(repo.root, cwd, otarget)
347 347 reltarget = repo.pathto(abstarget, cwd)
348 348 target = repo.wjoin(abstarget)
349 349 src = repo.wjoin(abssrc)
350 350 state = repo.dirstate[abstarget]
351 351
352 352 # check for collisions
353 353 prevsrc = targets.get(abstarget)
354 354 if prevsrc is not None:
355 355 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
356 356 (reltarget, repo.pathto(abssrc, cwd),
357 357 repo.pathto(prevsrc, cwd)))
358 358 return
359 359
360 360 # check for overwrites
361 361 exists = os.path.exists(target)
362 362 if (not after and exists or after and state in 'mn'):
363 363 if not opts['force']:
364 364 ui.warn(_('%s: not overwriting - file exists\n') %
365 365 reltarget)
366 366 return
367 367
368 368 if after:
369 369 if not exists:
370 370 return
371 371 elif not dryrun:
372 372 try:
373 373 if exists:
374 374 os.unlink(target)
375 375 targetdir = os.path.dirname(target) or '.'
376 376 if not os.path.isdir(targetdir):
377 377 os.makedirs(targetdir)
378 378 util.copyfile(src, target)
379 379 except IOError, inst:
380 380 if inst.errno == errno.ENOENT:
381 381 ui.warn(_('%s: deleted in working copy\n') % relsrc)
382 382 else:
383 383 ui.warn(_('%s: cannot copy - %s\n') %
384 384 (relsrc, inst.strerror))
385 385 return True # report a failure
386 386
387 387 if ui.verbose or not exact:
388 388 action = rename and "moving" or "copying"
389 389 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
390 390
391 391 targets[abstarget] = abssrc
392 392
393 393 # fix up dirstate
394 394 origsrc = repo.dirstate.copied(abssrc) or abssrc
395 395 if abstarget == origsrc: # copying back a copy?
396 396 if state not in 'mn' and not dryrun:
397 397 repo.dirstate.normallookup(abstarget)
398 398 else:
399 399 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
400 400 if not ui.quiet:
401 401 ui.warn(_("%s has not been committed yet, so no copy "
402 402 "data will be stored for %s.\n")
403 403 % (repo.pathto(origsrc, cwd), reltarget))
404 404 if repo.dirstate[abstarget] in '?r' and not dryrun:
405 405 repo.add([abstarget])
406 406 elif not dryrun:
407 407 repo.copy(origsrc, abstarget)
408 408
409 409 if rename and not dryrun:
410 410 repo.remove([abssrc], not after)
411 411
412 412 # pat: ossep
413 413 # dest ossep
414 414 # srcs: list of (hgsep, hgsep, ossep, bool)
415 415 # return: function that takes hgsep and returns ossep
416 416 def targetpathfn(pat, dest, srcs):
417 417 if os.path.isdir(pat):
418 418 abspfx = util.canonpath(repo.root, cwd, pat)
419 419 abspfx = util.localpath(abspfx)
420 420 if destdirexists:
421 421 striplen = len(os.path.split(abspfx)[0])
422 422 else:
423 423 striplen = len(abspfx)
424 424 if striplen:
425 425 striplen += len(os.sep)
426 426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 427 elif destdirexists:
428 428 res = lambda p: os.path.join(dest,
429 429 os.path.basename(util.localpath(p)))
430 430 else:
431 431 res = lambda p: dest
432 432 return res
433 433
434 434 # pat: ossep
435 435 # dest ossep
436 436 # srcs: list of (hgsep, hgsep, ossep, bool)
437 437 # return: function that takes hgsep and returns ossep
438 438 def targetpathafterfn(pat, dest, srcs):
439 439 if util.patkind(pat, None)[0]:
440 440 # a mercurial pattern
441 441 res = lambda p: os.path.join(dest,
442 442 os.path.basename(util.localpath(p)))
443 443 else:
444 444 abspfx = util.canonpath(repo.root, cwd, pat)
445 445 if len(abspfx) < len(srcs[0][0]):
446 446 # A directory. Either the target path contains the last
447 447 # component of the source path or it does not.
448 448 def evalpath(striplen):
449 449 score = 0
450 450 for s in srcs:
451 451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 452 if os.path.exists(t):
453 453 score += 1
454 454 return score
455 455
456 456 abspfx = util.localpath(abspfx)
457 457 striplen = len(abspfx)
458 458 if striplen:
459 459 striplen += len(os.sep)
460 460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 461 score = evalpath(striplen)
462 462 striplen1 = len(os.path.split(abspfx)[0])
463 463 if striplen1:
464 464 striplen1 += len(os.sep)
465 465 if evalpath(striplen1) > score:
466 466 striplen = striplen1
467 467 res = lambda p: os.path.join(dest,
468 468 util.localpath(p)[striplen:])
469 469 else:
470 470 # a file
471 471 if destdirexists:
472 472 res = lambda p: os.path.join(dest,
473 473 os.path.basename(util.localpath(p)))
474 474 else:
475 475 res = lambda p: dest
476 476 return res
477 477
478 478
479 479 pats = util.expand_glob(pats)
480 480 if not pats:
481 481 raise util.Abort(_('no source or destination specified'))
482 482 if len(pats) == 1:
483 483 raise util.Abort(_('no destination specified'))
484 484 dest = pats.pop()
485 485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 486 if not destdirexists:
487 487 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
488 488 raise util.Abort(_('with multiple sources, destination must be an '
489 489 'existing directory'))
490 490 if util.endswithsep(dest):
491 491 raise util.Abort(_('destination %s is not a directory') % dest)
492 492
493 493 tfn = targetpathfn
494 494 if after:
495 495 tfn = targetpathafterfn
496 496 copylist = []
497 497 for pat in pats:
498 498 srcs = walkpat(pat)
499 499 if not srcs:
500 500 continue
501 501 copylist.append((tfn(pat, dest, srcs), srcs))
502 502 if not copylist:
503 503 raise util.Abort(_('no files to copy'))
504 504
505 505 errors = 0
506 506 for targetpath, srcs in copylist:
507 507 for abssrc, relsrc, exact in srcs:
508 508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 509 errors += 1
510 510
511 511 if errors:
512 512 ui.warn(_('(consider using --after)\n'))
513 513
514 514 return errors
515 515
516 516 def service(opts, parentfn=None, initfn=None, runfn=None):
517 517 '''Run a command as a service.'''
518 518
519 519 if opts['daemon'] and not opts['daemon_pipefds']:
520 520 rfd, wfd = os.pipe()
521 521 args = sys.argv[:]
522 522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
523 523 # Don't pass --cwd to the child process, because we've already
524 524 # changed directory.
525 525 for i in xrange(1,len(args)):
526 526 if args[i].startswith('--cwd='):
527 527 del args[i]
528 528 break
529 529 elif args[i].startswith('--cwd'):
530 530 del args[i:i+2]
531 531 break
532 532 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
533 533 args[0], args)
534 534 os.close(wfd)
535 535 os.read(rfd, 1)
536 536 if parentfn:
537 537 return parentfn(pid)
538 538 else:
539 539 os._exit(0)
540 540
541 541 if initfn:
542 542 initfn()
543 543
544 544 if opts['pid_file']:
545 545 fp = open(opts['pid_file'], 'w')
546 546 fp.write(str(os.getpid()) + '\n')
547 547 fp.close()
548 548
549 549 if opts['daemon_pipefds']:
550 550 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
551 551 os.close(rfd)
552 552 try:
553 553 os.setsid()
554 554 except AttributeError:
555 555 pass
556 556 os.write(wfd, 'y')
557 557 os.close(wfd)
558 558 sys.stdout.flush()
559 559 sys.stderr.flush()
560 560 fd = os.open(util.nulldev, os.O_RDWR)
561 561 if fd != 0: os.dup2(fd, 0)
562 562 if fd != 1: os.dup2(fd, 1)
563 563 if fd != 2: os.dup2(fd, 2)
564 564 if fd not in (0, 1, 2): os.close(fd)
565 565
566 566 if runfn:
567 567 return runfn()
568 568
569 569 class changeset_printer(object):
570 570 '''show changeset information when templating not requested.'''
571 571
572 572 def __init__(self, ui, repo, patch, diffopts, buffered):
573 573 self.ui = ui
574 574 self.repo = repo
575 575 self.buffered = buffered
576 576 self.patch = patch
577 577 self.diffopts = diffopts
578 578 self.header = {}
579 579 self.hunk = {}
580 580 self.lastheader = None
581 581
582 582 def flush(self, rev):
583 583 if rev in self.header:
584 584 h = self.header[rev]
585 585 if h != self.lastheader:
586 586 self.lastheader = h
587 587 self.ui.write(h)
588 588 del self.header[rev]
589 589 if rev in self.hunk:
590 590 self.ui.write(self.hunk[rev])
591 591 del self.hunk[rev]
592 592 return 1
593 593 return 0
594 594
595 595 def show(self, ctx, copies=(), **props):
596 596 if self.buffered:
597 597 self.ui.pushbuffer()
598 598 self._show(ctx, copies, props)
599 599 self.hunk[ctx.rev()] = self.ui.popbuffer()
600 600 else:
601 601 self._show(ctx, copies, props)
602 602
603 603 def _show(self, ctx, copies, props):
604 604 '''show a single changeset or file revision'''
605 605 changenode = ctx.node()
606 606 rev = ctx.rev()
607 607
608 608 if self.ui.quiet:
609 609 self.ui.write("%d:%s\n" % (rev, short(changenode)))
610 610 return
611 611
612 612 log = self.repo.changelog
613 613 changes = log.read(changenode)
614 614 date = util.datestr(changes[2])
615 615 extra = changes[5]
616 616 branch = extra.get("branch")
617 617
618 618 hexfunc = self.ui.debugflag and hex or short
619 619
620 620 parents = [(p, hexfunc(log.node(p)))
621 621 for p in self._meaningful_parentrevs(log, rev)]
622 622
623 623 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
624 624
625 625 # don't show the default branch name
626 626 if branch != 'default':
627 627 branch = util.tolocal(branch)
628 628 self.ui.write(_("branch: %s\n") % branch)
629 629 for tag in self.repo.nodetags(changenode):
630 630 self.ui.write(_("tag: %s\n") % tag)
631 631 for parent in parents:
632 632 self.ui.write(_("parent: %d:%s\n") % parent)
633 633
634 634 if self.ui.debugflag:
635 635 self.ui.write(_("manifest: %d:%s\n") %
636 636 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
637 637 self.ui.write(_("user: %s\n") % changes[1])
638 638 self.ui.write(_("date: %s\n") % date)
639 639
640 640 if self.ui.debugflag:
641 641 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
642 642 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
643 643 files):
644 644 if value:
645 645 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
646 646 elif changes[3] and self.ui.verbose:
647 647 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
648 648 if copies and self.ui.verbose:
649 649 copies = ['%s (%s)' % c for c in copies]
650 650 self.ui.write(_("copies: %s\n") % ' '.join(copies))
651 651
652 652 if extra and self.ui.debugflag:
653 653 for key, value in util.sort(extra.items()):
654 654 self.ui.write(_("extra: %s=%s\n")
655 655 % (key, value.encode('string_escape')))
656 656
657 657 description = changes[4].strip()
658 658 if description:
659 659 if self.ui.verbose:
660 660 self.ui.write(_("description:\n"))
661 661 self.ui.write(description)
662 662 self.ui.write("\n\n")
663 663 else:
664 664 self.ui.write(_("summary: %s\n") %
665 665 description.splitlines()[0])
666 666 self.ui.write("\n")
667 667
668 668 self.showpatch(changenode)
669 669
670 670 def showpatch(self, node):
671 671 if self.patch:
672 672 prev = self.repo.changelog.parents(node)[0]
673 673 chunks = patch.diff(self.repo, prev, node, match=self.patch,
674 674 opts=patch.diffopts(self.ui, self.diffopts))
675 675 for chunk in chunks:
676 676 self.ui.write(chunk)
677 677 self.ui.write("\n")
678 678
679 679 def _meaningful_parentrevs(self, log, rev):
680 680 """Return list of meaningful (or all if debug) parentrevs for rev.
681 681
682 682 For merges (two non-nullrev revisions) both parents are meaningful.
683 683 Otherwise the first parent revision is considered meaningful if it
684 684 is not the preceding revision.
685 685 """
686 686 parents = log.parentrevs(rev)
687 687 if not self.ui.debugflag and parents[1] == nullrev:
688 688 if parents[0] >= rev - 1:
689 689 parents = []
690 690 else:
691 691 parents = [parents[0]]
692 692 return parents
693 693
694 694
695 695 class changeset_templater(changeset_printer):
696 696 '''format changeset information.'''
697 697
698 698 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
699 699 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
700 700 filters = templatefilters.filters.copy()
701 701 filters['formatnode'] = (ui.debugflag and (lambda x: x)
702 702 or (lambda x: x[:12]))
703 703 self.t = templater.templater(mapfile, filters,
704 704 cache={
705 705 'parent': '{rev}:{node|formatnode} ',
706 706 'manifest': '{rev}:{node|formatnode}',
707 707 'filecopy': '{name} ({source})'})
708 708
709 709 def use_template(self, t):
710 710 '''set template string to use'''
711 711 self.t.cache['changeset'] = t
712 712
713 713 def _meaningful_parentrevs(self, ctx):
714 714 """Return list of meaningful (or all if debug) parentrevs for rev.
715 715 """
716 716 parents = ctx.parents()
717 717 if len(parents) > 1:
718 718 return parents
719 719 if self.ui.debugflag:
720 720 return [parents[0], self.repo['null']]
721 721 if parents[0].rev() >= ctx.rev() - 1:
722 722 return []
723 723 return parents
724 724
725 725 def _show(self, ctx, copies, props):
726 726 '''show a single changeset or file revision'''
727 727
728 728 def showlist(name, values, plural=None, **args):
729 729 '''expand set of values.
730 730 name is name of key in template map.
731 731 values is list of strings or dicts.
732 732 plural is plural of name, if not simply name + 's'.
733 733
734 734 expansion works like this, given name 'foo'.
735 735
736 736 if values is empty, expand 'no_foos'.
737 737
738 738 if 'foo' not in template map, return values as a string,
739 739 joined by space.
740 740
741 741 expand 'start_foos'.
742 742
743 743 for each value, expand 'foo'. if 'last_foo' in template
744 744 map, expand it instead of 'foo' for last key.
745 745
746 746 expand 'end_foos'.
747 747 '''
748 748 if plural: names = plural
749 749 else: names = name + 's'
750 750 if not values:
751 751 noname = 'no_' + names
752 752 if noname in self.t:
753 753 yield self.t(noname, **args)
754 754 return
755 755 if name not in self.t:
756 756 if isinstance(values[0], str):
757 757 yield ' '.join(values)
758 758 else:
759 759 for v in values:
760 760 yield dict(v, **args)
761 761 return
762 762 startname = 'start_' + names
763 763 if startname in self.t:
764 764 yield self.t(startname, **args)
765 765 vargs = args.copy()
766 766 def one(v, tag=name):
767 767 try:
768 768 vargs.update(v)
769 769 except (AttributeError, ValueError):
770 770 try:
771 771 for a, b in v:
772 772 vargs[a] = b
773 773 except ValueError:
774 774 vargs[name] = v
775 775 return self.t(tag, **vargs)
776 776 lastname = 'last_' + name
777 777 if lastname in self.t:
778 778 last = values.pop()
779 779 else:
780 780 last = None
781 781 for v in values:
782 782 yield one(v)
783 783 if last is not None:
784 784 yield one(last, tag=lastname)
785 785 endname = 'end_' + names
786 786 if endname in self.t:
787 787 yield self.t(endname, **args)
788 788
789 789 def showbranches(**args):
790 790 branch = ctx.branch()
791 791 if branch != 'default':
792 792 branch = util.tolocal(branch)
793 793 return showlist('branch', [branch], plural='branches', **args)
794 794
795 795 def showparents(**args):
796 796 parents = [[('rev', p.rev()), ('node', p.hex())]
797 797 for p in self._meaningful_parentrevs(ctx)]
798 798 return showlist('parent', parents, **args)
799 799
800 800 def showtags(**args):
801 801 return showlist('tag', ctx.tags(), **args)
802 802
803 803 def showextras(**args):
804 804 for key, value in util.sort(ctx.extra().items()):
805 805 args = args.copy()
806 806 args.update(dict(key=key, value=value))
807 807 yield self.t('extra', **args)
808 808
809 809 def showcopies(**args):
810 810 c = [{'name': x[0], 'source': x[1]} for x in copies]
811 811 return showlist('file_copy', c, plural='file_copies', **args)
812 812
813 813 files = []
814 814 def getfiles():
815 815 if not files:
816 816 files[:] = self.repo.status(ctx.parents()[0].node(),
817 817 ctx.node())[:3]
818 818 return files
819 819 def showfiles(**args):
820 820 return showlist('file', ctx.files(), **args)
821 821 def showmods(**args):
822 822 return showlist('file_mod', getfiles()[0], **args)
823 823 def showadds(**args):
824 824 return showlist('file_add', getfiles()[1], **args)
825 825 def showdels(**args):
826 826 return showlist('file_del', getfiles()[2], **args)
827 827 def showmanifest(**args):
828 828 args = args.copy()
829 829 args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
830 830 node=hex(ctx.changeset()[0])))
831 831 return self.t('manifest', **args)
832 832
833 def showdiffstat(**args):
834 diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
835 files, adds, removes = 0, 0, 0
836 for i in patch.diffstatdata(util.iterlines(diff)):
837 files += 1
838 adds += i[1]
839 removes += i[2]
840 return '%s: +%s/-%s' % (files, adds, removes)
841
833 842 defprops = {
834 843 'author': ctx.user(),
835 844 'branches': showbranches,
836 845 'date': ctx.date(),
837 846 'desc': ctx.description().strip(),
838 847 'file_adds': showadds,
839 848 'file_dels': showdels,
840 849 'file_mods': showmods,
841 850 'files': showfiles,
842 851 'file_copies': showcopies,
843 852 'manifest': showmanifest,
844 853 'node': ctx.hex(),
845 854 'parents': showparents,
846 855 'rev': ctx.rev(),
847 856 'tags': showtags,
848 857 'extras': showextras,
858 'diffstat': showdiffstat,
849 859 }
850 860 props = props.copy()
851 861 props.update(defprops)
852 862
853 863 try:
854 864 if self.ui.debugflag and 'header_debug' in self.t:
855 865 key = 'header_debug'
856 866 elif self.ui.quiet and 'header_quiet' in self.t:
857 867 key = 'header_quiet'
858 868 elif self.ui.verbose and 'header_verbose' in self.t:
859 869 key = 'header_verbose'
860 870 elif 'header' in self.t:
861 871 key = 'header'
862 872 else:
863 873 key = ''
864 874 if key:
865 875 h = templater.stringify(self.t(key, **props))
866 876 if self.buffered:
867 877 self.header[ctx.rev()] = h
868 878 else:
869 879 self.ui.write(h)
870 880 if self.ui.debugflag and 'changeset_debug' in self.t:
871 881 key = 'changeset_debug'
872 882 elif self.ui.quiet and 'changeset_quiet' in self.t:
873 883 key = 'changeset_quiet'
874 884 elif self.ui.verbose and 'changeset_verbose' in self.t:
875 885 key = 'changeset_verbose'
876 886 else:
877 887 key = 'changeset'
878 888 self.ui.write(templater.stringify(self.t(key, **props)))
879 889 self.showpatch(ctx.node())
880 890 except KeyError, inst:
881 891 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
882 892 inst.args[0]))
883 893 except SyntaxError, inst:
884 894 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
885 895
886 896 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
887 897 """show one changeset using template or regular display.
888 898
889 899 Display format will be the first non-empty hit of:
890 900 1. option 'template'
891 901 2. option 'style'
892 902 3. [ui] setting 'logtemplate'
893 903 4. [ui] setting 'style'
894 904 If all of these values are either the unset or the empty string,
895 905 regular display via changeset_printer() is done.
896 906 """
897 907 # options
898 908 patch = False
899 909 if opts.get('patch'):
900 910 patch = matchfn or matchall(repo)
901 911
902 912 tmpl = opts.get('template')
903 913 mapfile = None
904 914 if tmpl:
905 915 tmpl = templater.parsestring(tmpl, quoted=False)
906 916 else:
907 917 mapfile = opts.get('style')
908 918 # ui settings
909 919 if not mapfile:
910 920 tmpl = ui.config('ui', 'logtemplate')
911 921 if tmpl:
912 922 tmpl = templater.parsestring(tmpl)
913 923 else:
914 924 mapfile = ui.config('ui', 'style')
915 925
916 926 if tmpl or mapfile:
917 927 if mapfile:
918 928 if not os.path.split(mapfile)[0]:
919 929 mapname = (templater.templatepath('map-cmdline.' + mapfile)
920 930 or templater.templatepath(mapfile))
921 931 if mapname: mapfile = mapname
922 932 try:
923 933 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
924 934 except SyntaxError, inst:
925 935 raise util.Abort(inst.args[0])
926 936 if tmpl: t.use_template(tmpl)
927 937 return t
928 938 return changeset_printer(ui, repo, patch, opts, buffered)
929 939
930 940 def finddate(ui, repo, date):
931 941 """Find the tipmost changeset that matches the given date spec"""
932 942 df = util.matchdate(date)
933 943 get = util.cachefunc(lambda r: repo[r].changeset())
934 944 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
935 945 results = {}
936 946 for st, rev, fns in changeiter:
937 947 if st == 'add':
938 948 d = get(rev)[2]
939 949 if df(d[0]):
940 950 results[rev] = d
941 951 elif st == 'iter':
942 952 if rev in results:
943 953 ui.status(_("Found revision %s from %s\n") %
944 954 (rev, util.datestr(results[rev])))
945 955 return str(rev)
946 956
947 957 raise util.Abort(_("revision matching date not found"))
948 958
949 959 def walkchangerevs(ui, repo, pats, change, opts):
950 960 '''Iterate over files and the revs in which they changed.
951 961
952 962 Callers most commonly need to iterate backwards over the history
953 963 in which they are interested. Doing so has awful (quadratic-looking)
954 964 performance, so we use iterators in a "windowed" way.
955 965
956 966 We walk a window of revisions in the desired order. Within the
957 967 window, we first walk forwards to gather data, then in the desired
958 968 order (usually backwards) to display it.
959 969
960 970 This function returns an (iterator, matchfn) tuple. The iterator
961 971 yields 3-tuples. They will be of one of the following forms:
962 972
963 973 "window", incrementing, lastrev: stepping through a window,
964 974 positive if walking forwards through revs, last rev in the
965 975 sequence iterated over - use to reset state for the current window
966 976
967 977 "add", rev, fns: out-of-order traversal of the given file names
968 978 fns, which changed during revision rev - use to gather data for
969 979 possible display
970 980
971 981 "iter", rev, None: in-order traversal of the revs earlier iterated
972 982 over with "add" - use to display data'''
973 983
974 984 def increasing_windows(start, end, windowsize=8, sizelimit=512):
975 985 if start < end:
976 986 while start < end:
977 987 yield start, min(windowsize, end-start)
978 988 start += windowsize
979 989 if windowsize < sizelimit:
980 990 windowsize *= 2
981 991 else:
982 992 while start > end:
983 993 yield start, min(windowsize, start-end-1)
984 994 start -= windowsize
985 995 if windowsize < sizelimit:
986 996 windowsize *= 2
987 997
988 998 m = match(repo, pats, opts)
989 999 follow = opts.get('follow') or opts.get('follow_first')
990 1000
991 1001 if not len(repo):
992 1002 return [], m
993 1003
994 1004 if follow:
995 1005 defrange = '%s:0' % repo['.'].rev()
996 1006 else:
997 1007 defrange = '-1:0'
998 1008 revs = revrange(repo, opts['rev'] or [defrange])
999 1009 wanted = {}
1000 1010 slowpath = m.anypats() or (m.files() and opts.get('removed'))
1001 1011 fncache = {}
1002 1012
1003 1013 if not slowpath and not m.files():
1004 1014 # No files, no patterns. Display all revs.
1005 1015 wanted = dict.fromkeys(revs)
1006 1016 copies = []
1007 1017 if not slowpath:
1008 1018 # Only files, no patterns. Check the history of each file.
1009 1019 def filerevgen(filelog, node):
1010 1020 cl_count = len(repo)
1011 1021 if node is None:
1012 1022 last = len(filelog) - 1
1013 1023 else:
1014 1024 last = filelog.rev(node)
1015 1025 for i, window in increasing_windows(last, nullrev):
1016 1026 revs = []
1017 1027 for j in xrange(i - window, i + 1):
1018 1028 n = filelog.node(j)
1019 1029 revs.append((filelog.linkrev(j),
1020 1030 follow and filelog.renamed(n)))
1021 1031 revs.reverse()
1022 1032 for rev in revs:
1023 1033 # only yield rev for which we have the changelog, it can
1024 1034 # happen while doing "hg log" during a pull or commit
1025 1035 if rev[0] < cl_count:
1026 1036 yield rev
1027 1037 def iterfiles():
1028 1038 for filename in m.files():
1029 1039 yield filename, None
1030 1040 for filename_node in copies:
1031 1041 yield filename_node
1032 1042 minrev, maxrev = min(revs), max(revs)
1033 1043 for file_, node in iterfiles():
1034 1044 filelog = repo.file(file_)
1035 1045 if not len(filelog):
1036 1046 if node is None:
1037 1047 # A zero count may be a directory or deleted file, so
1038 1048 # try to find matching entries on the slow path.
1039 1049 if follow:
1040 1050 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1041 1051 slowpath = True
1042 1052 break
1043 1053 else:
1044 1054 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1045 1055 % (file_, short(node)))
1046 1056 continue
1047 1057 for rev, copied in filerevgen(filelog, node):
1048 1058 if rev <= maxrev:
1049 1059 if rev < minrev:
1050 1060 break
1051 1061 fncache.setdefault(rev, [])
1052 1062 fncache[rev].append(file_)
1053 1063 wanted[rev] = 1
1054 1064 if follow and copied:
1055 1065 copies.append(copied)
1056 1066 if slowpath:
1057 1067 if follow:
1058 1068 raise util.Abort(_('can only follow copies/renames for explicit '
1059 1069 'file names'))
1060 1070
1061 1071 # The slow path checks files modified in every changeset.
1062 1072 def changerevgen():
1063 1073 for i, window in increasing_windows(len(repo) - 1, nullrev):
1064 1074 for j in xrange(i - window, i + 1):
1065 1075 yield j, change(j)[3]
1066 1076
1067 1077 for rev, changefiles in changerevgen():
1068 1078 matches = filter(m, changefiles)
1069 1079 if matches:
1070 1080 fncache[rev] = matches
1071 1081 wanted[rev] = 1
1072 1082
1073 1083 class followfilter:
1074 1084 def __init__(self, onlyfirst=False):
1075 1085 self.startrev = nullrev
1076 1086 self.roots = []
1077 1087 self.onlyfirst = onlyfirst
1078 1088
1079 1089 def match(self, rev):
1080 1090 def realparents(rev):
1081 1091 if self.onlyfirst:
1082 1092 return repo.changelog.parentrevs(rev)[0:1]
1083 1093 else:
1084 1094 return filter(lambda x: x != nullrev,
1085 1095 repo.changelog.parentrevs(rev))
1086 1096
1087 1097 if self.startrev == nullrev:
1088 1098 self.startrev = rev
1089 1099 return True
1090 1100
1091 1101 if rev > self.startrev:
1092 1102 # forward: all descendants
1093 1103 if not self.roots:
1094 1104 self.roots.append(self.startrev)
1095 1105 for parent in realparents(rev):
1096 1106 if parent in self.roots:
1097 1107 self.roots.append(rev)
1098 1108 return True
1099 1109 else:
1100 1110 # backwards: all parents
1101 1111 if not self.roots:
1102 1112 self.roots.extend(realparents(self.startrev))
1103 1113 if rev in self.roots:
1104 1114 self.roots.remove(rev)
1105 1115 self.roots.extend(realparents(rev))
1106 1116 return True
1107 1117
1108 1118 return False
1109 1119
1110 1120 # it might be worthwhile to do this in the iterator if the rev range
1111 1121 # is descending and the prune args are all within that range
1112 1122 for rev in opts.get('prune', ()):
1113 1123 rev = repo.changelog.rev(repo.lookup(rev))
1114 1124 ff = followfilter()
1115 1125 stop = min(revs[0], revs[-1])
1116 1126 for x in xrange(rev, stop-1, -1):
1117 1127 if ff.match(x) and x in wanted:
1118 1128 del wanted[x]
1119 1129
1120 1130 def iterate():
1121 1131 if follow and not m.files():
1122 1132 ff = followfilter(onlyfirst=opts.get('follow_first'))
1123 1133 def want(rev):
1124 1134 if ff.match(rev) and rev in wanted:
1125 1135 return True
1126 1136 return False
1127 1137 else:
1128 1138 def want(rev):
1129 1139 return rev in wanted
1130 1140
1131 1141 for i, window in increasing_windows(0, len(revs)):
1132 1142 yield 'window', revs[0] < revs[-1], revs[-1]
1133 1143 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1134 1144 for rev in util.sort(list(nrevs)):
1135 1145 fns = fncache.get(rev)
1136 1146 if not fns:
1137 1147 def fns_generator():
1138 1148 for f in change(rev)[3]:
1139 1149 if m(f):
1140 1150 yield f
1141 1151 fns = fns_generator()
1142 1152 yield 'add', rev, fns
1143 1153 for rev in nrevs:
1144 1154 yield 'iter', rev, None
1145 1155 return iterate(), m
1146 1156
1147 1157 def commit(ui, repo, commitfunc, pats, opts):
1148 1158 '''commit the specified files or all outstanding changes'''
1149 1159 date = opts.get('date')
1150 1160 if date:
1151 1161 opts['date'] = util.parsedate(date)
1152 1162 message = logmessage(opts)
1153 1163
1154 1164 # extract addremove carefully -- this function can be called from a command
1155 1165 # that doesn't support addremove
1156 1166 if opts.get('addremove'):
1157 1167 addremove(repo, pats, opts)
1158 1168
1159 1169 m = match(repo, pats, opts)
1160 1170 if pats:
1161 1171 modified, added, removed = repo.status(match=m)[:3]
1162 1172 files = util.sort(modified + added + removed)
1163 1173
1164 1174 def is_dir(f):
1165 1175 name = f + '/'
1166 1176 i = bisect.bisect(files, name)
1167 1177 return i < len(files) and files[i].startswith(name)
1168 1178
1169 1179 for f in m.files():
1170 1180 if f == '.':
1171 1181 continue
1172 1182 if f not in files:
1173 1183 rf = repo.wjoin(f)
1174 1184 rel = repo.pathto(f)
1175 1185 try:
1176 1186 mode = os.lstat(rf)[stat.ST_MODE]
1177 1187 except OSError:
1178 1188 if is_dir(f): # deleted directory ?
1179 1189 continue
1180 1190 raise util.Abort(_("file %s not found!") % rel)
1181 1191 if stat.S_ISDIR(mode):
1182 1192 if not is_dir(f):
1183 1193 raise util.Abort(_("no match under directory %s!")
1184 1194 % rel)
1185 1195 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1186 1196 raise util.Abort(_("can't commit %s: "
1187 1197 "unsupported file type!") % rel)
1188 1198 elif f not in repo.dirstate:
1189 1199 raise util.Abort(_("file %s not tracked!") % rel)
1190 1200 m = matchfiles(repo, files)
1191 1201 try:
1192 1202 return commitfunc(ui, repo, message, m, opts)
1193 1203 except ValueError, inst:
1194 1204 raise util.Abort(str(inst))
@@ -1,408 +1,410
1 1 # help.py - help data for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9
10 10 helptable = (
11 11 (["dates"], _("Date Formats"),
12 12 _(r'''
13 13 Some commands allow the user to specify a date, e.g.:
14 14 * backout, commit, import, tag: Specify the commit date.
15 15 * log, revert, update: Select revision(s) by date.
16 16
17 17 Many date formats are valid. Here are some examples:
18 18
19 19 "Wed Dec 6 13:18:29 2006" (local timezone assumed)
20 20 "Dec 6 13:18 -0600" (year assumed, time offset provided)
21 21 "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
22 22 "Dec 6" (midnight)
23 23 "13:18" (today assumed)
24 24 "3:39" (3:39AM assumed)
25 25 "3:39pm" (15:39)
26 26 "2006-12-06 13:18:29" (ISO 8601 format)
27 27 "2006-12-6 13:18"
28 28 "2006-12-6"
29 29 "12-6"
30 30 "12/6"
31 31 "12/6/6" (Dec 6 2006)
32 32
33 33 Lastly, there is Mercurial's internal format:
34 34
35 35 "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
36 36
37 37 This is the internal representation format for dates. unixtime is
38 38 the number of seconds since the epoch (1970-01-01 00:00 UTC). offset
39 39 is the offset of the local timezone, in seconds west of UTC (negative
40 40 if the timezone is east of UTC).
41 41
42 42 The log command also accepts date ranges:
43 43
44 44 "<{datetime}" - at or before a given date/time
45 45 ">{datetime}" - on or after a given date/time
46 46 "{datetime} to {datetime}" - a date range, inclusive
47 47 "-{days}" - within a given number of days of today
48 48 ''')),
49 49
50 50 (["patterns"], _("File Name Patterns"),
51 51 _(r'''
52 52 Mercurial accepts several notations for identifying one or more
53 53 files at a time.
54 54
55 55 By default, Mercurial treats filenames as shell-style extended
56 56 glob patterns.
57 57
58 58 Alternate pattern notations must be specified explicitly.
59 59
60 60 To use a plain path name without any pattern matching, start it
61 61 with "path:". These path names must completely match starting at
62 62 the current repository root.
63 63
64 64 To use an extended glob, start a name with "glob:". Globs are
65 65 rooted at the current directory; a glob such as "*.c" will only
66 66 match files in the current directory ending with ".c".
67 67
68 68 The supported glob syntax extensions are "**" to match any string
69 69 across path separators and "{a,b}" to mean "a or b".
70 70
71 71 To use a Perl/Python regular expression, start a name with "re:".
72 72 Regexp pattern matching is anchored at the root of the repository.
73 73
74 74 Plain examples:
75 75
76 76 path:foo/bar a name bar in a directory named foo in the root of
77 77 the repository
78 78 path:path:name a file or directory named "path:name"
79 79
80 80 Glob examples:
81 81
82 82 glob:*.c any name ending in ".c" in the current directory
83 83 *.c any name ending in ".c" in the current directory
84 84 **.c any name ending in ".c" in any subdirectory of the
85 85 current directory including itself.
86 86 foo/*.c any name ending in ".c" in the directory foo
87 87 foo/**.c any name ending in ".c" in any subdirectory of foo
88 88 including itself.
89 89
90 90 Regexp examples:
91 91
92 92 re:.*\.c$ any name ending in ".c", anywhere in the repository
93 93
94 94 ''')),
95 95
96 96 (['environment', 'env'], _('Environment Variables'),
97 97 _(r'''
98 98 HG::
99 99 Path to the 'hg' executable, automatically passed when running hooks,
100 100 extensions or external tools. If unset or empty, this is the hg
101 101 executable's name if it's frozen, or an executable named 'hg'
102 102 (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
103 103 Windows) is searched.
104 104
105 105 HGEDITOR::
106 106 This is the name of the editor to run when committing. See EDITOR.
107 107
108 108 (deprecated, use .hgrc)
109 109
110 110 HGENCODING::
111 111 This overrides the default locale setting detected by Mercurial.
112 112 This setting is used to convert data including usernames,
113 113 changeset descriptions, tag names, and branches. This setting can
114 114 be overridden with the --encoding command-line option.
115 115
116 116 HGENCODINGMODE::
117 117 This sets Mercurial's behavior for handling unknown characters
118 118 while transcoding user input. The default is "strict", which
119 119 causes Mercurial to abort if it can't map a character. Other
120 120 settings include "replace", which replaces unknown characters, and
121 121 "ignore", which drops them. This setting can be overridden with
122 122 the --encodingmode command-line option.
123 123
124 124 HGMERGE::
125 125 An executable to use for resolving merge conflicts. The program
126 126 will be executed with three arguments: local file, remote file,
127 127 ancestor file.
128 128
129 129 (deprecated, use .hgrc)
130 130
131 131 HGRCPATH::
132 132 A list of files or directories to search for hgrc files. Item
133 133 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
134 134 platform default search path is used. If empty, only the .hg/hgrc
135 135 from the current repository is read.
136 136
137 137 For each element in HGRCPATH:
138 138 * if it's a directory, all directories ending with .rc are added
139 139 * otherwise, the directory itself will be added
140 140
141 141 HGUSER::
142 142 This is the string used as the author of a commit. If not set,
143 143 available values will be considered in this order:
144 144
145 145 * HGUSER (deprecated)
146 146 * hgrc files from the HGRCPATH
147 147 * EMAIL
148 148 * interactive prompt
149 149 * LOGNAME (with '@hostname' appended)
150 150
151 151 (deprecated, use .hgrc)
152 152
153 153 EMAIL::
154 154 May be used as the author of a commit; see HGUSER.
155 155
156 156 LOGNAME::
157 157 May be used as the author of a commit; see HGUSER.
158 158
159 159 VISUAL::
160 160 This is the name of the editor to use when committing. See EDITOR.
161 161
162 162 EDITOR::
163 163 Sometimes Mercurial needs to open a text file in an editor
164 164 for a user to modify, for example when writing commit messages.
165 165 The editor it uses is determined by looking at the environment
166 166 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
167 167 non-empty one is chosen. If all of them are empty, the editor
168 168 defaults to 'vi'.
169 169
170 170 PYTHONPATH::
171 171 This is used by Python to find imported modules and may need to be set
172 172 appropriately if this Mercurial is not installed system-wide.
173 173 ''')),
174 174
175 175 (['revs', 'revisions'], _('Specifying Single Revisions'),
176 176 _(r'''
177 177 Mercurial supports several ways to specify individual
178 178 revisions.
179 179
180 180 A plain integer is treated as a revision number. Negative
181 181 integers are treated as toplogical offsets from the tip, with
182 182 -1 denoting the tip. As such, negative numbers are only useful
183 183 if you've memorized your local tree numbers and want to save
184 184 typing a single digit. This editor suggests copy and paste.
185 185
186 186 A 40-digit hexadecimal string is treated as a unique revision
187 187 identifier.
188 188
189 189 A hexadecimal string less than 40 characters long is treated as a
190 190 unique revision identifier, and referred to as a short-form
191 191 identifier. A short-form identifier is only valid if it is the
192 192 prefix of exactly one full-length identifier.
193 193
194 194 Any other string is treated as a tag name, which is a symbolic
195 195 name associated with a revision identifier. Tag names may not
196 196 contain the ":" character.
197 197
198 198 The reserved name "tip" is a special tag that always identifies
199 199 the most recent revision.
200 200
201 201 The reserved name "null" indicates the null revision. This is the
202 202 revision of an empty repository, and the parent of revision 0.
203 203
204 204 The reserved name "." indicates the working directory parent. If
205 205 no working directory is checked out, it is equivalent to null.
206 206 If an uncommitted merge is in progress, "." is the revision of
207 207 the first parent.
208 208 ''')),
209 209
210 210 (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
211 211 _(r'''
212 212 When Mercurial accepts more than one revision, they may be
213 213 specified individually, or provided as a topologically continuous
214 214 range, separated by the ":" character.
215 215
216 216 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
217 217 are revision identifiers. Both BEGIN and END are optional. If
218 218 BEGIN is not specified, it defaults to revision number 0. If END
219 219 is not specified, it defaults to the tip. The range ":" thus
220 220 means "all revisions".
221 221
222 222 If BEGIN is greater than END, revisions are treated in reverse
223 223 order.
224 224
225 225 A range acts as a closed interval. This means that a range of 3:5
226 226 gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
227 227 ''')),
228 228
229 229 (['diffs'], _('Diff Formats'),
230 230 _(r'''
231 231 Mercurial's default format for showing changes between two versions
232 232 of a file is compatible with the unified format of GNU diff, which
233 233 can be used by GNU patch and many other standard tools.
234 234
235 235 While this standard format is often enough, it does not encode the
236 236 following information:
237 237
238 238 - executable status and other permission bits
239 239 - copy or rename information
240 240 - changes in binary files
241 241 - creation or deletion of empty files
242 242
243 243 Mercurial also supports the extended diff format from the git VCS
244 244 which addresses these limitations. The git diff format is not
245 245 produced by default because a few widespread tools still do not
246 246 understand this format.
247 247
248 248 This means that when generating diffs from a Mercurial repository
249 249 (e.g. with "hg export"), you should be careful about things like
250 250 file copies and renames or other things mentioned above, because
251 251 when applying a standard diff to a different repository, this extra
252 252 information is lost. Mercurial's internal operations (like push and
253 253 pull) are not affected by this, because they use an internal binary
254 254 format for communicating changes.
255 255
256 256 To make Mercurial produce the git extended diff format, use the
257 257 --git option available for many commands, or set 'git = True' in the
258 258 [diff] section of your hgrc. You do not need to set this option when
259 259 importing diffs in this format or using them in the mq extension.
260 260 ''')),
261 261 (['templating'], _('Template Usage'),
262 262 _(r'''
263 263 Mercurial allows you to customize output of commands through
264 264 templates. You can either pass in a template from the command line,
265 265 via the --template option, or select an existing template-style (--style).
266 266
267 267 You can customize output for any "log-like" command: log, outgoing,
268 268 incoming, tip, parents, heads and glog.
269 269
270 270 Three styles are packaged with Mercurial: default (the style used
271 271 when no explicit preference is passed), compact and changelog. Usage:
272 272
273 273 $ hg log -r1 --style changelog
274 274
275 275 A template is a piece of text, with markup to invoke variable expansion:
276 276
277 277 $ hg log -r1 --template "{node}\n"
278 278 b56ce7b07c52de7d5fd79fb89701ea538af65746
279 279
280 280 Strings in curly braces are called keywords. The availability of
281 281 keywords depends on the exact context of the templater. These keywords
282 282 are usually available for templating a log-like command:
283 283
284 284 - author: String. The unmodified author of the changeset.
285 285 - branches: String. The name of the branch on which the changeset
286 286 was committed. Will be empty if the branch name was default.
287 287 - date: Date information. The date when the changeset was committed.
288 288 - desc: String. The text of the changeset description.
289 - diffstat: String. Statistics of changes with the following format:
290 "modified files: +added/-removed lines"
289 291 - files: List of strings. All files modified, added, or removed by
290 292 this changeset.
291 293 - file_adds: List of strings. Files added by this changeset.
292 294 - file_mods: List of strings. Files modified by this changeset.
293 295 - file_dels: List of strings. Files removed by this changeset.
294 296 - node: String. The changeset identification hash, as a 40-character
295 297 hexadecimal string.
296 298 - parents: List of strings. The parents of the changeset.
297 299 - rev: Integer. The repository-local changeset revision number.
298 300 - tags: List of strings. Any tags associated with the changeset.
299 301
300 302 The "date" keyword does not produce human-readable output. If you
301 303 want to use a date in your output, you can use a filter to process it.
302 304 Filters are functions which return a string based on the input variable.
303 305 You can also use a chain of filters to get the desired output:
304 306
305 307 $ hg tip --template "{date|isodate}\n"
306 308 2008-08-21 18:22 +0000
307 309
308 310 List of filters:
309 311
310 312 - addbreaks: Any text. Add an XHTML "<br />" tag before the end of
311 313 every line except the last.
312 314 - age: Date. Returns a human-readable date/time difference between
313 315 the given date/time and the current date/time.
314 316 - basename: Any text. Treats the text as a path, and returns the
315 317 last component of the path after splitting by the path
316 318 separator (ignoring trailing seprators). For example,
317 319 "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "bar".
318 320 - date: Date. Returns a date in a Unix date format, including
319 321 the timezone: "Mon Sep 04 15:13:13 2006 0700".
320 322 - domain: Any text. Finds the first string that looks like an email
321 323 address, and extracts just the domain component.
322 324 Example: 'User <user@example.com>' becomes 'example.com'.
323 325 - email: Any text. Extracts the first string that looks like an email
324 326 address. Example: 'User <user@example.com>' becomes
325 327 'user@example.com'.
326 328 - escape: Any text. Replaces the special XML/XHTML characters "&",
327 329 "<" and ">" with XML entities.
328 330 - fill68: Any text. Wraps the text to fit in 68 columns.
329 331 - fill76: Any text. Wraps the text to fit in 76 columns.
330 332 - firstline: Any text. Returns the first line of text.
331 333 - hgdate: Date. Returns the date as a pair of numbers:
332 334 "1157407993 25200" (Unix timestamp, timezone offset).
333 335 - isodate: Date. Returns the date in ISO 8601 format.
334 336 - obfuscate: Any text. Returns the input text rendered as a sequence
335 337 of XML entities.
336 338 - person: Any text. Returns the text before an email address.
337 339 - rfc822date: Date. Returns a date using the same format used
338 340 in email headers.
339 341 - short: Changeset hash. Returns the short form of a changeset hash,
340 342 i.e. a 12-byte hexadecimal string.
341 343 - shortdate: Date. Returns a date like "2006-09-18".
342 344 - strip: Any text. Strips all leading and trailing whitespace.
343 345 - tabindent: Any text. Returns the text, with every line except the
344 346 first starting with a tab character.
345 347 - urlescape: Any text. Escapes all "special" characters. For example,
346 348 "foo bar" becomes "foo%20bar".
347 349 - user: Any text. Returns the user portion of an email address.
348 350 ''')),
349 351
350 352 (['urls'], _('Url Paths'),
351 353 _(r'''
352 354 Valid URLs are of the form:
353 355
354 356 local/filesystem/path (or file://local/filesystem/path)
355 357 http://[user[:pass]@]host[:port]/[path]
356 358 https://[user[:pass]@]host[:port]/[path]
357 359 ssh://[user[:pass]@]host[:port]/[path]
358 360
359 361 Paths in the local filesystem can either point to Mercurial
360 362 repositories or to bundle files (as created by 'hg bundle' or
361 363 'hg incoming --bundle').
362 364
363 365 An optional identifier after # indicates a particular branch, tag,
364 366 or changeset to use from the remote repository.
365 367
366 368 Some features, such as pushing to http:// and https:// URLs are
367 369 only possible if the feature is explicitly enabled on the
368 370 remote Mercurial server.
369 371
370 372 Some notes about using SSH with Mercurial:
371 373 - SSH requires an accessible shell account on the destination machine
372 374 and a copy of hg in the remote path or specified with as remotecmd.
373 375 - path is relative to the remote user's home directory by default.
374 376 Use an extra slash at the start of a path to specify an absolute path:
375 377 ssh://example.com//tmp/repository
376 378 - Mercurial doesn't use its own compression via SSH; the right thing
377 379 to do is to configure it in your ~/.ssh/config, e.g.:
378 380 Host *.mylocalnetwork.example.com
379 381 Compression no
380 382 Host *
381 383 Compression yes
382 384 Alternatively specify "ssh -C" as your ssh command in your hgrc or
383 385 with the --ssh command line option.
384 386
385 387 These urls can all be stored in your hgrc with path aliases under the
386 388 [paths] section like so:
387 389 [paths]
388 390 alias1 = URL1
389 391 alias2 = URL2
390 392 ...
391 393
392 394 You can then use the alias for any command that uses a url (for example
393 395 'hg pull alias1' would pull from the 'alias1' path).
394 396
395 397 Two path aliases are special because they are used as defaults
396 398 when you do not provide the url to a command:
397 399
398 400 default:
399 401 When you create a repository with hg clone, the clone command saves
400 402 the location of the source repository as the new repository's
401 403 'default' path. This is then used when you omit path from push-
402 404 and pull-like commands (including in and out).
403 405
404 406 default-push:
405 407 The push command will look for a path named 'default-push', and
406 408 prefer it over 'default' if both are defined.
407 409 ''')),
408 410 )
@@ -1,2019 +1,2024
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback, error
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import imp, unicodedata
19 19
20 20 # Python compatibility
21 21
22 22 try:
23 23 set = set
24 24 frozenset = frozenset
25 25 except NameError:
26 26 from sets import Set as set, ImmutableSet as frozenset
27 27
28 28 _md5 = None
29 29 def md5(s):
30 30 global _md5
31 31 if _md5 is None:
32 32 try:
33 33 import hashlib
34 34 _md5 = hashlib.md5
35 35 except ImportError:
36 36 import md5
37 37 _md5 = md5.md5
38 38 return _md5(s)
39 39
40 40 _sha1 = None
41 41 def sha1(s):
42 42 global _sha1
43 43 if _sha1 is None:
44 44 try:
45 45 import hashlib
46 46 _sha1 = hashlib.sha1
47 47 except ImportError:
48 48 import sha
49 49 _sha1 = sha.sha
50 50 return _sha1(s)
51 51
52 52 try:
53 53 import subprocess
54 54 subprocess.Popen # trigger ImportError early
55 55 closefds = os.name == 'posix'
56 56 def popen2(cmd, mode='t', bufsize=-1):
57 57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 58 close_fds=closefds,
59 59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
60 60 return p.stdin, p.stdout
61 61 def popen3(cmd, mode='t', bufsize=-1):
62 62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 63 close_fds=closefds,
64 64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 65 stderr=subprocess.PIPE)
66 66 return p.stdin, p.stdout, p.stderr
67 67 def Popen3(cmd, capturestderr=False, bufsize=-1):
68 68 stderr = capturestderr and subprocess.PIPE or None
69 69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
70 70 close_fds=closefds,
71 71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
72 72 stderr=stderr)
73 73 p.fromchild = p.stdout
74 74 p.tochild = p.stdin
75 75 p.childerr = p.stderr
76 76 return p
77 77 except ImportError:
78 78 subprocess = None
79 79 from popen2 import Popen3
80 80 popen2 = os.popen2
81 81 popen3 = os.popen3
82 82
83 83
84 84 _encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
85 85
86 86 try:
87 87 _encoding = os.environ.get("HGENCODING")
88 88 if sys.platform == 'darwin' and not _encoding:
89 89 # On darwin, getpreferredencoding ignores the locale environment and
90 90 # always returns mac-roman. We override this if the environment is
91 91 # not C (has been customized by the user).
92 92 locale.setlocale(locale.LC_CTYPE, '')
93 93 _encoding = locale.getlocale()[1]
94 94 if not _encoding:
95 95 _encoding = locale.getpreferredencoding() or 'ascii'
96 96 _encoding = _encodingfixup.get(_encoding, _encoding)
97 97 except locale.Error:
98 98 _encoding = 'ascii'
99 99 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
100 100 _fallbackencoding = 'ISO-8859-1'
101 101
102 102 def tolocal(s):
103 103 """
104 104 Convert a string from internal UTF-8 to local encoding
105 105
106 106 All internal strings should be UTF-8 but some repos before the
107 107 implementation of locale support may contain latin1 or possibly
108 108 other character sets. We attempt to decode everything strictly
109 109 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
110 110 replace unknown characters.
111 111 """
112 112 for e in ('UTF-8', _fallbackencoding):
113 113 try:
114 114 u = s.decode(e) # attempt strict decoding
115 115 return u.encode(_encoding, "replace")
116 116 except LookupError, k:
117 117 raise Abort(_("%s, please check your locale settings") % k)
118 118 except UnicodeDecodeError:
119 119 pass
120 120 u = s.decode("utf-8", "replace") # last ditch
121 121 return u.encode(_encoding, "replace")
122 122
123 123 def fromlocal(s):
124 124 """
125 125 Convert a string from the local character encoding to UTF-8
126 126
127 127 We attempt to decode strings using the encoding mode set by
128 128 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
129 129 characters will cause an error message. Other modes include
130 130 'replace', which replaces unknown characters with a special
131 131 Unicode character, and 'ignore', which drops the character.
132 132 """
133 133 try:
134 134 return s.decode(_encoding, _encodingmode).encode("utf-8")
135 135 except UnicodeDecodeError, inst:
136 136 sub = s[max(0, inst.start-10):inst.start+10]
137 137 raise Abort("decoding near '%s': %s!" % (sub, inst))
138 138 except LookupError, k:
139 139 raise Abort(_("%s, please check your locale settings") % k)
140 140
141 141 def colwidth(s):
142 142 "Find the column width of a UTF-8 string for display"
143 143 d = s.decode(_encoding, 'replace')
144 144 if hasattr(unicodedata, 'east_asian_width'):
145 145 w = unicodedata.east_asian_width
146 146 return sum([w(c) in 'WF' and 2 or 1 for c in d])
147 147 return len(d)
148 148
149 149 def version():
150 150 """Return version information if available."""
151 151 try:
152 152 import __version__
153 153 return __version__.version
154 154 except ImportError:
155 155 return 'unknown'
156 156
157 157 # used by parsedate
158 158 defaultdateformats = (
159 159 '%Y-%m-%d %H:%M:%S',
160 160 '%Y-%m-%d %I:%M:%S%p',
161 161 '%Y-%m-%d %H:%M',
162 162 '%Y-%m-%d %I:%M%p',
163 163 '%Y-%m-%d',
164 164 '%m-%d',
165 165 '%m/%d',
166 166 '%m/%d/%y',
167 167 '%m/%d/%Y',
168 168 '%a %b %d %H:%M:%S %Y',
169 169 '%a %b %d %I:%M:%S%p %Y',
170 170 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
171 171 '%b %d %H:%M:%S %Y',
172 172 '%b %d %I:%M:%S%p %Y',
173 173 '%b %d %H:%M:%S',
174 174 '%b %d %I:%M:%S%p',
175 175 '%b %d %H:%M',
176 176 '%b %d %I:%M%p',
177 177 '%b %d %Y',
178 178 '%b %d',
179 179 '%H:%M:%S',
180 180 '%I:%M:%SP',
181 181 '%H:%M',
182 182 '%I:%M%p',
183 183 )
184 184
185 185 extendeddateformats = defaultdateformats + (
186 186 "%Y",
187 187 "%Y-%m",
188 188 "%b",
189 189 "%b %Y",
190 190 )
191 191
192 192 # differences from SafeConfigParser:
193 193 # - case-sensitive keys
194 194 # - allows values that are not strings (this means that you may not
195 195 # be able to save the configuration to a file)
196 196 class configparser(ConfigParser.SafeConfigParser):
197 197 def optionxform(self, optionstr):
198 198 return optionstr
199 199
200 200 def set(self, section, option, value):
201 201 return ConfigParser.ConfigParser.set(self, section, option, value)
202 202
203 203 def _interpolate(self, section, option, rawval, vars):
204 204 if not isinstance(rawval, basestring):
205 205 return rawval
206 206 return ConfigParser.SafeConfigParser._interpolate(self, section,
207 207 option, rawval, vars)
208 208
209 209 def cachefunc(func):
210 210 '''cache the result of function calls'''
211 211 # XXX doesn't handle keywords args
212 212 cache = {}
213 213 if func.func_code.co_argcount == 1:
214 214 # we gain a small amount of time because
215 215 # we don't need to pack/unpack the list
216 216 def f(arg):
217 217 if arg not in cache:
218 218 cache[arg] = func(arg)
219 219 return cache[arg]
220 220 else:
221 221 def f(*args):
222 222 if args not in cache:
223 223 cache[args] = func(*args)
224 224 return cache[args]
225 225
226 226 return f
227 227
228 228 def pipefilter(s, cmd):
229 229 '''filter string S through command CMD, returning its output'''
230 230 (pin, pout) = popen2(cmd, 'b')
231 231 def writer():
232 232 try:
233 233 pin.write(s)
234 234 pin.close()
235 235 except IOError, inst:
236 236 if inst.errno != errno.EPIPE:
237 237 raise
238 238
239 239 # we should use select instead on UNIX, but this will work on most
240 240 # systems, including Windows
241 241 w = threading.Thread(target=writer)
242 242 w.start()
243 243 f = pout.read()
244 244 pout.close()
245 245 w.join()
246 246 return f
247 247
248 248 def tempfilter(s, cmd):
249 249 '''filter string S through a pair of temporary files with CMD.
250 250 CMD is used as a template to create the real command to be run,
251 251 with the strings INFILE and OUTFILE replaced by the real names of
252 252 the temporary files generated.'''
253 253 inname, outname = None, None
254 254 try:
255 255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
256 256 fp = os.fdopen(infd, 'wb')
257 257 fp.write(s)
258 258 fp.close()
259 259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
260 260 os.close(outfd)
261 261 cmd = cmd.replace('INFILE', inname)
262 262 cmd = cmd.replace('OUTFILE', outname)
263 263 code = os.system(cmd)
264 264 if sys.platform == 'OpenVMS' and code & 1:
265 265 code = 0
266 266 if code: raise Abort(_("command '%s' failed: %s") %
267 267 (cmd, explain_exit(code)))
268 268 return open(outname, 'rb').read()
269 269 finally:
270 270 try:
271 271 if inname: os.unlink(inname)
272 272 except: pass
273 273 try:
274 274 if outname: os.unlink(outname)
275 275 except: pass
276 276
277 277 filtertable = {
278 278 'tempfile:': tempfilter,
279 279 'pipe:': pipefilter,
280 280 }
281 281
282 282 def filter(s, cmd):
283 283 "filter a string through a command that transforms its input to its output"
284 284 for name, fn in filtertable.iteritems():
285 285 if cmd.startswith(name):
286 286 return fn(s, cmd[len(name):].lstrip())
287 287 return pipefilter(s, cmd)
288 288
289 289 def binary(s):
290 290 """return true if a string is binary data"""
291 291 if s and '\0' in s:
292 292 return True
293 293 return False
294 294
295 295 def unique(g):
296 296 """return the uniq elements of iterable g"""
297 297 return dict.fromkeys(g).keys()
298 298
299 299 def sort(l):
300 300 if not isinstance(l, list):
301 301 l = list(l)
302 302 l.sort()
303 303 return l
304 304
305 305 def increasingchunks(source, min=1024, max=65536):
306 306 '''return no less than min bytes per chunk while data remains,
307 307 doubling min after each chunk until it reaches max'''
308 308 def log2(x):
309 309 if not x:
310 310 return 0
311 311 i = 0
312 312 while x:
313 313 x >>= 1
314 314 i += 1
315 315 return i - 1
316 316
317 317 buf = []
318 318 blen = 0
319 319 for chunk in source:
320 320 buf.append(chunk)
321 321 blen += len(chunk)
322 322 if blen >= min:
323 323 if min < max:
324 324 min = min << 1
325 325 nmin = 1 << log2(blen)
326 326 if nmin > min:
327 327 min = nmin
328 328 if min > max:
329 329 min = max
330 330 yield ''.join(buf)
331 331 blen = 0
332 332 buf = []
333 333 if buf:
334 334 yield ''.join(buf)
335 335
336 336 class Abort(Exception):
337 337 """Raised if a command needs to print an error and exit."""
338 338
339 339 def always(fn): return True
340 340 def never(fn): return False
341 341
342 342 def expand_glob(pats):
343 343 '''On Windows, expand the implicit globs in a list of patterns'''
344 344 if os.name != 'nt':
345 345 return list(pats)
346 346 ret = []
347 347 for p in pats:
348 348 kind, name = patkind(p, None)
349 349 if kind is None:
350 350 globbed = glob.glob(name)
351 351 if globbed:
352 352 ret.extend(globbed)
353 353 continue
354 354 # if we couldn't expand the glob, just keep it around
355 355 ret.append(p)
356 356 return ret
357 357
358 358 def patkind(name, default):
359 359 """Split a string into an optional pattern kind prefix and the
360 360 actual pattern."""
361 361 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
362 362 if name.startswith(prefix + ':'): return name.split(':', 1)
363 363 return default, name
364 364
365 365 def globre(pat, head='^', tail='$'):
366 366 "convert a glob pattern into a regexp"
367 367 i, n = 0, len(pat)
368 368 res = ''
369 369 group = 0
370 370 def peek(): return i < n and pat[i]
371 371 while i < n:
372 372 c = pat[i]
373 373 i = i+1
374 374 if c == '*':
375 375 if peek() == '*':
376 376 i += 1
377 377 res += '.*'
378 378 else:
379 379 res += '[^/]*'
380 380 elif c == '?':
381 381 res += '.'
382 382 elif c == '[':
383 383 j = i
384 384 if j < n and pat[j] in '!]':
385 385 j += 1
386 386 while j < n and pat[j] != ']':
387 387 j += 1
388 388 if j >= n:
389 389 res += '\\['
390 390 else:
391 391 stuff = pat[i:j].replace('\\','\\\\')
392 392 i = j + 1
393 393 if stuff[0] == '!':
394 394 stuff = '^' + stuff[1:]
395 395 elif stuff[0] == '^':
396 396 stuff = '\\' + stuff
397 397 res = '%s[%s]' % (res, stuff)
398 398 elif c == '{':
399 399 group += 1
400 400 res += '(?:'
401 401 elif c == '}' and group:
402 402 res += ')'
403 403 group -= 1
404 404 elif c == ',' and group:
405 405 res += '|'
406 406 elif c == '\\':
407 407 p = peek()
408 408 if p:
409 409 i += 1
410 410 res += re.escape(p)
411 411 else:
412 412 res += re.escape(c)
413 413 else:
414 414 res += re.escape(c)
415 415 return head + res + tail
416 416
417 417 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
418 418
419 419 def pathto(root, n1, n2):
420 420 '''return the relative path from one place to another.
421 421 root should use os.sep to separate directories
422 422 n1 should use os.sep to separate directories
423 423 n2 should use "/" to separate directories
424 424 returns an os.sep-separated path.
425 425
426 426 If n1 is a relative path, it's assumed it's
427 427 relative to root.
428 428 n2 should always be relative to root.
429 429 '''
430 430 if not n1: return localpath(n2)
431 431 if os.path.isabs(n1):
432 432 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
433 433 return os.path.join(root, localpath(n2))
434 434 n2 = '/'.join((pconvert(root), n2))
435 435 a, b = splitpath(n1), n2.split('/')
436 436 a.reverse()
437 437 b.reverse()
438 438 while a and b and a[-1] == b[-1]:
439 439 a.pop()
440 440 b.pop()
441 441 b.reverse()
442 442 return os.sep.join((['..'] * len(a)) + b) or '.'
443 443
444 444 def canonpath(root, cwd, myname):
445 445 """return the canonical path of myname, given cwd and root"""
446 446 if root == os.sep:
447 447 rootsep = os.sep
448 448 elif endswithsep(root):
449 449 rootsep = root
450 450 else:
451 451 rootsep = root + os.sep
452 452 name = myname
453 453 if not os.path.isabs(name):
454 454 name = os.path.join(root, cwd, name)
455 455 name = os.path.normpath(name)
456 456 audit_path = path_auditor(root)
457 457 if name != rootsep and name.startswith(rootsep):
458 458 name = name[len(rootsep):]
459 459 audit_path(name)
460 460 return pconvert(name)
461 461 elif name == root:
462 462 return ''
463 463 else:
464 464 # Determine whether `name' is in the hierarchy at or beneath `root',
465 465 # by iterating name=dirname(name) until that causes no change (can't
466 466 # check name == '/', because that doesn't work on windows). For each
467 467 # `name', compare dev/inode numbers. If they match, the list `rel'
468 468 # holds the reversed list of components making up the relative file
469 469 # name we want.
470 470 root_st = os.stat(root)
471 471 rel = []
472 472 while True:
473 473 try:
474 474 name_st = os.stat(name)
475 475 except OSError:
476 476 break
477 477 if samestat(name_st, root_st):
478 478 if not rel:
479 479 # name was actually the same as root (maybe a symlink)
480 480 return ''
481 481 rel.reverse()
482 482 name = os.path.join(*rel)
483 483 audit_path(name)
484 484 return pconvert(name)
485 485 dirname, basename = os.path.split(name)
486 486 rel.append(basename)
487 487 if dirname == name:
488 488 break
489 489 name = dirname
490 490
491 491 raise Abort('%s not under root' % myname)
492 492
493 493 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
494 494 """build a function to match a set of file patterns
495 495
496 496 arguments:
497 497 canonroot - the canonical root of the tree you're matching against
498 498 cwd - the current working directory, if relevant
499 499 names - patterns to find
500 500 inc - patterns to include
501 501 exc - patterns to exclude
502 502 dflt_pat - if a pattern in names has no explicit type, assume this one
503 503 src - where these patterns came from (e.g. .hgignore)
504 504
505 505 a pattern is one of:
506 506 'glob:<glob>' - a glob relative to cwd
507 507 're:<regexp>' - a regular expression
508 508 'path:<path>' - a path relative to canonroot
509 509 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
510 510 'relpath:<path>' - a path relative to cwd
511 511 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
512 512 '<something>' - one of the cases above, selected by the dflt_pat argument
513 513
514 514 returns:
515 515 a 3-tuple containing
516 516 - list of roots (places where one should start a recursive walk of the fs);
517 517 this often matches the explicit non-pattern names passed in, but also
518 518 includes the initial part of glob: patterns that has no glob characters
519 519 - a bool match(filename) function
520 520 - a bool indicating if any patterns were passed in
521 521 """
522 522
523 523 # a common case: no patterns at all
524 524 if not names and not inc and not exc:
525 525 return [], always, False
526 526
527 527 def contains_glob(name):
528 528 for c in name:
529 529 if c in _globchars: return True
530 530 return False
531 531
532 532 def regex(kind, name, tail):
533 533 '''convert a pattern into a regular expression'''
534 534 if not name:
535 535 return ''
536 536 if kind == 're':
537 537 return name
538 538 elif kind == 'path':
539 539 return '^' + re.escape(name) + '(?:/|$)'
540 540 elif kind == 'relglob':
541 541 return globre(name, '(?:|.*/)', tail)
542 542 elif kind == 'relpath':
543 543 return re.escape(name) + '(?:/|$)'
544 544 elif kind == 'relre':
545 545 if name.startswith('^'):
546 546 return name
547 547 return '.*' + name
548 548 return globre(name, '', tail)
549 549
550 550 def matchfn(pats, tail):
551 551 """build a matching function from a set of patterns"""
552 552 if not pats:
553 553 return
554 554 try:
555 555 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
556 556 if len(pat) > 20000:
557 557 raise OverflowError()
558 558 return re.compile(pat).match
559 559 except OverflowError:
560 560 # We're using a Python with a tiny regex engine and we
561 561 # made it explode, so we'll divide the pattern list in two
562 562 # until it works
563 563 l = len(pats)
564 564 if l < 2:
565 565 raise
566 566 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
567 567 return lambda s: a(s) or b(s)
568 568 except re.error:
569 569 for k, p in pats:
570 570 try:
571 571 re.compile('(?:%s)' % regex(k, p, tail))
572 572 except re.error:
573 573 if src:
574 574 raise Abort("%s: invalid pattern (%s): %s" %
575 575 (src, k, p))
576 576 else:
577 577 raise Abort("invalid pattern (%s): %s" % (k, p))
578 578 raise Abort("invalid pattern")
579 579
580 580 def globprefix(pat):
581 581 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
582 582 root = []
583 583 for p in pat.split('/'):
584 584 if contains_glob(p): break
585 585 root.append(p)
586 586 return '/'.join(root) or '.'
587 587
588 588 def normalizepats(names, default):
589 589 pats = []
590 590 roots = []
591 591 anypats = False
592 592 for kind, name in [patkind(p, default) for p in names]:
593 593 if kind in ('glob', 'relpath'):
594 594 name = canonpath(canonroot, cwd, name)
595 595 elif kind in ('relglob', 'path'):
596 596 name = normpath(name)
597 597
598 598 pats.append((kind, name))
599 599
600 600 if kind in ('glob', 're', 'relglob', 'relre'):
601 601 anypats = True
602 602
603 603 if kind == 'glob':
604 604 root = globprefix(name)
605 605 roots.append(root)
606 606 elif kind in ('relpath', 'path'):
607 607 roots.append(name or '.')
608 608 elif kind == 'relglob':
609 609 roots.append('.')
610 610 return roots, pats, anypats
611 611
612 612 roots, pats, anypats = normalizepats(names, dflt_pat)
613 613
614 614 patmatch = matchfn(pats, '$') or always
615 615 incmatch = always
616 616 if inc:
617 617 dummy, inckinds, dummy = normalizepats(inc, 'glob')
618 618 incmatch = matchfn(inckinds, '(?:/|$)')
619 619 excmatch = never
620 620 if exc:
621 621 dummy, exckinds, dummy = normalizepats(exc, 'glob')
622 622 excmatch = matchfn(exckinds, '(?:/|$)')
623 623
624 624 if not names and inc and not exc:
625 625 # common case: hgignore patterns
626 626 match = incmatch
627 627 else:
628 628 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
629 629
630 630 return (roots, match, (inc or exc or anypats) and True)
631 631
632 632 _hgexecutable = None
633 633
634 634 def main_is_frozen():
635 635 """return True if we are a frozen executable.
636 636
637 637 The code supports py2exe (most common, Windows only) and tools/freeze
638 638 (portable, not much used).
639 639 """
640 640 return (hasattr(sys, "frozen") or # new py2exe
641 641 hasattr(sys, "importers") or # old py2exe
642 642 imp.is_frozen("__main__")) # tools/freeze
643 643
644 644 def hgexecutable():
645 645 """return location of the 'hg' executable.
646 646
647 647 Defaults to $HG or 'hg' in the search path.
648 648 """
649 649 if _hgexecutable is None:
650 650 hg = os.environ.get('HG')
651 651 if hg:
652 652 set_hgexecutable(hg)
653 653 elif main_is_frozen():
654 654 set_hgexecutable(sys.executable)
655 655 else:
656 656 set_hgexecutable(find_exe('hg') or 'hg')
657 657 return _hgexecutable
658 658
659 659 def set_hgexecutable(path):
660 660 """set location of the 'hg' executable"""
661 661 global _hgexecutable
662 662 _hgexecutable = path
663 663
664 664 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
665 665 '''enhanced shell command execution.
666 666 run with environment maybe modified, maybe in different dir.
667 667
668 668 if command fails and onerr is None, return status. if ui object,
669 669 print error message and return status, else raise onerr object as
670 670 exception.'''
671 671 def py2shell(val):
672 672 'convert python object into string that is useful to shell'
673 673 if val in (None, False):
674 674 return '0'
675 675 if val == True:
676 676 return '1'
677 677 return str(val)
678 678 oldenv = {}
679 679 for k in environ:
680 680 oldenv[k] = os.environ.get(k)
681 681 if cwd is not None:
682 682 oldcwd = os.getcwd()
683 683 origcmd = cmd
684 684 if os.name == 'nt':
685 685 cmd = '"%s"' % cmd
686 686 try:
687 687 for k, v in environ.iteritems():
688 688 os.environ[k] = py2shell(v)
689 689 os.environ['HG'] = hgexecutable()
690 690 if cwd is not None and oldcwd != cwd:
691 691 os.chdir(cwd)
692 692 rc = os.system(cmd)
693 693 if sys.platform == 'OpenVMS' and rc & 1:
694 694 rc = 0
695 695 if rc and onerr:
696 696 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
697 697 explain_exit(rc)[0])
698 698 if errprefix:
699 699 errmsg = '%s: %s' % (errprefix, errmsg)
700 700 try:
701 701 onerr.warn(errmsg + '\n')
702 702 except AttributeError:
703 703 raise onerr(errmsg)
704 704 return rc
705 705 finally:
706 706 for k, v in oldenv.iteritems():
707 707 if v is None:
708 708 del os.environ[k]
709 709 else:
710 710 os.environ[k] = v
711 711 if cwd is not None and oldcwd != cwd:
712 712 os.chdir(oldcwd)
713 713
714 714 def checksignature(func):
715 715 '''wrap a function with code to check for calling errors'''
716 716 def check(*args, **kwargs):
717 717 try:
718 718 return func(*args, **kwargs)
719 719 except TypeError:
720 720 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
721 721 raise error.SignatureError
722 722 raise
723 723
724 724 return check
725 725
726 726 # os.path.lexists is not available on python2.3
727 727 def lexists(filename):
728 728 "test whether a file with this name exists. does not follow symlinks"
729 729 try:
730 730 os.lstat(filename)
731 731 except:
732 732 return False
733 733 return True
734 734
735 735 def rename(src, dst):
736 736 """forcibly rename a file"""
737 737 try:
738 738 os.rename(src, dst)
739 739 except OSError, err: # FIXME: check err (EEXIST ?)
740 740 # on windows, rename to existing file is not allowed, so we
741 741 # must delete destination first. but if file is open, unlink
742 742 # schedules it for delete but does not delete it. rename
743 743 # happens immediately even for open files, so we rename
744 744 # destination to a temporary name, then delete that. then
745 745 # rename is safe to do.
746 746 temp = dst + "-force-rename"
747 747 os.rename(dst, temp)
748 748 os.unlink(temp)
749 749 os.rename(src, dst)
750 750
751 751 def unlink(f):
752 752 """unlink and remove the directory if it is empty"""
753 753 os.unlink(f)
754 754 # try removing directories that might now be empty
755 755 try:
756 756 os.removedirs(os.path.dirname(f))
757 757 except OSError:
758 758 pass
759 759
760 760 def copyfile(src, dest):
761 761 "copy a file, preserving mode and atime/mtime"
762 762 if os.path.islink(src):
763 763 try:
764 764 os.unlink(dest)
765 765 except:
766 766 pass
767 767 os.symlink(os.readlink(src), dest)
768 768 else:
769 769 try:
770 770 shutil.copyfile(src, dest)
771 771 shutil.copystat(src, dest)
772 772 except shutil.Error, inst:
773 773 raise Abort(str(inst))
774 774
775 775 def copyfiles(src, dst, hardlink=None):
776 776 """Copy a directory tree using hardlinks if possible"""
777 777
778 778 if hardlink is None:
779 779 hardlink = (os.stat(src).st_dev ==
780 780 os.stat(os.path.dirname(dst)).st_dev)
781 781
782 782 if os.path.isdir(src):
783 783 os.mkdir(dst)
784 784 for name, kind in osutil.listdir(src):
785 785 srcname = os.path.join(src, name)
786 786 dstname = os.path.join(dst, name)
787 787 copyfiles(srcname, dstname, hardlink)
788 788 else:
789 789 if hardlink:
790 790 try:
791 791 os_link(src, dst)
792 792 except (IOError, OSError):
793 793 hardlink = False
794 794 shutil.copy(src, dst)
795 795 else:
796 796 shutil.copy(src, dst)
797 797
798 798 class path_auditor(object):
799 799 '''ensure that a filesystem path contains no banned components.
800 800 the following properties of a path are checked:
801 801
802 802 - under top-level .hg
803 803 - starts at the root of a windows drive
804 804 - contains ".."
805 805 - traverses a symlink (e.g. a/symlink_here/b)
806 806 - inside a nested repository'''
807 807
808 808 def __init__(self, root):
809 809 self.audited = set()
810 810 self.auditeddir = set()
811 811 self.root = root
812 812
813 813 def __call__(self, path):
814 814 if path in self.audited:
815 815 return
816 816 normpath = os.path.normcase(path)
817 817 parts = splitpath(normpath)
818 818 if (os.path.splitdrive(path)[0]
819 819 or parts[0].lower() in ('.hg', '.hg.', '')
820 820 or os.pardir in parts):
821 821 raise Abort(_("path contains illegal component: %s") % path)
822 822 if '.hg' in path.lower():
823 823 lparts = [p.lower() for p in parts]
824 824 for p in '.hg', '.hg.':
825 825 if p in lparts[1:]:
826 826 pos = lparts.index(p)
827 827 base = os.path.join(*parts[:pos])
828 828 raise Abort(_('path %r is inside repo %r') % (path, base))
829 829 def check(prefix):
830 830 curpath = os.path.join(self.root, prefix)
831 831 try:
832 832 st = os.lstat(curpath)
833 833 except OSError, err:
834 834 # EINVAL can be raised as invalid path syntax under win32.
835 835 # They must be ignored for patterns can be checked too.
836 836 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
837 837 raise
838 838 else:
839 839 if stat.S_ISLNK(st.st_mode):
840 840 raise Abort(_('path %r traverses symbolic link %r') %
841 841 (path, prefix))
842 842 elif (stat.S_ISDIR(st.st_mode) and
843 843 os.path.isdir(os.path.join(curpath, '.hg'))):
844 844 raise Abort(_('path %r is inside repo %r') %
845 845 (path, prefix))
846 846 parts.pop()
847 847 prefixes = []
848 848 for n in range(len(parts)):
849 849 prefix = os.sep.join(parts)
850 850 if prefix in self.auditeddir:
851 851 break
852 852 check(prefix)
853 853 prefixes.append(prefix)
854 854 parts.pop()
855 855
856 856 self.audited.add(path)
857 857 # only add prefixes to the cache after checking everything: we don't
858 858 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
859 859 self.auditeddir.update(prefixes)
860 860
861 861 def _makelock_file(info, pathname):
862 862 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
863 863 os.write(ld, info)
864 864 os.close(ld)
865 865
866 866 def _readlock_file(pathname):
867 867 return posixfile(pathname).read()
868 868
869 869 def nlinks(pathname):
870 870 """Return number of hardlinks for the given file."""
871 871 return os.lstat(pathname).st_nlink
872 872
873 873 if hasattr(os, 'link'):
874 874 os_link = os.link
875 875 else:
876 876 def os_link(src, dst):
877 877 raise OSError(0, _("Hardlinks not supported"))
878 878
879 879 def fstat(fp):
880 880 '''stat file object that may not have fileno method.'''
881 881 try:
882 882 return os.fstat(fp.fileno())
883 883 except AttributeError:
884 884 return os.stat(fp.name)
885 885
886 886 posixfile = file
887 887
888 888 def openhardlinks():
889 889 '''return true if it is safe to hold open file handles to hardlinks'''
890 890 return True
891 891
892 892 def _statfiles(files):
893 893 'Stat each file in files and yield stat or None if file does not exist.'
894 894 lstat = os.lstat
895 895 for nf in files:
896 896 try:
897 897 st = lstat(nf)
898 898 except OSError, err:
899 899 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
900 900 raise
901 901 st = None
902 902 yield st
903 903
904 904 def _statfiles_clustered(files):
905 905 '''Stat each file in files and yield stat or None if file does not exist.
906 906 Cluster and cache stat per directory to minimize number of OS stat calls.'''
907 907 ncase = os.path.normcase
908 908 sep = os.sep
909 909 dircache = {} # dirname -> filename -> status | None if file does not exist
910 910 for nf in files:
911 911 nf = ncase(nf)
912 912 pos = nf.rfind(sep)
913 913 if pos == -1:
914 914 dir, base = '.', nf
915 915 else:
916 916 dir, base = nf[:pos+1], nf[pos+1:]
917 917 cache = dircache.get(dir, None)
918 918 if cache is None:
919 919 try:
920 920 dmap = dict([(ncase(n), s)
921 921 for n, k, s in osutil.listdir(dir, True)])
922 922 except OSError, err:
923 923 # handle directory not found in Python version prior to 2.5
924 924 # Python <= 2.4 returns native Windows code 3 in errno
925 925 # Python >= 2.5 returns ENOENT and adds winerror field
926 926 # EINVAL is raised if dir is not a directory.
927 927 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
928 928 errno.ENOTDIR):
929 929 raise
930 930 dmap = {}
931 931 cache = dircache.setdefault(dir, dmap)
932 932 yield cache.get(base, None)
933 933
934 934 if sys.platform == 'win32':
935 935 statfiles = _statfiles_clustered
936 936 else:
937 937 statfiles = _statfiles
938 938
939 939 getuser_fallback = None
940 940
941 941 def getuser():
942 942 '''return name of current user'''
943 943 try:
944 944 return getpass.getuser()
945 945 except ImportError:
946 946 # import of pwd will fail on windows - try fallback
947 947 if getuser_fallback:
948 948 return getuser_fallback()
949 949 # raised if win32api not available
950 950 raise Abort(_('user name not available - set USERNAME '
951 951 'environment variable'))
952 952
953 953 def username(uid=None):
954 954 """Return the name of the user with the given uid.
955 955
956 956 If uid is None, return the name of the current user."""
957 957 try:
958 958 import pwd
959 959 if uid is None:
960 960 uid = os.getuid()
961 961 try:
962 962 return pwd.getpwuid(uid)[0]
963 963 except KeyError:
964 964 return str(uid)
965 965 except ImportError:
966 966 return None
967 967
968 968 def groupname(gid=None):
969 969 """Return the name of the group with the given gid.
970 970
971 971 If gid is None, return the name of the current group."""
972 972 try:
973 973 import grp
974 974 if gid is None:
975 975 gid = os.getgid()
976 976 try:
977 977 return grp.getgrgid(gid)[0]
978 978 except KeyError:
979 979 return str(gid)
980 980 except ImportError:
981 981 return None
982 982
983 983 # File system features
984 984
985 985 def checkcase(path):
986 986 """
987 987 Check whether the given path is on a case-sensitive filesystem
988 988
989 989 Requires a path (like /foo/.hg) ending with a foldable final
990 990 directory component.
991 991 """
992 992 s1 = os.stat(path)
993 993 d, b = os.path.split(path)
994 994 p2 = os.path.join(d, b.upper())
995 995 if path == p2:
996 996 p2 = os.path.join(d, b.lower())
997 997 try:
998 998 s2 = os.stat(p2)
999 999 if s2 == s1:
1000 1000 return False
1001 1001 return True
1002 1002 except:
1003 1003 return True
1004 1004
1005 1005 _fspathcache = {}
1006 1006 def fspath(name, root):
1007 1007 '''Get name in the case stored in the filesystem
1008 1008
1009 1009 The name is either relative to root, or it is an absolute path starting
1010 1010 with root. Note that this function is unnecessary, and should not be
1011 1011 called, for case-sensitive filesystems (simply because it's expensive).
1012 1012 '''
1013 1013 # If name is absolute, make it relative
1014 1014 if name.lower().startswith(root.lower()):
1015 1015 l = len(root)
1016 1016 if name[l] == os.sep or name[l] == os.altsep:
1017 1017 l = l + 1
1018 1018 name = name[l:]
1019 1019
1020 1020 if not os.path.exists(os.path.join(root, name)):
1021 1021 return None
1022 1022
1023 1023 seps = os.sep
1024 1024 if os.altsep:
1025 1025 seps = seps + os.altsep
1026 1026 # Protect backslashes. This gets silly very quickly.
1027 1027 seps.replace('\\','\\\\')
1028 1028 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1029 1029 dir = os.path.normcase(os.path.normpath(root))
1030 1030 result = []
1031 1031 for part, sep in pattern.findall(name):
1032 1032 if sep:
1033 1033 result.append(sep)
1034 1034 continue
1035 1035
1036 1036 if dir not in _fspathcache:
1037 1037 _fspathcache[dir] = os.listdir(dir)
1038 1038 contents = _fspathcache[dir]
1039 1039
1040 1040 lpart = part.lower()
1041 1041 for n in contents:
1042 1042 if n.lower() == lpart:
1043 1043 result.append(n)
1044 1044 break
1045 1045 else:
1046 1046 # Cannot happen, as the file exists!
1047 1047 result.append(part)
1048 1048 dir = os.path.join(dir, lpart)
1049 1049
1050 1050 return ''.join(result)
1051 1051
1052 1052 def checkexec(path):
1053 1053 """
1054 1054 Check whether the given path is on a filesystem with UNIX-like exec flags
1055 1055
1056 1056 Requires a directory (like /foo/.hg)
1057 1057 """
1058 1058
1059 1059 # VFAT on some Linux versions can flip mode but it doesn't persist
1060 1060 # a FS remount. Frequently we can detect it if files are created
1061 1061 # with exec bit on.
1062 1062
1063 1063 try:
1064 1064 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1065 1065 fh, fn = tempfile.mkstemp("", "", path)
1066 1066 try:
1067 1067 os.close(fh)
1068 1068 m = os.stat(fn).st_mode & 0777
1069 1069 new_file_has_exec = m & EXECFLAGS
1070 1070 os.chmod(fn, m ^ EXECFLAGS)
1071 1071 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1072 1072 finally:
1073 1073 os.unlink(fn)
1074 1074 except (IOError, OSError):
1075 1075 # we don't care, the user probably won't be able to commit anyway
1076 1076 return False
1077 1077 return not (new_file_has_exec or exec_flags_cannot_flip)
1078 1078
1079 1079 def checklink(path):
1080 1080 """check whether the given path is on a symlink-capable filesystem"""
1081 1081 # mktemp is not racy because symlink creation will fail if the
1082 1082 # file already exists
1083 1083 name = tempfile.mktemp(dir=path)
1084 1084 try:
1085 1085 os.symlink(".", name)
1086 1086 os.unlink(name)
1087 1087 return True
1088 1088 except (OSError, AttributeError):
1089 1089 return False
1090 1090
1091 1091 _umask = os.umask(0)
1092 1092 os.umask(_umask)
1093 1093
1094 1094 def needbinarypatch():
1095 1095 """return True if patches should be applied in binary mode by default."""
1096 1096 return os.name == 'nt'
1097 1097
1098 1098 def endswithsep(path):
1099 1099 '''Check path ends with os.sep or os.altsep.'''
1100 1100 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1101 1101
1102 1102 def splitpath(path):
1103 1103 '''Split path by os.sep.
1104 1104 Note that this function does not use os.altsep because this is
1105 1105 an alternative of simple "xxx.split(os.sep)".
1106 1106 It is recommended to use os.path.normpath() before using this
1107 1107 function if need.'''
1108 1108 return path.split(os.sep)
1109 1109
1110 1110 def gui():
1111 1111 '''Are we running in a GUI?'''
1112 1112 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1113 1113
1114 1114 def lookup_reg(key, name=None, scope=None):
1115 1115 return None
1116 1116
1117 1117 # Platform specific variants
1118 1118 if os.name == 'nt':
1119 1119 import msvcrt
1120 1120 nulldev = 'NUL:'
1121 1121
1122 1122 class winstdout:
1123 1123 '''stdout on windows misbehaves if sent through a pipe'''
1124 1124
1125 1125 def __init__(self, fp):
1126 1126 self.fp = fp
1127 1127
1128 1128 def __getattr__(self, key):
1129 1129 return getattr(self.fp, key)
1130 1130
1131 1131 def close(self):
1132 1132 try:
1133 1133 self.fp.close()
1134 1134 except: pass
1135 1135
1136 1136 def write(self, s):
1137 1137 try:
1138 1138 # This is workaround for "Not enough space" error on
1139 1139 # writing large size of data to console.
1140 1140 limit = 16000
1141 1141 l = len(s)
1142 1142 start = 0
1143 1143 while start < l:
1144 1144 end = start + limit
1145 1145 self.fp.write(s[start:end])
1146 1146 start = end
1147 1147 except IOError, inst:
1148 1148 if inst.errno != 0: raise
1149 1149 self.close()
1150 1150 raise IOError(errno.EPIPE, 'Broken pipe')
1151 1151
1152 1152 def flush(self):
1153 1153 try:
1154 1154 return self.fp.flush()
1155 1155 except IOError, inst:
1156 1156 if inst.errno != errno.EINVAL: raise
1157 1157 self.close()
1158 1158 raise IOError(errno.EPIPE, 'Broken pipe')
1159 1159
1160 1160 sys.stdout = winstdout(sys.stdout)
1161 1161
1162 1162 def _is_win_9x():
1163 1163 '''return true if run on windows 95, 98 or me.'''
1164 1164 try:
1165 1165 return sys.getwindowsversion()[3] == 1
1166 1166 except AttributeError:
1167 1167 return 'command' in os.environ.get('comspec', '')
1168 1168
1169 1169 def openhardlinks():
1170 1170 return not _is_win_9x and "win32api" in locals()
1171 1171
1172 1172 def system_rcpath():
1173 1173 try:
1174 1174 return system_rcpath_win32()
1175 1175 except:
1176 1176 return [r'c:\mercurial\mercurial.ini']
1177 1177
1178 1178 def user_rcpath():
1179 1179 '''return os-specific hgrc search path to the user dir'''
1180 1180 try:
1181 1181 path = user_rcpath_win32()
1182 1182 except:
1183 1183 home = os.path.expanduser('~')
1184 1184 path = [os.path.join(home, 'mercurial.ini'),
1185 1185 os.path.join(home, '.hgrc')]
1186 1186 userprofile = os.environ.get('USERPROFILE')
1187 1187 if userprofile:
1188 1188 path.append(os.path.join(userprofile, 'mercurial.ini'))
1189 1189 path.append(os.path.join(userprofile, '.hgrc'))
1190 1190 return path
1191 1191
1192 1192 def parse_patch_output(output_line):
1193 1193 """parses the output produced by patch and returns the file name"""
1194 1194 pf = output_line[14:]
1195 1195 if pf[0] == '`':
1196 1196 pf = pf[1:-1] # Remove the quotes
1197 1197 return pf
1198 1198
1199 1199 def sshargs(sshcmd, host, user, port):
1200 1200 '''Build argument list for ssh or Plink'''
1201 1201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1202 1202 args = user and ("%s@%s" % (user, host)) or host
1203 1203 return port and ("%s %s %s" % (args, pflag, port)) or args
1204 1204
1205 1205 def testpid(pid):
1206 1206 '''return False if pid dead, True if running or not known'''
1207 1207 return True
1208 1208
1209 1209 def set_flags(f, l, x):
1210 1210 pass
1211 1211
1212 1212 def set_binary(fd):
1213 1213 # When run without console, pipes may expose invalid
1214 1214 # fileno(), usually set to -1.
1215 1215 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1216 1216 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1217 1217
1218 1218 def pconvert(path):
1219 1219 return '/'.join(splitpath(path))
1220 1220
1221 1221 def localpath(path):
1222 1222 return path.replace('/', '\\')
1223 1223
1224 1224 def normpath(path):
1225 1225 return pconvert(os.path.normpath(path))
1226 1226
1227 1227 makelock = _makelock_file
1228 1228 readlock = _readlock_file
1229 1229
1230 1230 def samestat(s1, s2):
1231 1231 return False
1232 1232
1233 1233 # A sequence of backslashes is special iff it precedes a double quote:
1234 1234 # - if there's an even number of backslashes, the double quote is not
1235 1235 # quoted (i.e. it ends the quoted region)
1236 1236 # - if there's an odd number of backslashes, the double quote is quoted
1237 1237 # - in both cases, every pair of backslashes is unquoted into a single
1238 1238 # backslash
1239 1239 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1240 1240 # So, to quote a string, we must surround it in double quotes, double
1241 1241 # the number of backslashes that preceed double quotes and add another
1242 1242 # backslash before every double quote (being careful with the double
1243 1243 # quote we've appended to the end)
1244 1244 _quotere = None
1245 1245 def shellquote(s):
1246 1246 global _quotere
1247 1247 if _quotere is None:
1248 1248 _quotere = re.compile(r'(\\*)("|\\$)')
1249 1249 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1250 1250
1251 1251 def quotecommand(cmd):
1252 1252 """Build a command string suitable for os.popen* calls."""
1253 1253 # The extra quotes are needed because popen* runs the command
1254 1254 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1255 1255 return '"' + cmd + '"'
1256 1256
1257 1257 def popen(command, mode='r'):
1258 1258 # Work around "popen spawned process may not write to stdout
1259 1259 # under windows"
1260 1260 # http://bugs.python.org/issue1366
1261 1261 command += " 2> %s" % nulldev
1262 1262 return os.popen(quotecommand(command), mode)
1263 1263
1264 1264 def explain_exit(code):
1265 1265 return _("exited with status %d") % code, code
1266 1266
1267 1267 # if you change this stub into a real check, please try to implement the
1268 1268 # username and groupname functions above, too.
1269 1269 def isowner(fp, st=None):
1270 1270 return True
1271 1271
1272 1272 def find_exe(command):
1273 1273 '''Find executable for command searching like cmd.exe does.
1274 1274 If command is a basename then PATH is searched for command.
1275 1275 PATH isn't searched if command is an absolute or relative path.
1276 1276 An extension from PATHEXT is found and added if not present.
1277 1277 If command isn't found None is returned.'''
1278 1278 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1279 1279 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
1280 1280 if os.path.splitext(command)[1].lower() in pathexts:
1281 1281 pathexts = ['']
1282 1282
1283 1283 def findexisting(pathcommand):
1284 1284 'Will append extension (if needed) and return existing file'
1285 1285 for ext in pathexts:
1286 1286 executable = pathcommand + ext
1287 1287 if os.path.exists(executable):
1288 1288 return executable
1289 1289 return None
1290 1290
1291 1291 if os.sep in command:
1292 1292 return findexisting(command)
1293 1293
1294 1294 for path in os.environ.get('PATH', '').split(os.pathsep):
1295 1295 executable = findexisting(os.path.join(path, command))
1296 1296 if executable is not None:
1297 1297 return executable
1298 1298 return None
1299 1299
1300 1300 def set_signal_handler():
1301 1301 try:
1302 1302 set_signal_handler_win32()
1303 1303 except NameError:
1304 1304 pass
1305 1305
1306 1306 try:
1307 1307 # override functions with win32 versions if possible
1308 1308 from util_win32 import *
1309 1309 if not _is_win_9x():
1310 1310 posixfile = posixfile_nt
1311 1311 except ImportError:
1312 1312 pass
1313 1313
1314 1314 else:
1315 1315 nulldev = '/dev/null'
1316 1316
1317 1317 def rcfiles(path):
1318 1318 rcs = [os.path.join(path, 'hgrc')]
1319 1319 rcdir = os.path.join(path, 'hgrc.d')
1320 1320 try:
1321 1321 rcs.extend([os.path.join(rcdir, f)
1322 1322 for f, kind in osutil.listdir(rcdir)
1323 1323 if f.endswith(".rc")])
1324 1324 except OSError:
1325 1325 pass
1326 1326 return rcs
1327 1327
1328 1328 def system_rcpath():
1329 1329 path = []
1330 1330 # old mod_python does not set sys.argv
1331 1331 if len(getattr(sys, 'argv', [])) > 0:
1332 1332 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1333 1333 '/../etc/mercurial'))
1334 1334 path.extend(rcfiles('/etc/mercurial'))
1335 1335 return path
1336 1336
1337 1337 def user_rcpath():
1338 1338 return [os.path.expanduser('~/.hgrc')]
1339 1339
1340 1340 def parse_patch_output(output_line):
1341 1341 """parses the output produced by patch and returns the file name"""
1342 1342 pf = output_line[14:]
1343 1343 if os.sys.platform == 'OpenVMS':
1344 1344 if pf[0] == '`':
1345 1345 pf = pf[1:-1] # Remove the quotes
1346 1346 else:
1347 1347 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1348 1348 pf = pf[1:-1] # Remove the quotes
1349 1349 return pf
1350 1350
1351 1351 def sshargs(sshcmd, host, user, port):
1352 1352 '''Build argument list for ssh'''
1353 1353 args = user and ("%s@%s" % (user, host)) or host
1354 1354 return port and ("%s -p %s" % (args, port)) or args
1355 1355
1356 1356 def is_exec(f):
1357 1357 """check whether a file is executable"""
1358 1358 return (os.lstat(f).st_mode & 0100 != 0)
1359 1359
1360 1360 def set_flags(f, l, x):
1361 1361 s = os.lstat(f).st_mode
1362 1362 if l:
1363 1363 if not stat.S_ISLNK(s):
1364 1364 # switch file to link
1365 1365 data = file(f).read()
1366 1366 os.unlink(f)
1367 1367 try:
1368 1368 os.symlink(data, f)
1369 1369 except:
1370 1370 # failed to make a link, rewrite file
1371 1371 file(f, "w").write(data)
1372 1372 # no chmod needed at this point
1373 1373 return
1374 1374 if stat.S_ISLNK(s):
1375 1375 # switch link to file
1376 1376 data = os.readlink(f)
1377 1377 os.unlink(f)
1378 1378 file(f, "w").write(data)
1379 1379 s = 0666 & ~_umask # avoid restatting for chmod
1380 1380
1381 1381 sx = s & 0100
1382 1382 if x and not sx:
1383 1383 # Turn on +x for every +r bit when making a file executable
1384 1384 # and obey umask.
1385 1385 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1386 1386 elif not x and sx:
1387 1387 # Turn off all +x bits
1388 1388 os.chmod(f, s & 0666)
1389 1389
1390 1390 def set_binary(fd):
1391 1391 pass
1392 1392
1393 1393 def pconvert(path):
1394 1394 return path
1395 1395
1396 1396 def localpath(path):
1397 1397 return path
1398 1398
1399 1399 normpath = os.path.normpath
1400 1400 samestat = os.path.samestat
1401 1401
1402 1402 def makelock(info, pathname):
1403 1403 try:
1404 1404 os.symlink(info, pathname)
1405 1405 except OSError, why:
1406 1406 if why.errno == errno.EEXIST:
1407 1407 raise
1408 1408 else:
1409 1409 _makelock_file(info, pathname)
1410 1410
1411 1411 def readlock(pathname):
1412 1412 try:
1413 1413 return os.readlink(pathname)
1414 1414 except OSError, why:
1415 1415 if why.errno in (errno.EINVAL, errno.ENOSYS):
1416 1416 return _readlock_file(pathname)
1417 1417 else:
1418 1418 raise
1419 1419
1420 1420 def shellquote(s):
1421 1421 if os.sys.platform == 'OpenVMS':
1422 1422 return '"%s"' % s
1423 1423 else:
1424 1424 return "'%s'" % s.replace("'", "'\\''")
1425 1425
1426 1426 def quotecommand(cmd):
1427 1427 return cmd
1428 1428
1429 1429 def popen(command, mode='r'):
1430 1430 return os.popen(command, mode)
1431 1431
1432 1432 def testpid(pid):
1433 1433 '''return False if pid dead, True if running or not sure'''
1434 1434 if os.sys.platform == 'OpenVMS':
1435 1435 return True
1436 1436 try:
1437 1437 os.kill(pid, 0)
1438 1438 return True
1439 1439 except OSError, inst:
1440 1440 return inst.errno != errno.ESRCH
1441 1441
1442 1442 def explain_exit(code):
1443 1443 """return a 2-tuple (desc, code) describing a process's status"""
1444 1444 if os.WIFEXITED(code):
1445 1445 val = os.WEXITSTATUS(code)
1446 1446 return _("exited with status %d") % val, val
1447 1447 elif os.WIFSIGNALED(code):
1448 1448 val = os.WTERMSIG(code)
1449 1449 return _("killed by signal %d") % val, val
1450 1450 elif os.WIFSTOPPED(code):
1451 1451 val = os.WSTOPSIG(code)
1452 1452 return _("stopped by signal %d") % val, val
1453 1453 raise ValueError(_("invalid exit code"))
1454 1454
1455 1455 def isowner(fp, st=None):
1456 1456 """Return True if the file object f belongs to the current user.
1457 1457
1458 1458 The return value of a util.fstat(f) may be passed as the st argument.
1459 1459 """
1460 1460 if st is None:
1461 1461 st = fstat(fp)
1462 1462 return st.st_uid == os.getuid()
1463 1463
1464 1464 def find_exe(command):
1465 1465 '''Find executable for command searching like which does.
1466 1466 If command is a basename then PATH is searched for command.
1467 1467 PATH isn't searched if command is an absolute or relative path.
1468 1468 If command isn't found None is returned.'''
1469 1469 if sys.platform == 'OpenVMS':
1470 1470 return command
1471 1471
1472 1472 def findexisting(executable):
1473 1473 'Will return executable if existing file'
1474 1474 if os.path.exists(executable):
1475 1475 return executable
1476 1476 return None
1477 1477
1478 1478 if os.sep in command:
1479 1479 return findexisting(command)
1480 1480
1481 1481 for path in os.environ.get('PATH', '').split(os.pathsep):
1482 1482 executable = findexisting(os.path.join(path, command))
1483 1483 if executable is not None:
1484 1484 return executable
1485 1485 return None
1486 1486
1487 1487 def set_signal_handler():
1488 1488 pass
1489 1489
1490 1490 def mktempcopy(name, emptyok=False, createmode=None):
1491 1491 """Create a temporary file with the same contents from name
1492 1492
1493 1493 The permission bits are copied from the original file.
1494 1494
1495 1495 If the temporary file is going to be truncated immediately, you
1496 1496 can use emptyok=True as an optimization.
1497 1497
1498 1498 Returns the name of the temporary file.
1499 1499 """
1500 1500 d, fn = os.path.split(name)
1501 1501 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1502 1502 os.close(fd)
1503 1503 # Temporary files are created with mode 0600, which is usually not
1504 1504 # what we want. If the original file already exists, just copy
1505 1505 # its mode. Otherwise, manually obey umask.
1506 1506 try:
1507 1507 st_mode = os.lstat(name).st_mode & 0777
1508 1508 except OSError, inst:
1509 1509 if inst.errno != errno.ENOENT:
1510 1510 raise
1511 1511 st_mode = createmode
1512 1512 if st_mode is None:
1513 1513 st_mode = ~_umask
1514 1514 st_mode &= 0666
1515 1515 os.chmod(temp, st_mode)
1516 1516 if emptyok:
1517 1517 return temp
1518 1518 try:
1519 1519 try:
1520 1520 ifp = posixfile(name, "rb")
1521 1521 except IOError, inst:
1522 1522 if inst.errno == errno.ENOENT:
1523 1523 return temp
1524 1524 if not getattr(inst, 'filename', None):
1525 1525 inst.filename = name
1526 1526 raise
1527 1527 ofp = posixfile(temp, "wb")
1528 1528 for chunk in filechunkiter(ifp):
1529 1529 ofp.write(chunk)
1530 1530 ifp.close()
1531 1531 ofp.close()
1532 1532 except:
1533 1533 try: os.unlink(temp)
1534 1534 except: pass
1535 1535 raise
1536 1536 return temp
1537 1537
1538 1538 class atomictempfile(posixfile):
1539 1539 """file-like object that atomically updates a file
1540 1540
1541 1541 All writes will be redirected to a temporary copy of the original
1542 1542 file. When rename is called, the copy is renamed to the original
1543 1543 name, making the changes visible.
1544 1544 """
1545 1545 def __init__(self, name, mode, createmode):
1546 1546 self.__name = name
1547 1547 self.temp = mktempcopy(name, emptyok=('w' in mode),
1548 1548 createmode=createmode)
1549 1549 posixfile.__init__(self, self.temp, mode)
1550 1550
1551 1551 def rename(self):
1552 1552 if not self.closed:
1553 1553 posixfile.close(self)
1554 1554 rename(self.temp, localpath(self.__name))
1555 1555
1556 1556 def __del__(self):
1557 1557 if not self.closed:
1558 1558 try:
1559 1559 os.unlink(self.temp)
1560 1560 except: pass
1561 1561 posixfile.close(self)
1562 1562
1563 1563 def makedirs(name, mode=None):
1564 1564 """recursive directory creation with parent mode inheritance"""
1565 1565 try:
1566 1566 os.mkdir(name)
1567 1567 if mode is not None:
1568 1568 os.chmod(name, mode)
1569 1569 return
1570 1570 except OSError, err:
1571 1571 if err.errno == errno.EEXIST:
1572 1572 return
1573 1573 if err.errno != errno.ENOENT:
1574 1574 raise
1575 1575 parent = os.path.abspath(os.path.dirname(name))
1576 1576 makedirs(parent, mode)
1577 1577 makedirs(name, mode)
1578 1578
1579 1579 class opener(object):
1580 1580 """Open files relative to a base directory
1581 1581
1582 1582 This class is used to hide the details of COW semantics and
1583 1583 remote file access from higher level code.
1584 1584 """
1585 1585 def __init__(self, base, audit=True):
1586 1586 self.base = base
1587 1587 if audit:
1588 1588 self.audit_path = path_auditor(base)
1589 1589 else:
1590 1590 self.audit_path = always
1591 1591 self.createmode = None
1592 1592
1593 1593 def __getattr__(self, name):
1594 1594 if name == '_can_symlink':
1595 1595 self._can_symlink = checklink(self.base)
1596 1596 return self._can_symlink
1597 1597 raise AttributeError(name)
1598 1598
1599 1599 def _fixfilemode(self, name):
1600 1600 if self.createmode is None:
1601 1601 return
1602 1602 os.chmod(name, self.createmode & 0666)
1603 1603
1604 1604 def __call__(self, path, mode="r", text=False, atomictemp=False):
1605 1605 self.audit_path(path)
1606 1606 f = os.path.join(self.base, path)
1607 1607
1608 1608 if not text and "b" not in mode:
1609 1609 mode += "b" # for that other OS
1610 1610
1611 1611 nlink = -1
1612 1612 if mode not in ("r", "rb"):
1613 1613 try:
1614 1614 nlink = nlinks(f)
1615 1615 except OSError:
1616 1616 nlink = 0
1617 1617 d = os.path.dirname(f)
1618 1618 if not os.path.isdir(d):
1619 1619 makedirs(d, self.createmode)
1620 1620 if atomictemp:
1621 1621 return atomictempfile(f, mode, self.createmode)
1622 1622 if nlink > 1:
1623 1623 rename(mktempcopy(f), f)
1624 1624 fp = posixfile(f, mode)
1625 1625 if nlink == 0:
1626 1626 self._fixfilemode(f)
1627 1627 return fp
1628 1628
1629 1629 def symlink(self, src, dst):
1630 1630 self.audit_path(dst)
1631 1631 linkname = os.path.join(self.base, dst)
1632 1632 try:
1633 1633 os.unlink(linkname)
1634 1634 except OSError:
1635 1635 pass
1636 1636
1637 1637 dirname = os.path.dirname(linkname)
1638 1638 if not os.path.exists(dirname):
1639 1639 makedirs(dirname, self.createmode)
1640 1640
1641 1641 if self._can_symlink:
1642 1642 try:
1643 1643 os.symlink(src, linkname)
1644 1644 except OSError, err:
1645 1645 raise OSError(err.errno, _('could not symlink to %r: %s') %
1646 1646 (src, err.strerror), linkname)
1647 1647 else:
1648 1648 f = self(dst, "w")
1649 1649 f.write(src)
1650 1650 f.close()
1651 1651 self._fixfilemode(dst)
1652 1652
1653 1653 class chunkbuffer(object):
1654 1654 """Allow arbitrary sized chunks of data to be efficiently read from an
1655 1655 iterator over chunks of arbitrary size."""
1656 1656
1657 1657 def __init__(self, in_iter):
1658 1658 """in_iter is the iterator that's iterating over the input chunks.
1659 1659 targetsize is how big a buffer to try to maintain."""
1660 1660 self.iter = iter(in_iter)
1661 1661 self.buf = ''
1662 1662 self.targetsize = 2**16
1663 1663
1664 1664 def read(self, l):
1665 1665 """Read L bytes of data from the iterator of chunks of data.
1666 1666 Returns less than L bytes if the iterator runs dry."""
1667 1667 if l > len(self.buf) and self.iter:
1668 1668 # Clamp to a multiple of self.targetsize
1669 1669 targetsize = max(l, self.targetsize)
1670 1670 collector = cStringIO.StringIO()
1671 1671 collector.write(self.buf)
1672 1672 collected = len(self.buf)
1673 1673 for chunk in self.iter:
1674 1674 collector.write(chunk)
1675 1675 collected += len(chunk)
1676 1676 if collected >= targetsize:
1677 1677 break
1678 1678 if collected < targetsize:
1679 1679 self.iter = False
1680 1680 self.buf = collector.getvalue()
1681 1681 if len(self.buf) == l:
1682 1682 s, self.buf = str(self.buf), ''
1683 1683 else:
1684 1684 s, self.buf = self.buf[:l], buffer(self.buf, l)
1685 1685 return s
1686 1686
1687 1687 def filechunkiter(f, size=65536, limit=None):
1688 1688 """Create a generator that produces the data in the file size
1689 1689 (default 65536) bytes at a time, up to optional limit (default is
1690 1690 to read all data). Chunks may be less than size bytes if the
1691 1691 chunk is the last chunk in the file, or the file is a socket or
1692 1692 some other type of file that sometimes reads less data than is
1693 1693 requested."""
1694 1694 assert size >= 0
1695 1695 assert limit is None or limit >= 0
1696 1696 while True:
1697 1697 if limit is None: nbytes = size
1698 1698 else: nbytes = min(limit, size)
1699 1699 s = nbytes and f.read(nbytes)
1700 1700 if not s: break
1701 1701 if limit: limit -= len(s)
1702 1702 yield s
1703 1703
1704 1704 def makedate():
1705 1705 lt = time.localtime()
1706 1706 if lt[8] == 1 and time.daylight:
1707 1707 tz = time.altzone
1708 1708 else:
1709 1709 tz = time.timezone
1710 1710 return time.mktime(lt), tz
1711 1711
1712 1712 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1713 1713 """represent a (unixtime, offset) tuple as a localized time.
1714 1714 unixtime is seconds since the epoch, and offset is the time zone's
1715 1715 number of seconds away from UTC. if timezone is false, do not
1716 1716 append time zone to string."""
1717 1717 t, tz = date or makedate()
1718 1718 if "%1" in format or "%2" in format:
1719 1719 sign = (tz > 0) and "-" or "+"
1720 1720 minutes = abs(tz) / 60
1721 1721 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1722 1722 format = format.replace("%2", "%02d" % (minutes % 60))
1723 1723 s = time.strftime(format, time.gmtime(float(t) - tz))
1724 1724 return s
1725 1725
1726 1726 def shortdate(date=None):
1727 1727 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1728 1728 return datestr(date, format='%Y-%m-%d')
1729 1729
1730 1730 def strdate(string, format, defaults=[]):
1731 1731 """parse a localized time string and return a (unixtime, offset) tuple.
1732 1732 if the string cannot be parsed, ValueError is raised."""
1733 1733 def timezone(string):
1734 1734 tz = string.split()[-1]
1735 1735 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1736 1736 sign = (tz[0] == "+") and 1 or -1
1737 1737 hours = int(tz[1:3])
1738 1738 minutes = int(tz[3:5])
1739 1739 return -sign * (hours * 60 + minutes) * 60
1740 1740 if tz == "GMT" or tz == "UTC":
1741 1741 return 0
1742 1742 return None
1743 1743
1744 1744 # NOTE: unixtime = localunixtime + offset
1745 1745 offset, date = timezone(string), string
1746 1746 if offset != None:
1747 1747 date = " ".join(string.split()[:-1])
1748 1748
1749 1749 # add missing elements from defaults
1750 1750 for part in defaults:
1751 1751 found = [True for p in part if ("%"+p) in format]
1752 1752 if not found:
1753 1753 date += "@" + defaults[part]
1754 1754 format += "@%" + part[0]
1755 1755
1756 1756 timetuple = time.strptime(date, format)
1757 1757 localunixtime = int(calendar.timegm(timetuple))
1758 1758 if offset is None:
1759 1759 # local timezone
1760 1760 unixtime = int(time.mktime(timetuple))
1761 1761 offset = unixtime - localunixtime
1762 1762 else:
1763 1763 unixtime = localunixtime + offset
1764 1764 return unixtime, offset
1765 1765
1766 1766 def parsedate(date, formats=None, defaults=None):
1767 1767 """parse a localized date/time string and return a (unixtime, offset) tuple.
1768 1768
1769 1769 The date may be a "unixtime offset" string or in one of the specified
1770 1770 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1771 1771 """
1772 1772 if not date:
1773 1773 return 0, 0
1774 1774 if isinstance(date, tuple) and len(date) == 2:
1775 1775 return date
1776 1776 if not formats:
1777 1777 formats = defaultdateformats
1778 1778 date = date.strip()
1779 1779 try:
1780 1780 when, offset = map(int, date.split(' '))
1781 1781 except ValueError:
1782 1782 # fill out defaults
1783 1783 if not defaults:
1784 1784 defaults = {}
1785 1785 now = makedate()
1786 1786 for part in "d mb yY HI M S".split():
1787 1787 if part not in defaults:
1788 1788 if part[0] in "HMS":
1789 1789 defaults[part] = "00"
1790 1790 else:
1791 1791 defaults[part] = datestr(now, "%" + part[0])
1792 1792
1793 1793 for format in formats:
1794 1794 try:
1795 1795 when, offset = strdate(date, format, defaults)
1796 1796 except (ValueError, OverflowError):
1797 1797 pass
1798 1798 else:
1799 1799 break
1800 1800 else:
1801 1801 raise Abort(_('invalid date: %r ') % date)
1802 1802 # validate explicit (probably user-specified) date and
1803 1803 # time zone offset. values must fit in signed 32 bits for
1804 1804 # current 32-bit linux runtimes. timezones go from UTC-12
1805 1805 # to UTC+14
1806 1806 if abs(when) > 0x7fffffff:
1807 1807 raise Abort(_('date exceeds 32 bits: %d') % when)
1808 1808 if offset < -50400 or offset > 43200:
1809 1809 raise Abort(_('impossible time zone offset: %d') % offset)
1810 1810 return when, offset
1811 1811
1812 1812 def matchdate(date):
1813 1813 """Return a function that matches a given date match specifier
1814 1814
1815 1815 Formats include:
1816 1816
1817 1817 '{date}' match a given date to the accuracy provided
1818 1818
1819 1819 '<{date}' on or before a given date
1820 1820
1821 1821 '>{date}' on or after a given date
1822 1822
1823 1823 """
1824 1824
1825 1825 def lower(date):
1826 1826 d = dict(mb="1", d="1")
1827 1827 return parsedate(date, extendeddateformats, d)[0]
1828 1828
1829 1829 def upper(date):
1830 1830 d = dict(mb="12", HI="23", M="59", S="59")
1831 1831 for days in "31 30 29".split():
1832 1832 try:
1833 1833 d["d"] = days
1834 1834 return parsedate(date, extendeddateformats, d)[0]
1835 1835 except:
1836 1836 pass
1837 1837 d["d"] = "28"
1838 1838 return parsedate(date, extendeddateformats, d)[0]
1839 1839
1840 1840 if date[0] == "<":
1841 1841 when = upper(date[1:])
1842 1842 return lambda x: x <= when
1843 1843 elif date[0] == ">":
1844 1844 when = lower(date[1:])
1845 1845 return lambda x: x >= when
1846 1846 elif date[0] == "-":
1847 1847 try:
1848 1848 days = int(date[1:])
1849 1849 except ValueError:
1850 1850 raise Abort(_("invalid day spec: %s") % date[1:])
1851 1851 when = makedate()[0] - days * 3600 * 24
1852 1852 return lambda x: x >= when
1853 1853 elif " to " in date:
1854 1854 a, b = date.split(" to ")
1855 1855 start, stop = lower(a), upper(b)
1856 1856 return lambda x: x >= start and x <= stop
1857 1857 else:
1858 1858 start, stop = lower(date), upper(date)
1859 1859 return lambda x: x >= start and x <= stop
1860 1860
1861 1861 def shortuser(user):
1862 1862 """Return a short representation of a user name or email address."""
1863 1863 f = user.find('@')
1864 1864 if f >= 0:
1865 1865 user = user[:f]
1866 1866 f = user.find('<')
1867 1867 if f >= 0:
1868 1868 user = user[f+1:]
1869 1869 f = user.find(' ')
1870 1870 if f >= 0:
1871 1871 user = user[:f]
1872 1872 f = user.find('.')
1873 1873 if f >= 0:
1874 1874 user = user[:f]
1875 1875 return user
1876 1876
1877 1877 def email(author):
1878 1878 '''get email of author.'''
1879 1879 r = author.find('>')
1880 1880 if r == -1: r = None
1881 1881 return author[author.find('<')+1:r]
1882 1882
1883 1883 def ellipsis(text, maxlength=400):
1884 1884 """Trim string to at most maxlength (default: 400) characters."""
1885 1885 if len(text) <= maxlength:
1886 1886 return text
1887 1887 else:
1888 1888 return "%s..." % (text[:maxlength-3])
1889 1889
1890 1890 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1891 1891 '''yield every hg repository under path, recursively.'''
1892 1892 def errhandler(err):
1893 1893 if err.filename == path:
1894 1894 raise err
1895 1895 if followsym and hasattr(os.path, 'samestat'):
1896 1896 def _add_dir_if_not_there(dirlst, dirname):
1897 1897 match = False
1898 1898 samestat = os.path.samestat
1899 1899 dirstat = os.stat(dirname)
1900 1900 for lstdirstat in dirlst:
1901 1901 if samestat(dirstat, lstdirstat):
1902 1902 match = True
1903 1903 break
1904 1904 if not match:
1905 1905 dirlst.append(dirstat)
1906 1906 return not match
1907 1907 else:
1908 1908 followsym = False
1909 1909
1910 1910 if (seen_dirs is None) and followsym:
1911 1911 seen_dirs = []
1912 1912 _add_dir_if_not_there(seen_dirs, path)
1913 1913 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1914 1914 if '.hg' in dirs:
1915 1915 yield root # found a repository
1916 1916 qroot = os.path.join(root, '.hg', 'patches')
1917 1917 if os.path.isdir(os.path.join(qroot, '.hg')):
1918 1918 yield qroot # we have a patch queue repo here
1919 1919 if recurse:
1920 1920 # avoid recursing inside the .hg directory
1921 1921 dirs.remove('.hg')
1922 1922 else:
1923 1923 dirs[:] = [] # don't descend further
1924 1924 elif followsym:
1925 1925 newdirs = []
1926 1926 for d in dirs:
1927 1927 fname = os.path.join(root, d)
1928 1928 if _add_dir_if_not_there(seen_dirs, fname):
1929 1929 if os.path.islink(fname):
1930 1930 for hgname in walkrepos(fname, True, seen_dirs):
1931 1931 yield hgname
1932 1932 else:
1933 1933 newdirs.append(d)
1934 1934 dirs[:] = newdirs
1935 1935
1936 1936 _rcpath = None
1937 1937
1938 1938 def os_rcpath():
1939 1939 '''return default os-specific hgrc search path'''
1940 1940 path = system_rcpath()
1941 1941 path.extend(user_rcpath())
1942 1942 path = [os.path.normpath(f) for f in path]
1943 1943 return path
1944 1944
1945 1945 def rcpath():
1946 1946 '''return hgrc search path. if env var HGRCPATH is set, use it.
1947 1947 for each item in path, if directory, use files ending in .rc,
1948 1948 else use item.
1949 1949 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1950 1950 if no HGRCPATH, use default os-specific path.'''
1951 1951 global _rcpath
1952 1952 if _rcpath is None:
1953 1953 if 'HGRCPATH' in os.environ:
1954 1954 _rcpath = []
1955 1955 for p in os.environ['HGRCPATH'].split(os.pathsep):
1956 1956 if not p: continue
1957 1957 if os.path.isdir(p):
1958 1958 for f, kind in osutil.listdir(p):
1959 1959 if f.endswith('.rc'):
1960 1960 _rcpath.append(os.path.join(p, f))
1961 1961 else:
1962 1962 _rcpath.append(p)
1963 1963 else:
1964 1964 _rcpath = os_rcpath()
1965 1965 return _rcpath
1966 1966
1967 1967 def bytecount(nbytes):
1968 1968 '''return byte count formatted as readable string, with units'''
1969 1969
1970 1970 units = (
1971 1971 (100, 1<<30, _('%.0f GB')),
1972 1972 (10, 1<<30, _('%.1f GB')),
1973 1973 (1, 1<<30, _('%.2f GB')),
1974 1974 (100, 1<<20, _('%.0f MB')),
1975 1975 (10, 1<<20, _('%.1f MB')),
1976 1976 (1, 1<<20, _('%.2f MB')),
1977 1977 (100, 1<<10, _('%.0f KB')),
1978 1978 (10, 1<<10, _('%.1f KB')),
1979 1979 (1, 1<<10, _('%.2f KB')),
1980 1980 (1, 1, _('%.0f bytes')),
1981 1981 )
1982 1982
1983 1983 for multiplier, divisor, format in units:
1984 1984 if nbytes >= divisor * multiplier:
1985 1985 return format % (nbytes / float(divisor))
1986 1986 return units[-1][2] % nbytes
1987 1987
1988 1988 def drop_scheme(scheme, path):
1989 1989 sc = scheme + ':'
1990 1990 if path.startswith(sc):
1991 1991 path = path[len(sc):]
1992 1992 if path.startswith('//'):
1993 1993 path = path[2:]
1994 1994 return path
1995 1995
1996 1996 def uirepr(s):
1997 1997 # Avoid double backslash in Windows path repr()
1998 1998 return repr(s).replace('\\\\', '\\')
1999 1999
2000 2000 def termwidth():
2001 2001 if 'COLUMNS' in os.environ:
2002 2002 try:
2003 2003 return int(os.environ['COLUMNS'])
2004 2004 except ValueError:
2005 2005 pass
2006 2006 try:
2007 2007 import termios, array, fcntl
2008 2008 for dev in (sys.stdout, sys.stdin):
2009 2009 try:
2010 2010 fd = dev.fileno()
2011 2011 if not os.isatty(fd):
2012 2012 continue
2013 2013 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
2014 2014 return array.array('h', arri)[1]
2015 2015 except ValueError:
2016 2016 pass
2017 2017 except ImportError:
2018 2018 pass
2019 2019 return 80
2020
2021 def iterlines(iterator):
2022 for chunk in iterator:
2023 for line in chunk.splitlines():
2024 yield line
@@ -1,130 +1,130
1 1 #!/bin/sh
2 2
3 3 hg init a
4 4 cd a
5 5 echo a > a
6 6 hg add a
7 7 echo line 1 > b
8 8 echo line 2 >> b
9 9 hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 10 hg add b
11 11 echo other 1 > c
12 12 echo other 2 >> c
13 13 echo >> c
14 14 echo other 3 >> c
15 15 hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
16 16 hg add c
17 17 hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 18 echo c >> c
19 19 hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 20 echo foo > .hg/branch
21 21 hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 22 hg co -q 3
23 23 echo other 4 >> d
24 24 hg add d
25 25 hg commit -m 'new head' -d '1500000 0' -u 'person'
26 26 hg merge -q foo
27 27 hg commit -m 'merge' -d '1500001 0' -u 'person'
28 28 # second branch starting at nullrev
29 29 hg update null
30 30 echo second > second
31 31 hg add second
32 32 hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
33 33 echo third > third
34 34 hg add third
35 35 hg commit -m third -d "2020-01-01 10:01"
36 36
37 37 # make sure user/global hgrc does not affect tests
38 38 echo '[ui]' > .hg/hgrc
39 39 echo 'logtemplate =' >> .hg/hgrc
40 40 echo 'style =' >> .hg/hgrc
41 41
42 42 echo '# default style is like normal output'
43 43 echo '# normal'
44 44 hg log > log.out
45 45 hg log --style default > style.out
46 46 diff log.out style.out
47 47 echo '# verbose'
48 48 hg log -v > log.out
49 49 hg log -v --style default > style.out
50 50 diff log.out style.out
51 51 echo '# debug'
52 52 hg log --debug > log.out
53 53 hg log --debug --style default > style.out
54 54 diff log.out style.out
55 55
56 56 echo '# revision with no copies (used to print a traceback)'
57 57 hg tip -v --template '\n'
58 58
59 59 echo '# compact style works'
60 60 hg log --style compact
61 61 hg log -v --style compact
62 62 hg log --debug --style compact
63 63
64 64 echo '# error if style not readable'
65 65 touch q
66 66 chmod 0 q
67 67 hg log --style ./q
68 68
69 69 echo '# error if no style'
70 70 hg log --style notexist
71 71
72 72 echo '# error if style missing key'
73 73 echo 'q = q' > t
74 74 hg log --style ./t
75 75
76 76 echo '# error if include fails'
77 77 echo 'changeset = q' >> t
78 78 hg log --style ./t
79 79
80 80 echo '# include works'
81 81 rm q
82 82 echo '{rev}' > q
83 83 hg log --style ./t
84 84
85 85 echo '# ui.style works'
86 86 echo '[ui]' > .hg/hgrc
87 87 echo 'style = t' >> .hg/hgrc
88 88 hg log
89 89
90 90 echo '# issue338'
91 91 hg log --style=changelog > changelog
92 92 cat changelog
93 93
94 94 echo "# keys work"
95 95 for key in author branches date desc file_adds file_dels file_mods \
96 files manifest node parents rev tags; do
96 files manifest node parents rev tags diffstat; do
97 97 for mode in '' --verbose --debug; do
98 98 hg log $mode --template "$key$mode: {$key}\n"
99 99 done
100 100 done
101 101
102 102 echo '# filters work'
103 103 hg log --template '{author|domain}\n'
104 104 hg log --template '{author|person}\n'
105 105 hg log --template '{author|user}\n'
106 106 hg log --template '{date|age}\n' > /dev/null || exit 1
107 107 hg log -l1 --template '{date|age}\n'
108 108 hg log --template '{date|date}\n'
109 109 hg log --template '{date|isodate}\n'
110 110 hg log --template '{date|isodatesec}\n'
111 111 hg log --template '{date|rfc822date}\n'
112 112 hg log --template '{desc|firstline}\n'
113 113 hg log --template '{node|short}\n'
114 114 hg log --template '<changeset author="{author|xmlescape}"/>\n'
115 115
116 116 echo '# formatnode filter works'
117 117 echo '# quiet'
118 118 hg -q log -r 0 --template '#node|formatnode#\n'
119 119 echo '# normal'
120 120 hg log -r 0 --template '#node|formatnode#\n'
121 121 echo '# verbose'
122 122 hg -v log -r 0 --template '#node|formatnode#\n'
123 123 echo '# debug'
124 124 hg --debug log -r 0 --template '#node|formatnode#\n'
125 125
126 126 echo '# error on syntax'
127 127 echo 'x = "f' >> t
128 128 hg log
129 129
130 130 echo '# done'
@@ -1,640 +1,667
1 1 created new head
2 2 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
3 3 created new head
4 4 # default style is like normal output
5 5 # normal
6 6 # verbose
7 7 # debug
8 8 # revision with no copies (used to print a traceback)
9 9
10 10 # compact style works
11 11 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
12 12 third
13 13
14 14 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
15 15 second
16 16
17 17 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
18 18 merge
19 19
20 20 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
21 21 new head
22 22
23 23 4 32a18f097fcc 1970-01-17 04:53 +0000 person
24 24 new branch
25 25
26 26 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
27 27 no user, no domain
28 28
29 29 2 97054abb4ab8 1970-01-14 21:20 +0000 other
30 30 no person
31 31
32 32 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
33 33 other 1
34 34
35 35 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
36 36 line 1
37 37
38 38 8[tip] 946e2bd9c565 2020-01-01 10:01 +0000 test
39 39 third
40 40
41 41 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
42 42 second
43 43
44 44 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
45 45 merge
46 46
47 47 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
48 48 new head
49 49
50 50 4 32a18f097fcc 1970-01-17 04:53 +0000 person
51 51 new branch
52 52
53 53 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
54 54 no user, no domain
55 55
56 56 2 97054abb4ab8 1970-01-14 21:20 +0000 other
57 57 no person
58 58
59 59 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
60 60 other 1
61 61
62 62 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
63 63 line 1
64 64
65 65 8[tip]:7,-1 946e2bd9c565 2020-01-01 10:01 +0000 test
66 66 third
67 67
68 68 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 user
69 69 second
70 70
71 71 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person
72 72 merge
73 73
74 74 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
75 75 new head
76 76
77 77 4:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person
78 78 new branch
79 79
80 80 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
81 81 no user, no domain
82 82
83 83 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other
84 84 no person
85 85
86 86 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
87 87 other 1
88 88
89 89 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
90 90 line 1
91 91
92 92 # error if style not readable
93 93 abort: Permission denied: ./q
94 94 # error if no style
95 95 abort: style not found: notexist
96 96 # error if style missing key
97 97 abort: ./t: no key named 'changeset'
98 98 # error if include fails
99 99 abort: template file ./q: Permission denied
100 100 # include works
101 101 8
102 102 7
103 103 6
104 104 5
105 105 4
106 106 3
107 107 2
108 108 1
109 109 0
110 110 # ui.style works
111 111 8
112 112 7
113 113 6
114 114 5
115 115 4
116 116 3
117 117 2
118 118 1
119 119 0
120 120 # issue338
121 121 2020-01-01 test <test>
122 122
123 123 * third:
124 124 third
125 125 [946e2bd9c565] [tip]
126 126
127 127 1970-01-12 User Name <user@hostname>
128 128
129 129 * second:
130 130 second
131 131 [29114dbae42b]
132 132
133 133 1970-01-18 person <person>
134 134
135 135 * merge
136 136 [c7b487c6c50e]
137 137
138 138 * d:
139 139 new head
140 140 [13207e5a10d9]
141 141
142 142 1970-01-17 person <person>
143 143
144 144 * new branch
145 145 [32a18f097fcc]
146 146
147 147 1970-01-16 person <person>
148 148
149 149 * c:
150 150 no user, no domain
151 151 [10e46f2dcbf4]
152 152
153 153 1970-01-14 other <other@place>
154 154
155 155 * c:
156 156 no person
157 157 [97054abb4ab8]
158 158
159 159 1970-01-13 A. N. Other <other@place>
160 160
161 161 * b:
162 162 other 1 other 2
163 163
164 164 other 3
165 165 [b608e9d1a3f0]
166 166
167 167 1970-01-12 User Name <user@hostname>
168 168
169 169 * a:
170 170 line 1 line 2
171 171 [1e4e1b8f71e0]
172 172
173 173 # keys work
174 174 author: test
175 175 author: User Name <user@hostname>
176 176 author: person
177 177 author: person
178 178 author: person
179 179 author: person
180 180 author: other@place
181 181 author: A. N. Other <other@place>
182 182 author: User Name <user@hostname>
183 183 author--verbose: test
184 184 author--verbose: User Name <user@hostname>
185 185 author--verbose: person
186 186 author--verbose: person
187 187 author--verbose: person
188 188 author--verbose: person
189 189 author--verbose: other@place
190 190 author--verbose: A. N. Other <other@place>
191 191 author--verbose: User Name <user@hostname>
192 192 author--debug: test
193 193 author--debug: User Name <user@hostname>
194 194 author--debug: person
195 195 author--debug: person
196 196 author--debug: person
197 197 author--debug: person
198 198 author--debug: other@place
199 199 author--debug: A. N. Other <other@place>
200 200 author--debug: User Name <user@hostname>
201 201 branches:
202 202 branches:
203 203 branches:
204 204 branches:
205 205 branches: foo
206 206 branches:
207 207 branches:
208 208 branches:
209 209 branches:
210 210 branches--verbose:
211 211 branches--verbose:
212 212 branches--verbose:
213 213 branches--verbose:
214 214 branches--verbose: foo
215 215 branches--verbose:
216 216 branches--verbose:
217 217 branches--verbose:
218 218 branches--verbose:
219 219 branches--debug:
220 220 branches--debug:
221 221 branches--debug:
222 222 branches--debug:
223 223 branches--debug: foo
224 224 branches--debug:
225 225 branches--debug:
226 226 branches--debug:
227 227 branches--debug:
228 228 date: 1577872860.00
229 229 date: 1000000.00
230 230 date: 1500001.00
231 231 date: 1500000.00
232 232 date: 1400000.00
233 233 date: 1300000.00
234 234 date: 1200000.00
235 235 date: 1100000.00
236 236 date: 1000000.00
237 237 date--verbose: 1577872860.00
238 238 date--verbose: 1000000.00
239 239 date--verbose: 1500001.00
240 240 date--verbose: 1500000.00
241 241 date--verbose: 1400000.00
242 242 date--verbose: 1300000.00
243 243 date--verbose: 1200000.00
244 244 date--verbose: 1100000.00
245 245 date--verbose: 1000000.00
246 246 date--debug: 1577872860.00
247 247 date--debug: 1000000.00
248 248 date--debug: 1500001.00
249 249 date--debug: 1500000.00
250 250 date--debug: 1400000.00
251 251 date--debug: 1300000.00
252 252 date--debug: 1200000.00
253 253 date--debug: 1100000.00
254 254 date--debug: 1000000.00
255 255 desc: third
256 256 desc: second
257 257 desc: merge
258 258 desc: new head
259 259 desc: new branch
260 260 desc: no user, no domain
261 261 desc: no person
262 262 desc: other 1
263 263 other 2
264 264
265 265 other 3
266 266 desc: line 1
267 267 line 2
268 268 desc--verbose: third
269 269 desc--verbose: second
270 270 desc--verbose: merge
271 271 desc--verbose: new head
272 272 desc--verbose: new branch
273 273 desc--verbose: no user, no domain
274 274 desc--verbose: no person
275 275 desc--verbose: other 1
276 276 other 2
277 277
278 278 other 3
279 279 desc--verbose: line 1
280 280 line 2
281 281 desc--debug: third
282 282 desc--debug: second
283 283 desc--debug: merge
284 284 desc--debug: new head
285 285 desc--debug: new branch
286 286 desc--debug: no user, no domain
287 287 desc--debug: no person
288 288 desc--debug: other 1
289 289 other 2
290 290
291 291 other 3
292 292 desc--debug: line 1
293 293 line 2
294 294 file_adds: third
295 295 file_adds: second
296 296 file_adds:
297 297 file_adds: d
298 298 file_adds:
299 299 file_adds:
300 300 file_adds: c
301 301 file_adds: b
302 302 file_adds: a
303 303 file_adds--verbose: third
304 304 file_adds--verbose: second
305 305 file_adds--verbose:
306 306 file_adds--verbose: d
307 307 file_adds--verbose:
308 308 file_adds--verbose:
309 309 file_adds--verbose: c
310 310 file_adds--verbose: b
311 311 file_adds--verbose: a
312 312 file_adds--debug: third
313 313 file_adds--debug: second
314 314 file_adds--debug:
315 315 file_adds--debug: d
316 316 file_adds--debug:
317 317 file_adds--debug:
318 318 file_adds--debug: c
319 319 file_adds--debug: b
320 320 file_adds--debug: a
321 321 file_dels:
322 322 file_dels:
323 323 file_dels:
324 324 file_dels:
325 325 file_dels:
326 326 file_dels:
327 327 file_dels:
328 328 file_dels:
329 329 file_dels:
330 330 file_dels--verbose:
331 331 file_dels--verbose:
332 332 file_dels--verbose:
333 333 file_dels--verbose:
334 334 file_dels--verbose:
335 335 file_dels--verbose:
336 336 file_dels--verbose:
337 337 file_dels--verbose:
338 338 file_dels--verbose:
339 339 file_dels--debug:
340 340 file_dels--debug:
341 341 file_dels--debug:
342 342 file_dels--debug:
343 343 file_dels--debug:
344 344 file_dels--debug:
345 345 file_dels--debug:
346 346 file_dels--debug:
347 347 file_dels--debug:
348 348 file_mods:
349 349 file_mods:
350 350 file_mods:
351 351 file_mods:
352 352 file_mods:
353 353 file_mods: c
354 354 file_mods:
355 355 file_mods:
356 356 file_mods:
357 357 file_mods--verbose:
358 358 file_mods--verbose:
359 359 file_mods--verbose:
360 360 file_mods--verbose:
361 361 file_mods--verbose:
362 362 file_mods--verbose: c
363 363 file_mods--verbose:
364 364 file_mods--verbose:
365 365 file_mods--verbose:
366 366 file_mods--debug:
367 367 file_mods--debug:
368 368 file_mods--debug:
369 369 file_mods--debug:
370 370 file_mods--debug:
371 371 file_mods--debug: c
372 372 file_mods--debug:
373 373 file_mods--debug:
374 374 file_mods--debug:
375 375 files: third
376 376 files: second
377 377 files:
378 378 files: d
379 379 files:
380 380 files: c
381 381 files: c
382 382 files: b
383 383 files: a
384 384 files--verbose: third
385 385 files--verbose: second
386 386 files--verbose:
387 387 files--verbose: d
388 388 files--verbose:
389 389 files--verbose: c
390 390 files--verbose: c
391 391 files--verbose: b
392 392 files--verbose: a
393 393 files--debug: third
394 394 files--debug: second
395 395 files--debug:
396 396 files--debug: d
397 397 files--debug:
398 398 files--debug: c
399 399 files--debug: c
400 400 files--debug: b
401 401 files--debug: a
402 402 manifest: 8:8a0d8faab8b2
403 403 manifest: 7:f2dbc354b94e
404 404 manifest: 6:91015e9dbdd7
405 405 manifest: 5:4dc3def4f9b4
406 406 manifest: 4:90ae8dda64e1
407 407 manifest: 3:cb5a1327723b
408 408 manifest: 2:6e0e82995c35
409 409 manifest: 1:4e8d705b1e53
410 410 manifest: 0:a0c8bcbbb45c
411 411 manifest--verbose: 8:8a0d8faab8b2
412 412 manifest--verbose: 7:f2dbc354b94e
413 413 manifest--verbose: 6:91015e9dbdd7
414 414 manifest--verbose: 5:4dc3def4f9b4
415 415 manifest--verbose: 4:90ae8dda64e1
416 416 manifest--verbose: 3:cb5a1327723b
417 417 manifest--verbose: 2:6e0e82995c35
418 418 manifest--verbose: 1:4e8d705b1e53
419 419 manifest--verbose: 0:a0c8bcbbb45c
420 420 manifest--debug: 8:8a0d8faab8b2eee97dcfccabbcb18f413c9d097b
421 421 manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
422 422 manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf
423 423 manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
424 424 manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363
425 425 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
426 426 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
427 427 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
428 428 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
429 429 node: 946e2bd9c565394777d74d9669045b39e856e3ea
430 430 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
431 431 node: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
432 432 node: 13207e5a10d9fd28ec424934298e176197f2c67f
433 433 node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
434 434 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
435 435 node: 97054abb4ab824450e9164180baf491ae0078465
436 436 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
437 437 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
438 438 node--verbose: 946e2bd9c565394777d74d9669045b39e856e3ea
439 439 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
440 440 node--verbose: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
441 441 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
442 442 node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
443 443 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
444 444 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
445 445 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
446 446 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
447 447 node--debug: 946e2bd9c565394777d74d9669045b39e856e3ea
448 448 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
449 449 node--debug: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f
450 450 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
451 451 node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4
452 452 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
453 453 node--debug: 97054abb4ab824450e9164180baf491ae0078465
454 454 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
455 455 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
456 456 parents:
457 457 parents: -1:000000000000
458 458 parents: 5:13207e5a10d9 4:32a18f097fcc
459 459 parents: 3:10e46f2dcbf4
460 460 parents:
461 461 parents:
462 462 parents:
463 463 parents:
464 464 parents:
465 465 parents--verbose:
466 466 parents--verbose: -1:000000000000
467 467 parents--verbose: 5:13207e5a10d9 4:32a18f097fcc
468 468 parents--verbose: 3:10e46f2dcbf4
469 469 parents--verbose:
470 470 parents--verbose:
471 471 parents--verbose:
472 472 parents--verbose:
473 473 parents--verbose:
474 474 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
475 475 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
476 476 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:32a18f097fcccf76ef282f62f8a85b3adf8d13c4
477 477 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
478 478 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
479 479 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
480 480 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
481 481 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
482 482 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
483 483 rev: 8
484 484 rev: 7
485 485 rev: 6
486 486 rev: 5
487 487 rev: 4
488 488 rev: 3
489 489 rev: 2
490 490 rev: 1
491 491 rev: 0
492 492 rev--verbose: 8
493 493 rev--verbose: 7
494 494 rev--verbose: 6
495 495 rev--verbose: 5
496 496 rev--verbose: 4
497 497 rev--verbose: 3
498 498 rev--verbose: 2
499 499 rev--verbose: 1
500 500 rev--verbose: 0
501 501 rev--debug: 8
502 502 rev--debug: 7
503 503 rev--debug: 6
504 504 rev--debug: 5
505 505 rev--debug: 4
506 506 rev--debug: 3
507 507 rev--debug: 2
508 508 rev--debug: 1
509 509 rev--debug: 0
510 510 tags: tip
511 511 tags:
512 512 tags:
513 513 tags:
514 514 tags:
515 515 tags:
516 516 tags:
517 517 tags:
518 518 tags:
519 519 tags--verbose: tip
520 520 tags--verbose:
521 521 tags--verbose:
522 522 tags--verbose:
523 523 tags--verbose:
524 524 tags--verbose:
525 525 tags--verbose:
526 526 tags--verbose:
527 527 tags--verbose:
528 528 tags--debug: tip
529 529 tags--debug:
530 530 tags--debug:
531 531 tags--debug:
532 532 tags--debug:
533 533 tags--debug:
534 534 tags--debug:
535 535 tags--debug:
536 536 tags--debug:
537 diffstat: 1: +1/-0
538 diffstat: 1: +1/-0
539 diffstat: 0: +0/-0
540 diffstat: 1: +1/-0
541 diffstat: 0: +0/-0
542 diffstat: 1: +1/-0
543 diffstat: 1: +4/-0
544 diffstat: 1: +2/-0
545 diffstat: 1: +1/-0
546 diffstat--verbose: 1: +1/-0
547 diffstat--verbose: 1: +1/-0
548 diffstat--verbose: 0: +0/-0
549 diffstat--verbose: 1: +1/-0
550 diffstat--verbose: 0: +0/-0
551 diffstat--verbose: 1: +1/-0
552 diffstat--verbose: 1: +4/-0
553 diffstat--verbose: 1: +2/-0
554 diffstat--verbose: 1: +1/-0
555 diffstat--debug: 1: +1/-0
556 diffstat--debug: 1: +1/-0
557 diffstat--debug: 0: +0/-0
558 diffstat--debug: 1: +1/-0
559 diffstat--debug: 0: +0/-0
560 diffstat--debug: 1: +1/-0
561 diffstat--debug: 1: +4/-0
562 diffstat--debug: 1: +2/-0
563 diffstat--debug: 1: +1/-0
537 564 # filters work
538 565
539 566 hostname
540 567
541 568
542 569
543 570
544 571 place
545 572 place
546 573 hostname
547 574 test
548 575 User Name
549 576 person
550 577 person
551 578 person
552 579 person
553 580 other
554 581 A. N. Other
555 582 User Name
556 583 test
557 584 user
558 585 person
559 586 person
560 587 person
561 588 person
562 589 other
563 590 other
564 591 user
565 592 in the future
566 593 Wed Jan 01 10:01:00 2020 +0000
567 594 Mon Jan 12 13:46:40 1970 +0000
568 595 Sun Jan 18 08:40:01 1970 +0000
569 596 Sun Jan 18 08:40:00 1970 +0000
570 597 Sat Jan 17 04:53:20 1970 +0000
571 598 Fri Jan 16 01:06:40 1970 +0000
572 599 Wed Jan 14 21:20:00 1970 +0000
573 600 Tue Jan 13 17:33:20 1970 +0000
574 601 Mon Jan 12 13:46:40 1970 +0000
575 602 2020-01-01 10:01 +0000
576 603 1970-01-12 13:46 +0000
577 604 1970-01-18 08:40 +0000
578 605 1970-01-18 08:40 +0000
579 606 1970-01-17 04:53 +0000
580 607 1970-01-16 01:06 +0000
581 608 1970-01-14 21:20 +0000
582 609 1970-01-13 17:33 +0000
583 610 1970-01-12 13:46 +0000
584 611 2020-01-01 10:01:00 +0000
585 612 1970-01-12 13:46:40 +0000
586 613 1970-01-18 08:40:01 +0000
587 614 1970-01-18 08:40:00 +0000
588 615 1970-01-17 04:53:20 +0000
589 616 1970-01-16 01:06:40 +0000
590 617 1970-01-14 21:20:00 +0000
591 618 1970-01-13 17:33:20 +0000
592 619 1970-01-12 13:46:40 +0000
593 620 Wed, 01 Jan 2020 10:01:00 +0000
594 621 Mon, 12 Jan 1970 13:46:40 +0000
595 622 Sun, 18 Jan 1970 08:40:01 +0000
596 623 Sun, 18 Jan 1970 08:40:00 +0000
597 624 Sat, 17 Jan 1970 04:53:20 +0000
598 625 Fri, 16 Jan 1970 01:06:40 +0000
599 626 Wed, 14 Jan 1970 21:20:00 +0000
600 627 Tue, 13 Jan 1970 17:33:20 +0000
601 628 Mon, 12 Jan 1970 13:46:40 +0000
602 629 third
603 630 second
604 631 merge
605 632 new head
606 633 new branch
607 634 no user, no domain
608 635 no person
609 636 other 1
610 637 line 1
611 638 946e2bd9c565
612 639 29114dbae42b
613 640 c7b487c6c50e
614 641 13207e5a10d9
615 642 32a18f097fcc
616 643 10e46f2dcbf4
617 644 97054abb4ab8
618 645 b608e9d1a3f0
619 646 1e4e1b8f71e0
620 647 <changeset author="test"/>
621 648 <changeset author="User Name &lt;user@hostname&gt;"/>
622 649 <changeset author="person"/>
623 650 <changeset author="person"/>
624 651 <changeset author="person"/>
625 652 <changeset author="person"/>
626 653 <changeset author="other@place"/>
627 654 <changeset author="A. N. Other &lt;other@place&gt;"/>
628 655 <changeset author="User Name &lt;user@hostname&gt;"/>
629 656 # formatnode filter works
630 657 # quiet
631 658 1e4e1b8f71e0
632 659 # normal
633 660 1e4e1b8f71e0
634 661 # verbose
635 662 1e4e1b8f71e0
636 663 # debug
637 664 1e4e1b8f71e05681d422154f5421e385fec3454f
638 665 # error on syntax
639 666 abort: t:3: unmatched quotes
640 667 # done
General Comments 0
You need to be logged in to leave comments. Login now