##// END OF EJS Templates
scmutil: create directories in a race-safe way during update...
Bryan O'Sullivan -
r18668:4034b8d5 default
parent child Browse files
Show More
@@ -1,1003 +1,1003 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases
11 11 import match as matchmod
12 12 import os, errno, re, stat, sys, glob
13 13
14 14 def nochangesfound(ui, repo, excluded=None):
15 15 '''Report no changes for push/pull, excluded is None or a list of
16 16 nodes excluded from the push/pull.
17 17 '''
18 18 secretlist = []
19 19 if excluded:
20 20 for n in excluded:
21 21 if n not in repo:
22 22 # discovery should not have included the filtered revision,
23 23 # we have to explicitly exclude it until discovery is cleanup.
24 24 continue
25 25 ctx = repo[n]
26 26 if ctx.phase() >= phases.secret and not ctx.extinct():
27 27 secretlist.append(n)
28 28
29 29 if secretlist:
30 30 ui.status(_("no changes found (ignored %d secret changesets)\n")
31 31 % len(secretlist))
32 32 else:
33 33 ui.status(_("no changes found\n"))
34 34
35 35 def checknewlabel(repo, lbl, kind):
36 36 if lbl in ['tip', '.', 'null']:
37 37 raise util.Abort(_("the name '%s' is reserved") % lbl)
38 38 for c in (':', '\0', '\n', '\r'):
39 39 if c in lbl:
40 40 raise util.Abort(_("%r cannot be used in a name") % c)
41 41 try:
42 42 int(lbl)
43 43 raise util.Abort(_("a %s cannot have an integer as its name") % kind)
44 44 except ValueError:
45 45 pass
46 46
47 47 def checkfilename(f):
48 48 '''Check that the filename f is an acceptable filename for a tracked file'''
49 49 if '\r' in f or '\n' in f:
50 50 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
51 51
52 52 def checkportable(ui, f):
53 53 '''Check if filename f is portable and warn or abort depending on config'''
54 54 checkfilename(f)
55 55 abort, warn = checkportabilityalert(ui)
56 56 if abort or warn:
57 57 msg = util.checkwinfilename(f)
58 58 if msg:
59 59 msg = "%s: %r" % (msg, f)
60 60 if abort:
61 61 raise util.Abort(msg)
62 62 ui.warn(_("warning: %s\n") % msg)
63 63
64 64 def checkportabilityalert(ui):
65 65 '''check if the user's config requests nothing, a warning, or abort for
66 66 non-portable filenames'''
67 67 val = ui.config('ui', 'portablefilenames', 'warn')
68 68 lval = val.lower()
69 69 bval = util.parsebool(val)
70 70 abort = os.name == 'nt' or lval == 'abort'
71 71 warn = bval or lval == 'warn'
72 72 if bval is None and not (warn or abort or lval == 'ignore'):
73 73 raise error.ConfigError(
74 74 _("ui.portablefilenames value is invalid ('%s')") % val)
75 75 return abort, warn
76 76
77 77 class casecollisionauditor(object):
78 78 def __init__(self, ui, abort, dirstate):
79 79 self._ui = ui
80 80 self._abort = abort
81 81 allfiles = '\0'.join(dirstate._map)
82 82 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
83 83 self._dirstate = dirstate
84 84 # The purpose of _newfiles is so that we don't complain about
85 85 # case collisions if someone were to call this object with the
86 86 # same filename twice.
87 87 self._newfiles = set()
88 88
89 89 def __call__(self, f):
90 90 fl = encoding.lower(f)
91 91 if (fl in self._loweredfiles and f not in self._dirstate and
92 92 f not in self._newfiles):
93 93 msg = _('possible case-folding collision for %s') % f
94 94 if self._abort:
95 95 raise util.Abort(msg)
96 96 self._ui.warn(_("warning: %s\n") % msg)
97 97 self._loweredfiles.add(fl)
98 98 self._newfiles.add(f)
99 99
100 100 class pathauditor(object):
101 101 '''ensure that a filesystem path contains no banned components.
102 102 the following properties of a path are checked:
103 103
104 104 - ends with a directory separator
105 105 - under top-level .hg
106 106 - starts at the root of a windows drive
107 107 - contains ".."
108 108 - traverses a symlink (e.g. a/symlink_here/b)
109 109 - inside a nested repository (a callback can be used to approve
110 110 some nested repositories, e.g., subrepositories)
111 111 '''
112 112
113 113 def __init__(self, root, callback=None):
114 114 self.audited = set()
115 115 self.auditeddir = set()
116 116 self.root = root
117 117 self.callback = callback
118 118 if os.path.lexists(root) and not util.checkcase(root):
119 119 self.normcase = util.normcase
120 120 else:
121 121 self.normcase = lambda x: x
122 122
123 123 def __call__(self, path):
124 124 '''Check the relative path.
125 125 path may contain a pattern (e.g. foodir/**.txt)'''
126 126
127 127 path = util.localpath(path)
128 128 normpath = self.normcase(path)
129 129 if normpath in self.audited:
130 130 return
131 131 # AIX ignores "/" at end of path, others raise EISDIR.
132 132 if util.endswithsep(path):
133 133 raise util.Abort(_("path ends in directory separator: %s") % path)
134 134 parts = util.splitpath(path)
135 135 if (os.path.splitdrive(path)[0]
136 136 or parts[0].lower() in ('.hg', '.hg.', '')
137 137 or os.pardir in parts):
138 138 raise util.Abort(_("path contains illegal component: %s") % path)
139 139 if '.hg' in path.lower():
140 140 lparts = [p.lower() for p in parts]
141 141 for p in '.hg', '.hg.':
142 142 if p in lparts[1:]:
143 143 pos = lparts.index(p)
144 144 base = os.path.join(*parts[:pos])
145 145 raise util.Abort(_("path '%s' is inside nested repo %r")
146 146 % (path, base))
147 147
148 148 normparts = util.splitpath(normpath)
149 149 assert len(parts) == len(normparts)
150 150
151 151 parts.pop()
152 152 normparts.pop()
153 153 prefixes = []
154 154 while parts:
155 155 prefix = os.sep.join(parts)
156 156 normprefix = os.sep.join(normparts)
157 157 if normprefix in self.auditeddir:
158 158 break
159 159 curpath = os.path.join(self.root, prefix)
160 160 try:
161 161 st = os.lstat(curpath)
162 162 except OSError, err:
163 163 # EINVAL can be raised as invalid path syntax under win32.
164 164 # They must be ignored for patterns can be checked too.
165 165 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
166 166 raise
167 167 else:
168 168 if stat.S_ISLNK(st.st_mode):
169 169 raise util.Abort(
170 170 _('path %r traverses symbolic link %r')
171 171 % (path, prefix))
172 172 elif (stat.S_ISDIR(st.st_mode) and
173 173 os.path.isdir(os.path.join(curpath, '.hg'))):
174 174 if not self.callback or not self.callback(curpath):
175 175 raise util.Abort(_("path '%s' is inside nested "
176 176 "repo %r")
177 177 % (path, prefix))
178 178 prefixes.append(normprefix)
179 179 parts.pop()
180 180 normparts.pop()
181 181
182 182 self.audited.add(normpath)
183 183 # only add prefixes to the cache after checking everything: we don't
184 184 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
185 185 self.auditeddir.update(prefixes)
186 186
187 187 def check(self, path):
188 188 try:
189 189 self(path)
190 190 return True
191 191 except (OSError, util.Abort):
192 192 return False
193 193
194 194 class abstractvfs(object):
195 195 """Abstract base class; cannot be instantiated"""
196 196
197 197 def __init__(self, *args, **kwargs):
198 198 '''Prevent instantiation; don't call this from subclasses.'''
199 199 raise NotImplementedError('attempted instantiating ' + str(type(self)))
200 200
201 201 def tryread(self, path):
202 202 '''gracefully return an empty string for missing files'''
203 203 try:
204 204 return self.read(path)
205 205 except IOError, inst:
206 206 if inst.errno != errno.ENOENT:
207 207 raise
208 208 return ""
209 209
210 210 def read(self, path):
211 211 fp = self(path, 'rb')
212 212 try:
213 213 return fp.read()
214 214 finally:
215 215 fp.close()
216 216
217 217 def write(self, path, data):
218 218 fp = self(path, 'wb')
219 219 try:
220 220 return fp.write(data)
221 221 finally:
222 222 fp.close()
223 223
224 224 def append(self, path, data):
225 225 fp = self(path, 'ab')
226 226 try:
227 227 return fp.write(data)
228 228 finally:
229 229 fp.close()
230 230
231 231 def exists(self, path=None):
232 232 return os.path.exists(self.join(path))
233 233
234 234 def isdir(self, path=None):
235 235 return os.path.isdir(self.join(path))
236 236
237 237 def makedir(self, path=None, notindexed=True):
238 238 return util.makedir(self.join(path), notindexed)
239 239
240 240 def makedirs(self, path=None, mode=None):
241 241 return util.makedirs(self.join(path), mode)
242 242
243 243 def mkdir(self, path=None):
244 244 return os.mkdir(self.join(path))
245 245
246 246 def readdir(self, path=None, stat=None, skip=None):
247 247 return osutil.listdir(self.join(path), stat, skip)
248 248
249 249 def stat(self, path=None):
250 250 return os.stat(self.join(path))
251 251
252 252 class vfs(abstractvfs):
253 253 '''Operate files relative to a base directory
254 254
255 255 This class is used to hide the details of COW semantics and
256 256 remote file access from higher level code.
257 257 '''
258 258 def __init__(self, base, audit=True, expand=False):
259 259 if expand:
260 260 base = os.path.realpath(util.expandpath(base))
261 261 self.base = base
262 262 self._setmustaudit(audit)
263 263 self.createmode = None
264 264 self._trustnlink = None
265 265
266 266 def _getmustaudit(self):
267 267 return self._audit
268 268
269 269 def _setmustaudit(self, onoff):
270 270 self._audit = onoff
271 271 if onoff:
272 272 self.audit = pathauditor(self.base)
273 273 else:
274 274 self.audit = util.always
275 275
276 276 mustaudit = property(_getmustaudit, _setmustaudit)
277 277
278 278 @util.propertycache
279 279 def _cansymlink(self):
280 280 return util.checklink(self.base)
281 281
282 282 @util.propertycache
283 283 def _chmod(self):
284 284 return util.checkexec(self.base)
285 285
286 286 def _fixfilemode(self, name):
287 287 if self.createmode is None or not self._chmod:
288 288 return
289 289 os.chmod(name, self.createmode & 0666)
290 290
291 291 def __call__(self, path, mode="r", text=False, atomictemp=False):
292 292 if self._audit:
293 293 r = util.checkosfilename(path)
294 294 if r:
295 295 raise util.Abort("%s: %r" % (r, path))
296 296 self.audit(path)
297 297 f = self.join(path)
298 298
299 299 if not text and "b" not in mode:
300 300 mode += "b" # for that other OS
301 301
302 302 nlink = -1
303 303 if mode not in ('r', 'rb'):
304 304 dirname, basename = util.split(f)
305 305 # If basename is empty, then the path is malformed because it points
306 306 # to a directory. Let the posixfile() call below raise IOError.
307 307 if basename:
308 308 if atomictemp:
309 309 if not os.path.isdir(dirname):
310 util.makedirs(dirname, self.createmode)
310 util.ensuredirs(dirname, self.createmode)
311 311 return util.atomictempfile(f, mode, self.createmode)
312 312 try:
313 313 if 'w' in mode:
314 314 util.unlink(f)
315 315 nlink = 0
316 316 else:
317 317 # nlinks() may behave differently for files on Windows
318 318 # shares if the file is open.
319 319 fd = util.posixfile(f)
320 320 nlink = util.nlinks(f)
321 321 if nlink < 1:
322 322 nlink = 2 # force mktempcopy (issue1922)
323 323 fd.close()
324 324 except (OSError, IOError), e:
325 325 if e.errno != errno.ENOENT:
326 326 raise
327 327 nlink = 0
328 328 if not os.path.isdir(dirname):
329 util.makedirs(dirname, self.createmode)
329 util.ensuredirs(dirname, self.createmode)
330 330 if nlink > 0:
331 331 if self._trustnlink is None:
332 332 self._trustnlink = nlink > 1 or util.checknlink(f)
333 333 if nlink > 1 or not self._trustnlink:
334 334 util.rename(util.mktempcopy(f), f)
335 335 fp = util.posixfile(f, mode)
336 336 if nlink == 0:
337 337 self._fixfilemode(f)
338 338 return fp
339 339
340 340 def symlink(self, src, dst):
341 341 self.audit(dst)
342 342 linkname = self.join(dst)
343 343 try:
344 344 os.unlink(linkname)
345 345 except OSError:
346 346 pass
347 347
348 348 dirname = os.path.dirname(linkname)
349 349 if not os.path.exists(dirname):
350 util.makedirs(dirname, self.createmode)
350 util.ensuredirs(dirname, self.createmode)
351 351
352 352 if self._cansymlink:
353 353 try:
354 354 os.symlink(src, linkname)
355 355 except OSError, err:
356 356 raise OSError(err.errno, _('could not symlink to %r: %s') %
357 357 (src, err.strerror), linkname)
358 358 else:
359 359 self.write(dst, src)
360 360
361 361 def join(self, path):
362 362 if path:
363 363 return os.path.join(self.base, path)
364 364 else:
365 365 return self.base
366 366
367 367 opener = vfs
368 368
369 369 class auditvfs(object):
370 370 def __init__(self, vfs):
371 371 self.vfs = vfs
372 372
373 373 def _getmustaudit(self):
374 374 return self.vfs.mustaudit
375 375
376 376 def _setmustaudit(self, onoff):
377 377 self.vfs.mustaudit = onoff
378 378
379 379 mustaudit = property(_getmustaudit, _setmustaudit)
380 380
381 381 class filtervfs(abstractvfs, auditvfs):
382 382 '''Wrapper vfs for filtering filenames with a function.'''
383 383
384 384 def __init__(self, vfs, filter):
385 385 auditvfs.__init__(self, vfs)
386 386 self._filter = filter
387 387
388 388 def __call__(self, path, *args, **kwargs):
389 389 return self.vfs(self._filter(path), *args, **kwargs)
390 390
391 391 def join(self, path):
392 392 if path:
393 393 return self.vfs.join(self._filter(path))
394 394 else:
395 395 return self.vfs.join(path)
396 396
397 397 filteropener = filtervfs
398 398
399 399 class readonlyvfs(abstractvfs, auditvfs):
400 400 '''Wrapper vfs preventing any writing.'''
401 401
402 402 def __init__(self, vfs):
403 403 auditvfs.__init__(self, vfs)
404 404
405 405 def __call__(self, path, mode='r', *args, **kw):
406 406 if mode not in ('r', 'rb'):
407 407 raise util.Abort('this vfs is read only')
408 408 return self.vfs(path, mode, *args, **kw)
409 409
410 410
411 411 def canonpath(root, cwd, myname, auditor=None):
412 412 '''return the canonical path of myname, given cwd and root'''
413 413 if util.endswithsep(root):
414 414 rootsep = root
415 415 else:
416 416 rootsep = root + os.sep
417 417 name = myname
418 418 if not os.path.isabs(name):
419 419 name = os.path.join(root, cwd, name)
420 420 name = os.path.normpath(name)
421 421 if auditor is None:
422 422 auditor = pathauditor(root)
423 423 if name != rootsep and name.startswith(rootsep):
424 424 name = name[len(rootsep):]
425 425 auditor(name)
426 426 return util.pconvert(name)
427 427 elif name == root:
428 428 return ''
429 429 else:
430 430 # Determine whether `name' is in the hierarchy at or beneath `root',
431 431 # by iterating name=dirname(name) until that causes no change (can't
432 432 # check name == '/', because that doesn't work on windows). The list
433 433 # `rel' holds the reversed list of components making up the relative
434 434 # file name we want.
435 435 rel = []
436 436 while True:
437 437 try:
438 438 s = util.samefile(name, root)
439 439 except OSError:
440 440 s = False
441 441 if s:
442 442 if not rel:
443 443 # name was actually the same as root (maybe a symlink)
444 444 return ''
445 445 rel.reverse()
446 446 name = os.path.join(*rel)
447 447 auditor(name)
448 448 return util.pconvert(name)
449 449 dirname, basename = util.split(name)
450 450 rel.append(basename)
451 451 if dirname == name:
452 452 break
453 453 name = dirname
454 454
455 455 raise util.Abort(_("%s not under root '%s'") % (myname, root))
456 456
457 457 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
458 458 '''yield every hg repository under path, always recursively.
459 459 The recurse flag will only control recursion into repo working dirs'''
460 460 def errhandler(err):
461 461 if err.filename == path:
462 462 raise err
463 463 samestat = getattr(os.path, 'samestat', None)
464 464 if followsym and samestat is not None:
465 465 def adddir(dirlst, dirname):
466 466 match = False
467 467 dirstat = os.stat(dirname)
468 468 for lstdirstat in dirlst:
469 469 if samestat(dirstat, lstdirstat):
470 470 match = True
471 471 break
472 472 if not match:
473 473 dirlst.append(dirstat)
474 474 return not match
475 475 else:
476 476 followsym = False
477 477
478 478 if (seen_dirs is None) and followsym:
479 479 seen_dirs = []
480 480 adddir(seen_dirs, path)
481 481 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
482 482 dirs.sort()
483 483 if '.hg' in dirs:
484 484 yield root # found a repository
485 485 qroot = os.path.join(root, '.hg', 'patches')
486 486 if os.path.isdir(os.path.join(qroot, '.hg')):
487 487 yield qroot # we have a patch queue repo here
488 488 if recurse:
489 489 # avoid recursing inside the .hg directory
490 490 dirs.remove('.hg')
491 491 else:
492 492 dirs[:] = [] # don't descend further
493 493 elif followsym:
494 494 newdirs = []
495 495 for d in dirs:
496 496 fname = os.path.join(root, d)
497 497 if adddir(seen_dirs, fname):
498 498 if os.path.islink(fname):
499 499 for hgname in walkrepos(fname, True, seen_dirs):
500 500 yield hgname
501 501 else:
502 502 newdirs.append(d)
503 503 dirs[:] = newdirs
504 504
505 505 def osrcpath():
506 506 '''return default os-specific hgrc search path'''
507 507 path = systemrcpath()
508 508 path.extend(userrcpath())
509 509 path = [os.path.normpath(f) for f in path]
510 510 return path
511 511
512 512 _rcpath = None
513 513
514 514 def rcpath():
515 515 '''return hgrc search path. if env var HGRCPATH is set, use it.
516 516 for each item in path, if directory, use files ending in .rc,
517 517 else use item.
518 518 make HGRCPATH empty to only look in .hg/hgrc of current repo.
519 519 if no HGRCPATH, use default os-specific path.'''
520 520 global _rcpath
521 521 if _rcpath is None:
522 522 if 'HGRCPATH' in os.environ:
523 523 _rcpath = []
524 524 for p in os.environ['HGRCPATH'].split(os.pathsep):
525 525 if not p:
526 526 continue
527 527 p = util.expandpath(p)
528 528 if os.path.isdir(p):
529 529 for f, kind in osutil.listdir(p):
530 530 if f.endswith('.rc'):
531 531 _rcpath.append(os.path.join(p, f))
532 532 else:
533 533 _rcpath.append(p)
534 534 else:
535 535 _rcpath = osrcpath()
536 536 return _rcpath
537 537
538 538 if os.name != 'nt':
539 539
540 540 def rcfiles(path):
541 541 rcs = [os.path.join(path, 'hgrc')]
542 542 rcdir = os.path.join(path, 'hgrc.d')
543 543 try:
544 544 rcs.extend([os.path.join(rcdir, f)
545 545 for f, kind in osutil.listdir(rcdir)
546 546 if f.endswith(".rc")])
547 547 except OSError:
548 548 pass
549 549 return rcs
550 550
551 551 def systemrcpath():
552 552 path = []
553 553 if sys.platform == 'plan9':
554 554 root = 'lib/mercurial'
555 555 else:
556 556 root = 'etc/mercurial'
557 557 # old mod_python does not set sys.argv
558 558 if len(getattr(sys, 'argv', [])) > 0:
559 559 p = os.path.dirname(os.path.dirname(sys.argv[0]))
560 560 path.extend(rcfiles(os.path.join(p, root)))
561 561 path.extend(rcfiles('/' + root))
562 562 return path
563 563
564 564 def userrcpath():
565 565 if sys.platform == 'plan9':
566 566 return [os.environ['home'] + '/lib/hgrc']
567 567 else:
568 568 return [os.path.expanduser('~/.hgrc')]
569 569
570 570 else:
571 571
572 572 import _winreg
573 573
574 574 def systemrcpath():
575 575 '''return default os-specific hgrc search path'''
576 576 rcpath = []
577 577 filename = util.executablepath()
578 578 # Use mercurial.ini found in directory with hg.exe
579 579 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
580 580 if os.path.isfile(progrc):
581 581 rcpath.append(progrc)
582 582 return rcpath
583 583 # Use hgrc.d found in directory with hg.exe
584 584 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
585 585 if os.path.isdir(progrcd):
586 586 for f, kind in osutil.listdir(progrcd):
587 587 if f.endswith('.rc'):
588 588 rcpath.append(os.path.join(progrcd, f))
589 589 return rcpath
590 590 # else look for a system rcpath in the registry
591 591 value = util.lookupreg('SOFTWARE\\Mercurial', None,
592 592 _winreg.HKEY_LOCAL_MACHINE)
593 593 if not isinstance(value, str) or not value:
594 594 return rcpath
595 595 value = util.localpath(value)
596 596 for p in value.split(os.pathsep):
597 597 if p.lower().endswith('mercurial.ini'):
598 598 rcpath.append(p)
599 599 elif os.path.isdir(p):
600 600 for f, kind in osutil.listdir(p):
601 601 if f.endswith('.rc'):
602 602 rcpath.append(os.path.join(p, f))
603 603 return rcpath
604 604
605 605 def userrcpath():
606 606 '''return os-specific hgrc search path to the user dir'''
607 607 home = os.path.expanduser('~')
608 608 path = [os.path.join(home, 'mercurial.ini'),
609 609 os.path.join(home, '.hgrc')]
610 610 userprofile = os.environ.get('USERPROFILE')
611 611 if userprofile:
612 612 path.append(os.path.join(userprofile, 'mercurial.ini'))
613 613 path.append(os.path.join(userprofile, '.hgrc'))
614 614 return path
615 615
616 616 def revsingle(repo, revspec, default='.'):
617 617 if not revspec:
618 618 return repo[default]
619 619
620 620 l = revrange(repo, [revspec])
621 621 if len(l) < 1:
622 622 raise util.Abort(_('empty revision set'))
623 623 return repo[l[-1]]
624 624
625 625 def revpair(repo, revs):
626 626 if not revs:
627 627 return repo.dirstate.p1(), None
628 628
629 629 l = revrange(repo, revs)
630 630
631 631 if len(l) == 0:
632 632 if revs:
633 633 raise util.Abort(_('empty revision range'))
634 634 return repo.dirstate.p1(), None
635 635
636 636 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
637 637 return repo.lookup(l[0]), None
638 638
639 639 return repo.lookup(l[0]), repo.lookup(l[-1])
640 640
641 641 _revrangesep = ':'
642 642
643 643 def revrange(repo, revs):
644 644 """Yield revision as strings from a list of revision specifications."""
645 645
646 646 def revfix(repo, val, defval):
647 647 if not val and val != 0 and defval is not None:
648 648 return defval
649 649 return repo[val].rev()
650 650
651 651 seen, l = set(), []
652 652 for spec in revs:
653 653 if l and not seen:
654 654 seen = set(l)
655 655 # attempt to parse old-style ranges first to deal with
656 656 # things like old-tag which contain query metacharacters
657 657 try:
658 658 if isinstance(spec, int):
659 659 seen.add(spec)
660 660 l.append(spec)
661 661 continue
662 662
663 663 if _revrangesep in spec:
664 664 start, end = spec.split(_revrangesep, 1)
665 665 start = revfix(repo, start, 0)
666 666 end = revfix(repo, end, len(repo) - 1)
667 667 if end == nullrev and start <= 0:
668 668 start = nullrev
669 669 rangeiter = repo.changelog.revs(start, end)
670 670 if not seen and not l:
671 671 # by far the most common case: revs = ["-1:0"]
672 672 l = list(rangeiter)
673 673 # defer syncing seen until next iteration
674 674 continue
675 675 newrevs = set(rangeiter)
676 676 if seen:
677 677 newrevs.difference_update(seen)
678 678 seen.update(newrevs)
679 679 else:
680 680 seen = newrevs
681 681 l.extend(sorted(newrevs, reverse=start > end))
682 682 continue
683 683 elif spec and spec in repo: # single unquoted rev
684 684 rev = revfix(repo, spec, None)
685 685 if rev in seen:
686 686 continue
687 687 seen.add(rev)
688 688 l.append(rev)
689 689 continue
690 690 except error.RepoLookupError:
691 691 pass
692 692
693 693 # fall through to new-style queries if old-style fails
694 694 m = revset.match(repo.ui, spec)
695 695 dl = [r for r in m(repo, list(repo)) if r not in seen]
696 696 l.extend(dl)
697 697 seen.update(dl)
698 698
699 699 return l
700 700
701 701 def expandpats(pats):
702 702 if not util.expandglobs:
703 703 return list(pats)
704 704 ret = []
705 705 for p in pats:
706 706 kind, name = matchmod._patsplit(p, None)
707 707 if kind is None:
708 708 try:
709 709 globbed = glob.glob(name)
710 710 except re.error:
711 711 globbed = [name]
712 712 if globbed:
713 713 ret.extend(globbed)
714 714 continue
715 715 ret.append(p)
716 716 return ret
717 717
718 718 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
719 719 if pats == ("",):
720 720 pats = []
721 721 if not globbed and default == 'relpath':
722 722 pats = expandpats(pats or [])
723 723
724 724 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
725 725 default)
726 726 def badfn(f, msg):
727 727 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
728 728 m.bad = badfn
729 729 return m, pats
730 730
731 731 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
732 732 return matchandpats(ctx, pats, opts, globbed, default)[0]
733 733
734 734 def matchall(repo):
735 735 return matchmod.always(repo.root, repo.getcwd())
736 736
737 737 def matchfiles(repo, files):
738 738 return matchmod.exact(repo.root, repo.getcwd(), files)
739 739
740 740 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
741 741 if dry_run is None:
742 742 dry_run = opts.get('dry_run')
743 743 if similarity is None:
744 744 similarity = float(opts.get('similarity') or 0)
745 745 # we'd use status here, except handling of symlinks and ignore is tricky
746 746 added, unknown, deleted, removed = [], [], [], []
747 747 audit_path = pathauditor(repo.root)
748 748 m = match(repo[None], pats, opts)
749 749 rejected = []
750 750 m.bad = lambda x, y: rejected.append(x)
751 751
752 752 ctx = repo[None]
753 753 walkresults = repo.dirstate.walk(m, sorted(ctx.substate), True, False)
754 754 for abs in sorted(walkresults):
755 755 st = walkresults[abs]
756 756 dstate = repo.dirstate[abs]
757 757 if dstate == '?' and audit_path.check(abs):
758 758 unknown.append(abs)
759 759 if repo.ui.verbose or not m.exact(abs):
760 760 rel = m.rel(abs)
761 761 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
762 762 elif (dstate != 'r' and (not st or
763 763 (stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode)))):
764 764 deleted.append(abs)
765 765 if repo.ui.verbose or not m.exact(abs):
766 766 rel = m.rel(abs)
767 767 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
768 768 # for finding renames
769 769 elif dstate == 'r':
770 770 removed.append(abs)
771 771 elif dstate == 'a':
772 772 added.append(abs)
773 773 copies = {}
774 774 if similarity > 0:
775 775 for old, new, score in similar.findrenames(repo,
776 776 added + unknown, removed + deleted, similarity):
777 777 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
778 778 repo.ui.status(_('recording removal of %s as rename to %s '
779 779 '(%d%% similar)\n') %
780 780 (m.rel(old), m.rel(new), score * 100))
781 781 copies[new] = old
782 782
783 783 if not dry_run:
784 784 wctx = repo[None]
785 785 wlock = repo.wlock()
786 786 try:
787 787 wctx.forget(deleted)
788 788 wctx.add(unknown)
789 789 for new, old in copies.iteritems():
790 790 wctx.copy(old, new)
791 791 finally:
792 792 wlock.release()
793 793
794 794 for f in rejected:
795 795 if f in m.files():
796 796 return 1
797 797 return 0
798 798
799 799 def updatedir(ui, repo, patches, similarity=0):
800 800 '''Update dirstate after patch application according to metadata'''
801 801 if not patches:
802 802 return []
803 803 copies = []
804 804 removes = set()
805 805 cfiles = patches.keys()
806 806 cwd = repo.getcwd()
807 807 if cwd:
808 808 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
809 809 for f in patches:
810 810 gp = patches[f]
811 811 if not gp:
812 812 continue
813 813 if gp.op == 'RENAME':
814 814 copies.append((gp.oldpath, gp.path))
815 815 removes.add(gp.oldpath)
816 816 elif gp.op == 'COPY':
817 817 copies.append((gp.oldpath, gp.path))
818 818 elif gp.op == 'DELETE':
819 819 removes.add(gp.path)
820 820
821 821 wctx = repo[None]
822 822 for src, dst in copies:
823 823 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
824 824 if (not similarity) and removes:
825 825 wctx.remove(sorted(removes), True)
826 826
827 827 for f in patches:
828 828 gp = patches[f]
829 829 if gp and gp.mode:
830 830 islink, isexec = gp.mode
831 831 dst = repo.wjoin(gp.path)
832 832 # patch won't create empty files
833 833 if gp.op == 'ADD' and not os.path.lexists(dst):
834 834 flags = (isexec and 'x' or '') + (islink and 'l' or '')
835 835 repo.wwrite(gp.path, '', flags)
836 836 util.setflags(dst, islink, isexec)
837 837 addremove(repo, cfiles, similarity=similarity)
838 838 files = patches.keys()
839 839 files.extend([r for r in removes if r not in files])
840 840 return sorted(files)
841 841
842 842 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
843 843 """Update the dirstate to reflect the intent of copying src to dst. For
844 844 different reasons it might not end with dst being marked as copied from src.
845 845 """
846 846 origsrc = repo.dirstate.copied(src) or src
847 847 if dst == origsrc: # copying back a copy?
848 848 if repo.dirstate[dst] not in 'mn' and not dryrun:
849 849 repo.dirstate.normallookup(dst)
850 850 else:
851 851 if repo.dirstate[origsrc] == 'a' and origsrc == src:
852 852 if not ui.quiet:
853 853 ui.warn(_("%s has not been committed yet, so no copy "
854 854 "data will be stored for %s.\n")
855 855 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
856 856 if repo.dirstate[dst] in '?r' and not dryrun:
857 857 wctx.add([dst])
858 858 elif not dryrun:
859 859 wctx.copy(origsrc, dst)
860 860
861 861 def readrequires(opener, supported):
862 862 '''Reads and parses .hg/requires and checks if all entries found
863 863 are in the list of supported features.'''
864 864 requirements = set(opener.read("requires").splitlines())
865 865 missings = []
866 866 for r in requirements:
867 867 if r not in supported:
868 868 if not r or not r[0].isalnum():
869 869 raise error.RequirementError(_(".hg/requires file is corrupt"))
870 870 missings.append(r)
871 871 missings.sort()
872 872 if missings:
873 873 raise error.RequirementError(
874 874 _("unknown repository format: requires features '%s' (upgrade "
875 875 "Mercurial)") % "', '".join(missings))
876 876 return requirements
877 877
878 878 class filecacheentry(object):
879 879 def __init__(self, path, stat=True):
880 880 self.path = path
881 881 self.cachestat = None
882 882 self._cacheable = None
883 883
884 884 if stat:
885 885 self.cachestat = filecacheentry.stat(self.path)
886 886
887 887 if self.cachestat:
888 888 self._cacheable = self.cachestat.cacheable()
889 889 else:
890 890 # None means we don't know yet
891 891 self._cacheable = None
892 892
893 893 def refresh(self):
894 894 if self.cacheable():
895 895 self.cachestat = filecacheentry.stat(self.path)
896 896
897 897 def cacheable(self):
898 898 if self._cacheable is not None:
899 899 return self._cacheable
900 900
901 901 # we don't know yet, assume it is for now
902 902 return True
903 903
904 904 def changed(self):
905 905 # no point in going further if we can't cache it
906 906 if not self.cacheable():
907 907 return True
908 908
909 909 newstat = filecacheentry.stat(self.path)
910 910
911 911 # we may not know if it's cacheable yet, check again now
912 912 if newstat and self._cacheable is None:
913 913 self._cacheable = newstat.cacheable()
914 914
915 915 # check again
916 916 if not self._cacheable:
917 917 return True
918 918
919 919 if self.cachestat != newstat:
920 920 self.cachestat = newstat
921 921 return True
922 922 else:
923 923 return False
924 924
925 925 @staticmethod
926 926 def stat(path):
927 927 try:
928 928 return util.cachestat(path)
929 929 except OSError, e:
930 930 if e.errno != errno.ENOENT:
931 931 raise
932 932
933 933 class filecache(object):
934 934 '''A property like decorator that tracks a file under .hg/ for updates.
935 935
936 936 Records stat info when called in _filecache.
937 937
938 938 On subsequent calls, compares old stat info with new info, and recreates
939 939 the object when needed, updating the new stat info in _filecache.
940 940
941 941 Mercurial either atomic renames or appends for files under .hg,
942 942 so to ensure the cache is reliable we need the filesystem to be able
943 943 to tell us if a file has been replaced. If it can't, we fallback to
944 944 recreating the object on every call (essentially the same behaviour as
945 945 propertycache).'''
946 946 def __init__(self, path):
947 947 self.path = path
948 948
949 949 def join(self, obj, fname):
950 950 """Used to compute the runtime path of the cached file.
951 951
952 952 Users should subclass filecache and provide their own version of this
953 953 function to call the appropriate join function on 'obj' (an instance
954 954 of the class that its member function was decorated).
955 955 """
956 956 return obj.join(fname)
957 957
958 958 def __call__(self, func):
959 959 self.func = func
960 960 self.name = func.__name__
961 961 return self
962 962
963 963 def __get__(self, obj, type=None):
964 964 # do we need to check if the file changed?
965 965 if self.name in obj.__dict__:
966 966 assert self.name in obj._filecache, self.name
967 967 return obj.__dict__[self.name]
968 968
969 969 entry = obj._filecache.get(self.name)
970 970
971 971 if entry:
972 972 if entry.changed():
973 973 entry.obj = self.func(obj)
974 974 else:
975 975 path = self.join(obj, self.path)
976 976
977 977 # We stat -before- creating the object so our cache doesn't lie if
978 978 # a writer modified between the time we read and stat
979 979 entry = filecacheentry(path)
980 980 entry.obj = self.func(obj)
981 981
982 982 obj._filecache[self.name] = entry
983 983
984 984 obj.__dict__[self.name] = entry.obj
985 985 return entry.obj
986 986
987 987 def __set__(self, obj, value):
988 988 if self.name not in obj._filecache:
989 989 # we add an entry for the missing value because X in __dict__
990 990 # implies X in _filecache
991 991 ce = filecacheentry(self.join(obj, self.path), False)
992 992 obj._filecache[self.name] = ce
993 993 else:
994 994 ce = obj._filecache[self.name]
995 995
996 996 ce.obj = value # update cached copy
997 997 obj.__dict__[self.name] = value # update copy returned by obj.x
998 998
999 999 def __delete__(self, obj):
1000 1000 try:
1001 1001 del obj.__dict__[self.name]
1002 1002 except KeyError:
1003 1003 raise AttributeError(self.name)
@@ -1,1854 +1,1864 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding, collections
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, datetime, calendar, textwrap, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 27 cachestat = platform.cachestat
28 28 checkexec = platform.checkexec
29 29 checklink = platform.checklink
30 30 copymode = platform.copymode
31 31 executablepath = platform.executablepath
32 32 expandglobs = platform.expandglobs
33 33 explainexit = platform.explainexit
34 34 findexe = platform.findexe
35 35 gethgcmd = platform.gethgcmd
36 36 getuser = platform.getuser
37 37 groupmembers = platform.groupmembers
38 38 groupname = platform.groupname
39 39 hidewindow = platform.hidewindow
40 40 isexec = platform.isexec
41 41 isowner = platform.isowner
42 42 localpath = platform.localpath
43 43 lookupreg = platform.lookupreg
44 44 makedir = platform.makedir
45 45 nlinks = platform.nlinks
46 46 normpath = platform.normpath
47 47 normcase = platform.normcase
48 48 openhardlinks = platform.openhardlinks
49 49 oslink = platform.oslink
50 50 parsepatchoutput = platform.parsepatchoutput
51 51 pconvert = platform.pconvert
52 52 popen = platform.popen
53 53 posixfile = platform.posixfile
54 54 quotecommand = platform.quotecommand
55 55 realpath = platform.realpath
56 56 rename = platform.rename
57 57 samedevice = platform.samedevice
58 58 samefile = platform.samefile
59 59 samestat = platform.samestat
60 60 setbinary = platform.setbinary
61 61 setflags = platform.setflags
62 62 setsignalhandler = platform.setsignalhandler
63 63 shellquote = platform.shellquote
64 64 spawndetached = platform.spawndetached
65 65 split = platform.split
66 66 sshargs = platform.sshargs
67 67 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
68 68 termwidth = platform.termwidth
69 69 testpid = platform.testpid
70 70 umask = platform.umask
71 71 unlink = platform.unlink
72 72 unlinkpath = platform.unlinkpath
73 73 username = platform.username
74 74
75 75 # Python compatibility
76 76
77 77 _notset = object()
78 78
79 79 def safehasattr(thing, attr):
80 80 return getattr(thing, attr, _notset) is not _notset
81 81
82 82 def sha1(s=''):
83 83 '''
84 84 Low-overhead wrapper around Python's SHA support
85 85
86 86 >>> f = _fastsha1
87 87 >>> a = sha1()
88 88 >>> a = f()
89 89 >>> a.hexdigest()
90 90 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
91 91 '''
92 92
93 93 return _fastsha1(s)
94 94
95 95 def _fastsha1(s=''):
96 96 # This function will import sha1 from hashlib or sha (whichever is
97 97 # available) and overwrite itself with it on the first call.
98 98 # Subsequent calls will go directly to the imported function.
99 99 if sys.version_info >= (2, 5):
100 100 from hashlib import sha1 as _sha1
101 101 else:
102 102 from sha import sha as _sha1
103 103 global _fastsha1, sha1
104 104 _fastsha1 = sha1 = _sha1
105 105 return _sha1(s)
106 106
107 107 try:
108 108 buffer = buffer
109 109 except NameError:
110 110 if sys.version_info[0] < 3:
111 111 def buffer(sliceable, offset=0):
112 112 return sliceable[offset:]
113 113 else:
114 114 def buffer(sliceable, offset=0):
115 115 return memoryview(sliceable)[offset:]
116 116
117 117 import subprocess
118 118 closefds = os.name == 'posix'
119 119
120 120 def popen2(cmd, env=None, newlines=False):
121 121 # Setting bufsize to -1 lets the system decide the buffer size.
122 122 # The default for bufsize is 0, meaning unbuffered. This leads to
123 123 # poor performance on Mac OS X: http://bugs.python.org/issue4194
124 124 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
125 125 close_fds=closefds,
126 126 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
127 127 universal_newlines=newlines,
128 128 env=env)
129 129 return p.stdin, p.stdout
130 130
131 131 def popen3(cmd, env=None, newlines=False):
132 132 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
133 133 close_fds=closefds,
134 134 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
135 135 stderr=subprocess.PIPE,
136 136 universal_newlines=newlines,
137 137 env=env)
138 138 return p.stdin, p.stdout, p.stderr
139 139
140 140 def version():
141 141 """Return version information if available."""
142 142 try:
143 143 import __version__
144 144 return __version__.version
145 145 except ImportError:
146 146 return 'unknown'
147 147
148 148 # used by parsedate
149 149 defaultdateformats = (
150 150 '%Y-%m-%d %H:%M:%S',
151 151 '%Y-%m-%d %I:%M:%S%p',
152 152 '%Y-%m-%d %H:%M',
153 153 '%Y-%m-%d %I:%M%p',
154 154 '%Y-%m-%d',
155 155 '%m-%d',
156 156 '%m/%d',
157 157 '%m/%d/%y',
158 158 '%m/%d/%Y',
159 159 '%a %b %d %H:%M:%S %Y',
160 160 '%a %b %d %I:%M:%S%p %Y',
161 161 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
162 162 '%b %d %H:%M:%S %Y',
163 163 '%b %d %I:%M:%S%p %Y',
164 164 '%b %d %H:%M:%S',
165 165 '%b %d %I:%M:%S%p',
166 166 '%b %d %H:%M',
167 167 '%b %d %I:%M%p',
168 168 '%b %d %Y',
169 169 '%b %d',
170 170 '%H:%M:%S',
171 171 '%I:%M:%S%p',
172 172 '%H:%M',
173 173 '%I:%M%p',
174 174 )
175 175
176 176 extendeddateformats = defaultdateformats + (
177 177 "%Y",
178 178 "%Y-%m",
179 179 "%b",
180 180 "%b %Y",
181 181 )
182 182
183 183 def cachefunc(func):
184 184 '''cache the result of function calls'''
185 185 # XXX doesn't handle keywords args
186 186 cache = {}
187 187 if func.func_code.co_argcount == 1:
188 188 # we gain a small amount of time because
189 189 # we don't need to pack/unpack the list
190 190 def f(arg):
191 191 if arg not in cache:
192 192 cache[arg] = func(arg)
193 193 return cache[arg]
194 194 else:
195 195 def f(*args):
196 196 if args not in cache:
197 197 cache[args] = func(*args)
198 198 return cache[args]
199 199
200 200 return f
201 201
202 202 try:
203 203 collections.deque.remove
204 204 deque = collections.deque
205 205 except AttributeError:
206 206 # python 2.4 lacks deque.remove
207 207 class deque(collections.deque):
208 208 def remove(self, val):
209 209 for i, v in enumerate(self):
210 210 if v == val:
211 211 del self[i]
212 212 break
213 213
214 214 class lrucachedict(object):
215 215 '''cache most recent gets from or sets to this dictionary'''
216 216 def __init__(self, maxsize):
217 217 self._cache = {}
218 218 self._maxsize = maxsize
219 219 self._order = deque()
220 220
221 221 def __getitem__(self, key):
222 222 value = self._cache[key]
223 223 self._order.remove(key)
224 224 self._order.append(key)
225 225 return value
226 226
227 227 def __setitem__(self, key, value):
228 228 if key not in self._cache:
229 229 if len(self._cache) >= self._maxsize:
230 230 del self._cache[self._order.popleft()]
231 231 else:
232 232 self._order.remove(key)
233 233 self._cache[key] = value
234 234 self._order.append(key)
235 235
236 236 def __contains__(self, key):
237 237 return key in self._cache
238 238
239 239 def lrucachefunc(func):
240 240 '''cache most recent results of function calls'''
241 241 cache = {}
242 242 order = deque()
243 243 if func.func_code.co_argcount == 1:
244 244 def f(arg):
245 245 if arg not in cache:
246 246 if len(cache) > 20:
247 247 del cache[order.popleft()]
248 248 cache[arg] = func(arg)
249 249 else:
250 250 order.remove(arg)
251 251 order.append(arg)
252 252 return cache[arg]
253 253 else:
254 254 def f(*args):
255 255 if args not in cache:
256 256 if len(cache) > 20:
257 257 del cache[order.popleft()]
258 258 cache[args] = func(*args)
259 259 else:
260 260 order.remove(args)
261 261 order.append(args)
262 262 return cache[args]
263 263
264 264 return f
265 265
266 266 class propertycache(object):
267 267 def __init__(self, func):
268 268 self.func = func
269 269 self.name = func.__name__
270 270 def __get__(self, obj, type=None):
271 271 result = self.func(obj)
272 272 self.cachevalue(obj, result)
273 273 return result
274 274
275 275 def cachevalue(self, obj, value):
276 276 setattr(obj, self.name, value)
277 277
278 278 def pipefilter(s, cmd):
279 279 '''filter string S through command CMD, returning its output'''
280 280 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
281 281 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
282 282 pout, perr = p.communicate(s)
283 283 return pout
284 284
285 285 def tempfilter(s, cmd):
286 286 '''filter string S through a pair of temporary files with CMD.
287 287 CMD is used as a template to create the real command to be run,
288 288 with the strings INFILE and OUTFILE replaced by the real names of
289 289 the temporary files generated.'''
290 290 inname, outname = None, None
291 291 try:
292 292 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
293 293 fp = os.fdopen(infd, 'wb')
294 294 fp.write(s)
295 295 fp.close()
296 296 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
297 297 os.close(outfd)
298 298 cmd = cmd.replace('INFILE', inname)
299 299 cmd = cmd.replace('OUTFILE', outname)
300 300 code = os.system(cmd)
301 301 if sys.platform == 'OpenVMS' and code & 1:
302 302 code = 0
303 303 if code:
304 304 raise Abort(_("command '%s' failed: %s") %
305 305 (cmd, explainexit(code)))
306 306 fp = open(outname, 'rb')
307 307 r = fp.read()
308 308 fp.close()
309 309 return r
310 310 finally:
311 311 try:
312 312 if inname:
313 313 os.unlink(inname)
314 314 except OSError:
315 315 pass
316 316 try:
317 317 if outname:
318 318 os.unlink(outname)
319 319 except OSError:
320 320 pass
321 321
322 322 filtertable = {
323 323 'tempfile:': tempfilter,
324 324 'pipe:': pipefilter,
325 325 }
326 326
327 327 def filter(s, cmd):
328 328 "filter a string through a command that transforms its input to its output"
329 329 for name, fn in filtertable.iteritems():
330 330 if cmd.startswith(name):
331 331 return fn(s, cmd[len(name):].lstrip())
332 332 return pipefilter(s, cmd)
333 333
334 334 def binary(s):
335 335 """return true if a string is binary data"""
336 336 return bool(s and '\0' in s)
337 337
338 338 def increasingchunks(source, min=1024, max=65536):
339 339 '''return no less than min bytes per chunk while data remains,
340 340 doubling min after each chunk until it reaches max'''
341 341 def log2(x):
342 342 if not x:
343 343 return 0
344 344 i = 0
345 345 while x:
346 346 x >>= 1
347 347 i += 1
348 348 return i - 1
349 349
350 350 buf = []
351 351 blen = 0
352 352 for chunk in source:
353 353 buf.append(chunk)
354 354 blen += len(chunk)
355 355 if blen >= min:
356 356 if min < max:
357 357 min = min << 1
358 358 nmin = 1 << log2(blen)
359 359 if nmin > min:
360 360 min = nmin
361 361 if min > max:
362 362 min = max
363 363 yield ''.join(buf)
364 364 blen = 0
365 365 buf = []
366 366 if buf:
367 367 yield ''.join(buf)
368 368
369 369 Abort = error.Abort
370 370
371 371 def always(fn):
372 372 return True
373 373
374 374 def never(fn):
375 375 return False
376 376
377 377 def pathto(root, n1, n2):
378 378 '''return the relative path from one place to another.
379 379 root should use os.sep to separate directories
380 380 n1 should use os.sep to separate directories
381 381 n2 should use "/" to separate directories
382 382 returns an os.sep-separated path.
383 383
384 384 If n1 is a relative path, it's assumed it's
385 385 relative to root.
386 386 n2 should always be relative to root.
387 387 '''
388 388 if not n1:
389 389 return localpath(n2)
390 390 if os.path.isabs(n1):
391 391 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
392 392 return os.path.join(root, localpath(n2))
393 393 n2 = '/'.join((pconvert(root), n2))
394 394 a, b = splitpath(n1), n2.split('/')
395 395 a.reverse()
396 396 b.reverse()
397 397 while a and b and a[-1] == b[-1]:
398 398 a.pop()
399 399 b.pop()
400 400 b.reverse()
401 401 return os.sep.join((['..'] * len(a)) + b) or '.'
402 402
403 403 _hgexecutable = None
404 404
405 405 def mainfrozen():
406 406 """return True if we are a frozen executable.
407 407
408 408 The code supports py2exe (most common, Windows only) and tools/freeze
409 409 (portable, not much used).
410 410 """
411 411 return (safehasattr(sys, "frozen") or # new py2exe
412 412 safehasattr(sys, "importers") or # old py2exe
413 413 imp.is_frozen("__main__")) # tools/freeze
414 414
415 415 def hgexecutable():
416 416 """return location of the 'hg' executable.
417 417
418 418 Defaults to $HG or 'hg' in the search path.
419 419 """
420 420 if _hgexecutable is None:
421 421 hg = os.environ.get('HG')
422 422 mainmod = sys.modules['__main__']
423 423 if hg:
424 424 _sethgexecutable(hg)
425 425 elif mainfrozen():
426 426 _sethgexecutable(sys.executable)
427 427 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
428 428 _sethgexecutable(mainmod.__file__)
429 429 else:
430 430 exe = findexe('hg') or os.path.basename(sys.argv[0])
431 431 _sethgexecutable(exe)
432 432 return _hgexecutable
433 433
434 434 def _sethgexecutable(path):
435 435 """set location of the 'hg' executable"""
436 436 global _hgexecutable
437 437 _hgexecutable = path
438 438
439 439 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
440 440 '''enhanced shell command execution.
441 441 run with environment maybe modified, maybe in different dir.
442 442
443 443 if command fails and onerr is None, return status. if ui object,
444 444 print error message and return status, else raise onerr object as
445 445 exception.
446 446
447 447 if out is specified, it is assumed to be a file-like object that has a
448 448 write() method. stdout and stderr will be redirected to out.'''
449 449 try:
450 450 sys.stdout.flush()
451 451 except Exception:
452 452 pass
453 453 def py2shell(val):
454 454 'convert python object into string that is useful to shell'
455 455 if val is None or val is False:
456 456 return '0'
457 457 if val is True:
458 458 return '1'
459 459 return str(val)
460 460 origcmd = cmd
461 461 cmd = quotecommand(cmd)
462 462 if sys.platform == 'plan9':
463 463 # subprocess kludge to work around issues in half-baked Python
464 464 # ports, notably bichued/python:
465 465 if not cwd is None:
466 466 os.chdir(cwd)
467 467 rc = os.system(cmd)
468 468 else:
469 469 env = dict(os.environ)
470 470 env.update((k, py2shell(v)) for k, v in environ.iteritems())
471 471 env['HG'] = hgexecutable()
472 472 if out is None or out == sys.__stdout__:
473 473 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
474 474 env=env, cwd=cwd)
475 475 else:
476 476 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
477 477 env=env, cwd=cwd, stdout=subprocess.PIPE,
478 478 stderr=subprocess.STDOUT)
479 479 for line in proc.stdout:
480 480 out.write(line)
481 481 proc.wait()
482 482 rc = proc.returncode
483 483 if sys.platform == 'OpenVMS' and rc & 1:
484 484 rc = 0
485 485 if rc and onerr:
486 486 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
487 487 explainexit(rc)[0])
488 488 if errprefix:
489 489 errmsg = '%s: %s' % (errprefix, errmsg)
490 490 try:
491 491 onerr.warn(errmsg + '\n')
492 492 except AttributeError:
493 493 raise onerr(errmsg)
494 494 return rc
495 495
496 496 def checksignature(func):
497 497 '''wrap a function with code to check for calling errors'''
498 498 def check(*args, **kwargs):
499 499 try:
500 500 return func(*args, **kwargs)
501 501 except TypeError:
502 502 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
503 503 raise error.SignatureError
504 504 raise
505 505
506 506 return check
507 507
508 508 def copyfile(src, dest):
509 509 "copy a file, preserving mode and atime/mtime"
510 510 if os.path.lexists(dest):
511 511 unlink(dest)
512 512 if os.path.islink(src):
513 513 os.symlink(os.readlink(src), dest)
514 514 else:
515 515 try:
516 516 shutil.copyfile(src, dest)
517 517 shutil.copymode(src, dest)
518 518 except shutil.Error, inst:
519 519 raise Abort(str(inst))
520 520
521 521 def copyfiles(src, dst, hardlink=None):
522 522 """Copy a directory tree using hardlinks if possible"""
523 523
524 524 if hardlink is None:
525 525 hardlink = (os.stat(src).st_dev ==
526 526 os.stat(os.path.dirname(dst)).st_dev)
527 527
528 528 num = 0
529 529 if os.path.isdir(src):
530 530 os.mkdir(dst)
531 531 for name, kind in osutil.listdir(src):
532 532 srcname = os.path.join(src, name)
533 533 dstname = os.path.join(dst, name)
534 534 hardlink, n = copyfiles(srcname, dstname, hardlink)
535 535 num += n
536 536 else:
537 537 if hardlink:
538 538 try:
539 539 oslink(src, dst)
540 540 except (IOError, OSError):
541 541 hardlink = False
542 542 shutil.copy(src, dst)
543 543 else:
544 544 shutil.copy(src, dst)
545 545 num += 1
546 546
547 547 return hardlink, num
548 548
549 549 _winreservednames = '''con prn aux nul
550 550 com1 com2 com3 com4 com5 com6 com7 com8 com9
551 551 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
552 552 _winreservedchars = ':*?"<>|'
553 553 def checkwinfilename(path):
554 554 '''Check that the base-relative path is a valid filename on Windows.
555 555 Returns None if the path is ok, or a UI string describing the problem.
556 556
557 557 >>> checkwinfilename("just/a/normal/path")
558 558 >>> checkwinfilename("foo/bar/con.xml")
559 559 "filename contains 'con', which is reserved on Windows"
560 560 >>> checkwinfilename("foo/con.xml/bar")
561 561 "filename contains 'con', which is reserved on Windows"
562 562 >>> checkwinfilename("foo/bar/xml.con")
563 563 >>> checkwinfilename("foo/bar/AUX/bla.txt")
564 564 "filename contains 'AUX', which is reserved on Windows"
565 565 >>> checkwinfilename("foo/bar/bla:.txt")
566 566 "filename contains ':', which is reserved on Windows"
567 567 >>> checkwinfilename("foo/bar/b\07la.txt")
568 568 "filename contains '\\\\x07', which is invalid on Windows"
569 569 >>> checkwinfilename("foo/bar/bla ")
570 570 "filename ends with ' ', which is not allowed on Windows"
571 571 >>> checkwinfilename("../bar")
572 572 '''
573 573 for n in path.replace('\\', '/').split('/'):
574 574 if not n:
575 575 continue
576 576 for c in n:
577 577 if c in _winreservedchars:
578 578 return _("filename contains '%s', which is reserved "
579 579 "on Windows") % c
580 580 if ord(c) <= 31:
581 581 return _("filename contains %r, which is invalid "
582 582 "on Windows") % c
583 583 base = n.split('.')[0]
584 584 if base and base.lower() in _winreservednames:
585 585 return _("filename contains '%s', which is reserved "
586 586 "on Windows") % base
587 587 t = n[-1]
588 588 if t in '. ' and n not in '..':
589 589 return _("filename ends with '%s', which is not allowed "
590 590 "on Windows") % t
591 591
592 592 if os.name == 'nt':
593 593 checkosfilename = checkwinfilename
594 594 else:
595 595 checkosfilename = platform.checkosfilename
596 596
597 597 def makelock(info, pathname):
598 598 try:
599 599 return os.symlink(info, pathname)
600 600 except OSError, why:
601 601 if why.errno == errno.EEXIST:
602 602 raise
603 603 except AttributeError: # no symlink in os
604 604 pass
605 605
606 606 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
607 607 os.write(ld, info)
608 608 os.close(ld)
609 609
610 610 def readlock(pathname):
611 611 try:
612 612 return os.readlink(pathname)
613 613 except OSError, why:
614 614 if why.errno not in (errno.EINVAL, errno.ENOSYS):
615 615 raise
616 616 except AttributeError: # no symlink in os
617 617 pass
618 618 fp = posixfile(pathname)
619 619 r = fp.read()
620 620 fp.close()
621 621 return r
622 622
623 623 def fstat(fp):
624 624 '''stat file object that may not have fileno method.'''
625 625 try:
626 626 return os.fstat(fp.fileno())
627 627 except AttributeError:
628 628 return os.stat(fp.name)
629 629
630 630 # File system features
631 631
632 632 def checkcase(path):
633 633 """
634 634 Check whether the given path is on a case-sensitive filesystem
635 635
636 636 Requires a path (like /foo/.hg) ending with a foldable final
637 637 directory component.
638 638 """
639 639 s1 = os.stat(path)
640 640 d, b = os.path.split(path)
641 641 b2 = b.upper()
642 642 if b == b2:
643 643 b2 = b.lower()
644 644 if b == b2:
645 645 return True # no evidence against case sensitivity
646 646 p2 = os.path.join(d, b2)
647 647 try:
648 648 s2 = os.stat(p2)
649 649 if s2 == s1:
650 650 return False
651 651 return True
652 652 except OSError:
653 653 return True
654 654
655 655 try:
656 656 import re2
657 657 _re2 = None
658 658 except ImportError:
659 659 _re2 = False
660 660
661 661 def compilere(pat):
662 662 '''Compile a regular expression, using re2 if possible
663 663
664 664 For best performance, use only re2-compatible regexp features.'''
665 665 global _re2
666 666 if _re2 is None:
667 667 try:
668 668 re2.compile
669 669 _re2 = True
670 670 except ImportError:
671 671 _re2 = False
672 672 if _re2:
673 673 try:
674 674 return re2.compile(pat)
675 675 except re2.error:
676 676 pass
677 677 return re.compile(pat)
678 678
679 679 _fspathcache = {}
680 680 def fspath(name, root):
681 681 '''Get name in the case stored in the filesystem
682 682
683 683 The name should be relative to root, and be normcase-ed for efficiency.
684 684
685 685 Note that this function is unnecessary, and should not be
686 686 called, for case-sensitive filesystems (simply because it's expensive).
687 687
688 688 The root should be normcase-ed, too.
689 689 '''
690 690 def find(p, contents):
691 691 for n in contents:
692 692 if normcase(n) == p:
693 693 return n
694 694 return None
695 695
696 696 seps = os.sep
697 697 if os.altsep:
698 698 seps = seps + os.altsep
699 699 # Protect backslashes. This gets silly very quickly.
700 700 seps.replace('\\','\\\\')
701 701 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
702 702 dir = os.path.normpath(root)
703 703 result = []
704 704 for part, sep in pattern.findall(name):
705 705 if sep:
706 706 result.append(sep)
707 707 continue
708 708
709 709 if dir not in _fspathcache:
710 710 _fspathcache[dir] = os.listdir(dir)
711 711 contents = _fspathcache[dir]
712 712
713 713 found = find(part, contents)
714 714 if not found:
715 715 # retry "once per directory" per "dirstate.walk" which
716 716 # may take place for each patches of "hg qpush", for example
717 717 contents = os.listdir(dir)
718 718 _fspathcache[dir] = contents
719 719 found = find(part, contents)
720 720
721 721 result.append(found or part)
722 722 dir = os.path.join(dir, part)
723 723
724 724 return ''.join(result)
725 725
726 726 def checknlink(testfile):
727 727 '''check whether hardlink count reporting works properly'''
728 728
729 729 # testfile may be open, so we need a separate file for checking to
730 730 # work around issue2543 (or testfile may get lost on Samba shares)
731 731 f1 = testfile + ".hgtmp1"
732 732 if os.path.lexists(f1):
733 733 return False
734 734 try:
735 735 posixfile(f1, 'w').close()
736 736 except IOError:
737 737 return False
738 738
739 739 f2 = testfile + ".hgtmp2"
740 740 fd = None
741 741 try:
742 742 try:
743 743 oslink(f1, f2)
744 744 except OSError:
745 745 return False
746 746
747 747 # nlinks() may behave differently for files on Windows shares if
748 748 # the file is open.
749 749 fd = posixfile(f2)
750 750 return nlinks(f2) > 1
751 751 finally:
752 752 if fd is not None:
753 753 fd.close()
754 754 for f in (f1, f2):
755 755 try:
756 756 os.unlink(f)
757 757 except OSError:
758 758 pass
759 759
760 760 return False
761 761
762 762 def endswithsep(path):
763 763 '''Check path ends with os.sep or os.altsep.'''
764 764 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
765 765
766 766 def splitpath(path):
767 767 '''Split path by os.sep.
768 768 Note that this function does not use os.altsep because this is
769 769 an alternative of simple "xxx.split(os.sep)".
770 770 It is recommended to use os.path.normpath() before using this
771 771 function if need.'''
772 772 return path.split(os.sep)
773 773
774 774 def gui():
775 775 '''Are we running in a GUI?'''
776 776 if sys.platform == 'darwin':
777 777 if 'SSH_CONNECTION' in os.environ:
778 778 # handle SSH access to a box where the user is logged in
779 779 return False
780 780 elif getattr(osutil, 'isgui', None):
781 781 # check if a CoreGraphics session is available
782 782 return osutil.isgui()
783 783 else:
784 784 # pure build; use a safe default
785 785 return True
786 786 else:
787 787 return os.name == "nt" or os.environ.get("DISPLAY")
788 788
789 789 def mktempcopy(name, emptyok=False, createmode=None):
790 790 """Create a temporary file with the same contents from name
791 791
792 792 The permission bits are copied from the original file.
793 793
794 794 If the temporary file is going to be truncated immediately, you
795 795 can use emptyok=True as an optimization.
796 796
797 797 Returns the name of the temporary file.
798 798 """
799 799 d, fn = os.path.split(name)
800 800 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
801 801 os.close(fd)
802 802 # Temporary files are created with mode 0600, which is usually not
803 803 # what we want. If the original file already exists, just copy
804 804 # its mode. Otherwise, manually obey umask.
805 805 copymode(name, temp, createmode)
806 806 if emptyok:
807 807 return temp
808 808 try:
809 809 try:
810 810 ifp = posixfile(name, "rb")
811 811 except IOError, inst:
812 812 if inst.errno == errno.ENOENT:
813 813 return temp
814 814 if not getattr(inst, 'filename', None):
815 815 inst.filename = name
816 816 raise
817 817 ofp = posixfile(temp, "wb")
818 818 for chunk in filechunkiter(ifp):
819 819 ofp.write(chunk)
820 820 ifp.close()
821 821 ofp.close()
822 822 except: # re-raises
823 823 try: os.unlink(temp)
824 824 except OSError: pass
825 825 raise
826 826 return temp
827 827
828 828 class atomictempfile(object):
829 829 '''writable file object that atomically updates a file
830 830
831 831 All writes will go to a temporary copy of the original file. Call
832 832 close() when you are done writing, and atomictempfile will rename
833 833 the temporary copy to the original name, making the changes
834 834 visible. If the object is destroyed without being closed, all your
835 835 writes are discarded.
836 836 '''
837 837 def __init__(self, name, mode='w+b', createmode=None):
838 838 self.__name = name # permanent name
839 839 self._tempname = mktempcopy(name, emptyok=('w' in mode),
840 840 createmode=createmode)
841 841 self._fp = posixfile(self._tempname, mode)
842 842
843 843 # delegated methods
844 844 self.write = self._fp.write
845 845 self.seek = self._fp.seek
846 846 self.tell = self._fp.tell
847 847 self.fileno = self._fp.fileno
848 848
849 849 def close(self):
850 850 if not self._fp.closed:
851 851 self._fp.close()
852 852 rename(self._tempname, localpath(self.__name))
853 853
854 854 def discard(self):
855 855 if not self._fp.closed:
856 856 try:
857 857 os.unlink(self._tempname)
858 858 except OSError:
859 859 pass
860 860 self._fp.close()
861 861
862 862 def __del__(self):
863 863 if safehasattr(self, '_fp'): # constructor actually did something
864 864 self.discard()
865 865
866 866 def makedirs(name, mode=None):
867 867 """recursive directory creation with parent mode inheritance"""
868 868 try:
869 869 os.mkdir(name)
870 870 except OSError, err:
871 871 if err.errno == errno.EEXIST:
872 872 return
873 873 if err.errno != errno.ENOENT or not name:
874 874 raise
875 875 parent = os.path.dirname(os.path.abspath(name))
876 876 if parent == name:
877 877 raise
878 878 makedirs(parent, mode)
879 879 os.mkdir(name)
880 880 if mode is not None:
881 881 os.chmod(name, mode)
882 882
883 def ensuredirs(name, mode=None):
884 """race-safe recursive directory creation"""
885 try:
886 makedirs(name, mode)
887 except OSError, err:
888 if err.errno == errno.EEXIST and os.path.isdir(name):
889 # someone else seems to have won a directory creation race
890 return
891 raise
892
883 893 def readfile(path):
884 894 fp = open(path, 'rb')
885 895 try:
886 896 return fp.read()
887 897 finally:
888 898 fp.close()
889 899
890 900 def writefile(path, text):
891 901 fp = open(path, 'wb')
892 902 try:
893 903 fp.write(text)
894 904 finally:
895 905 fp.close()
896 906
897 907 def appendfile(path, text):
898 908 fp = open(path, 'ab')
899 909 try:
900 910 fp.write(text)
901 911 finally:
902 912 fp.close()
903 913
904 914 class chunkbuffer(object):
905 915 """Allow arbitrary sized chunks of data to be efficiently read from an
906 916 iterator over chunks of arbitrary size."""
907 917
908 918 def __init__(self, in_iter):
909 919 """in_iter is the iterator that's iterating over the input chunks.
910 920 targetsize is how big a buffer to try to maintain."""
911 921 def splitbig(chunks):
912 922 for chunk in chunks:
913 923 if len(chunk) > 2**20:
914 924 pos = 0
915 925 while pos < len(chunk):
916 926 end = pos + 2 ** 18
917 927 yield chunk[pos:end]
918 928 pos = end
919 929 else:
920 930 yield chunk
921 931 self.iter = splitbig(in_iter)
922 932 self._queue = deque()
923 933
924 934 def read(self, l):
925 935 """Read L bytes of data from the iterator of chunks of data.
926 936 Returns less than L bytes if the iterator runs dry."""
927 937 left = l
928 938 buf = []
929 939 queue = self._queue
930 940 while left > 0:
931 941 # refill the queue
932 942 if not queue:
933 943 target = 2**18
934 944 for chunk in self.iter:
935 945 queue.append(chunk)
936 946 target -= len(chunk)
937 947 if target <= 0:
938 948 break
939 949 if not queue:
940 950 break
941 951
942 952 chunk = queue.popleft()
943 953 left -= len(chunk)
944 954 if left < 0:
945 955 queue.appendleft(chunk[left:])
946 956 buf.append(chunk[:left])
947 957 else:
948 958 buf.append(chunk)
949 959
950 960 return ''.join(buf)
951 961
952 962 def filechunkiter(f, size=65536, limit=None):
953 963 """Create a generator that produces the data in the file size
954 964 (default 65536) bytes at a time, up to optional limit (default is
955 965 to read all data). Chunks may be less than size bytes if the
956 966 chunk is the last chunk in the file, or the file is a socket or
957 967 some other type of file that sometimes reads less data than is
958 968 requested."""
959 969 assert size >= 0
960 970 assert limit is None or limit >= 0
961 971 while True:
962 972 if limit is None:
963 973 nbytes = size
964 974 else:
965 975 nbytes = min(limit, size)
966 976 s = nbytes and f.read(nbytes)
967 977 if not s:
968 978 break
969 979 if limit:
970 980 limit -= len(s)
971 981 yield s
972 982
973 983 def makedate():
974 984 ct = time.time()
975 985 if ct < 0:
976 986 hint = _("check your clock")
977 987 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
978 988 delta = (datetime.datetime.utcfromtimestamp(ct) -
979 989 datetime.datetime.fromtimestamp(ct))
980 990 tz = delta.days * 86400 + delta.seconds
981 991 return ct, tz
982 992
983 993 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
984 994 """represent a (unixtime, offset) tuple as a localized time.
985 995 unixtime is seconds since the epoch, and offset is the time zone's
986 996 number of seconds away from UTC. if timezone is false, do not
987 997 append time zone to string."""
988 998 t, tz = date or makedate()
989 999 if t < 0:
990 1000 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
991 1001 tz = 0
992 1002 if "%1" in format or "%2" in format:
993 1003 sign = (tz > 0) and "-" or "+"
994 1004 minutes = abs(tz) // 60
995 1005 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
996 1006 format = format.replace("%2", "%02d" % (minutes % 60))
997 1007 try:
998 1008 t = time.gmtime(float(t) - tz)
999 1009 except ValueError:
1000 1010 # time was out of range
1001 1011 t = time.gmtime(sys.maxint)
1002 1012 s = time.strftime(format, t)
1003 1013 return s
1004 1014
1005 1015 def shortdate(date=None):
1006 1016 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1007 1017 return datestr(date, format='%Y-%m-%d')
1008 1018
1009 1019 def strdate(string, format, defaults=[]):
1010 1020 """parse a localized time string and return a (unixtime, offset) tuple.
1011 1021 if the string cannot be parsed, ValueError is raised."""
1012 1022 def timezone(string):
1013 1023 tz = string.split()[-1]
1014 1024 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1015 1025 sign = (tz[0] == "+") and 1 or -1
1016 1026 hours = int(tz[1:3])
1017 1027 minutes = int(tz[3:5])
1018 1028 return -sign * (hours * 60 + minutes) * 60
1019 1029 if tz == "GMT" or tz == "UTC":
1020 1030 return 0
1021 1031 return None
1022 1032
1023 1033 # NOTE: unixtime = localunixtime + offset
1024 1034 offset, date = timezone(string), string
1025 1035 if offset is not None:
1026 1036 date = " ".join(string.split()[:-1])
1027 1037
1028 1038 # add missing elements from defaults
1029 1039 usenow = False # default to using biased defaults
1030 1040 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1031 1041 found = [True for p in part if ("%"+p) in format]
1032 1042 if not found:
1033 1043 date += "@" + defaults[part][usenow]
1034 1044 format += "@%" + part[0]
1035 1045 else:
1036 1046 # We've found a specific time element, less specific time
1037 1047 # elements are relative to today
1038 1048 usenow = True
1039 1049
1040 1050 timetuple = time.strptime(date, format)
1041 1051 localunixtime = int(calendar.timegm(timetuple))
1042 1052 if offset is None:
1043 1053 # local timezone
1044 1054 unixtime = int(time.mktime(timetuple))
1045 1055 offset = unixtime - localunixtime
1046 1056 else:
1047 1057 unixtime = localunixtime + offset
1048 1058 return unixtime, offset
1049 1059
1050 1060 def parsedate(date, formats=None, bias={}):
1051 1061 """parse a localized date/time and return a (unixtime, offset) tuple.
1052 1062
1053 1063 The date may be a "unixtime offset" string or in one of the specified
1054 1064 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1055 1065
1056 1066 >>> parsedate(' today ') == parsedate(\
1057 1067 datetime.date.today().strftime('%b %d'))
1058 1068 True
1059 1069 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1060 1070 datetime.timedelta(days=1)\
1061 1071 ).strftime('%b %d'))
1062 1072 True
1063 1073 >>> now, tz = makedate()
1064 1074 >>> strnow, strtz = parsedate('now')
1065 1075 >>> (strnow - now) < 1
1066 1076 True
1067 1077 >>> tz == strtz
1068 1078 True
1069 1079 """
1070 1080 if not date:
1071 1081 return 0, 0
1072 1082 if isinstance(date, tuple) and len(date) == 2:
1073 1083 return date
1074 1084 if not formats:
1075 1085 formats = defaultdateformats
1076 1086 date = date.strip()
1077 1087
1078 1088 if date == _('now'):
1079 1089 return makedate()
1080 1090 if date == _('today'):
1081 1091 date = datetime.date.today().strftime('%b %d')
1082 1092 elif date == _('yesterday'):
1083 1093 date = (datetime.date.today() -
1084 1094 datetime.timedelta(days=1)).strftime('%b %d')
1085 1095
1086 1096 try:
1087 1097 when, offset = map(int, date.split(' '))
1088 1098 except ValueError:
1089 1099 # fill out defaults
1090 1100 now = makedate()
1091 1101 defaults = {}
1092 1102 for part in ("d", "mb", "yY", "HI", "M", "S"):
1093 1103 # this piece is for rounding the specific end of unknowns
1094 1104 b = bias.get(part)
1095 1105 if b is None:
1096 1106 if part[0] in "HMS":
1097 1107 b = "00"
1098 1108 else:
1099 1109 b = "0"
1100 1110
1101 1111 # this piece is for matching the generic end to today's date
1102 1112 n = datestr(now, "%" + part[0])
1103 1113
1104 1114 defaults[part] = (b, n)
1105 1115
1106 1116 for format in formats:
1107 1117 try:
1108 1118 when, offset = strdate(date, format, defaults)
1109 1119 except (ValueError, OverflowError):
1110 1120 pass
1111 1121 else:
1112 1122 break
1113 1123 else:
1114 1124 raise Abort(_('invalid date: %r') % date)
1115 1125 # validate explicit (probably user-specified) date and
1116 1126 # time zone offset. values must fit in signed 32 bits for
1117 1127 # current 32-bit linux runtimes. timezones go from UTC-12
1118 1128 # to UTC+14
1119 1129 if abs(when) > 0x7fffffff:
1120 1130 raise Abort(_('date exceeds 32 bits: %d') % when)
1121 1131 if when < 0:
1122 1132 raise Abort(_('negative date value: %d') % when)
1123 1133 if offset < -50400 or offset > 43200:
1124 1134 raise Abort(_('impossible time zone offset: %d') % offset)
1125 1135 return when, offset
1126 1136
1127 1137 def matchdate(date):
1128 1138 """Return a function that matches a given date match specifier
1129 1139
1130 1140 Formats include:
1131 1141
1132 1142 '{date}' match a given date to the accuracy provided
1133 1143
1134 1144 '<{date}' on or before a given date
1135 1145
1136 1146 '>{date}' on or after a given date
1137 1147
1138 1148 >>> p1 = parsedate("10:29:59")
1139 1149 >>> p2 = parsedate("10:30:00")
1140 1150 >>> p3 = parsedate("10:30:59")
1141 1151 >>> p4 = parsedate("10:31:00")
1142 1152 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1143 1153 >>> f = matchdate("10:30")
1144 1154 >>> f(p1[0])
1145 1155 False
1146 1156 >>> f(p2[0])
1147 1157 True
1148 1158 >>> f(p3[0])
1149 1159 True
1150 1160 >>> f(p4[0])
1151 1161 False
1152 1162 >>> f(p5[0])
1153 1163 False
1154 1164 """
1155 1165
1156 1166 def lower(date):
1157 1167 d = dict(mb="1", d="1")
1158 1168 return parsedate(date, extendeddateformats, d)[0]
1159 1169
1160 1170 def upper(date):
1161 1171 d = dict(mb="12", HI="23", M="59", S="59")
1162 1172 for days in ("31", "30", "29"):
1163 1173 try:
1164 1174 d["d"] = days
1165 1175 return parsedate(date, extendeddateformats, d)[0]
1166 1176 except Abort:
1167 1177 pass
1168 1178 d["d"] = "28"
1169 1179 return parsedate(date, extendeddateformats, d)[0]
1170 1180
1171 1181 date = date.strip()
1172 1182
1173 1183 if not date:
1174 1184 raise Abort(_("dates cannot consist entirely of whitespace"))
1175 1185 elif date[0] == "<":
1176 1186 if not date[1:]:
1177 1187 raise Abort(_("invalid day spec, use '<DATE'"))
1178 1188 when = upper(date[1:])
1179 1189 return lambda x: x <= when
1180 1190 elif date[0] == ">":
1181 1191 if not date[1:]:
1182 1192 raise Abort(_("invalid day spec, use '>DATE'"))
1183 1193 when = lower(date[1:])
1184 1194 return lambda x: x >= when
1185 1195 elif date[0] == "-":
1186 1196 try:
1187 1197 days = int(date[1:])
1188 1198 except ValueError:
1189 1199 raise Abort(_("invalid day spec: %s") % date[1:])
1190 1200 if days < 0:
1191 1201 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1192 1202 % date[1:])
1193 1203 when = makedate()[0] - days * 3600 * 24
1194 1204 return lambda x: x >= when
1195 1205 elif " to " in date:
1196 1206 a, b = date.split(" to ")
1197 1207 start, stop = lower(a), upper(b)
1198 1208 return lambda x: x >= start and x <= stop
1199 1209 else:
1200 1210 start, stop = lower(date), upper(date)
1201 1211 return lambda x: x >= start and x <= stop
1202 1212
1203 1213 def shortuser(user):
1204 1214 """Return a short representation of a user name or email address."""
1205 1215 f = user.find('@')
1206 1216 if f >= 0:
1207 1217 user = user[:f]
1208 1218 f = user.find('<')
1209 1219 if f >= 0:
1210 1220 user = user[f + 1:]
1211 1221 f = user.find(' ')
1212 1222 if f >= 0:
1213 1223 user = user[:f]
1214 1224 f = user.find('.')
1215 1225 if f >= 0:
1216 1226 user = user[:f]
1217 1227 return user
1218 1228
1219 1229 def emailuser(user):
1220 1230 """Return the user portion of an email address."""
1221 1231 f = user.find('@')
1222 1232 if f >= 0:
1223 1233 user = user[:f]
1224 1234 f = user.find('<')
1225 1235 if f >= 0:
1226 1236 user = user[f + 1:]
1227 1237 return user
1228 1238
1229 1239 def email(author):
1230 1240 '''get email of author.'''
1231 1241 r = author.find('>')
1232 1242 if r == -1:
1233 1243 r = None
1234 1244 return author[author.find('<') + 1:r]
1235 1245
1236 1246 def _ellipsis(text, maxlength):
1237 1247 if len(text) <= maxlength:
1238 1248 return text, False
1239 1249 else:
1240 1250 return "%s..." % (text[:maxlength - 3]), True
1241 1251
1242 1252 def ellipsis(text, maxlength=400):
1243 1253 """Trim string to at most maxlength (default: 400) characters."""
1244 1254 try:
1245 1255 # use unicode not to split at intermediate multi-byte sequence
1246 1256 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1247 1257 maxlength)
1248 1258 if not truncated:
1249 1259 return text
1250 1260 return utext.encode(encoding.encoding)
1251 1261 except (UnicodeDecodeError, UnicodeEncodeError):
1252 1262 return _ellipsis(text, maxlength)[0]
1253 1263
1254 1264 _byteunits = (
1255 1265 (100, 1 << 30, _('%.0f GB')),
1256 1266 (10, 1 << 30, _('%.1f GB')),
1257 1267 (1, 1 << 30, _('%.2f GB')),
1258 1268 (100, 1 << 20, _('%.0f MB')),
1259 1269 (10, 1 << 20, _('%.1f MB')),
1260 1270 (1, 1 << 20, _('%.2f MB')),
1261 1271 (100, 1 << 10, _('%.0f KB')),
1262 1272 (10, 1 << 10, _('%.1f KB')),
1263 1273 (1, 1 << 10, _('%.2f KB')),
1264 1274 (1, 1, _('%.0f bytes')),
1265 1275 )
1266 1276
1267 1277 def bytecount(nbytes):
1268 1278 '''return byte count formatted as readable string, with units'''
1269 1279
1270 1280 for multiplier, divisor, format in _byteunits:
1271 1281 if nbytes >= divisor * multiplier:
1272 1282 return format % (nbytes / float(divisor))
1273 1283 return _byteunits[-1][2] % nbytes
1274 1284
1275 1285 def uirepr(s):
1276 1286 # Avoid double backslash in Windows path repr()
1277 1287 return repr(s).replace('\\\\', '\\')
1278 1288
1279 1289 # delay import of textwrap
1280 1290 def MBTextWrapper(**kwargs):
1281 1291 class tw(textwrap.TextWrapper):
1282 1292 """
1283 1293 Extend TextWrapper for width-awareness.
1284 1294
1285 1295 Neither number of 'bytes' in any encoding nor 'characters' is
1286 1296 appropriate to calculate terminal columns for specified string.
1287 1297
1288 1298 Original TextWrapper implementation uses built-in 'len()' directly,
1289 1299 so overriding is needed to use width information of each characters.
1290 1300
1291 1301 In addition, characters classified into 'ambiguous' width are
1292 1302 treated as wide in East Asian area, but as narrow in other.
1293 1303
1294 1304 This requires use decision to determine width of such characters.
1295 1305 """
1296 1306 def __init__(self, **kwargs):
1297 1307 textwrap.TextWrapper.__init__(self, **kwargs)
1298 1308
1299 1309 # for compatibility between 2.4 and 2.6
1300 1310 if getattr(self, 'drop_whitespace', None) is None:
1301 1311 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1302 1312
1303 1313 def _cutdown(self, ucstr, space_left):
1304 1314 l = 0
1305 1315 colwidth = encoding.ucolwidth
1306 1316 for i in xrange(len(ucstr)):
1307 1317 l += colwidth(ucstr[i])
1308 1318 if space_left < l:
1309 1319 return (ucstr[:i], ucstr[i:])
1310 1320 return ucstr, ''
1311 1321
1312 1322 # overriding of base class
1313 1323 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1314 1324 space_left = max(width - cur_len, 1)
1315 1325
1316 1326 if self.break_long_words:
1317 1327 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1318 1328 cur_line.append(cut)
1319 1329 reversed_chunks[-1] = res
1320 1330 elif not cur_line:
1321 1331 cur_line.append(reversed_chunks.pop())
1322 1332
1323 1333 # this overriding code is imported from TextWrapper of python 2.6
1324 1334 # to calculate columns of string by 'encoding.ucolwidth()'
1325 1335 def _wrap_chunks(self, chunks):
1326 1336 colwidth = encoding.ucolwidth
1327 1337
1328 1338 lines = []
1329 1339 if self.width <= 0:
1330 1340 raise ValueError("invalid width %r (must be > 0)" % self.width)
1331 1341
1332 1342 # Arrange in reverse order so items can be efficiently popped
1333 1343 # from a stack of chucks.
1334 1344 chunks.reverse()
1335 1345
1336 1346 while chunks:
1337 1347
1338 1348 # Start the list of chunks that will make up the current line.
1339 1349 # cur_len is just the length of all the chunks in cur_line.
1340 1350 cur_line = []
1341 1351 cur_len = 0
1342 1352
1343 1353 # Figure out which static string will prefix this line.
1344 1354 if lines:
1345 1355 indent = self.subsequent_indent
1346 1356 else:
1347 1357 indent = self.initial_indent
1348 1358
1349 1359 # Maximum width for this line.
1350 1360 width = self.width - len(indent)
1351 1361
1352 1362 # First chunk on line is whitespace -- drop it, unless this
1353 1363 # is the very beginning of the text (i.e. no lines started yet).
1354 1364 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1355 1365 del chunks[-1]
1356 1366
1357 1367 while chunks:
1358 1368 l = colwidth(chunks[-1])
1359 1369
1360 1370 # Can at least squeeze this chunk onto the current line.
1361 1371 if cur_len + l <= width:
1362 1372 cur_line.append(chunks.pop())
1363 1373 cur_len += l
1364 1374
1365 1375 # Nope, this line is full.
1366 1376 else:
1367 1377 break
1368 1378
1369 1379 # The current line is full, and the next chunk is too big to
1370 1380 # fit on *any* line (not just this one).
1371 1381 if chunks and colwidth(chunks[-1]) > width:
1372 1382 self._handle_long_word(chunks, cur_line, cur_len, width)
1373 1383
1374 1384 # If the last chunk on this line is all whitespace, drop it.
1375 1385 if (self.drop_whitespace and
1376 1386 cur_line and cur_line[-1].strip() == ''):
1377 1387 del cur_line[-1]
1378 1388
1379 1389 # Convert current line back to a string and store it in list
1380 1390 # of all lines (return value).
1381 1391 if cur_line:
1382 1392 lines.append(indent + ''.join(cur_line))
1383 1393
1384 1394 return lines
1385 1395
1386 1396 global MBTextWrapper
1387 1397 MBTextWrapper = tw
1388 1398 return tw(**kwargs)
1389 1399
1390 1400 def wrap(line, width, initindent='', hangindent=''):
1391 1401 maxindent = max(len(hangindent), len(initindent))
1392 1402 if width <= maxindent:
1393 1403 # adjust for weird terminal size
1394 1404 width = max(78, maxindent + 1)
1395 1405 line = line.decode(encoding.encoding, encoding.encodingmode)
1396 1406 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1397 1407 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1398 1408 wrapper = MBTextWrapper(width=width,
1399 1409 initial_indent=initindent,
1400 1410 subsequent_indent=hangindent)
1401 1411 return wrapper.fill(line).encode(encoding.encoding)
1402 1412
1403 1413 def iterlines(iterator):
1404 1414 for chunk in iterator:
1405 1415 for line in chunk.splitlines():
1406 1416 yield line
1407 1417
1408 1418 def expandpath(path):
1409 1419 return os.path.expanduser(os.path.expandvars(path))
1410 1420
1411 1421 def hgcmd():
1412 1422 """Return the command used to execute current hg
1413 1423
1414 1424 This is different from hgexecutable() because on Windows we want
1415 1425 to avoid things opening new shell windows like batch files, so we
1416 1426 get either the python call or current executable.
1417 1427 """
1418 1428 if mainfrozen():
1419 1429 return [sys.executable]
1420 1430 return gethgcmd()
1421 1431
1422 1432 def rundetached(args, condfn):
1423 1433 """Execute the argument list in a detached process.
1424 1434
1425 1435 condfn is a callable which is called repeatedly and should return
1426 1436 True once the child process is known to have started successfully.
1427 1437 At this point, the child process PID is returned. If the child
1428 1438 process fails to start or finishes before condfn() evaluates to
1429 1439 True, return -1.
1430 1440 """
1431 1441 # Windows case is easier because the child process is either
1432 1442 # successfully starting and validating the condition or exiting
1433 1443 # on failure. We just poll on its PID. On Unix, if the child
1434 1444 # process fails to start, it will be left in a zombie state until
1435 1445 # the parent wait on it, which we cannot do since we expect a long
1436 1446 # running process on success. Instead we listen for SIGCHLD telling
1437 1447 # us our child process terminated.
1438 1448 terminated = set()
1439 1449 def handler(signum, frame):
1440 1450 terminated.add(os.wait())
1441 1451 prevhandler = None
1442 1452 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1443 1453 if SIGCHLD is not None:
1444 1454 prevhandler = signal.signal(SIGCHLD, handler)
1445 1455 try:
1446 1456 pid = spawndetached(args)
1447 1457 while not condfn():
1448 1458 if ((pid in terminated or not testpid(pid))
1449 1459 and not condfn()):
1450 1460 return -1
1451 1461 time.sleep(0.1)
1452 1462 return pid
1453 1463 finally:
1454 1464 if prevhandler is not None:
1455 1465 signal.signal(signal.SIGCHLD, prevhandler)
1456 1466
1457 1467 try:
1458 1468 any, all = any, all
1459 1469 except NameError:
1460 1470 def any(iterable):
1461 1471 for i in iterable:
1462 1472 if i:
1463 1473 return True
1464 1474 return False
1465 1475
1466 1476 def all(iterable):
1467 1477 for i in iterable:
1468 1478 if not i:
1469 1479 return False
1470 1480 return True
1471 1481
1472 1482 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1473 1483 """Return the result of interpolating items in the mapping into string s.
1474 1484
1475 1485 prefix is a single character string, or a two character string with
1476 1486 a backslash as the first character if the prefix needs to be escaped in
1477 1487 a regular expression.
1478 1488
1479 1489 fn is an optional function that will be applied to the replacement text
1480 1490 just before replacement.
1481 1491
1482 1492 escape_prefix is an optional flag that allows using doubled prefix for
1483 1493 its escaping.
1484 1494 """
1485 1495 fn = fn or (lambda s: s)
1486 1496 patterns = '|'.join(mapping.keys())
1487 1497 if escape_prefix:
1488 1498 patterns += '|' + prefix
1489 1499 if len(prefix) > 1:
1490 1500 prefix_char = prefix[1:]
1491 1501 else:
1492 1502 prefix_char = prefix
1493 1503 mapping[prefix_char] = prefix_char
1494 1504 r = re.compile(r'%s(%s)' % (prefix, patterns))
1495 1505 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1496 1506
1497 1507 def getport(port):
1498 1508 """Return the port for a given network service.
1499 1509
1500 1510 If port is an integer, it's returned as is. If it's a string, it's
1501 1511 looked up using socket.getservbyname(). If there's no matching
1502 1512 service, util.Abort is raised.
1503 1513 """
1504 1514 try:
1505 1515 return int(port)
1506 1516 except ValueError:
1507 1517 pass
1508 1518
1509 1519 try:
1510 1520 return socket.getservbyname(port)
1511 1521 except socket.error:
1512 1522 raise Abort(_("no port number associated with service '%s'") % port)
1513 1523
1514 1524 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1515 1525 '0': False, 'no': False, 'false': False, 'off': False,
1516 1526 'never': False}
1517 1527
1518 1528 def parsebool(s):
1519 1529 """Parse s into a boolean.
1520 1530
1521 1531 If s is not a valid boolean, returns None.
1522 1532 """
1523 1533 return _booleans.get(s.lower(), None)
1524 1534
1525 1535 _hexdig = '0123456789ABCDEFabcdef'
1526 1536 _hextochr = dict((a + b, chr(int(a + b, 16)))
1527 1537 for a in _hexdig for b in _hexdig)
1528 1538
1529 1539 def _urlunquote(s):
1530 1540 """Decode HTTP/HTML % encoding.
1531 1541
1532 1542 >>> _urlunquote('abc%20def')
1533 1543 'abc def'
1534 1544 """
1535 1545 res = s.split('%')
1536 1546 # fastpath
1537 1547 if len(res) == 1:
1538 1548 return s
1539 1549 s = res[0]
1540 1550 for item in res[1:]:
1541 1551 try:
1542 1552 s += _hextochr[item[:2]] + item[2:]
1543 1553 except KeyError:
1544 1554 s += '%' + item
1545 1555 except UnicodeDecodeError:
1546 1556 s += unichr(int(item[:2], 16)) + item[2:]
1547 1557 return s
1548 1558
1549 1559 class url(object):
1550 1560 r"""Reliable URL parser.
1551 1561
1552 1562 This parses URLs and provides attributes for the following
1553 1563 components:
1554 1564
1555 1565 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1556 1566
1557 1567 Missing components are set to None. The only exception is
1558 1568 fragment, which is set to '' if present but empty.
1559 1569
1560 1570 If parsefragment is False, fragment is included in query. If
1561 1571 parsequery is False, query is included in path. If both are
1562 1572 False, both fragment and query are included in path.
1563 1573
1564 1574 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1565 1575
1566 1576 Note that for backward compatibility reasons, bundle URLs do not
1567 1577 take host names. That means 'bundle://../' has a path of '../'.
1568 1578
1569 1579 Examples:
1570 1580
1571 1581 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1572 1582 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1573 1583 >>> url('ssh://[::1]:2200//home/joe/repo')
1574 1584 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1575 1585 >>> url('file:///home/joe/repo')
1576 1586 <url scheme: 'file', path: '/home/joe/repo'>
1577 1587 >>> url('file:///c:/temp/foo/')
1578 1588 <url scheme: 'file', path: 'c:/temp/foo/'>
1579 1589 >>> url('bundle:foo')
1580 1590 <url scheme: 'bundle', path: 'foo'>
1581 1591 >>> url('bundle://../foo')
1582 1592 <url scheme: 'bundle', path: '../foo'>
1583 1593 >>> url(r'c:\foo\bar')
1584 1594 <url path: 'c:\\foo\\bar'>
1585 1595 >>> url(r'\\blah\blah\blah')
1586 1596 <url path: '\\\\blah\\blah\\blah'>
1587 1597 >>> url(r'\\blah\blah\blah#baz')
1588 1598 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1589 1599
1590 1600 Authentication credentials:
1591 1601
1592 1602 >>> url('ssh://joe:xyz@x/repo')
1593 1603 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1594 1604 >>> url('ssh://joe@x/repo')
1595 1605 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1596 1606
1597 1607 Query strings and fragments:
1598 1608
1599 1609 >>> url('http://host/a?b#c')
1600 1610 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1601 1611 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1602 1612 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1603 1613 """
1604 1614
1605 1615 _safechars = "!~*'()+"
1606 1616 _safepchars = "/!~*'()+:"
1607 1617 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1608 1618
1609 1619 def __init__(self, path, parsequery=True, parsefragment=True):
1610 1620 # We slowly chomp away at path until we have only the path left
1611 1621 self.scheme = self.user = self.passwd = self.host = None
1612 1622 self.port = self.path = self.query = self.fragment = None
1613 1623 self._localpath = True
1614 1624 self._hostport = ''
1615 1625 self._origpath = path
1616 1626
1617 1627 if parsefragment and '#' in path:
1618 1628 path, self.fragment = path.split('#', 1)
1619 1629 if not path:
1620 1630 path = None
1621 1631
1622 1632 # special case for Windows drive letters and UNC paths
1623 1633 if hasdriveletter(path) or path.startswith(r'\\'):
1624 1634 self.path = path
1625 1635 return
1626 1636
1627 1637 # For compatibility reasons, we can't handle bundle paths as
1628 1638 # normal URLS
1629 1639 if path.startswith('bundle:'):
1630 1640 self.scheme = 'bundle'
1631 1641 path = path[7:]
1632 1642 if path.startswith('//'):
1633 1643 path = path[2:]
1634 1644 self.path = path
1635 1645 return
1636 1646
1637 1647 if self._matchscheme(path):
1638 1648 parts = path.split(':', 1)
1639 1649 if parts[0]:
1640 1650 self.scheme, path = parts
1641 1651 self._localpath = False
1642 1652
1643 1653 if not path:
1644 1654 path = None
1645 1655 if self._localpath:
1646 1656 self.path = ''
1647 1657 return
1648 1658 else:
1649 1659 if self._localpath:
1650 1660 self.path = path
1651 1661 return
1652 1662
1653 1663 if parsequery and '?' in path:
1654 1664 path, self.query = path.split('?', 1)
1655 1665 if not path:
1656 1666 path = None
1657 1667 if not self.query:
1658 1668 self.query = None
1659 1669
1660 1670 # // is required to specify a host/authority
1661 1671 if path and path.startswith('//'):
1662 1672 parts = path[2:].split('/', 1)
1663 1673 if len(parts) > 1:
1664 1674 self.host, path = parts
1665 1675 path = path
1666 1676 else:
1667 1677 self.host = parts[0]
1668 1678 path = None
1669 1679 if not self.host:
1670 1680 self.host = None
1671 1681 # path of file:///d is /d
1672 1682 # path of file:///d:/ is d:/, not /d:/
1673 1683 if path and not hasdriveletter(path):
1674 1684 path = '/' + path
1675 1685
1676 1686 if self.host and '@' in self.host:
1677 1687 self.user, self.host = self.host.rsplit('@', 1)
1678 1688 if ':' in self.user:
1679 1689 self.user, self.passwd = self.user.split(':', 1)
1680 1690 if not self.host:
1681 1691 self.host = None
1682 1692
1683 1693 # Don't split on colons in IPv6 addresses without ports
1684 1694 if (self.host and ':' in self.host and
1685 1695 not (self.host.startswith('[') and self.host.endswith(']'))):
1686 1696 self._hostport = self.host
1687 1697 self.host, self.port = self.host.rsplit(':', 1)
1688 1698 if not self.host:
1689 1699 self.host = None
1690 1700
1691 1701 if (self.host and self.scheme == 'file' and
1692 1702 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1693 1703 raise Abort(_('file:// URLs can only refer to localhost'))
1694 1704
1695 1705 self.path = path
1696 1706
1697 1707 # leave the query string escaped
1698 1708 for a in ('user', 'passwd', 'host', 'port',
1699 1709 'path', 'fragment'):
1700 1710 v = getattr(self, a)
1701 1711 if v is not None:
1702 1712 setattr(self, a, _urlunquote(v))
1703 1713
1704 1714 def __repr__(self):
1705 1715 attrs = []
1706 1716 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1707 1717 'query', 'fragment'):
1708 1718 v = getattr(self, a)
1709 1719 if v is not None:
1710 1720 attrs.append('%s: %r' % (a, v))
1711 1721 return '<url %s>' % ', '.join(attrs)
1712 1722
1713 1723 def __str__(self):
1714 1724 r"""Join the URL's components back into a URL string.
1715 1725
1716 1726 Examples:
1717 1727
1718 1728 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1719 1729 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1720 1730 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1721 1731 'http://user:pw@host:80/?foo=bar&baz=42'
1722 1732 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1723 1733 'http://user:pw@host:80/?foo=bar%3dbaz'
1724 1734 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1725 1735 'ssh://user:pw@[::1]:2200//home/joe#'
1726 1736 >>> str(url('http://localhost:80//'))
1727 1737 'http://localhost:80//'
1728 1738 >>> str(url('http://localhost:80/'))
1729 1739 'http://localhost:80/'
1730 1740 >>> str(url('http://localhost:80'))
1731 1741 'http://localhost:80/'
1732 1742 >>> str(url('bundle:foo'))
1733 1743 'bundle:foo'
1734 1744 >>> str(url('bundle://../foo'))
1735 1745 'bundle:../foo'
1736 1746 >>> str(url('path'))
1737 1747 'path'
1738 1748 >>> str(url('file:///tmp/foo/bar'))
1739 1749 'file:///tmp/foo/bar'
1740 1750 >>> str(url('file:///c:/tmp/foo/bar'))
1741 1751 'file:///c:/tmp/foo/bar'
1742 1752 >>> print url(r'bundle:foo\bar')
1743 1753 bundle:foo\bar
1744 1754 """
1745 1755 if self._localpath:
1746 1756 s = self.path
1747 1757 if self.scheme == 'bundle':
1748 1758 s = 'bundle:' + s
1749 1759 if self.fragment:
1750 1760 s += '#' + self.fragment
1751 1761 return s
1752 1762
1753 1763 s = self.scheme + ':'
1754 1764 if self.user or self.passwd or self.host:
1755 1765 s += '//'
1756 1766 elif self.scheme and (not self.path or self.path.startswith('/')
1757 1767 or hasdriveletter(self.path)):
1758 1768 s += '//'
1759 1769 if hasdriveletter(self.path):
1760 1770 s += '/'
1761 1771 if self.user:
1762 1772 s += urllib.quote(self.user, safe=self._safechars)
1763 1773 if self.passwd:
1764 1774 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1765 1775 if self.user or self.passwd:
1766 1776 s += '@'
1767 1777 if self.host:
1768 1778 if not (self.host.startswith('[') and self.host.endswith(']')):
1769 1779 s += urllib.quote(self.host)
1770 1780 else:
1771 1781 s += self.host
1772 1782 if self.port:
1773 1783 s += ':' + urllib.quote(self.port)
1774 1784 if self.host:
1775 1785 s += '/'
1776 1786 if self.path:
1777 1787 # TODO: similar to the query string, we should not unescape the
1778 1788 # path when we store it, the path might contain '%2f' = '/',
1779 1789 # which we should *not* escape.
1780 1790 s += urllib.quote(self.path, safe=self._safepchars)
1781 1791 if self.query:
1782 1792 # we store the query in escaped form.
1783 1793 s += '?' + self.query
1784 1794 if self.fragment is not None:
1785 1795 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1786 1796 return s
1787 1797
1788 1798 def authinfo(self):
1789 1799 user, passwd = self.user, self.passwd
1790 1800 try:
1791 1801 self.user, self.passwd = None, None
1792 1802 s = str(self)
1793 1803 finally:
1794 1804 self.user, self.passwd = user, passwd
1795 1805 if not self.user:
1796 1806 return (s, None)
1797 1807 # authinfo[1] is passed to urllib2 password manager, and its
1798 1808 # URIs must not contain credentials. The host is passed in the
1799 1809 # URIs list because Python < 2.4.3 uses only that to search for
1800 1810 # a password.
1801 1811 return (s, (None, (s, self.host),
1802 1812 self.user, self.passwd or ''))
1803 1813
1804 1814 def isabs(self):
1805 1815 if self.scheme and self.scheme != 'file':
1806 1816 return True # remote URL
1807 1817 if hasdriveletter(self.path):
1808 1818 return True # absolute for our purposes - can't be joined()
1809 1819 if self.path.startswith(r'\\'):
1810 1820 return True # Windows UNC path
1811 1821 if self.path.startswith('/'):
1812 1822 return True # POSIX-style
1813 1823 return False
1814 1824
1815 1825 def localpath(self):
1816 1826 if self.scheme == 'file' or self.scheme == 'bundle':
1817 1827 path = self.path or '/'
1818 1828 # For Windows, we need to promote hosts containing drive
1819 1829 # letters to paths with drive letters.
1820 1830 if hasdriveletter(self._hostport):
1821 1831 path = self._hostport + '/' + self.path
1822 1832 elif (self.host is not None and self.path
1823 1833 and not hasdriveletter(path)):
1824 1834 path = '/' + path
1825 1835 return path
1826 1836 return self._origpath
1827 1837
1828 1838 def hasscheme(path):
1829 1839 return bool(url(path).scheme)
1830 1840
1831 1841 def hasdriveletter(path):
1832 1842 return path and path[1:2] == ':' and path[0:1].isalpha()
1833 1843
1834 1844 def urllocalpath(path):
1835 1845 return url(path, parsequery=False, parsefragment=False).localpath()
1836 1846
1837 1847 def hidepassword(u):
1838 1848 '''hide user credential in a url string'''
1839 1849 u = url(u)
1840 1850 if u.passwd:
1841 1851 u.passwd = '***'
1842 1852 return str(u)
1843 1853
1844 1854 def removeauth(u):
1845 1855 '''remove all authentication information from a url string'''
1846 1856 u = url(u)
1847 1857 u.user = u.passwd = None
1848 1858 return str(u)
1849 1859
1850 1860 def isatty(fd):
1851 1861 try:
1852 1862 return fd.isatty()
1853 1863 except AttributeError:
1854 1864 return False
General Comments 0
You need to be logged in to leave comments. Login now