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