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