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