##// END OF EJS Templates
util: add a doctest for empty sha() calls
Matt Mackall -
r15392:d7bfbc92 stable
parent child Browse files
Show More
@@ -1,451 +1,451 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import errno
13 13 import platform
14 14 import shutil
15 15 import stat
16 16 import tempfile
17 17
18 18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
19 19 from mercurial.i18n import _
20 20
21 21 shortname = '.hglf'
22 22 longname = 'largefiles'
23 23
24 24
25 25 # -- Portability wrappers ----------------------------------------------
26 26
27 27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
28 28 return dirstate.walk(matcher, [], unknown, ignored)
29 29
30 30 def repo_add(repo, list):
31 31 add = repo[None].add
32 32 return add(list)
33 33
34 34 def repo_remove(repo, list, unlink=False):
35 35 def remove(list, unlink):
36 36 wlock = repo.wlock()
37 37 try:
38 38 if unlink:
39 39 for f in list:
40 40 try:
41 41 util.unlinkpath(repo.wjoin(f))
42 42 except OSError, inst:
43 43 if inst.errno != errno.ENOENT:
44 44 raise
45 45 repo[None].forget(list)
46 46 finally:
47 47 wlock.release()
48 48 return remove(list, unlink=unlink)
49 49
50 50 def repo_forget(repo, list):
51 51 forget = repo[None].forget
52 52 return forget(list)
53 53
54 54 def findoutgoing(repo, remote, force):
55 55 from mercurial import discovery
56 56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
57 57 remote, force=force)
58 58 return repo.changelog.findmissing(common)
59 59
60 60 # -- Private worker functions ------------------------------------------
61 61
62 62 def getminsize(ui, assumelfiles, opt, default=10):
63 63 lfsize = opt
64 64 if not lfsize and assumelfiles:
65 65 lfsize = ui.config(longname, 'minsize', default=default)
66 66 if lfsize:
67 67 try:
68 68 lfsize = float(lfsize)
69 69 except ValueError:
70 70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
71 71 % lfsize)
72 72 if lfsize is None:
73 73 raise util.Abort(_('minimum size for largefiles must be specified'))
74 74 return lfsize
75 75
76 76 def link(src, dest):
77 77 try:
78 78 util.oslink(src, dest)
79 79 except OSError:
80 80 # if hardlinks fail, fallback on copy
81 81 shutil.copyfile(src, dest)
82 82 os.chmod(dest, os.stat(src).st_mode)
83 83
84 84 def usercachepath(ui, hash):
85 85 path = ui.configpath(longname, 'usercache', None)
86 86 if path:
87 87 path = os.path.join(path, hash)
88 88 else:
89 89 if os.name == 'nt':
90 90 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
91 91 path = os.path.join(appdata, longname, hash)
92 92 elif platform.system() == 'Darwin':
93 93 path = os.path.join(os.getenv('HOME'), 'Library', 'Caches',
94 94 longname, hash)
95 95 elif os.name == 'posix':
96 96 path = os.getenv('XDG_CACHE_HOME')
97 97 if path:
98 98 path = os.path.join(path, longname, hash)
99 99 else:
100 100 path = os.path.join(os.getenv('HOME'), '.cache', longname, hash)
101 101 else:
102 102 raise util.Abort(_('unknown operating system: %s\n') % os.name)
103 103 return path
104 104
105 105 def inusercache(ui, hash):
106 106 return os.path.exists(usercachepath(ui, hash))
107 107
108 108 def findfile(repo, hash):
109 109 if instore(repo, hash):
110 110 repo.ui.note(_('Found %s in store\n') % hash)
111 111 elif inusercache(repo.ui, hash):
112 112 repo.ui.note(_('Found %s in system cache\n') % hash)
113 113 link(usercachepath(repo.ui, hash), storepath(repo, hash))
114 114 else:
115 115 return None
116 116 return storepath(repo, hash)
117 117
118 118 class largefiles_dirstate(dirstate.dirstate):
119 119 def __getitem__(self, key):
120 120 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
121 121 def normal(self, f):
122 122 return super(largefiles_dirstate, self).normal(unixpath(f))
123 123 def remove(self, f):
124 124 return super(largefiles_dirstate, self).remove(unixpath(f))
125 125 def add(self, f):
126 126 return super(largefiles_dirstate, self).add(unixpath(f))
127 127 def drop(self, f):
128 128 return super(largefiles_dirstate, self).drop(unixpath(f))
129 129 def forget(self, f):
130 130 return super(largefiles_dirstate, self).forget(unixpath(f))
131 131
132 132 def openlfdirstate(ui, repo):
133 133 '''
134 134 Return a dirstate object that tracks largefiles: i.e. its root is
135 135 the repo root, but it is saved in .hg/largefiles/dirstate.
136 136 '''
137 137 admin = repo.join(longname)
138 138 opener = scmutil.opener(admin)
139 139 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
140 140 repo.dirstate._validate)
141 141
142 142 # If the largefiles dirstate does not exist, populate and create
143 143 # it. This ensures that we create it on the first meaningful
144 144 # largefiles operation in a new clone. It also gives us an easy
145 145 # way to forcibly rebuild largefiles state:
146 146 # rm .hg/largefiles/dirstate && hg status
147 147 # Or even, if things are really messed up:
148 148 # rm -rf .hg/largefiles && hg status
149 149 if not os.path.exists(os.path.join(admin, 'dirstate')):
150 150 util.makedirs(admin)
151 151 matcher = getstandinmatcher(repo)
152 152 for standin in dirstate_walk(repo.dirstate, matcher):
153 153 lfile = splitstandin(standin)
154 154 hash = readstandin(repo, lfile)
155 155 lfdirstate.normallookup(lfile)
156 156 try:
157 157 if hash == hashfile(lfile):
158 158 lfdirstate.normal(lfile)
159 159 except IOError, err:
160 160 if err.errno != errno.ENOENT:
161 161 raise
162 162
163 163 lfdirstate.write()
164 164
165 165 return lfdirstate
166 166
167 167 def lfdirstate_status(lfdirstate, repo, rev):
168 168 wlock = repo.wlock()
169 169 try:
170 170 match = match_.always(repo.root, repo.getcwd())
171 171 s = lfdirstate.status(match, [], False, False, False)
172 172 unsure, modified, added, removed, missing, unknown, ignored, clean = s
173 173 for lfile in unsure:
174 174 if repo[rev][standin(lfile)].data().strip() != \
175 175 hashfile(repo.wjoin(lfile)):
176 176 modified.append(lfile)
177 177 else:
178 178 clean.append(lfile)
179 179 lfdirstate.normal(lfile)
180 180 lfdirstate.write()
181 181 finally:
182 182 wlock.release()
183 183 return (modified, added, removed, missing, unknown, ignored, clean)
184 184
185 185 def listlfiles(repo, rev=None, matcher=None):
186 186 '''return a list of largefiles in the working copy or the
187 187 specified changeset'''
188 188
189 189 if matcher is None:
190 190 matcher = getstandinmatcher(repo)
191 191
192 192 # ignore unknown files in working directory
193 193 return [splitstandin(f)
194 194 for f in repo[rev].walk(matcher)
195 195 if rev is not None or repo.dirstate[f] != '?']
196 196
197 197 def instore(repo, hash):
198 198 return os.path.exists(storepath(repo, hash))
199 199
200 200 def storepath(repo, hash):
201 201 return repo.join(os.path.join(longname, hash))
202 202
203 203 def copyfromcache(repo, hash, filename):
204 204 '''Copy the specified largefile from the repo or system cache to
205 205 filename in the repository. Return true on success or false if the
206 206 file was not found in either cache (which should not happened:
207 207 this is meant to be called only after ensuring that the needed
208 208 largefile exists in the cache).'''
209 209 path = findfile(repo, hash)
210 210 if path is None:
211 211 return False
212 212 util.makedirs(os.path.dirname(repo.wjoin(filename)))
213 213 shutil.copy(path, repo.wjoin(filename))
214 214 return True
215 215
216 216 def copytostore(repo, rev, file, uploaded=False):
217 217 hash = readstandin(repo, file)
218 218 if instore(repo, hash):
219 219 return
220 220 copytostoreabsolute(repo, repo.wjoin(file), hash)
221 221
222 222 def copytostoreabsolute(repo, file, hash):
223 223 util.makedirs(os.path.dirname(storepath(repo, hash)))
224 224 if inusercache(repo.ui, hash):
225 225 link(usercachepath(repo.ui, hash), storepath(repo, hash))
226 226 else:
227 227 shutil.copyfile(file, storepath(repo, hash))
228 228 os.chmod(storepath(repo, hash), os.stat(file).st_mode)
229 229 linktousercache(repo, hash)
230 230
231 231 def linktousercache(repo, hash):
232 232 util.makedirs(os.path.dirname(usercachepath(repo.ui, hash)))
233 233 link(storepath(repo, hash), usercachepath(repo.ui, hash))
234 234
235 235 def getstandinmatcher(repo, pats=[], opts={}):
236 236 '''Return a match object that applies pats to the standin directory'''
237 237 standindir = repo.pathto(shortname)
238 238 if pats:
239 239 # patterns supplied: search standin directory relative to current dir
240 240 cwd = repo.getcwd()
241 241 if os.path.isabs(cwd):
242 242 # cwd is an absolute path for hg -R <reponame>
243 243 # work relative to the repository root in this case
244 244 cwd = ''
245 245 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
246 246 elif os.path.isdir(standindir):
247 247 # no patterns: relative to repo root
248 248 pats = [standindir]
249 249 else:
250 250 # no patterns and no standin dir: return matcher that matches nothing
251 251 match = match_.match(repo.root, None, [], exact=True)
252 252 match.matchfn = lambda f: False
253 253 return match
254 254 return getmatcher(repo, pats, opts, showbad=False)
255 255
256 256 def getmatcher(repo, pats=[], opts={}, showbad=True):
257 257 '''Wrapper around scmutil.match() that adds showbad: if false,
258 258 neuter the match object's bad() method so it does not print any
259 259 warnings about missing files or directories.'''
260 260 match = scmutil.match(repo[None], pats, opts)
261 261
262 262 if not showbad:
263 263 match.bad = lambda f, msg: None
264 264 return match
265 265
266 266 def composestandinmatcher(repo, rmatcher):
267 267 '''Return a matcher that accepts standins corresponding to the
268 268 files accepted by rmatcher. Pass the list of files in the matcher
269 269 as the paths specified by the user.'''
270 270 smatcher = getstandinmatcher(repo, rmatcher.files())
271 271 isstandin = smatcher.matchfn
272 272 def composed_matchfn(f):
273 273 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
274 274 smatcher.matchfn = composed_matchfn
275 275
276 276 return smatcher
277 277
278 278 def standin(filename):
279 279 '''Return the repo-relative path to the standin for the specified big
280 280 file.'''
281 281 # Notes:
282 282 # 1) Most callers want an absolute path, but _create_standin() needs
283 283 # it repo-relative so lfadd() can pass it to repo_add(). So leave
284 284 # it up to the caller to use repo.wjoin() to get an absolute path.
285 285 # 2) Join with '/' because that's what dirstate always uses, even on
286 286 # Windows. Change existing separator to '/' first in case we are
287 287 # passed filenames from an external source (like the command line).
288 288 return shortname + '/' + filename.replace(os.sep, '/')
289 289
290 290 def isstandin(filename):
291 291 '''Return true if filename is a big file standin. filename must be
292 292 in Mercurial's internal form (slash-separated).'''
293 293 return filename.startswith(shortname + '/')
294 294
295 295 def splitstandin(filename):
296 296 # Split on / because that's what dirstate always uses, even on Windows.
297 297 # Change local separator to / first just in case we are passed filenames
298 298 # from an external source (like the command line).
299 299 bits = filename.replace(os.sep, '/').split('/', 1)
300 300 if len(bits) == 2 and bits[0] == shortname:
301 301 return bits[1]
302 302 else:
303 303 return None
304 304
305 305 def updatestandin(repo, standin):
306 306 file = repo.wjoin(splitstandin(standin))
307 307 if os.path.exists(file):
308 308 hash = hashfile(file)
309 309 executable = getexecutable(file)
310 310 writestandin(repo, standin, hash, executable)
311 311
312 312 def readstandin(repo, filename, node=None):
313 313 '''read hex hash from standin for filename at given node, or working
314 314 directory if no node is given'''
315 315 return repo[node][standin(filename)].data().strip()
316 316
317 317 def writestandin(repo, standin, hash, executable):
318 318 '''write hash to <repo.root>/<standin>'''
319 319 writehash(hash, repo.wjoin(standin), executable)
320 320
321 321 def copyandhash(instream, outfile):
322 322 '''Read bytes from instream (iterable) and write them to outfile,
323 323 computing the SHA-1 hash of the data along the way. Close outfile
324 324 when done and return the binary hash.'''
325 325 hasher = util.sha1('')
326 326 for data in instream:
327 327 hasher.update(data)
328 328 outfile.write(data)
329 329
330 330 # Blecch: closing a file that somebody else opened is rude and
331 331 # wrong. But it's so darn convenient and practical! After all,
332 332 # outfile was opened just to copy and hash.
333 333 outfile.close()
334 334
335 335 return hasher.digest()
336 336
337 337 def hashrepofile(repo, file):
338 338 return hashfile(repo.wjoin(file))
339 339
340 340 def hashfile(file):
341 341 if not os.path.exists(file):
342 342 return ''
343 343 hasher = util.sha1('')
344 344 fd = open(file, 'rb')
345 345 for data in blockstream(fd):
346 346 hasher.update(data)
347 347 fd.close()
348 348 return hasher.hexdigest()
349 349
350 350 class limitreader(object):
351 351 def __init__(self, f, limit):
352 352 self.f = f
353 353 self.limit = limit
354 354
355 355 def read(self, length):
356 356 if self.limit == 0:
357 357 return ''
358 358 length = length > self.limit and self.limit or length
359 359 self.limit -= length
360 360 return self.f.read(length)
361 361
362 362 def close(self):
363 363 pass
364 364
365 365 def blockstream(infile, blocksize=128 * 1024):
366 366 """Generator that yields blocks of data from infile and closes infile."""
367 367 while True:
368 368 data = infile.read(blocksize)
369 369 if not data:
370 370 break
371 371 yield data
372 372 # same blecch as copyandhash() above
373 373 infile.close()
374 374
375 375 def readhash(filename):
376 376 rfile = open(filename, 'rb')
377 377 hash = rfile.read(40)
378 378 rfile.close()
379 379 if len(hash) < 40:
380 380 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
381 381 % (filename, len(hash)))
382 382 return hash
383 383
384 384 def writehash(hash, filename, executable):
385 385 util.makedirs(os.path.dirname(filename))
386 386 if os.path.exists(filename):
387 387 os.unlink(filename)
388 388 wfile = open(filename, 'wb')
389 389
390 390 try:
391 391 wfile.write(hash)
392 392 wfile.write('\n')
393 393 finally:
394 394 wfile.close()
395 395 if os.path.exists(filename):
396 396 os.chmod(filename, getmode(executable))
397 397
398 398 def getexecutable(filename):
399 399 mode = os.stat(filename).st_mode
400 400 return ((mode & stat.S_IXUSR) and
401 401 (mode & stat.S_IXGRP) and
402 402 (mode & stat.S_IXOTH))
403 403
404 404 def getmode(executable):
405 405 if executable:
406 406 return 0755
407 407 else:
408 408 return 0644
409 409
410 410 def urljoin(first, second, *arg):
411 411 def join(left, right):
412 412 if not left.endswith('/'):
413 413 left += '/'
414 414 if right.startswith('/'):
415 415 right = right[1:]
416 416 return left + right
417 417
418 418 url = join(first, second)
419 419 for a in arg:
420 420 url = join(url, a)
421 421 return url
422 422
423 423 def hexsha1(data):
424 424 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
425 425 object data"""
426 426 h = util.sha1()
427 427 for chunk in util.filechunkiter(data):
428 428 h.update(chunk)
429 429 return h.hexdigest()
430 430
431 431 def httpsendfile(ui, filename):
432 432 return httpconnection.httpsendfile(ui, filename, 'rb')
433 433
434 434 def unixpath(path):
435 435 '''Return a version of path normalized for use with the lfdirstate.'''
436 436 return os.path.normpath(path).replace(os.sep, '/')
437 437
438 438 def islfilesrepo(repo):
439 439 return ('largefiles' in repo.requirements and
440 440 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
441 441
442 442 def mkstemp(repo, prefix):
443 443 '''Returns a file descriptor and a filename corresponding to a temporary
444 444 file in the repo's largefiles store.'''
445 445 path = repo.join(longname)
446 util.makedirs(repo.join(path))
446 util.makedirs(path)
447 447 return tempfile.mkstemp(prefix=prefix, dir=path)
448 448
449 449 class storeprotonotcapable(Exception):
450 450 def __init__(self, storetypes):
451 451 self.storetypes = storetypes
@@ -1,1731 +1,1741 b''
1 1 # util.py - Mercurial utility functions and platform specfic 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 specfic 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
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, 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 nulldev = platform.nulldev
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 sshargs = platform.sshargs
66 66 statfiles = platform.statfiles
67 67 termwidth = platform.termwidth
68 68 testpid = platform.testpid
69 69 umask = platform.umask
70 70 unlink = platform.unlink
71 71 unlinkpath = platform.unlinkpath
72 72 username = platform.username
73 73
74 74 # Python compatibility
75 75
76 76 def sha1(s=''):
77 '''
78 Low-overhead wrapper around Python's SHA support
79
80 >>> f = _fastsha1
81 >>> a = sha1()
82 >>> a = f()
83 >>> a.hexdigest()
84 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
85 '''
86
77 87 return _fastsha1(s)
78 88
79 89 _notset = object()
80 90 def safehasattr(thing, attr):
81 91 return getattr(thing, attr, _notset) is not _notset
82 92
83 93 def _fastsha1(s=''):
84 94 # This function will import sha1 from hashlib or sha (whichever is
85 95 # available) and overwrite itself with it on the first call.
86 96 # Subsequent calls will go directly to the imported function.
87 97 if sys.version_info >= (2, 5):
88 98 from hashlib import sha1 as _sha1
89 99 else:
90 100 from sha import sha as _sha1
91 101 global _fastsha1, sha1
92 102 _fastsha1 = sha1 = _sha1
93 103 return _sha1(s)
94 104
95 105 import __builtin__
96 106
97 107 if sys.version_info[0] < 3:
98 108 def fakebuffer(sliceable, offset=0):
99 109 return sliceable[offset:]
100 110 else:
101 111 def fakebuffer(sliceable, offset=0):
102 112 return memoryview(sliceable)[offset:]
103 113 try:
104 114 buffer
105 115 except NameError:
106 116 __builtin__.buffer = fakebuffer
107 117
108 118 import subprocess
109 119 closefds = os.name == 'posix'
110 120
111 121 def popen2(cmd, env=None, newlines=False):
112 122 # Setting bufsize to -1 lets the system decide the buffer size.
113 123 # The default for bufsize is 0, meaning unbuffered. This leads to
114 124 # poor performance on Mac OS X: http://bugs.python.org/issue4194
115 125 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
116 126 close_fds=closefds,
117 127 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
118 128 universal_newlines=newlines,
119 129 env=env)
120 130 return p.stdin, p.stdout
121 131
122 132 def popen3(cmd, env=None, newlines=False):
123 133 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
124 134 close_fds=closefds,
125 135 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
126 136 stderr=subprocess.PIPE,
127 137 universal_newlines=newlines,
128 138 env=env)
129 139 return p.stdin, p.stdout, p.stderr
130 140
131 141 def version():
132 142 """Return version information if available."""
133 143 try:
134 144 import __version__
135 145 return __version__.version
136 146 except ImportError:
137 147 return 'unknown'
138 148
139 149 # used by parsedate
140 150 defaultdateformats = (
141 151 '%Y-%m-%d %H:%M:%S',
142 152 '%Y-%m-%d %I:%M:%S%p',
143 153 '%Y-%m-%d %H:%M',
144 154 '%Y-%m-%d %I:%M%p',
145 155 '%Y-%m-%d',
146 156 '%m-%d',
147 157 '%m/%d',
148 158 '%m/%d/%y',
149 159 '%m/%d/%Y',
150 160 '%a %b %d %H:%M:%S %Y',
151 161 '%a %b %d %I:%M:%S%p %Y',
152 162 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
153 163 '%b %d %H:%M:%S %Y',
154 164 '%b %d %I:%M:%S%p %Y',
155 165 '%b %d %H:%M:%S',
156 166 '%b %d %I:%M:%S%p',
157 167 '%b %d %H:%M',
158 168 '%b %d %I:%M%p',
159 169 '%b %d %Y',
160 170 '%b %d',
161 171 '%H:%M:%S',
162 172 '%I:%M:%S%p',
163 173 '%H:%M',
164 174 '%I:%M%p',
165 175 )
166 176
167 177 extendeddateformats = defaultdateformats + (
168 178 "%Y",
169 179 "%Y-%m",
170 180 "%b",
171 181 "%b %Y",
172 182 )
173 183
174 184 def cachefunc(func):
175 185 '''cache the result of function calls'''
176 186 # XXX doesn't handle keywords args
177 187 cache = {}
178 188 if func.func_code.co_argcount == 1:
179 189 # we gain a small amount of time because
180 190 # we don't need to pack/unpack the list
181 191 def f(arg):
182 192 if arg not in cache:
183 193 cache[arg] = func(arg)
184 194 return cache[arg]
185 195 else:
186 196 def f(*args):
187 197 if args not in cache:
188 198 cache[args] = func(*args)
189 199 return cache[args]
190 200
191 201 return f
192 202
193 203 def lrucachefunc(func):
194 204 '''cache most recent results of function calls'''
195 205 cache = {}
196 206 order = []
197 207 if func.func_code.co_argcount == 1:
198 208 def f(arg):
199 209 if arg not in cache:
200 210 if len(cache) > 20:
201 211 del cache[order.pop(0)]
202 212 cache[arg] = func(arg)
203 213 else:
204 214 order.remove(arg)
205 215 order.append(arg)
206 216 return cache[arg]
207 217 else:
208 218 def f(*args):
209 219 if args not in cache:
210 220 if len(cache) > 20:
211 221 del cache[order.pop(0)]
212 222 cache[args] = func(*args)
213 223 else:
214 224 order.remove(args)
215 225 order.append(args)
216 226 return cache[args]
217 227
218 228 return f
219 229
220 230 class propertycache(object):
221 231 def __init__(self, func):
222 232 self.func = func
223 233 self.name = func.__name__
224 234 def __get__(self, obj, type=None):
225 235 result = self.func(obj)
226 236 setattr(obj, self.name, result)
227 237 return result
228 238
229 239 def pipefilter(s, cmd):
230 240 '''filter string S through command CMD, returning its output'''
231 241 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
232 242 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
233 243 pout, perr = p.communicate(s)
234 244 return pout
235 245
236 246 def tempfilter(s, cmd):
237 247 '''filter string S through a pair of temporary files with CMD.
238 248 CMD is used as a template to create the real command to be run,
239 249 with the strings INFILE and OUTFILE replaced by the real names of
240 250 the temporary files generated.'''
241 251 inname, outname = None, None
242 252 try:
243 253 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
244 254 fp = os.fdopen(infd, 'wb')
245 255 fp.write(s)
246 256 fp.close()
247 257 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
248 258 os.close(outfd)
249 259 cmd = cmd.replace('INFILE', inname)
250 260 cmd = cmd.replace('OUTFILE', outname)
251 261 code = os.system(cmd)
252 262 if sys.platform == 'OpenVMS' and code & 1:
253 263 code = 0
254 264 if code:
255 265 raise Abort(_("command '%s' failed: %s") %
256 266 (cmd, explainexit(code)))
257 267 fp = open(outname, 'rb')
258 268 r = fp.read()
259 269 fp.close()
260 270 return r
261 271 finally:
262 272 try:
263 273 if inname:
264 274 os.unlink(inname)
265 275 except OSError:
266 276 pass
267 277 try:
268 278 if outname:
269 279 os.unlink(outname)
270 280 except OSError:
271 281 pass
272 282
273 283 filtertable = {
274 284 'tempfile:': tempfilter,
275 285 'pipe:': pipefilter,
276 286 }
277 287
278 288 def filter(s, cmd):
279 289 "filter a string through a command that transforms its input to its output"
280 290 for name, fn in filtertable.iteritems():
281 291 if cmd.startswith(name):
282 292 return fn(s, cmd[len(name):].lstrip())
283 293 return pipefilter(s, cmd)
284 294
285 295 def binary(s):
286 296 """return true if a string is binary data"""
287 297 return bool(s and '\0' in s)
288 298
289 299 def increasingchunks(source, min=1024, max=65536):
290 300 '''return no less than min bytes per chunk while data remains,
291 301 doubling min after each chunk until it reaches max'''
292 302 def log2(x):
293 303 if not x:
294 304 return 0
295 305 i = 0
296 306 while x:
297 307 x >>= 1
298 308 i += 1
299 309 return i - 1
300 310
301 311 buf = []
302 312 blen = 0
303 313 for chunk in source:
304 314 buf.append(chunk)
305 315 blen += len(chunk)
306 316 if blen >= min:
307 317 if min < max:
308 318 min = min << 1
309 319 nmin = 1 << log2(blen)
310 320 if nmin > min:
311 321 min = nmin
312 322 if min > max:
313 323 min = max
314 324 yield ''.join(buf)
315 325 blen = 0
316 326 buf = []
317 327 if buf:
318 328 yield ''.join(buf)
319 329
320 330 Abort = error.Abort
321 331
322 332 def always(fn):
323 333 return True
324 334
325 335 def never(fn):
326 336 return False
327 337
328 338 def pathto(root, n1, n2):
329 339 '''return the relative path from one place to another.
330 340 root should use os.sep to separate directories
331 341 n1 should use os.sep to separate directories
332 342 n2 should use "/" to separate directories
333 343 returns an os.sep-separated path.
334 344
335 345 If n1 is a relative path, it's assumed it's
336 346 relative to root.
337 347 n2 should always be relative to root.
338 348 '''
339 349 if not n1:
340 350 return localpath(n2)
341 351 if os.path.isabs(n1):
342 352 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
343 353 return os.path.join(root, localpath(n2))
344 354 n2 = '/'.join((pconvert(root), n2))
345 355 a, b = splitpath(n1), n2.split('/')
346 356 a.reverse()
347 357 b.reverse()
348 358 while a and b and a[-1] == b[-1]:
349 359 a.pop()
350 360 b.pop()
351 361 b.reverse()
352 362 return os.sep.join((['..'] * len(a)) + b) or '.'
353 363
354 364 _hgexecutable = None
355 365
356 366 def mainfrozen():
357 367 """return True if we are a frozen executable.
358 368
359 369 The code supports py2exe (most common, Windows only) and tools/freeze
360 370 (portable, not much used).
361 371 """
362 372 return (safehasattr(sys, "frozen") or # new py2exe
363 373 safehasattr(sys, "importers") or # old py2exe
364 374 imp.is_frozen("__main__")) # tools/freeze
365 375
366 376 def hgexecutable():
367 377 """return location of the 'hg' executable.
368 378
369 379 Defaults to $HG or 'hg' in the search path.
370 380 """
371 381 if _hgexecutable is None:
372 382 hg = os.environ.get('HG')
373 383 mainmod = sys.modules['__main__']
374 384 if hg:
375 385 _sethgexecutable(hg)
376 386 elif mainfrozen():
377 387 _sethgexecutable(sys.executable)
378 388 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
379 389 _sethgexecutable(mainmod.__file__)
380 390 else:
381 391 exe = findexe('hg') or os.path.basename(sys.argv[0])
382 392 _sethgexecutable(exe)
383 393 return _hgexecutable
384 394
385 395 def _sethgexecutable(path):
386 396 """set location of the 'hg' executable"""
387 397 global _hgexecutable
388 398 _hgexecutable = path
389 399
390 400 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
391 401 '''enhanced shell command execution.
392 402 run with environment maybe modified, maybe in different dir.
393 403
394 404 if command fails and onerr is None, return status. if ui object,
395 405 print error message and return status, else raise onerr object as
396 406 exception.
397 407
398 408 if out is specified, it is assumed to be a file-like object that has a
399 409 write() method. stdout and stderr will be redirected to out.'''
400 410 try:
401 411 sys.stdout.flush()
402 412 except Exception:
403 413 pass
404 414 def py2shell(val):
405 415 'convert python object into string that is useful to shell'
406 416 if val is None or val is False:
407 417 return '0'
408 418 if val is True:
409 419 return '1'
410 420 return str(val)
411 421 origcmd = cmd
412 422 cmd = quotecommand(cmd)
413 423 env = dict(os.environ)
414 424 env.update((k, py2shell(v)) for k, v in environ.iteritems())
415 425 env['HG'] = hgexecutable()
416 426 if out is None or out == sys.__stdout__:
417 427 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
418 428 env=env, cwd=cwd)
419 429 else:
420 430 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
421 431 env=env, cwd=cwd, stdout=subprocess.PIPE,
422 432 stderr=subprocess.STDOUT)
423 433 for line in proc.stdout:
424 434 out.write(line)
425 435 proc.wait()
426 436 rc = proc.returncode
427 437 if sys.platform == 'OpenVMS' and rc & 1:
428 438 rc = 0
429 439 if rc and onerr:
430 440 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
431 441 explainexit(rc)[0])
432 442 if errprefix:
433 443 errmsg = '%s: %s' % (errprefix, errmsg)
434 444 try:
435 445 onerr.warn(errmsg + '\n')
436 446 except AttributeError:
437 447 raise onerr(errmsg)
438 448 return rc
439 449
440 450 def checksignature(func):
441 451 '''wrap a function with code to check for calling errors'''
442 452 def check(*args, **kwargs):
443 453 try:
444 454 return func(*args, **kwargs)
445 455 except TypeError:
446 456 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
447 457 raise error.SignatureError
448 458 raise
449 459
450 460 return check
451 461
452 462 def copyfile(src, dest):
453 463 "copy a file, preserving mode and atime/mtime"
454 464 if os.path.islink(src):
455 465 try:
456 466 os.unlink(dest)
457 467 except OSError:
458 468 pass
459 469 os.symlink(os.readlink(src), dest)
460 470 else:
461 471 try:
462 472 shutil.copyfile(src, dest)
463 473 shutil.copymode(src, dest)
464 474 except shutil.Error, inst:
465 475 raise Abort(str(inst))
466 476
467 477 def copyfiles(src, dst, hardlink=None):
468 478 """Copy a directory tree using hardlinks if possible"""
469 479
470 480 if hardlink is None:
471 481 hardlink = (os.stat(src).st_dev ==
472 482 os.stat(os.path.dirname(dst)).st_dev)
473 483
474 484 num = 0
475 485 if os.path.isdir(src):
476 486 os.mkdir(dst)
477 487 for name, kind in osutil.listdir(src):
478 488 srcname = os.path.join(src, name)
479 489 dstname = os.path.join(dst, name)
480 490 hardlink, n = copyfiles(srcname, dstname, hardlink)
481 491 num += n
482 492 else:
483 493 if hardlink:
484 494 try:
485 495 oslink(src, dst)
486 496 except (IOError, OSError):
487 497 hardlink = False
488 498 shutil.copy(src, dst)
489 499 else:
490 500 shutil.copy(src, dst)
491 501 num += 1
492 502
493 503 return hardlink, num
494 504
495 505 _winreservednames = '''con prn aux nul
496 506 com1 com2 com3 com4 com5 com6 com7 com8 com9
497 507 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
498 508 _winreservedchars = ':*?"<>|'
499 509 def checkwinfilename(path):
500 510 '''Check that the base-relative path is a valid filename on Windows.
501 511 Returns None if the path is ok, or a UI string describing the problem.
502 512
503 513 >>> checkwinfilename("just/a/normal/path")
504 514 >>> checkwinfilename("foo/bar/con.xml")
505 515 "filename contains 'con', which is reserved on Windows"
506 516 >>> checkwinfilename("foo/con.xml/bar")
507 517 "filename contains 'con', which is reserved on Windows"
508 518 >>> checkwinfilename("foo/bar/xml.con")
509 519 >>> checkwinfilename("foo/bar/AUX/bla.txt")
510 520 "filename contains 'AUX', which is reserved on Windows"
511 521 >>> checkwinfilename("foo/bar/bla:.txt")
512 522 "filename contains ':', which is reserved on Windows"
513 523 >>> checkwinfilename("foo/bar/b\07la.txt")
514 524 "filename contains '\\\\x07', which is invalid on Windows"
515 525 >>> checkwinfilename("foo/bar/bla ")
516 526 "filename ends with ' ', which is not allowed on Windows"
517 527 >>> checkwinfilename("../bar")
518 528 '''
519 529 for n in path.replace('\\', '/').split('/'):
520 530 if not n:
521 531 continue
522 532 for c in n:
523 533 if c in _winreservedchars:
524 534 return _("filename contains '%s', which is reserved "
525 535 "on Windows") % c
526 536 if ord(c) <= 31:
527 537 return _("filename contains %r, which is invalid "
528 538 "on Windows") % c
529 539 base = n.split('.')[0]
530 540 if base and base.lower() in _winreservednames:
531 541 return _("filename contains '%s', which is reserved "
532 542 "on Windows") % base
533 543 t = n[-1]
534 544 if t in '. ' and n not in '..':
535 545 return _("filename ends with '%s', which is not allowed "
536 546 "on Windows") % t
537 547
538 548 if os.name == 'nt':
539 549 checkosfilename = checkwinfilename
540 550 else:
541 551 checkosfilename = platform.checkosfilename
542 552
543 553 def makelock(info, pathname):
544 554 try:
545 555 return os.symlink(info, pathname)
546 556 except OSError, why:
547 557 if why.errno == errno.EEXIST:
548 558 raise
549 559 except AttributeError: # no symlink in os
550 560 pass
551 561
552 562 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
553 563 os.write(ld, info)
554 564 os.close(ld)
555 565
556 566 def readlock(pathname):
557 567 try:
558 568 return os.readlink(pathname)
559 569 except OSError, why:
560 570 if why.errno not in (errno.EINVAL, errno.ENOSYS):
561 571 raise
562 572 except AttributeError: # no symlink in os
563 573 pass
564 574 fp = posixfile(pathname)
565 575 r = fp.read()
566 576 fp.close()
567 577 return r
568 578
569 579 def fstat(fp):
570 580 '''stat file object that may not have fileno method.'''
571 581 try:
572 582 return os.fstat(fp.fileno())
573 583 except AttributeError:
574 584 return os.stat(fp.name)
575 585
576 586 # File system features
577 587
578 588 def checkcase(path):
579 589 """
580 590 Check whether the given path is on a case-sensitive filesystem
581 591
582 592 Requires a path (like /foo/.hg) ending with a foldable final
583 593 directory component.
584 594 """
585 595 s1 = os.stat(path)
586 596 d, b = os.path.split(path)
587 597 p2 = os.path.join(d, b.upper())
588 598 if path == p2:
589 599 p2 = os.path.join(d, b.lower())
590 600 try:
591 601 s2 = os.stat(p2)
592 602 if s2 == s1:
593 603 return False
594 604 return True
595 605 except OSError:
596 606 return True
597 607
598 608 _fspathcache = {}
599 609 def fspath(name, root):
600 610 '''Get name in the case stored in the filesystem
601 611
602 612 The name is either relative to root, or it is an absolute path starting
603 613 with root. Note that this function is unnecessary, and should not be
604 614 called, for case-sensitive filesystems (simply because it's expensive).
605 615 '''
606 616 # If name is absolute, make it relative
607 617 if name.lower().startswith(root.lower()):
608 618 l = len(root)
609 619 if name[l] == os.sep or name[l] == os.altsep:
610 620 l = l + 1
611 621 name = name[l:]
612 622
613 623 if not os.path.lexists(os.path.join(root, name)):
614 624 return None
615 625
616 626 seps = os.sep
617 627 if os.altsep:
618 628 seps = seps + os.altsep
619 629 # Protect backslashes. This gets silly very quickly.
620 630 seps.replace('\\','\\\\')
621 631 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
622 632 dir = os.path.normcase(os.path.normpath(root))
623 633 result = []
624 634 for part, sep in pattern.findall(name):
625 635 if sep:
626 636 result.append(sep)
627 637 continue
628 638
629 639 if dir not in _fspathcache:
630 640 _fspathcache[dir] = os.listdir(dir)
631 641 contents = _fspathcache[dir]
632 642
633 643 lpart = part.lower()
634 644 lenp = len(part)
635 645 for n in contents:
636 646 if lenp == len(n) and n.lower() == lpart:
637 647 result.append(n)
638 648 break
639 649 else:
640 650 # Cannot happen, as the file exists!
641 651 result.append(part)
642 652 dir = os.path.join(dir, lpart)
643 653
644 654 return ''.join(result)
645 655
646 656 def checknlink(testfile):
647 657 '''check whether hardlink count reporting works properly'''
648 658
649 659 # testfile may be open, so we need a separate file for checking to
650 660 # work around issue2543 (or testfile may get lost on Samba shares)
651 661 f1 = testfile + ".hgtmp1"
652 662 if os.path.lexists(f1):
653 663 return False
654 664 try:
655 665 posixfile(f1, 'w').close()
656 666 except IOError:
657 667 return False
658 668
659 669 f2 = testfile + ".hgtmp2"
660 670 fd = None
661 671 try:
662 672 try:
663 673 oslink(f1, f2)
664 674 except OSError:
665 675 return False
666 676
667 677 # nlinks() may behave differently for files on Windows shares if
668 678 # the file is open.
669 679 fd = posixfile(f2)
670 680 return nlinks(f2) > 1
671 681 finally:
672 682 if fd is not None:
673 683 fd.close()
674 684 for f in (f1, f2):
675 685 try:
676 686 os.unlink(f)
677 687 except OSError:
678 688 pass
679 689
680 690 return False
681 691
682 692 def endswithsep(path):
683 693 '''Check path ends with os.sep or os.altsep.'''
684 694 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
685 695
686 696 def splitpath(path):
687 697 '''Split path by os.sep.
688 698 Note that this function does not use os.altsep because this is
689 699 an alternative of simple "xxx.split(os.sep)".
690 700 It is recommended to use os.path.normpath() before using this
691 701 function if need.'''
692 702 return path.split(os.sep)
693 703
694 704 def gui():
695 705 '''Are we running in a GUI?'''
696 706 if sys.platform == 'darwin':
697 707 if 'SSH_CONNECTION' in os.environ:
698 708 # handle SSH access to a box where the user is logged in
699 709 return False
700 710 elif getattr(osutil, 'isgui', None):
701 711 # check if a CoreGraphics session is available
702 712 return osutil.isgui()
703 713 else:
704 714 # pure build; use a safe default
705 715 return True
706 716 else:
707 717 return os.name == "nt" or os.environ.get("DISPLAY")
708 718
709 719 def mktempcopy(name, emptyok=False, createmode=None):
710 720 """Create a temporary file with the same contents from name
711 721
712 722 The permission bits are copied from the original file.
713 723
714 724 If the temporary file is going to be truncated immediately, you
715 725 can use emptyok=True as an optimization.
716 726
717 727 Returns the name of the temporary file.
718 728 """
719 729 d, fn = os.path.split(name)
720 730 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
721 731 os.close(fd)
722 732 # Temporary files are created with mode 0600, which is usually not
723 733 # what we want. If the original file already exists, just copy
724 734 # its mode. Otherwise, manually obey umask.
725 735 copymode(name, temp, createmode)
726 736 if emptyok:
727 737 return temp
728 738 try:
729 739 try:
730 740 ifp = posixfile(name, "rb")
731 741 except IOError, inst:
732 742 if inst.errno == errno.ENOENT:
733 743 return temp
734 744 if not getattr(inst, 'filename', None):
735 745 inst.filename = name
736 746 raise
737 747 ofp = posixfile(temp, "wb")
738 748 for chunk in filechunkiter(ifp):
739 749 ofp.write(chunk)
740 750 ifp.close()
741 751 ofp.close()
742 752 except:
743 753 try: os.unlink(temp)
744 754 except: pass
745 755 raise
746 756 return temp
747 757
748 758 class atomictempfile(object):
749 759 '''writeable file object that atomically updates a file
750 760
751 761 All writes will go to a temporary copy of the original file. Call
752 762 close() when you are done writing, and atomictempfile will rename
753 763 the temporary copy to the original name, making the changes
754 764 visible. If the object is destroyed without being closed, all your
755 765 writes are discarded.
756 766 '''
757 767 def __init__(self, name, mode='w+b', createmode=None):
758 768 self.__name = name # permanent name
759 769 self._tempname = mktempcopy(name, emptyok=('w' in mode),
760 770 createmode=createmode)
761 771 self._fp = posixfile(self._tempname, mode)
762 772
763 773 # delegated methods
764 774 self.write = self._fp.write
765 775 self.fileno = self._fp.fileno
766 776
767 777 def close(self):
768 778 if not self._fp.closed:
769 779 self._fp.close()
770 780 rename(self._tempname, localpath(self.__name))
771 781
772 782 def discard(self):
773 783 if not self._fp.closed:
774 784 try:
775 785 os.unlink(self._tempname)
776 786 except OSError:
777 787 pass
778 788 self._fp.close()
779 789
780 790 def __del__(self):
781 791 if safehasattr(self, '_fp'): # constructor actually did something
782 792 self.discard()
783 793
784 794 def makedirs(name, mode=None):
785 795 """recursive directory creation with parent mode inheritance"""
786 796 try:
787 797 os.mkdir(name)
788 798 except OSError, err:
789 799 if err.errno == errno.EEXIST:
790 800 return
791 801 if err.errno != errno.ENOENT or not name:
792 802 raise
793 803 parent = os.path.dirname(os.path.abspath(name))
794 804 if parent == name:
795 805 raise
796 806 makedirs(parent, mode)
797 807 os.mkdir(name)
798 808 if mode is not None:
799 809 os.chmod(name, mode)
800 810
801 811 def readfile(path):
802 812 fp = open(path, 'rb')
803 813 try:
804 814 return fp.read()
805 815 finally:
806 816 fp.close()
807 817
808 818 def writefile(path, text):
809 819 fp = open(path, 'wb')
810 820 try:
811 821 fp.write(text)
812 822 finally:
813 823 fp.close()
814 824
815 825 def appendfile(path, text):
816 826 fp = open(path, 'ab')
817 827 try:
818 828 fp.write(text)
819 829 finally:
820 830 fp.close()
821 831
822 832 class chunkbuffer(object):
823 833 """Allow arbitrary sized chunks of data to be efficiently read from an
824 834 iterator over chunks of arbitrary size."""
825 835
826 836 def __init__(self, in_iter):
827 837 """in_iter is the iterator that's iterating over the input chunks.
828 838 targetsize is how big a buffer to try to maintain."""
829 839 def splitbig(chunks):
830 840 for chunk in chunks:
831 841 if len(chunk) > 2**20:
832 842 pos = 0
833 843 while pos < len(chunk):
834 844 end = pos + 2 ** 18
835 845 yield chunk[pos:end]
836 846 pos = end
837 847 else:
838 848 yield chunk
839 849 self.iter = splitbig(in_iter)
840 850 self._queue = []
841 851
842 852 def read(self, l):
843 853 """Read L bytes of data from the iterator of chunks of data.
844 854 Returns less than L bytes if the iterator runs dry."""
845 855 left = l
846 856 buf = ''
847 857 queue = self._queue
848 858 while left > 0:
849 859 # refill the queue
850 860 if not queue:
851 861 target = 2**18
852 862 for chunk in self.iter:
853 863 queue.append(chunk)
854 864 target -= len(chunk)
855 865 if target <= 0:
856 866 break
857 867 if not queue:
858 868 break
859 869
860 870 chunk = queue.pop(0)
861 871 left -= len(chunk)
862 872 if left < 0:
863 873 queue.insert(0, chunk[left:])
864 874 buf += chunk[:left]
865 875 else:
866 876 buf += chunk
867 877
868 878 return buf
869 879
870 880 def filechunkiter(f, size=65536, limit=None):
871 881 """Create a generator that produces the data in the file size
872 882 (default 65536) bytes at a time, up to optional limit (default is
873 883 to read all data). Chunks may be less than size bytes if the
874 884 chunk is the last chunk in the file, or the file is a socket or
875 885 some other type of file that sometimes reads less data than is
876 886 requested."""
877 887 assert size >= 0
878 888 assert limit is None or limit >= 0
879 889 while True:
880 890 if limit is None:
881 891 nbytes = size
882 892 else:
883 893 nbytes = min(limit, size)
884 894 s = nbytes and f.read(nbytes)
885 895 if not s:
886 896 break
887 897 if limit:
888 898 limit -= len(s)
889 899 yield s
890 900
891 901 def makedate():
892 902 lt = time.localtime()
893 903 if lt[8] == 1 and time.daylight:
894 904 tz = time.altzone
895 905 else:
896 906 tz = time.timezone
897 907 t = time.mktime(lt)
898 908 if t < 0:
899 909 hint = _("check your clock")
900 910 raise Abort(_("negative timestamp: %d") % t, hint=hint)
901 911 return t, tz
902 912
903 913 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
904 914 """represent a (unixtime, offset) tuple as a localized time.
905 915 unixtime is seconds since the epoch, and offset is the time zone's
906 916 number of seconds away from UTC. if timezone is false, do not
907 917 append time zone to string."""
908 918 t, tz = date or makedate()
909 919 if t < 0:
910 920 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
911 921 tz = 0
912 922 if "%1" in format or "%2" in format:
913 923 sign = (tz > 0) and "-" or "+"
914 924 minutes = abs(tz) // 60
915 925 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
916 926 format = format.replace("%2", "%02d" % (minutes % 60))
917 927 try:
918 928 t = time.gmtime(float(t) - tz)
919 929 except ValueError:
920 930 # time was out of range
921 931 t = time.gmtime(sys.maxint)
922 932 s = time.strftime(format, t)
923 933 return s
924 934
925 935 def shortdate(date=None):
926 936 """turn (timestamp, tzoff) tuple into iso 8631 date."""
927 937 return datestr(date, format='%Y-%m-%d')
928 938
929 939 def strdate(string, format, defaults=[]):
930 940 """parse a localized time string and return a (unixtime, offset) tuple.
931 941 if the string cannot be parsed, ValueError is raised."""
932 942 def timezone(string):
933 943 tz = string.split()[-1]
934 944 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
935 945 sign = (tz[0] == "+") and 1 or -1
936 946 hours = int(tz[1:3])
937 947 minutes = int(tz[3:5])
938 948 return -sign * (hours * 60 + minutes) * 60
939 949 if tz == "GMT" or tz == "UTC":
940 950 return 0
941 951 return None
942 952
943 953 # NOTE: unixtime = localunixtime + offset
944 954 offset, date = timezone(string), string
945 955 if offset is not None:
946 956 date = " ".join(string.split()[:-1])
947 957
948 958 # add missing elements from defaults
949 959 usenow = False # default to using biased defaults
950 960 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
951 961 found = [True for p in part if ("%"+p) in format]
952 962 if not found:
953 963 date += "@" + defaults[part][usenow]
954 964 format += "@%" + part[0]
955 965 else:
956 966 # We've found a specific time element, less specific time
957 967 # elements are relative to today
958 968 usenow = True
959 969
960 970 timetuple = time.strptime(date, format)
961 971 localunixtime = int(calendar.timegm(timetuple))
962 972 if offset is None:
963 973 # local timezone
964 974 unixtime = int(time.mktime(timetuple))
965 975 offset = unixtime - localunixtime
966 976 else:
967 977 unixtime = localunixtime + offset
968 978 return unixtime, offset
969 979
970 980 def parsedate(date, formats=None, bias={}):
971 981 """parse a localized date/time and return a (unixtime, offset) tuple.
972 982
973 983 The date may be a "unixtime offset" string or in one of the specified
974 984 formats. If the date already is a (unixtime, offset) tuple, it is returned.
975 985 """
976 986 if not date:
977 987 return 0, 0
978 988 if isinstance(date, tuple) and len(date) == 2:
979 989 return date
980 990 if not formats:
981 991 formats = defaultdateformats
982 992 date = date.strip()
983 993 try:
984 994 when, offset = map(int, date.split(' '))
985 995 except ValueError:
986 996 # fill out defaults
987 997 now = makedate()
988 998 defaults = {}
989 999 for part in ("d", "mb", "yY", "HI", "M", "S"):
990 1000 # this piece is for rounding the specific end of unknowns
991 1001 b = bias.get(part)
992 1002 if b is None:
993 1003 if part[0] in "HMS":
994 1004 b = "00"
995 1005 else:
996 1006 b = "0"
997 1007
998 1008 # this piece is for matching the generic end to today's date
999 1009 n = datestr(now, "%" + part[0])
1000 1010
1001 1011 defaults[part] = (b, n)
1002 1012
1003 1013 for format in formats:
1004 1014 try:
1005 1015 when, offset = strdate(date, format, defaults)
1006 1016 except (ValueError, OverflowError):
1007 1017 pass
1008 1018 else:
1009 1019 break
1010 1020 else:
1011 1021 raise Abort(_('invalid date: %r') % date)
1012 1022 # validate explicit (probably user-specified) date and
1013 1023 # time zone offset. values must fit in signed 32 bits for
1014 1024 # current 32-bit linux runtimes. timezones go from UTC-12
1015 1025 # to UTC+14
1016 1026 if abs(when) > 0x7fffffff:
1017 1027 raise Abort(_('date exceeds 32 bits: %d') % when)
1018 1028 if when < 0:
1019 1029 raise Abort(_('negative date value: %d') % when)
1020 1030 if offset < -50400 or offset > 43200:
1021 1031 raise Abort(_('impossible time zone offset: %d') % offset)
1022 1032 return when, offset
1023 1033
1024 1034 def matchdate(date):
1025 1035 """Return a function that matches a given date match specifier
1026 1036
1027 1037 Formats include:
1028 1038
1029 1039 '{date}' match a given date to the accuracy provided
1030 1040
1031 1041 '<{date}' on or before a given date
1032 1042
1033 1043 '>{date}' on or after a given date
1034 1044
1035 1045 >>> p1 = parsedate("10:29:59")
1036 1046 >>> p2 = parsedate("10:30:00")
1037 1047 >>> p3 = parsedate("10:30:59")
1038 1048 >>> p4 = parsedate("10:31:00")
1039 1049 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1040 1050 >>> f = matchdate("10:30")
1041 1051 >>> f(p1[0])
1042 1052 False
1043 1053 >>> f(p2[0])
1044 1054 True
1045 1055 >>> f(p3[0])
1046 1056 True
1047 1057 >>> f(p4[0])
1048 1058 False
1049 1059 >>> f(p5[0])
1050 1060 False
1051 1061 """
1052 1062
1053 1063 def lower(date):
1054 1064 d = dict(mb="1", d="1")
1055 1065 return parsedate(date, extendeddateformats, d)[0]
1056 1066
1057 1067 def upper(date):
1058 1068 d = dict(mb="12", HI="23", M="59", S="59")
1059 1069 for days in ("31", "30", "29"):
1060 1070 try:
1061 1071 d["d"] = days
1062 1072 return parsedate(date, extendeddateformats, d)[0]
1063 1073 except:
1064 1074 pass
1065 1075 d["d"] = "28"
1066 1076 return parsedate(date, extendeddateformats, d)[0]
1067 1077
1068 1078 date = date.strip()
1069 1079
1070 1080 if not date:
1071 1081 raise Abort(_("dates cannot consist entirely of whitespace"))
1072 1082 elif date[0] == "<":
1073 1083 if not date[1:]:
1074 1084 raise Abort(_("invalid day spec, use '<DATE'"))
1075 1085 when = upper(date[1:])
1076 1086 return lambda x: x <= when
1077 1087 elif date[0] == ">":
1078 1088 if not date[1:]:
1079 1089 raise Abort(_("invalid day spec, use '>DATE'"))
1080 1090 when = lower(date[1:])
1081 1091 return lambda x: x >= when
1082 1092 elif date[0] == "-":
1083 1093 try:
1084 1094 days = int(date[1:])
1085 1095 except ValueError:
1086 1096 raise Abort(_("invalid day spec: %s") % date[1:])
1087 1097 if days < 0:
1088 1098 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1089 1099 % date[1:])
1090 1100 when = makedate()[0] - days * 3600 * 24
1091 1101 return lambda x: x >= when
1092 1102 elif " to " in date:
1093 1103 a, b = date.split(" to ")
1094 1104 start, stop = lower(a), upper(b)
1095 1105 return lambda x: x >= start and x <= stop
1096 1106 else:
1097 1107 start, stop = lower(date), upper(date)
1098 1108 return lambda x: x >= start and x <= stop
1099 1109
1100 1110 def shortuser(user):
1101 1111 """Return a short representation of a user name or email address."""
1102 1112 f = user.find('@')
1103 1113 if f >= 0:
1104 1114 user = user[:f]
1105 1115 f = user.find('<')
1106 1116 if f >= 0:
1107 1117 user = user[f + 1:]
1108 1118 f = user.find(' ')
1109 1119 if f >= 0:
1110 1120 user = user[:f]
1111 1121 f = user.find('.')
1112 1122 if f >= 0:
1113 1123 user = user[:f]
1114 1124 return user
1115 1125
1116 1126 def email(author):
1117 1127 '''get email of author.'''
1118 1128 r = author.find('>')
1119 1129 if r == -1:
1120 1130 r = None
1121 1131 return author[author.find('<') + 1:r]
1122 1132
1123 1133 def _ellipsis(text, maxlength):
1124 1134 if len(text) <= maxlength:
1125 1135 return text, False
1126 1136 else:
1127 1137 return "%s..." % (text[:maxlength - 3]), True
1128 1138
1129 1139 def ellipsis(text, maxlength=400):
1130 1140 """Trim string to at most maxlength (default: 400) characters."""
1131 1141 try:
1132 1142 # use unicode not to split at intermediate multi-byte sequence
1133 1143 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1134 1144 maxlength)
1135 1145 if not truncated:
1136 1146 return text
1137 1147 return utext.encode(encoding.encoding)
1138 1148 except (UnicodeDecodeError, UnicodeEncodeError):
1139 1149 return _ellipsis(text, maxlength)[0]
1140 1150
1141 1151 def bytecount(nbytes):
1142 1152 '''return byte count formatted as readable string, with units'''
1143 1153
1144 1154 units = (
1145 1155 (100, 1 << 30, _('%.0f GB')),
1146 1156 (10, 1 << 30, _('%.1f GB')),
1147 1157 (1, 1 << 30, _('%.2f GB')),
1148 1158 (100, 1 << 20, _('%.0f MB')),
1149 1159 (10, 1 << 20, _('%.1f MB')),
1150 1160 (1, 1 << 20, _('%.2f MB')),
1151 1161 (100, 1 << 10, _('%.0f KB')),
1152 1162 (10, 1 << 10, _('%.1f KB')),
1153 1163 (1, 1 << 10, _('%.2f KB')),
1154 1164 (1, 1, _('%.0f bytes')),
1155 1165 )
1156 1166
1157 1167 for multiplier, divisor, format in units:
1158 1168 if nbytes >= divisor * multiplier:
1159 1169 return format % (nbytes / float(divisor))
1160 1170 return units[-1][2] % nbytes
1161 1171
1162 1172 def uirepr(s):
1163 1173 # Avoid double backslash in Windows path repr()
1164 1174 return repr(s).replace('\\\\', '\\')
1165 1175
1166 1176 # delay import of textwrap
1167 1177 def MBTextWrapper(**kwargs):
1168 1178 class tw(textwrap.TextWrapper):
1169 1179 """
1170 1180 Extend TextWrapper for width-awareness.
1171 1181
1172 1182 Neither number of 'bytes' in any encoding nor 'characters' is
1173 1183 appropriate to calculate terminal columns for specified string.
1174 1184
1175 1185 Original TextWrapper implementation uses built-in 'len()' directly,
1176 1186 so overriding is needed to use width information of each characters.
1177 1187
1178 1188 In addition, characters classified into 'ambiguous' width are
1179 1189 treated as wide in east asian area, but as narrow in other.
1180 1190
1181 1191 This requires use decision to determine width of such characters.
1182 1192 """
1183 1193 def __init__(self, **kwargs):
1184 1194 textwrap.TextWrapper.__init__(self, **kwargs)
1185 1195
1186 1196 # for compatibility between 2.4 and 2.6
1187 1197 if getattr(self, 'drop_whitespace', None) is None:
1188 1198 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1189 1199
1190 1200 def _cutdown(self, ucstr, space_left):
1191 1201 l = 0
1192 1202 colwidth = encoding.ucolwidth
1193 1203 for i in xrange(len(ucstr)):
1194 1204 l += colwidth(ucstr[i])
1195 1205 if space_left < l:
1196 1206 return (ucstr[:i], ucstr[i:])
1197 1207 return ucstr, ''
1198 1208
1199 1209 # overriding of base class
1200 1210 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1201 1211 space_left = max(width - cur_len, 1)
1202 1212
1203 1213 if self.break_long_words:
1204 1214 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1205 1215 cur_line.append(cut)
1206 1216 reversed_chunks[-1] = res
1207 1217 elif not cur_line:
1208 1218 cur_line.append(reversed_chunks.pop())
1209 1219
1210 1220 # this overriding code is imported from TextWrapper of python 2.6
1211 1221 # to calculate columns of string by 'encoding.ucolwidth()'
1212 1222 def _wrap_chunks(self, chunks):
1213 1223 colwidth = encoding.ucolwidth
1214 1224
1215 1225 lines = []
1216 1226 if self.width <= 0:
1217 1227 raise ValueError("invalid width %r (must be > 0)" % self.width)
1218 1228
1219 1229 # Arrange in reverse order so items can be efficiently popped
1220 1230 # from a stack of chucks.
1221 1231 chunks.reverse()
1222 1232
1223 1233 while chunks:
1224 1234
1225 1235 # Start the list of chunks that will make up the current line.
1226 1236 # cur_len is just the length of all the chunks in cur_line.
1227 1237 cur_line = []
1228 1238 cur_len = 0
1229 1239
1230 1240 # Figure out which static string will prefix this line.
1231 1241 if lines:
1232 1242 indent = self.subsequent_indent
1233 1243 else:
1234 1244 indent = self.initial_indent
1235 1245
1236 1246 # Maximum width for this line.
1237 1247 width = self.width - len(indent)
1238 1248
1239 1249 # First chunk on line is whitespace -- drop it, unless this
1240 1250 # is the very beginning of the text (ie. no lines started yet).
1241 1251 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1242 1252 del chunks[-1]
1243 1253
1244 1254 while chunks:
1245 1255 l = colwidth(chunks[-1])
1246 1256
1247 1257 # Can at least squeeze this chunk onto the current line.
1248 1258 if cur_len + l <= width:
1249 1259 cur_line.append(chunks.pop())
1250 1260 cur_len += l
1251 1261
1252 1262 # Nope, this line is full.
1253 1263 else:
1254 1264 break
1255 1265
1256 1266 # The current line is full, and the next chunk is too big to
1257 1267 # fit on *any* line (not just this one).
1258 1268 if chunks and colwidth(chunks[-1]) > width:
1259 1269 self._handle_long_word(chunks, cur_line, cur_len, width)
1260 1270
1261 1271 # If the last chunk on this line is all whitespace, drop it.
1262 1272 if (self.drop_whitespace and
1263 1273 cur_line and cur_line[-1].strip() == ''):
1264 1274 del cur_line[-1]
1265 1275
1266 1276 # Convert current line back to a string and store it in list
1267 1277 # of all lines (return value).
1268 1278 if cur_line:
1269 1279 lines.append(indent + ''.join(cur_line))
1270 1280
1271 1281 return lines
1272 1282
1273 1283 global MBTextWrapper
1274 1284 MBTextWrapper = tw
1275 1285 return tw(**kwargs)
1276 1286
1277 1287 def wrap(line, width, initindent='', hangindent=''):
1278 1288 maxindent = max(len(hangindent), len(initindent))
1279 1289 if width <= maxindent:
1280 1290 # adjust for weird terminal size
1281 1291 width = max(78, maxindent + 1)
1282 1292 line = line.decode(encoding.encoding, encoding.encodingmode)
1283 1293 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1284 1294 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1285 1295 wrapper = MBTextWrapper(width=width,
1286 1296 initial_indent=initindent,
1287 1297 subsequent_indent=hangindent)
1288 1298 return wrapper.fill(line).encode(encoding.encoding)
1289 1299
1290 1300 def iterlines(iterator):
1291 1301 for chunk in iterator:
1292 1302 for line in chunk.splitlines():
1293 1303 yield line
1294 1304
1295 1305 def expandpath(path):
1296 1306 return os.path.expanduser(os.path.expandvars(path))
1297 1307
1298 1308 def hgcmd():
1299 1309 """Return the command used to execute current hg
1300 1310
1301 1311 This is different from hgexecutable() because on Windows we want
1302 1312 to avoid things opening new shell windows like batch files, so we
1303 1313 get either the python call or current executable.
1304 1314 """
1305 1315 if mainfrozen():
1306 1316 return [sys.executable]
1307 1317 return gethgcmd()
1308 1318
1309 1319 def rundetached(args, condfn):
1310 1320 """Execute the argument list in a detached process.
1311 1321
1312 1322 condfn is a callable which is called repeatedly and should return
1313 1323 True once the child process is known to have started successfully.
1314 1324 At this point, the child process PID is returned. If the child
1315 1325 process fails to start or finishes before condfn() evaluates to
1316 1326 True, return -1.
1317 1327 """
1318 1328 # Windows case is easier because the child process is either
1319 1329 # successfully starting and validating the condition or exiting
1320 1330 # on failure. We just poll on its PID. On Unix, if the child
1321 1331 # process fails to start, it will be left in a zombie state until
1322 1332 # the parent wait on it, which we cannot do since we expect a long
1323 1333 # running process on success. Instead we listen for SIGCHLD telling
1324 1334 # us our child process terminated.
1325 1335 terminated = set()
1326 1336 def handler(signum, frame):
1327 1337 terminated.add(os.wait())
1328 1338 prevhandler = None
1329 1339 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1330 1340 if SIGCHLD is not None:
1331 1341 prevhandler = signal.signal(SIGCHLD, handler)
1332 1342 try:
1333 1343 pid = spawndetached(args)
1334 1344 while not condfn():
1335 1345 if ((pid in terminated or not testpid(pid))
1336 1346 and not condfn()):
1337 1347 return -1
1338 1348 time.sleep(0.1)
1339 1349 return pid
1340 1350 finally:
1341 1351 if prevhandler is not None:
1342 1352 signal.signal(signal.SIGCHLD, prevhandler)
1343 1353
1344 1354 try:
1345 1355 any, all = any, all
1346 1356 except NameError:
1347 1357 def any(iterable):
1348 1358 for i in iterable:
1349 1359 if i:
1350 1360 return True
1351 1361 return False
1352 1362
1353 1363 def all(iterable):
1354 1364 for i in iterable:
1355 1365 if not i:
1356 1366 return False
1357 1367 return True
1358 1368
1359 1369 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1360 1370 """Return the result of interpolating items in the mapping into string s.
1361 1371
1362 1372 prefix is a single character string, or a two character string with
1363 1373 a backslash as the first character if the prefix needs to be escaped in
1364 1374 a regular expression.
1365 1375
1366 1376 fn is an optional function that will be applied to the replacement text
1367 1377 just before replacement.
1368 1378
1369 1379 escape_prefix is an optional flag that allows using doubled prefix for
1370 1380 its escaping.
1371 1381 """
1372 1382 fn = fn or (lambda s: s)
1373 1383 patterns = '|'.join(mapping.keys())
1374 1384 if escape_prefix:
1375 1385 patterns += '|' + prefix
1376 1386 if len(prefix) > 1:
1377 1387 prefix_char = prefix[1:]
1378 1388 else:
1379 1389 prefix_char = prefix
1380 1390 mapping[prefix_char] = prefix_char
1381 1391 r = re.compile(r'%s(%s)' % (prefix, patterns))
1382 1392 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1383 1393
1384 1394 def getport(port):
1385 1395 """Return the port for a given network service.
1386 1396
1387 1397 If port is an integer, it's returned as is. If it's a string, it's
1388 1398 looked up using socket.getservbyname(). If there's no matching
1389 1399 service, util.Abort is raised.
1390 1400 """
1391 1401 try:
1392 1402 return int(port)
1393 1403 except ValueError:
1394 1404 pass
1395 1405
1396 1406 try:
1397 1407 return socket.getservbyname(port)
1398 1408 except socket.error:
1399 1409 raise Abort(_("no port number associated with service '%s'") % port)
1400 1410
1401 1411 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1402 1412 '0': False, 'no': False, 'false': False, 'off': False,
1403 1413 'never': False}
1404 1414
1405 1415 def parsebool(s):
1406 1416 """Parse s into a boolean.
1407 1417
1408 1418 If s is not a valid boolean, returns None.
1409 1419 """
1410 1420 return _booleans.get(s.lower(), None)
1411 1421
1412 1422 _hexdig = '0123456789ABCDEFabcdef'
1413 1423 _hextochr = dict((a + b, chr(int(a + b, 16)))
1414 1424 for a in _hexdig for b in _hexdig)
1415 1425
1416 1426 def _urlunquote(s):
1417 1427 """unquote('abc%20def') -> 'abc def'."""
1418 1428 res = s.split('%')
1419 1429 # fastpath
1420 1430 if len(res) == 1:
1421 1431 return s
1422 1432 s = res[0]
1423 1433 for item in res[1:]:
1424 1434 try:
1425 1435 s += _hextochr[item[:2]] + item[2:]
1426 1436 except KeyError:
1427 1437 s += '%' + item
1428 1438 except UnicodeDecodeError:
1429 1439 s += unichr(int(item[:2], 16)) + item[2:]
1430 1440 return s
1431 1441
1432 1442 class url(object):
1433 1443 r"""Reliable URL parser.
1434 1444
1435 1445 This parses URLs and provides attributes for the following
1436 1446 components:
1437 1447
1438 1448 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1439 1449
1440 1450 Missing components are set to None. The only exception is
1441 1451 fragment, which is set to '' if present but empty.
1442 1452
1443 1453 If parsefragment is False, fragment is included in query. If
1444 1454 parsequery is False, query is included in path. If both are
1445 1455 False, both fragment and query are included in path.
1446 1456
1447 1457 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1448 1458
1449 1459 Note that for backward compatibility reasons, bundle URLs do not
1450 1460 take host names. That means 'bundle://../' has a path of '../'.
1451 1461
1452 1462 Examples:
1453 1463
1454 1464 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1455 1465 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1456 1466 >>> url('ssh://[::1]:2200//home/joe/repo')
1457 1467 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1458 1468 >>> url('file:///home/joe/repo')
1459 1469 <url scheme: 'file', path: '/home/joe/repo'>
1460 1470 >>> url('file:///c:/temp/foo/')
1461 1471 <url scheme: 'file', path: 'c:/temp/foo/'>
1462 1472 >>> url('bundle:foo')
1463 1473 <url scheme: 'bundle', path: 'foo'>
1464 1474 >>> url('bundle://../foo')
1465 1475 <url scheme: 'bundle', path: '../foo'>
1466 1476 >>> url(r'c:\foo\bar')
1467 1477 <url path: 'c:\\foo\\bar'>
1468 1478 >>> url(r'\\blah\blah\blah')
1469 1479 <url path: '\\\\blah\\blah\\blah'>
1470 1480 >>> url(r'\\blah\blah\blah#baz')
1471 1481 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1472 1482
1473 1483 Authentication credentials:
1474 1484
1475 1485 >>> url('ssh://joe:xyz@x/repo')
1476 1486 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1477 1487 >>> url('ssh://joe@x/repo')
1478 1488 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1479 1489
1480 1490 Query strings and fragments:
1481 1491
1482 1492 >>> url('http://host/a?b#c')
1483 1493 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1484 1494 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1485 1495 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1486 1496 """
1487 1497
1488 1498 _safechars = "!~*'()+"
1489 1499 _safepchars = "/!~*'()+"
1490 1500 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1491 1501
1492 1502 def __init__(self, path, parsequery=True, parsefragment=True):
1493 1503 # We slowly chomp away at path until we have only the path left
1494 1504 self.scheme = self.user = self.passwd = self.host = None
1495 1505 self.port = self.path = self.query = self.fragment = None
1496 1506 self._localpath = True
1497 1507 self._hostport = ''
1498 1508 self._origpath = path
1499 1509
1500 1510 if parsefragment and '#' in path:
1501 1511 path, self.fragment = path.split('#', 1)
1502 1512 if not path:
1503 1513 path = None
1504 1514
1505 1515 # special case for Windows drive letters and UNC paths
1506 1516 if hasdriveletter(path) or path.startswith(r'\\'):
1507 1517 self.path = path
1508 1518 return
1509 1519
1510 1520 # For compatibility reasons, we can't handle bundle paths as
1511 1521 # normal URLS
1512 1522 if path.startswith('bundle:'):
1513 1523 self.scheme = 'bundle'
1514 1524 path = path[7:]
1515 1525 if path.startswith('//'):
1516 1526 path = path[2:]
1517 1527 self.path = path
1518 1528 return
1519 1529
1520 1530 if self._matchscheme(path):
1521 1531 parts = path.split(':', 1)
1522 1532 if parts[0]:
1523 1533 self.scheme, path = parts
1524 1534 self._localpath = False
1525 1535
1526 1536 if not path:
1527 1537 path = None
1528 1538 if self._localpath:
1529 1539 self.path = ''
1530 1540 return
1531 1541 else:
1532 1542 if self._localpath:
1533 1543 self.path = path
1534 1544 return
1535 1545
1536 1546 if parsequery and '?' in path:
1537 1547 path, self.query = path.split('?', 1)
1538 1548 if not path:
1539 1549 path = None
1540 1550 if not self.query:
1541 1551 self.query = None
1542 1552
1543 1553 # // is required to specify a host/authority
1544 1554 if path and path.startswith('//'):
1545 1555 parts = path[2:].split('/', 1)
1546 1556 if len(parts) > 1:
1547 1557 self.host, path = parts
1548 1558 path = path
1549 1559 else:
1550 1560 self.host = parts[0]
1551 1561 path = None
1552 1562 if not self.host:
1553 1563 self.host = None
1554 1564 # path of file:///d is /d
1555 1565 # path of file:///d:/ is d:/, not /d:/
1556 1566 if path and not hasdriveletter(path):
1557 1567 path = '/' + path
1558 1568
1559 1569 if self.host and '@' in self.host:
1560 1570 self.user, self.host = self.host.rsplit('@', 1)
1561 1571 if ':' in self.user:
1562 1572 self.user, self.passwd = self.user.split(':', 1)
1563 1573 if not self.host:
1564 1574 self.host = None
1565 1575
1566 1576 # Don't split on colons in IPv6 addresses without ports
1567 1577 if (self.host and ':' in self.host and
1568 1578 not (self.host.startswith('[') and self.host.endswith(']'))):
1569 1579 self._hostport = self.host
1570 1580 self.host, self.port = self.host.rsplit(':', 1)
1571 1581 if not self.host:
1572 1582 self.host = None
1573 1583
1574 1584 if (self.host and self.scheme == 'file' and
1575 1585 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1576 1586 raise Abort(_('file:// URLs can only refer to localhost'))
1577 1587
1578 1588 self.path = path
1579 1589
1580 1590 # leave the query string escaped
1581 1591 for a in ('user', 'passwd', 'host', 'port',
1582 1592 'path', 'fragment'):
1583 1593 v = getattr(self, a)
1584 1594 if v is not None:
1585 1595 setattr(self, a, _urlunquote(v))
1586 1596
1587 1597 def __repr__(self):
1588 1598 attrs = []
1589 1599 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1590 1600 'query', 'fragment'):
1591 1601 v = getattr(self, a)
1592 1602 if v is not None:
1593 1603 attrs.append('%s: %r' % (a, v))
1594 1604 return '<url %s>' % ', '.join(attrs)
1595 1605
1596 1606 def __str__(self):
1597 1607 r"""Join the URL's components back into a URL string.
1598 1608
1599 1609 Examples:
1600 1610
1601 1611 >>> str(url('http://user:pw@host:80/?foo#bar'))
1602 1612 'http://user:pw@host:80/?foo#bar'
1603 1613 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1604 1614 'http://user:pw@host:80/?foo=bar&baz=42'
1605 1615 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1606 1616 'http://user:pw@host:80/?foo=bar%3dbaz'
1607 1617 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1608 1618 'ssh://user:pw@[::1]:2200//home/joe#'
1609 1619 >>> str(url('http://localhost:80//'))
1610 1620 'http://localhost:80//'
1611 1621 >>> str(url('http://localhost:80/'))
1612 1622 'http://localhost:80/'
1613 1623 >>> str(url('http://localhost:80'))
1614 1624 'http://localhost:80/'
1615 1625 >>> str(url('bundle:foo'))
1616 1626 'bundle:foo'
1617 1627 >>> str(url('bundle://../foo'))
1618 1628 'bundle:../foo'
1619 1629 >>> str(url('path'))
1620 1630 'path'
1621 1631 >>> str(url('file:///tmp/foo/bar'))
1622 1632 'file:///tmp/foo/bar'
1623 1633 >>> print url(r'bundle:foo\bar')
1624 1634 bundle:foo\bar
1625 1635 """
1626 1636 if self._localpath:
1627 1637 s = self.path
1628 1638 if self.scheme == 'bundle':
1629 1639 s = 'bundle:' + s
1630 1640 if self.fragment:
1631 1641 s += '#' + self.fragment
1632 1642 return s
1633 1643
1634 1644 s = self.scheme + ':'
1635 1645 if self.user or self.passwd or self.host:
1636 1646 s += '//'
1637 1647 elif self.scheme and (not self.path or self.path.startswith('/')):
1638 1648 s += '//'
1639 1649 if self.user:
1640 1650 s += urllib.quote(self.user, safe=self._safechars)
1641 1651 if self.passwd:
1642 1652 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1643 1653 if self.user or self.passwd:
1644 1654 s += '@'
1645 1655 if self.host:
1646 1656 if not (self.host.startswith('[') and self.host.endswith(']')):
1647 1657 s += urllib.quote(self.host)
1648 1658 else:
1649 1659 s += self.host
1650 1660 if self.port:
1651 1661 s += ':' + urllib.quote(self.port)
1652 1662 if self.host:
1653 1663 s += '/'
1654 1664 if self.path:
1655 1665 # TODO: similar to the query string, we should not unescape the
1656 1666 # path when we store it, the path might contain '%2f' = '/',
1657 1667 # which we should *not* escape.
1658 1668 s += urllib.quote(self.path, safe=self._safepchars)
1659 1669 if self.query:
1660 1670 # we store the query in escaped form.
1661 1671 s += '?' + self.query
1662 1672 if self.fragment is not None:
1663 1673 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1664 1674 return s
1665 1675
1666 1676 def authinfo(self):
1667 1677 user, passwd = self.user, self.passwd
1668 1678 try:
1669 1679 self.user, self.passwd = None, None
1670 1680 s = str(self)
1671 1681 finally:
1672 1682 self.user, self.passwd = user, passwd
1673 1683 if not self.user:
1674 1684 return (s, None)
1675 1685 # authinfo[1] is passed to urllib2 password manager, and its
1676 1686 # URIs must not contain credentials. The host is passed in the
1677 1687 # URIs list because Python < 2.4.3 uses only that to search for
1678 1688 # a password.
1679 1689 return (s, (None, (s, self.host),
1680 1690 self.user, self.passwd or ''))
1681 1691
1682 1692 def isabs(self):
1683 1693 if self.scheme and self.scheme != 'file':
1684 1694 return True # remote URL
1685 1695 if hasdriveletter(self.path):
1686 1696 return True # absolute for our purposes - can't be joined()
1687 1697 if self.path.startswith(r'\\'):
1688 1698 return True # Windows UNC path
1689 1699 if self.path.startswith('/'):
1690 1700 return True # POSIX-style
1691 1701 return False
1692 1702
1693 1703 def localpath(self):
1694 1704 if self.scheme == 'file' or self.scheme == 'bundle':
1695 1705 path = self.path or '/'
1696 1706 # For Windows, we need to promote hosts containing drive
1697 1707 # letters to paths with drive letters.
1698 1708 if hasdriveletter(self._hostport):
1699 1709 path = self._hostport + '/' + self.path
1700 1710 elif self.host is not None and self.path:
1701 1711 path = '/' + path
1702 1712 return path
1703 1713 return self._origpath
1704 1714
1705 1715 def hasscheme(path):
1706 1716 return bool(url(path).scheme)
1707 1717
1708 1718 def hasdriveletter(path):
1709 1719 return path[1:2] == ':' and path[0:1].isalpha()
1710 1720
1711 1721 def urllocalpath(path):
1712 1722 return url(path, parsequery=False, parsefragment=False).localpath()
1713 1723
1714 1724 def hidepassword(u):
1715 1725 '''hide user credential in a url string'''
1716 1726 u = url(u)
1717 1727 if u.passwd:
1718 1728 u.passwd = '***'
1719 1729 return str(u)
1720 1730
1721 1731 def removeauth(u):
1722 1732 '''remove all authentication information from a url string'''
1723 1733 u = url(u)
1724 1734 u.user = u.passwd = None
1725 1735 return str(u)
1726 1736
1727 1737 def isatty(fd):
1728 1738 try:
1729 1739 return fd.isatty()
1730 1740 except AttributeError:
1731 1741 return False
@@ -1,38 +1,38 b''
1 1 # this is hack to make sure no escape characters are inserted into the output
2 2 import os
3 3 if 'TERM' in os.environ:
4 4 del os.environ['TERM']
5 5 import doctest
6 6
7 import mercurial.util
8 doctest.testmod(mercurial.util)
9
7 10 import mercurial.changelog
8 11 doctest.testmod(mercurial.changelog)
9 12
10 13 import mercurial.dagparser
11 14 doctest.testmod(mercurial.dagparser, optionflags=doctest.NORMALIZE_WHITESPACE)
12 15
13 16 import mercurial.match
14 17 doctest.testmod(mercurial.match)
15 18
16 19 import mercurial.store
17 20 doctest.testmod(mercurial.store)
18 21
19 22 import mercurial.ui
20 23 doctest.testmod(mercurial.ui)
21 24
22 25 import mercurial.url
23 26 doctest.testmod(mercurial.url)
24 27
25 import mercurial.util
26 doctest.testmod(mercurial.util)
27
28 28 import mercurial.encoding
29 29 doctest.testmod(mercurial.encoding)
30 30
31 31 import mercurial.hgweb.hgwebdir_mod
32 32 doctest.testmod(mercurial.hgweb.hgwebdir_mod)
33 33
34 34 import hgext.convert.cvsps
35 35 doctest.testmod(hgext.convert.cvsps)
36 36
37 37 import mercurial.revset
38 38 doctest.testmod(mercurial.revset)
General Comments 0
You need to be logged in to leave comments. Login now