##// END OF EJS Templates
compression: use 'None' for no-compression...
Pierre-Yves David -
r26267:eca468b8 default
parent child Browse files
Show More
@@ -1,892 +1,894 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 branchmap,
24 branchmap,
25 dagutil,
25 dagutil,
26 discovery,
26 discovery,
27 error,
27 error,
28 mdiff,
28 mdiff,
29 phases,
29 phases,
30 util,
30 util,
31 )
31 )
32
32
33 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
34 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
35
35
36 def readexactly(stream, n):
36 def readexactly(stream, n):
37 '''read n bytes from stream.read and abort if less was available'''
37 '''read n bytes from stream.read and abort if less was available'''
38 s = stream.read(n)
38 s = stream.read(n)
39 if len(s) < n:
39 if len(s) < n:
40 raise util.Abort(_("stream ended unexpectedly"
40 raise util.Abort(_("stream ended unexpectedly"
41 " (got %d bytes, expected %d)")
41 " (got %d bytes, expected %d)")
42 % (len(s), n))
42 % (len(s), n))
43 return s
43 return s
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(">l", d)[0]
48 l = struct.unpack(">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise util.Abort(_("invalid chunk length %d") % l)
51 raise util.Abort(_("invalid chunk length %d") % l)
52 return ""
52 return ""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55 def chunkheader(length):
55 def chunkheader(length):
56 """return a changegroup chunk header (string)"""
56 """return a changegroup chunk header (string)"""
57 return struct.pack(">l", length + 4)
57 return struct.pack(">l", length + 4)
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(">l", 0)
61 return struct.pack(">l", 0)
62
62
63 def combineresults(results):
63 def combineresults(results):
64 """logic to combine 0 or more addchangegroup results into one"""
64 """logic to combine 0 or more addchangegroup results into one"""
65 changedheads = 0
65 changedheads = 0
66 result = 1
66 result = 1
67 for ret in results:
67 for ret in results:
68 # If any changegroup result is 0, return 0
68 # If any changegroup result is 0, return 0
69 if ret == 0:
69 if ret == 0:
70 result = 0
70 result = 0
71 break
71 break
72 if ret < -1:
72 if ret < -1:
73 changedheads += ret + 1
73 changedheads += ret + 1
74 elif ret > 1:
74 elif ret > 1:
75 changedheads += ret - 1
75 changedheads += ret - 1
76 if changedheads > 0:
76 if changedheads > 0:
77 result = 1 + changedheads
77 result = 1 + changedheads
78 elif changedheads < 0:
78 elif changedheads < 0:
79 result = -1 + changedheads
79 result = -1 + changedheads
80 return result
80 return result
81
81
82 bundletypes = {
82 bundletypes = {
83 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
83 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
84 # since the unification ssh accepts a header but there
84 # since the unification ssh accepts a header but there
85 # is no capability signaling it.
85 # is no capability signaling it.
86 "HG20": (), # special-cased below
86 "HG20": (), # special-cased below
87 "HG10UN": ("HG10UN", 'UN'),
87 "HG10UN": ("HG10UN", 'UN'),
88 "HG10BZ": ("HG10", 'BZ'),
88 "HG10BZ": ("HG10", 'BZ'),
89 "HG10GZ": ("HG10GZ", 'GZ'),
89 "HG10GZ": ("HG10GZ", 'GZ'),
90 }
90 }
91
91
92 # hgweb uses this list to communicate its preferred type
92 # hgweb uses this list to communicate its preferred type
93 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
93 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
94
94
95 def writebundle(ui, cg, filename, bundletype, vfs=None):
95 def writebundle(ui, cg, filename, bundletype, vfs=None):
96 """Write a bundle file and return its filename.
96 """Write a bundle file and return its filename.
97
97
98 Existing files will not be overwritten.
98 Existing files will not be overwritten.
99 If no filename is specified, a temporary file is created.
99 If no filename is specified, a temporary file is created.
100 bz2 compression can be turned off.
100 bz2 compression can be turned off.
101 The bundle file will be deleted in case of errors.
101 The bundle file will be deleted in case of errors.
102 """
102 """
103
103
104 fh = None
104 fh = None
105 cleanup = None
105 cleanup = None
106 try:
106 try:
107 if filename:
107 if filename:
108 if vfs:
108 if vfs:
109 fh = vfs.open(filename, "wb")
109 fh = vfs.open(filename, "wb")
110 else:
110 else:
111 fh = open(filename, "wb")
111 fh = open(filename, "wb")
112 else:
112 else:
113 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
113 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
114 fh = os.fdopen(fd, "wb")
114 fh = os.fdopen(fd, "wb")
115 cleanup = filename
115 cleanup = filename
116
116
117 if bundletype == "HG20":
117 if bundletype == "HG20":
118 from . import bundle2
118 from . import bundle2
119 bundle = bundle2.bundle20(ui)
119 bundle = bundle2.bundle20(ui)
120 part = bundle.newpart('changegroup', data=cg.getchunks())
120 part = bundle.newpart('changegroup', data=cg.getchunks())
121 part.addparam('version', cg.version)
121 part.addparam('version', cg.version)
122 z = util.compressors['UN']()
122 z = util.compressors['UN']()
123 chunkiter = bundle.getchunks()
123 chunkiter = bundle.getchunks()
124 else:
124 else:
125 if cg.version != '01':
125 if cg.version != '01':
126 raise util.Abort(_('old bundle types only supports v1 '
126 raise util.Abort(_('old bundle types only supports v1 '
127 'changegroups'))
127 'changegroups'))
128 header, comp = bundletypes[bundletype]
128 header, comp = bundletypes[bundletype]
129 fh.write(header)
129 fh.write(header)
130 if comp not in util.compressors:
130 if comp not in util.compressors:
131 raise util.Abort(_('unknown stream compression type: %s')
131 raise util.Abort(_('unknown stream compression type: %s')
132 % comp)
132 % comp)
133 z = util.compressors[comp]()
133 z = util.compressors[comp]()
134 chunkiter = cg.getchunks()
134 chunkiter = cg.getchunks()
135
135
136 # parse the changegroup data, otherwise we will block
136 # parse the changegroup data, otherwise we will block
137 # in case of sshrepo because we don't know the end of the stream
137 # in case of sshrepo because we don't know the end of the stream
138
138
139 # an empty chunkgroup is the end of the changegroup
139 # an empty chunkgroup is the end of the changegroup
140 # a changegroup has at least 2 chunkgroups (changelog and manifest).
140 # a changegroup has at least 2 chunkgroups (changelog and manifest).
141 # after that, an empty chunkgroup is the end of the changegroup
141 # after that, an empty chunkgroup is the end of the changegroup
142 for chunk in chunkiter:
142 for chunk in chunkiter:
143 fh.write(z.compress(chunk))
143 fh.write(z.compress(chunk))
144 fh.write(z.flush())
144 fh.write(z.flush())
145 cleanup = None
145 cleanup = None
146 return filename
146 return filename
147 finally:
147 finally:
148 if fh is not None:
148 if fh is not None:
149 fh.close()
149 fh.close()
150 if cleanup is not None:
150 if cleanup is not None:
151 if filename and vfs:
151 if filename and vfs:
152 vfs.unlink(cleanup)
152 vfs.unlink(cleanup)
153 else:
153 else:
154 os.unlink(cleanup)
154 os.unlink(cleanup)
155
155
156 class cg1unpacker(object):
156 class cg1unpacker(object):
157 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
157 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
158 deltaheadersize = struct.calcsize(deltaheader)
158 deltaheadersize = struct.calcsize(deltaheader)
159 version = '01'
159 version = '01'
160 def __init__(self, fh, alg):
160 def __init__(self, fh, alg):
161 if alg == 'UN':
162 alg = None # get more modern without breaking too much
161 if not alg in util.decompressors:
163 if not alg in util.decompressors:
162 raise util.Abort(_('unknown stream compression type: %s')
164 raise util.Abort(_('unknown stream compression type: %s')
163 % alg)
165 % alg)
164 self._stream = util.decompressors[alg](fh)
166 self._stream = util.decompressors[alg](fh)
165 self._type = alg
167 self._type = alg
166 self.callback = None
168 self.callback = None
167 def compressed(self):
169 def compressed(self):
168 return self._type != 'UN'
170 return self._type is not None
169 def read(self, l):
171 def read(self, l):
170 return self._stream.read(l)
172 return self._stream.read(l)
171 def seek(self, pos):
173 def seek(self, pos):
172 return self._stream.seek(pos)
174 return self._stream.seek(pos)
173 def tell(self):
175 def tell(self):
174 return self._stream.tell()
176 return self._stream.tell()
175 def close(self):
177 def close(self):
176 return self._stream.close()
178 return self._stream.close()
177
179
178 def chunklength(self):
180 def chunklength(self):
179 d = readexactly(self._stream, 4)
181 d = readexactly(self._stream, 4)
180 l = struct.unpack(">l", d)[0]
182 l = struct.unpack(">l", d)[0]
181 if l <= 4:
183 if l <= 4:
182 if l:
184 if l:
183 raise util.Abort(_("invalid chunk length %d") % l)
185 raise util.Abort(_("invalid chunk length %d") % l)
184 return 0
186 return 0
185 if self.callback:
187 if self.callback:
186 self.callback()
188 self.callback()
187 return l - 4
189 return l - 4
188
190
189 def changelogheader(self):
191 def changelogheader(self):
190 """v10 does not have a changelog header chunk"""
192 """v10 does not have a changelog header chunk"""
191 return {}
193 return {}
192
194
193 def manifestheader(self):
195 def manifestheader(self):
194 """v10 does not have a manifest header chunk"""
196 """v10 does not have a manifest header chunk"""
195 return {}
197 return {}
196
198
197 def filelogheader(self):
199 def filelogheader(self):
198 """return the header of the filelogs chunk, v10 only has the filename"""
200 """return the header of the filelogs chunk, v10 only has the filename"""
199 l = self.chunklength()
201 l = self.chunklength()
200 if not l:
202 if not l:
201 return {}
203 return {}
202 fname = readexactly(self._stream, l)
204 fname = readexactly(self._stream, l)
203 return {'filename': fname}
205 return {'filename': fname}
204
206
205 def _deltaheader(self, headertuple, prevnode):
207 def _deltaheader(self, headertuple, prevnode):
206 node, p1, p2, cs = headertuple
208 node, p1, p2, cs = headertuple
207 if prevnode is None:
209 if prevnode is None:
208 deltabase = p1
210 deltabase = p1
209 else:
211 else:
210 deltabase = prevnode
212 deltabase = prevnode
211 return node, p1, p2, deltabase, cs
213 return node, p1, p2, deltabase, cs
212
214
213 def deltachunk(self, prevnode):
215 def deltachunk(self, prevnode):
214 l = self.chunklength()
216 l = self.chunklength()
215 if not l:
217 if not l:
216 return {}
218 return {}
217 headerdata = readexactly(self._stream, self.deltaheadersize)
219 headerdata = readexactly(self._stream, self.deltaheadersize)
218 header = struct.unpack(self.deltaheader, headerdata)
220 header = struct.unpack(self.deltaheader, headerdata)
219 delta = readexactly(self._stream, l - self.deltaheadersize)
221 delta = readexactly(self._stream, l - self.deltaheadersize)
220 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
222 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
221 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
223 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
222 'deltabase': deltabase, 'delta': delta}
224 'deltabase': deltabase, 'delta': delta}
223
225
224 def getchunks(self):
226 def getchunks(self):
225 """returns all the chunks contains in the bundle
227 """returns all the chunks contains in the bundle
226
228
227 Used when you need to forward the binary stream to a file or another
229 Used when you need to forward the binary stream to a file or another
228 network API. To do so, it parse the changegroup data, otherwise it will
230 network API. To do so, it parse the changegroup data, otherwise it will
229 block in case of sshrepo because it don't know the end of the stream.
231 block in case of sshrepo because it don't know the end of the stream.
230 """
232 """
231 # an empty chunkgroup is the end of the changegroup
233 # an empty chunkgroup is the end of the changegroup
232 # a changegroup has at least 2 chunkgroups (changelog and manifest).
234 # a changegroup has at least 2 chunkgroups (changelog and manifest).
233 # after that, an empty chunkgroup is the end of the changegroup
235 # after that, an empty chunkgroup is the end of the changegroup
234 empty = False
236 empty = False
235 count = 0
237 count = 0
236 while not empty or count <= 2:
238 while not empty or count <= 2:
237 empty = True
239 empty = True
238 count += 1
240 count += 1
239 while True:
241 while True:
240 chunk = getchunk(self)
242 chunk = getchunk(self)
241 if not chunk:
243 if not chunk:
242 break
244 break
243 empty = False
245 empty = False
244 yield chunkheader(len(chunk))
246 yield chunkheader(len(chunk))
245 pos = 0
247 pos = 0
246 while pos < len(chunk):
248 while pos < len(chunk):
247 next = pos + 2**20
249 next = pos + 2**20
248 yield chunk[pos:next]
250 yield chunk[pos:next]
249 pos = next
251 pos = next
250 yield closechunk()
252 yield closechunk()
251
253
252 class cg2unpacker(cg1unpacker):
254 class cg2unpacker(cg1unpacker):
253 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
255 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
254 deltaheadersize = struct.calcsize(deltaheader)
256 deltaheadersize = struct.calcsize(deltaheader)
255 version = '02'
257 version = '02'
256
258
257 def _deltaheader(self, headertuple, prevnode):
259 def _deltaheader(self, headertuple, prevnode):
258 node, p1, p2, deltabase, cs = headertuple
260 node, p1, p2, deltabase, cs = headertuple
259 return node, p1, p2, deltabase, cs
261 return node, p1, p2, deltabase, cs
260
262
261 class headerlessfixup(object):
263 class headerlessfixup(object):
262 def __init__(self, fh, h):
264 def __init__(self, fh, h):
263 self._h = h
265 self._h = h
264 self._fh = fh
266 self._fh = fh
265 def read(self, n):
267 def read(self, n):
266 if self._h:
268 if self._h:
267 d, self._h = self._h[:n], self._h[n:]
269 d, self._h = self._h[:n], self._h[n:]
268 if len(d) < n:
270 if len(d) < n:
269 d += readexactly(self._fh, n - len(d))
271 d += readexactly(self._fh, n - len(d))
270 return d
272 return d
271 return readexactly(self._fh, n)
273 return readexactly(self._fh, n)
272
274
273 class cg1packer(object):
275 class cg1packer(object):
274 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
276 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
275 version = '01'
277 version = '01'
276 def __init__(self, repo, bundlecaps=None):
278 def __init__(self, repo, bundlecaps=None):
277 """Given a source repo, construct a bundler.
279 """Given a source repo, construct a bundler.
278
280
279 bundlecaps is optional and can be used to specify the set of
281 bundlecaps is optional and can be used to specify the set of
280 capabilities which can be used to build the bundle.
282 capabilities which can be used to build the bundle.
281 """
283 """
282 # Set of capabilities we can use to build the bundle.
284 # Set of capabilities we can use to build the bundle.
283 if bundlecaps is None:
285 if bundlecaps is None:
284 bundlecaps = set()
286 bundlecaps = set()
285 self._bundlecaps = bundlecaps
287 self._bundlecaps = bundlecaps
286 # experimental config: bundle.reorder
288 # experimental config: bundle.reorder
287 reorder = repo.ui.config('bundle', 'reorder', 'auto')
289 reorder = repo.ui.config('bundle', 'reorder', 'auto')
288 if reorder == 'auto':
290 if reorder == 'auto':
289 reorder = None
291 reorder = None
290 else:
292 else:
291 reorder = util.parsebool(reorder)
293 reorder = util.parsebool(reorder)
292 self._repo = repo
294 self._repo = repo
293 self._reorder = reorder
295 self._reorder = reorder
294 self._progress = repo.ui.progress
296 self._progress = repo.ui.progress
295 if self._repo.ui.verbose and not self._repo.ui.debugflag:
297 if self._repo.ui.verbose and not self._repo.ui.debugflag:
296 self._verbosenote = self._repo.ui.note
298 self._verbosenote = self._repo.ui.note
297 else:
299 else:
298 self._verbosenote = lambda s: None
300 self._verbosenote = lambda s: None
299
301
300 def close(self):
302 def close(self):
301 return closechunk()
303 return closechunk()
302
304
303 def fileheader(self, fname):
305 def fileheader(self, fname):
304 return chunkheader(len(fname)) + fname
306 return chunkheader(len(fname)) + fname
305
307
306 def group(self, nodelist, revlog, lookup, units=None):
308 def group(self, nodelist, revlog, lookup, units=None):
307 """Calculate a delta group, yielding a sequence of changegroup chunks
309 """Calculate a delta group, yielding a sequence of changegroup chunks
308 (strings).
310 (strings).
309
311
310 Given a list of changeset revs, return a set of deltas and
312 Given a list of changeset revs, return a set of deltas and
311 metadata corresponding to nodes. The first delta is
313 metadata corresponding to nodes. The first delta is
312 first parent(nodelist[0]) -> nodelist[0], the receiver is
314 first parent(nodelist[0]) -> nodelist[0], the receiver is
313 guaranteed to have this parent as it has all history before
315 guaranteed to have this parent as it has all history before
314 these changesets. In the case firstparent is nullrev the
316 these changesets. In the case firstparent is nullrev the
315 changegroup starts with a full revision.
317 changegroup starts with a full revision.
316
318
317 If units is not None, progress detail will be generated, units specifies
319 If units is not None, progress detail will be generated, units specifies
318 the type of revlog that is touched (changelog, manifest, etc.).
320 the type of revlog that is touched (changelog, manifest, etc.).
319 """
321 """
320 # if we don't have any revisions touched by these changesets, bail
322 # if we don't have any revisions touched by these changesets, bail
321 if len(nodelist) == 0:
323 if len(nodelist) == 0:
322 yield self.close()
324 yield self.close()
323 return
325 return
324
326
325 # for generaldelta revlogs, we linearize the revs; this will both be
327 # for generaldelta revlogs, we linearize the revs; this will both be
326 # much quicker and generate a much smaller bundle
328 # much quicker and generate a much smaller bundle
327 if (revlog._generaldelta and self._reorder is None) or self._reorder:
329 if (revlog._generaldelta and self._reorder is None) or self._reorder:
328 dag = dagutil.revlogdag(revlog)
330 dag = dagutil.revlogdag(revlog)
329 revs = set(revlog.rev(n) for n in nodelist)
331 revs = set(revlog.rev(n) for n in nodelist)
330 revs = dag.linearize(revs)
332 revs = dag.linearize(revs)
331 else:
333 else:
332 revs = sorted([revlog.rev(n) for n in nodelist])
334 revs = sorted([revlog.rev(n) for n in nodelist])
333
335
334 # add the parent of the first rev
336 # add the parent of the first rev
335 p = revlog.parentrevs(revs[0])[0]
337 p = revlog.parentrevs(revs[0])[0]
336 revs.insert(0, p)
338 revs.insert(0, p)
337
339
338 # build deltas
340 # build deltas
339 total = len(revs) - 1
341 total = len(revs) - 1
340 msgbundling = _('bundling')
342 msgbundling = _('bundling')
341 for r in xrange(len(revs) - 1):
343 for r in xrange(len(revs) - 1):
342 if units is not None:
344 if units is not None:
343 self._progress(msgbundling, r + 1, unit=units, total=total)
345 self._progress(msgbundling, r + 1, unit=units, total=total)
344 prev, curr = revs[r], revs[r + 1]
346 prev, curr = revs[r], revs[r + 1]
345 linknode = lookup(revlog.node(curr))
347 linknode = lookup(revlog.node(curr))
346 for c in self.revchunk(revlog, curr, prev, linknode):
348 for c in self.revchunk(revlog, curr, prev, linknode):
347 yield c
349 yield c
348
350
349 if units is not None:
351 if units is not None:
350 self._progress(msgbundling, None)
352 self._progress(msgbundling, None)
351 yield self.close()
353 yield self.close()
352
354
353 # filter any nodes that claim to be part of the known set
355 # filter any nodes that claim to be part of the known set
354 def prune(self, revlog, missing, commonrevs):
356 def prune(self, revlog, missing, commonrevs):
355 rr, rl = revlog.rev, revlog.linkrev
357 rr, rl = revlog.rev, revlog.linkrev
356 return [n for n in missing if rl(rr(n)) not in commonrevs]
358 return [n for n in missing if rl(rr(n)) not in commonrevs]
357
359
358 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
360 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
359 '''yield a sequence of changegroup chunks (strings)'''
361 '''yield a sequence of changegroup chunks (strings)'''
360 repo = self._repo
362 repo = self._repo
361 cl = repo.changelog
363 cl = repo.changelog
362 ml = repo.manifest
364 ml = repo.manifest
363
365
364 clrevorder = {}
366 clrevorder = {}
365 mfs = {} # needed manifests
367 mfs = {} # needed manifests
366 fnodes = {} # needed file nodes
368 fnodes = {} # needed file nodes
367 changedfiles = set()
369 changedfiles = set()
368
370
369 # Callback for the changelog, used to collect changed files and manifest
371 # Callback for the changelog, used to collect changed files and manifest
370 # nodes.
372 # nodes.
371 # Returns the linkrev node (identity in the changelog case).
373 # Returns the linkrev node (identity in the changelog case).
372 def lookupcl(x):
374 def lookupcl(x):
373 c = cl.read(x)
375 c = cl.read(x)
374 clrevorder[x] = len(clrevorder)
376 clrevorder[x] = len(clrevorder)
375 changedfiles.update(c[3])
377 changedfiles.update(c[3])
376 # record the first changeset introducing this manifest version
378 # record the first changeset introducing this manifest version
377 mfs.setdefault(c[0], x)
379 mfs.setdefault(c[0], x)
378 return x
380 return x
379
381
380 self._verbosenote(_('uncompressed size of bundle content:\n'))
382 self._verbosenote(_('uncompressed size of bundle content:\n'))
381 size = 0
383 size = 0
382 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
384 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
383 size += len(chunk)
385 size += len(chunk)
384 yield chunk
386 yield chunk
385 self._verbosenote(_('%8.i (changelog)\n') % size)
387 self._verbosenote(_('%8.i (changelog)\n') % size)
386
388
387 # We need to make sure that the linkrev in the changegroup refers to
389 # We need to make sure that the linkrev in the changegroup refers to
388 # the first changeset that introduced the manifest or file revision.
390 # the first changeset that introduced the manifest or file revision.
389 # The fastpath is usually safer than the slowpath, because the filelogs
391 # The fastpath is usually safer than the slowpath, because the filelogs
390 # are walked in revlog order.
392 # are walked in revlog order.
391 #
393 #
392 # When taking the slowpath with reorder=None and the manifest revlog
394 # When taking the slowpath with reorder=None and the manifest revlog
393 # uses generaldelta, the manifest may be walked in the "wrong" order.
395 # uses generaldelta, the manifest may be walked in the "wrong" order.
394 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
396 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
395 # cc0ff93d0c0c).
397 # cc0ff93d0c0c).
396 #
398 #
397 # When taking the fastpath, we are only vulnerable to reordering
399 # When taking the fastpath, we are only vulnerable to reordering
398 # of the changelog itself. The changelog never uses generaldelta, so
400 # of the changelog itself. The changelog never uses generaldelta, so
399 # it is only reordered when reorder=True. To handle this case, we
401 # it is only reordered when reorder=True. To handle this case, we
400 # simply take the slowpath, which already has the 'clrevorder' logic.
402 # simply take the slowpath, which already has the 'clrevorder' logic.
401 # This was also fixed in cc0ff93d0c0c.
403 # This was also fixed in cc0ff93d0c0c.
402 fastpathlinkrev = fastpathlinkrev and not self._reorder
404 fastpathlinkrev = fastpathlinkrev and not self._reorder
403 # Callback for the manifest, used to collect linkrevs for filelog
405 # Callback for the manifest, used to collect linkrevs for filelog
404 # revisions.
406 # revisions.
405 # Returns the linkrev node (collected in lookupcl).
407 # Returns the linkrev node (collected in lookupcl).
406 def lookupmf(x):
408 def lookupmf(x):
407 clnode = mfs[x]
409 clnode = mfs[x]
408 if not fastpathlinkrev:
410 if not fastpathlinkrev:
409 mdata = ml.readfast(x)
411 mdata = ml.readfast(x)
410 for f, n in mdata.iteritems():
412 for f, n in mdata.iteritems():
411 if f in changedfiles:
413 if f in changedfiles:
412 # record the first changeset introducing this filelog
414 # record the first changeset introducing this filelog
413 # version
415 # version
414 fclnodes = fnodes.setdefault(f, {})
416 fclnodes = fnodes.setdefault(f, {})
415 fclnode = fclnodes.setdefault(n, clnode)
417 fclnode = fclnodes.setdefault(n, clnode)
416 if clrevorder[clnode] < clrevorder[fclnode]:
418 if clrevorder[clnode] < clrevorder[fclnode]:
417 fclnodes[n] = clnode
419 fclnodes[n] = clnode
418 return clnode
420 return clnode
419
421
420 mfnodes = self.prune(ml, mfs, commonrevs)
422 mfnodes = self.prune(ml, mfs, commonrevs)
421 size = 0
423 size = 0
422 for chunk in self.group(mfnodes, ml, lookupmf, units=_('manifests')):
424 for chunk in self.group(mfnodes, ml, lookupmf, units=_('manifests')):
423 size += len(chunk)
425 size += len(chunk)
424 yield chunk
426 yield chunk
425 self._verbosenote(_('%8.i (manifests)\n') % size)
427 self._verbosenote(_('%8.i (manifests)\n') % size)
426
428
427 mfs.clear()
429 mfs.clear()
428 clrevs = set(cl.rev(x) for x in clnodes)
430 clrevs = set(cl.rev(x) for x in clnodes)
429
431
430 def linknodes(filerevlog, fname):
432 def linknodes(filerevlog, fname):
431 if fastpathlinkrev:
433 if fastpathlinkrev:
432 llr = filerevlog.linkrev
434 llr = filerevlog.linkrev
433 def genfilenodes():
435 def genfilenodes():
434 for r in filerevlog:
436 for r in filerevlog:
435 linkrev = llr(r)
437 linkrev = llr(r)
436 if linkrev in clrevs:
438 if linkrev in clrevs:
437 yield filerevlog.node(r), cl.node(linkrev)
439 yield filerevlog.node(r), cl.node(linkrev)
438 return dict(genfilenodes())
440 return dict(genfilenodes())
439 return fnodes.get(fname, {})
441 return fnodes.get(fname, {})
440
442
441 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
443 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
442 source):
444 source):
443 yield chunk
445 yield chunk
444
446
445 yield self.close()
447 yield self.close()
446
448
447 if clnodes:
449 if clnodes:
448 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
450 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
449
451
450 # The 'source' parameter is useful for extensions
452 # The 'source' parameter is useful for extensions
451 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
453 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
452 repo = self._repo
454 repo = self._repo
453 progress = self._progress
455 progress = self._progress
454 msgbundling = _('bundling')
456 msgbundling = _('bundling')
455
457
456 total = len(changedfiles)
458 total = len(changedfiles)
457 # for progress output
459 # for progress output
458 msgfiles = _('files')
460 msgfiles = _('files')
459 for i, fname in enumerate(sorted(changedfiles)):
461 for i, fname in enumerate(sorted(changedfiles)):
460 filerevlog = repo.file(fname)
462 filerevlog = repo.file(fname)
461 if not filerevlog:
463 if not filerevlog:
462 raise util.Abort(_("empty or missing revlog for %s") % fname)
464 raise util.Abort(_("empty or missing revlog for %s") % fname)
463
465
464 linkrevnodes = linknodes(filerevlog, fname)
466 linkrevnodes = linknodes(filerevlog, fname)
465 # Lookup for filenodes, we collected the linkrev nodes above in the
467 # Lookup for filenodes, we collected the linkrev nodes above in the
466 # fastpath case and with lookupmf in the slowpath case.
468 # fastpath case and with lookupmf in the slowpath case.
467 def lookupfilelog(x):
469 def lookupfilelog(x):
468 return linkrevnodes[x]
470 return linkrevnodes[x]
469
471
470 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
472 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
471 if filenodes:
473 if filenodes:
472 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
474 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
473 total=total)
475 total=total)
474 h = self.fileheader(fname)
476 h = self.fileheader(fname)
475 size = len(h)
477 size = len(h)
476 yield h
478 yield h
477 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
479 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
478 size += len(chunk)
480 size += len(chunk)
479 yield chunk
481 yield chunk
480 self._verbosenote(_('%8.i %s\n') % (size, fname))
482 self._verbosenote(_('%8.i %s\n') % (size, fname))
481 progress(msgbundling, None)
483 progress(msgbundling, None)
482
484
483 def deltaparent(self, revlog, rev, p1, p2, prev):
485 def deltaparent(self, revlog, rev, p1, p2, prev):
484 return prev
486 return prev
485
487
486 def revchunk(self, revlog, rev, prev, linknode):
488 def revchunk(self, revlog, rev, prev, linknode):
487 node = revlog.node(rev)
489 node = revlog.node(rev)
488 p1, p2 = revlog.parentrevs(rev)
490 p1, p2 = revlog.parentrevs(rev)
489 base = self.deltaparent(revlog, rev, p1, p2, prev)
491 base = self.deltaparent(revlog, rev, p1, p2, prev)
490
492
491 prefix = ''
493 prefix = ''
492 if revlog.iscensored(base) or revlog.iscensored(rev):
494 if revlog.iscensored(base) or revlog.iscensored(rev):
493 try:
495 try:
494 delta = revlog.revision(node)
496 delta = revlog.revision(node)
495 except error.CensoredNodeError as e:
497 except error.CensoredNodeError as e:
496 delta = e.tombstone
498 delta = e.tombstone
497 if base == nullrev:
499 if base == nullrev:
498 prefix = mdiff.trivialdiffheader(len(delta))
500 prefix = mdiff.trivialdiffheader(len(delta))
499 else:
501 else:
500 baselen = revlog.rawsize(base)
502 baselen = revlog.rawsize(base)
501 prefix = mdiff.replacediffheader(baselen, len(delta))
503 prefix = mdiff.replacediffheader(baselen, len(delta))
502 elif base == nullrev:
504 elif base == nullrev:
503 delta = revlog.revision(node)
505 delta = revlog.revision(node)
504 prefix = mdiff.trivialdiffheader(len(delta))
506 prefix = mdiff.trivialdiffheader(len(delta))
505 else:
507 else:
506 delta = revlog.revdiff(base, rev)
508 delta = revlog.revdiff(base, rev)
507 p1n, p2n = revlog.parents(node)
509 p1n, p2n = revlog.parents(node)
508 basenode = revlog.node(base)
510 basenode = revlog.node(base)
509 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
511 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
510 meta += prefix
512 meta += prefix
511 l = len(meta) + len(delta)
513 l = len(meta) + len(delta)
512 yield chunkheader(l)
514 yield chunkheader(l)
513 yield meta
515 yield meta
514 yield delta
516 yield delta
515 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
517 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
516 # do nothing with basenode, it is implicitly the previous one in HG10
518 # do nothing with basenode, it is implicitly the previous one in HG10
517 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
519 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
518
520
519 class cg2packer(cg1packer):
521 class cg2packer(cg1packer):
520 version = '02'
522 version = '02'
521 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
523 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
522
524
523 def __init__(self, repo, bundlecaps=None):
525 def __init__(self, repo, bundlecaps=None):
524 super(cg2packer, self).__init__(repo, bundlecaps)
526 super(cg2packer, self).__init__(repo, bundlecaps)
525 if self._reorder is None:
527 if self._reorder is None:
526 # Since generaldelta is directly supported by cg2, reordering
528 # Since generaldelta is directly supported by cg2, reordering
527 # generally doesn't help, so we disable it by default (treating
529 # generally doesn't help, so we disable it by default (treating
528 # bundle.reorder=auto just like bundle.reorder=False).
530 # bundle.reorder=auto just like bundle.reorder=False).
529 self._reorder = False
531 self._reorder = False
530
532
531 def deltaparent(self, revlog, rev, p1, p2, prev):
533 def deltaparent(self, revlog, rev, p1, p2, prev):
532 dp = revlog.deltaparent(rev)
534 dp = revlog.deltaparent(rev)
533 # avoid storing full revisions; pick prev in those cases
535 # avoid storing full revisions; pick prev in those cases
534 # also pick prev when we can't be sure remote has dp
536 # also pick prev when we can't be sure remote has dp
535 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
537 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
536 return prev
538 return prev
537 return dp
539 return dp
538
540
539 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
541 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
540 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
542 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
541
543
542 packermap = {'01': (cg1packer, cg1unpacker),
544 packermap = {'01': (cg1packer, cg1unpacker),
543 '02': (cg2packer, cg2unpacker)}
545 '02': (cg2packer, cg2unpacker)}
544
546
545 def _changegroupinfo(repo, nodes, source):
547 def _changegroupinfo(repo, nodes, source):
546 if repo.ui.verbose or source == 'bundle':
548 if repo.ui.verbose or source == 'bundle':
547 repo.ui.status(_("%d changesets found\n") % len(nodes))
549 repo.ui.status(_("%d changesets found\n") % len(nodes))
548 if repo.ui.debugflag:
550 if repo.ui.debugflag:
549 repo.ui.debug("list of changesets:\n")
551 repo.ui.debug("list of changesets:\n")
550 for node in nodes:
552 for node in nodes:
551 repo.ui.debug("%s\n" % hex(node))
553 repo.ui.debug("%s\n" % hex(node))
552
554
553 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
555 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
554 repo = repo.unfiltered()
556 repo = repo.unfiltered()
555 commonrevs = outgoing.common
557 commonrevs = outgoing.common
556 csets = outgoing.missing
558 csets = outgoing.missing
557 heads = outgoing.missingheads
559 heads = outgoing.missingheads
558 # We go through the fast path if we get told to, or if all (unfiltered
560 # We go through the fast path if we get told to, or if all (unfiltered
559 # heads have been requested (since we then know there all linkrevs will
561 # heads have been requested (since we then know there all linkrevs will
560 # be pulled by the client).
562 # be pulled by the client).
561 heads.sort()
563 heads.sort()
562 fastpathlinkrev = fastpath or (
564 fastpathlinkrev = fastpath or (
563 repo.filtername is None and heads == sorted(repo.heads()))
565 repo.filtername is None and heads == sorted(repo.heads()))
564
566
565 repo.hook('preoutgoing', throw=True, source=source)
567 repo.hook('preoutgoing', throw=True, source=source)
566 _changegroupinfo(repo, csets, source)
568 _changegroupinfo(repo, csets, source)
567 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
569 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
568
570
569 def getsubset(repo, outgoing, bundler, source, fastpath=False, version='01'):
571 def getsubset(repo, outgoing, bundler, source, fastpath=False, version='01'):
570 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
572 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
571 return packermap[version][1](util.chunkbuffer(gengroup), 'UN')
573 return packermap[version][1](util.chunkbuffer(gengroup), 'UN')
572
574
573 def changegroupsubset(repo, roots, heads, source, version='01'):
575 def changegroupsubset(repo, roots, heads, source, version='01'):
574 """Compute a changegroup consisting of all the nodes that are
576 """Compute a changegroup consisting of all the nodes that are
575 descendants of any of the roots and ancestors of any of the heads.
577 descendants of any of the roots and ancestors of any of the heads.
576 Return a chunkbuffer object whose read() method will return
578 Return a chunkbuffer object whose read() method will return
577 successive changegroup chunks.
579 successive changegroup chunks.
578
580
579 It is fairly complex as determining which filenodes and which
581 It is fairly complex as determining which filenodes and which
580 manifest nodes need to be included for the changeset to be complete
582 manifest nodes need to be included for the changeset to be complete
581 is non-trivial.
583 is non-trivial.
582
584
583 Another wrinkle is doing the reverse, figuring out which changeset in
585 Another wrinkle is doing the reverse, figuring out which changeset in
584 the changegroup a particular filenode or manifestnode belongs to.
586 the changegroup a particular filenode or manifestnode belongs to.
585 """
587 """
586 cl = repo.changelog
588 cl = repo.changelog
587 if not roots:
589 if not roots:
588 roots = [nullid]
590 roots = [nullid]
589 discbases = []
591 discbases = []
590 for n in roots:
592 for n in roots:
591 discbases.extend([p for p in cl.parents(n) if p != nullid])
593 discbases.extend([p for p in cl.parents(n) if p != nullid])
592 # TODO: remove call to nodesbetween.
594 # TODO: remove call to nodesbetween.
593 csets, roots, heads = cl.nodesbetween(roots, heads)
595 csets, roots, heads = cl.nodesbetween(roots, heads)
594 included = set(csets)
596 included = set(csets)
595 discbases = [n for n in discbases if n not in included]
597 discbases = [n for n in discbases if n not in included]
596 outgoing = discovery.outgoing(cl, discbases, heads)
598 outgoing = discovery.outgoing(cl, discbases, heads)
597 bundler = packermap[version][0](repo)
599 bundler = packermap[version][0](repo)
598 return getsubset(repo, outgoing, bundler, source, version=version)
600 return getsubset(repo, outgoing, bundler, source, version=version)
599
601
600 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
602 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
601 version='01'):
603 version='01'):
602 """Like getbundle, but taking a discovery.outgoing as an argument.
604 """Like getbundle, but taking a discovery.outgoing as an argument.
603
605
604 This is only implemented for local repos and reuses potentially
606 This is only implemented for local repos and reuses potentially
605 precomputed sets in outgoing. Returns a raw changegroup generator."""
607 precomputed sets in outgoing. Returns a raw changegroup generator."""
606 if not outgoing.missing:
608 if not outgoing.missing:
607 return None
609 return None
608 bundler = packermap[version][0](repo, bundlecaps)
610 bundler = packermap[version][0](repo, bundlecaps)
609 return getsubsetraw(repo, outgoing, bundler, source)
611 return getsubsetraw(repo, outgoing, bundler, source)
610
612
611 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None):
613 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None):
612 """Like getbundle, but taking a discovery.outgoing as an argument.
614 """Like getbundle, but taking a discovery.outgoing as an argument.
613
615
614 This is only implemented for local repos and reuses potentially
616 This is only implemented for local repos and reuses potentially
615 precomputed sets in outgoing."""
617 precomputed sets in outgoing."""
616 if not outgoing.missing:
618 if not outgoing.missing:
617 return None
619 return None
618 bundler = cg1packer(repo, bundlecaps)
620 bundler = cg1packer(repo, bundlecaps)
619 return getsubset(repo, outgoing, bundler, source)
621 return getsubset(repo, outgoing, bundler, source)
620
622
621 def computeoutgoing(repo, heads, common):
623 def computeoutgoing(repo, heads, common):
622 """Computes which revs are outgoing given a set of common
624 """Computes which revs are outgoing given a set of common
623 and a set of heads.
625 and a set of heads.
624
626
625 This is a separate function so extensions can have access to
627 This is a separate function so extensions can have access to
626 the logic.
628 the logic.
627
629
628 Returns a discovery.outgoing object.
630 Returns a discovery.outgoing object.
629 """
631 """
630 cl = repo.changelog
632 cl = repo.changelog
631 if common:
633 if common:
632 hasnode = cl.hasnode
634 hasnode = cl.hasnode
633 common = [n for n in common if hasnode(n)]
635 common = [n for n in common if hasnode(n)]
634 else:
636 else:
635 common = [nullid]
637 common = [nullid]
636 if not heads:
638 if not heads:
637 heads = cl.heads()
639 heads = cl.heads()
638 return discovery.outgoing(cl, common, heads)
640 return discovery.outgoing(cl, common, heads)
639
641
640 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None):
642 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None):
641 """Like changegroupsubset, but returns the set difference between the
643 """Like changegroupsubset, but returns the set difference between the
642 ancestors of heads and the ancestors common.
644 ancestors of heads and the ancestors common.
643
645
644 If heads is None, use the local heads. If common is None, use [nullid].
646 If heads is None, use the local heads. If common is None, use [nullid].
645
647
646 The nodes in common might not all be known locally due to the way the
648 The nodes in common might not all be known locally due to the way the
647 current discovery protocol works.
649 current discovery protocol works.
648 """
650 """
649 outgoing = computeoutgoing(repo, heads, common)
651 outgoing = computeoutgoing(repo, heads, common)
650 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
652 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
651
653
652 def changegroup(repo, basenodes, source):
654 def changegroup(repo, basenodes, source):
653 # to avoid a race we use changegroupsubset() (issue1320)
655 # to avoid a race we use changegroupsubset() (issue1320)
654 return changegroupsubset(repo, basenodes, repo.heads(), source)
656 return changegroupsubset(repo, basenodes, repo.heads(), source)
655
657
656 def addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
658 def addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
657 revisions = 0
659 revisions = 0
658 files = 0
660 files = 0
659 while True:
661 while True:
660 chunkdata = source.filelogheader()
662 chunkdata = source.filelogheader()
661 if not chunkdata:
663 if not chunkdata:
662 break
664 break
663 f = chunkdata["filename"]
665 f = chunkdata["filename"]
664 repo.ui.debug("adding %s revisions\n" % f)
666 repo.ui.debug("adding %s revisions\n" % f)
665 pr()
667 pr()
666 fl = repo.file(f)
668 fl = repo.file(f)
667 o = len(fl)
669 o = len(fl)
668 try:
670 try:
669 if not fl.addgroup(source, revmap, trp):
671 if not fl.addgroup(source, revmap, trp):
670 raise util.Abort(_("received file revlog group is empty"))
672 raise util.Abort(_("received file revlog group is empty"))
671 except error.CensoredBaseError as e:
673 except error.CensoredBaseError as e:
672 raise util.Abort(_("received delta base is censored: %s") % e)
674 raise util.Abort(_("received delta base is censored: %s") % e)
673 revisions += len(fl) - o
675 revisions += len(fl) - o
674 files += 1
676 files += 1
675 if f in needfiles:
677 if f in needfiles:
676 needs = needfiles[f]
678 needs = needfiles[f]
677 for new in xrange(o, len(fl)):
679 for new in xrange(o, len(fl)):
678 n = fl.node(new)
680 n = fl.node(new)
679 if n in needs:
681 if n in needs:
680 needs.remove(n)
682 needs.remove(n)
681 else:
683 else:
682 raise util.Abort(
684 raise util.Abort(
683 _("received spurious file revlog entry"))
685 _("received spurious file revlog entry"))
684 if not needs:
686 if not needs:
685 del needfiles[f]
687 del needfiles[f]
686 repo.ui.progress(_('files'), None)
688 repo.ui.progress(_('files'), None)
687
689
688 for f, needs in needfiles.iteritems():
690 for f, needs in needfiles.iteritems():
689 fl = repo.file(f)
691 fl = repo.file(f)
690 for n in needs:
692 for n in needs:
691 try:
693 try:
692 fl.rev(n)
694 fl.rev(n)
693 except error.LookupError:
695 except error.LookupError:
694 raise util.Abort(
696 raise util.Abort(
695 _('missing file data for %s:%s - run hg verify') %
697 _('missing file data for %s:%s - run hg verify') %
696 (f, hex(n)))
698 (f, hex(n)))
697
699
698 return revisions, files
700 return revisions, files
699
701
700 def addchangegroup(repo, source, srctype, url, emptyok=False,
702 def addchangegroup(repo, source, srctype, url, emptyok=False,
701 targetphase=phases.draft, expectedtotal=None):
703 targetphase=phases.draft, expectedtotal=None):
702 """Add the changegroup returned by source.read() to this repo.
704 """Add the changegroup returned by source.read() to this repo.
703 srctype is a string like 'push', 'pull', or 'unbundle'. url is
705 srctype is a string like 'push', 'pull', or 'unbundle'. url is
704 the URL of the repo where this changegroup is coming from.
706 the URL of the repo where this changegroup is coming from.
705
707
706 Return an integer summarizing the change to this repo:
708 Return an integer summarizing the change to this repo:
707 - nothing changed or no source: 0
709 - nothing changed or no source: 0
708 - more heads than before: 1+added heads (2..n)
710 - more heads than before: 1+added heads (2..n)
709 - fewer heads than before: -1-removed heads (-2..-n)
711 - fewer heads than before: -1-removed heads (-2..-n)
710 - number of heads stays the same: 1
712 - number of heads stays the same: 1
711 """
713 """
712 repo = repo.unfiltered()
714 repo = repo.unfiltered()
713 def csmap(x):
715 def csmap(x):
714 repo.ui.debug("add changeset %s\n" % short(x))
716 repo.ui.debug("add changeset %s\n" % short(x))
715 return len(cl)
717 return len(cl)
716
718
717 def revmap(x):
719 def revmap(x):
718 return cl.rev(x)
720 return cl.rev(x)
719
721
720 if not source:
722 if not source:
721 return 0
723 return 0
722
724
723 changesets = files = revisions = 0
725 changesets = files = revisions = 0
724
726
725 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
727 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
726 # The transaction could have been created before and already carries source
728 # The transaction could have been created before and already carries source
727 # information. In this case we use the top level data. We overwrite the
729 # information. In this case we use the top level data. We overwrite the
728 # argument because we need to use the top level value (if they exist) in
730 # argument because we need to use the top level value (if they exist) in
729 # this function.
731 # this function.
730 srctype = tr.hookargs.setdefault('source', srctype)
732 srctype = tr.hookargs.setdefault('source', srctype)
731 url = tr.hookargs.setdefault('url', url)
733 url = tr.hookargs.setdefault('url', url)
732
734
733 # write changelog data to temp files so concurrent readers will not see
735 # write changelog data to temp files so concurrent readers will not see
734 # inconsistent view
736 # inconsistent view
735 cl = repo.changelog
737 cl = repo.changelog
736 cl.delayupdate(tr)
738 cl.delayupdate(tr)
737 oldheads = cl.heads()
739 oldheads = cl.heads()
738 try:
740 try:
739 repo.hook('prechangegroup', throw=True, **tr.hookargs)
741 repo.hook('prechangegroup', throw=True, **tr.hookargs)
740
742
741 trp = weakref.proxy(tr)
743 trp = weakref.proxy(tr)
742 # pull off the changeset group
744 # pull off the changeset group
743 repo.ui.status(_("adding changesets\n"))
745 repo.ui.status(_("adding changesets\n"))
744 clstart = len(cl)
746 clstart = len(cl)
745 class prog(object):
747 class prog(object):
746 def __init__(self, step, total):
748 def __init__(self, step, total):
747 self._step = step
749 self._step = step
748 self._total = total
750 self._total = total
749 self._count = 1
751 self._count = 1
750 def __call__(self):
752 def __call__(self):
751 repo.ui.progress(self._step, self._count, unit=_('chunks'),
753 repo.ui.progress(self._step, self._count, unit=_('chunks'),
752 total=self._total)
754 total=self._total)
753 self._count += 1
755 self._count += 1
754 source.callback = prog(_('changesets'), expectedtotal)
756 source.callback = prog(_('changesets'), expectedtotal)
755
757
756 efiles = set()
758 efiles = set()
757 def onchangelog(cl, node):
759 def onchangelog(cl, node):
758 efiles.update(cl.read(node)[3])
760 efiles.update(cl.read(node)[3])
759
761
760 source.changelogheader()
762 source.changelogheader()
761 srccontent = cl.addgroup(source, csmap, trp,
763 srccontent = cl.addgroup(source, csmap, trp,
762 addrevisioncb=onchangelog)
764 addrevisioncb=onchangelog)
763 efiles = len(efiles)
765 efiles = len(efiles)
764
766
765 if not (srccontent or emptyok):
767 if not (srccontent or emptyok):
766 raise util.Abort(_("received changelog group is empty"))
768 raise util.Abort(_("received changelog group is empty"))
767 clend = len(cl)
769 clend = len(cl)
768 changesets = clend - clstart
770 changesets = clend - clstart
769 repo.ui.progress(_('changesets'), None)
771 repo.ui.progress(_('changesets'), None)
770
772
771 # pull off the manifest group
773 # pull off the manifest group
772 repo.ui.status(_("adding manifests\n"))
774 repo.ui.status(_("adding manifests\n"))
773 # manifests <= changesets
775 # manifests <= changesets
774 source.callback = prog(_('manifests'), changesets)
776 source.callback = prog(_('manifests'), changesets)
775 # no need to check for empty manifest group here:
777 # no need to check for empty manifest group here:
776 # if the result of the merge of 1 and 2 is the same in 3 and 4,
778 # if the result of the merge of 1 and 2 is the same in 3 and 4,
777 # no new manifest will be created and the manifest group will
779 # no new manifest will be created and the manifest group will
778 # be empty during the pull
780 # be empty during the pull
779 source.manifestheader()
781 source.manifestheader()
780 repo.manifest.addgroup(source, revmap, trp)
782 repo.manifest.addgroup(source, revmap, trp)
781 repo.ui.progress(_('manifests'), None)
783 repo.ui.progress(_('manifests'), None)
782
784
783 needfiles = {}
785 needfiles = {}
784 if repo.ui.configbool('server', 'validate', default=False):
786 if repo.ui.configbool('server', 'validate', default=False):
785 # validate incoming csets have their manifests
787 # validate incoming csets have their manifests
786 for cset in xrange(clstart, clend):
788 for cset in xrange(clstart, clend):
787 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
789 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
788 mfest = repo.manifest.readdelta(mfnode)
790 mfest = repo.manifest.readdelta(mfnode)
789 # store file nodes we must see
791 # store file nodes we must see
790 for f, n in mfest.iteritems():
792 for f, n in mfest.iteritems():
791 needfiles.setdefault(f, set()).add(n)
793 needfiles.setdefault(f, set()).add(n)
792
794
793 # process the files
795 # process the files
794 repo.ui.status(_("adding file changes\n"))
796 repo.ui.status(_("adding file changes\n"))
795 source.callback = None
797 source.callback = None
796 pr = prog(_('files'), efiles)
798 pr = prog(_('files'), efiles)
797 newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
799 newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
798 needfiles)
800 needfiles)
799 revisions += newrevs
801 revisions += newrevs
800 files += newfiles
802 files += newfiles
801
803
802 dh = 0
804 dh = 0
803 if oldheads:
805 if oldheads:
804 heads = cl.heads()
806 heads = cl.heads()
805 dh = len(heads) - len(oldheads)
807 dh = len(heads) - len(oldheads)
806 for h in heads:
808 for h in heads:
807 if h not in oldheads and repo[h].closesbranch():
809 if h not in oldheads and repo[h].closesbranch():
808 dh -= 1
810 dh -= 1
809 htext = ""
811 htext = ""
810 if dh:
812 if dh:
811 htext = _(" (%+d heads)") % dh
813 htext = _(" (%+d heads)") % dh
812
814
813 repo.ui.status(_("added %d changesets"
815 repo.ui.status(_("added %d changesets"
814 " with %d changes to %d files%s\n")
816 " with %d changes to %d files%s\n")
815 % (changesets, revisions, files, htext))
817 % (changesets, revisions, files, htext))
816 repo.invalidatevolatilesets()
818 repo.invalidatevolatilesets()
817
819
818 if changesets > 0:
820 if changesets > 0:
819 p = lambda: tr.writepending() and repo.root or ""
821 p = lambda: tr.writepending() and repo.root or ""
820 if 'node' not in tr.hookargs:
822 if 'node' not in tr.hookargs:
821 tr.hookargs['node'] = hex(cl.node(clstart))
823 tr.hookargs['node'] = hex(cl.node(clstart))
822 hookargs = dict(tr.hookargs)
824 hookargs = dict(tr.hookargs)
823 else:
825 else:
824 hookargs = dict(tr.hookargs)
826 hookargs = dict(tr.hookargs)
825 hookargs['node'] = hex(cl.node(clstart))
827 hookargs['node'] = hex(cl.node(clstart))
826 repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
828 repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
827
829
828 added = [cl.node(r) for r in xrange(clstart, clend)]
830 added = [cl.node(r) for r in xrange(clstart, clend)]
829 publishing = repo.publishing()
831 publishing = repo.publishing()
830 if srctype in ('push', 'serve'):
832 if srctype in ('push', 'serve'):
831 # Old servers can not push the boundary themselves.
833 # Old servers can not push the boundary themselves.
832 # New servers won't push the boundary if changeset already
834 # New servers won't push the boundary if changeset already
833 # exists locally as secret
835 # exists locally as secret
834 #
836 #
835 # We should not use added here but the list of all change in
837 # We should not use added here but the list of all change in
836 # the bundle
838 # the bundle
837 if publishing:
839 if publishing:
838 phases.advanceboundary(repo, tr, phases.public, srccontent)
840 phases.advanceboundary(repo, tr, phases.public, srccontent)
839 else:
841 else:
840 # Those changesets have been pushed from the outside, their
842 # Those changesets have been pushed from the outside, their
841 # phases are going to be pushed alongside. Therefor
843 # phases are going to be pushed alongside. Therefor
842 # `targetphase` is ignored.
844 # `targetphase` is ignored.
843 phases.advanceboundary(repo, tr, phases.draft, srccontent)
845 phases.advanceboundary(repo, tr, phases.draft, srccontent)
844 phases.retractboundary(repo, tr, phases.draft, added)
846 phases.retractboundary(repo, tr, phases.draft, added)
845 elif srctype != 'strip':
847 elif srctype != 'strip':
846 # publishing only alter behavior during push
848 # publishing only alter behavior during push
847 #
849 #
848 # strip should not touch boundary at all
850 # strip should not touch boundary at all
849 phases.retractboundary(repo, tr, targetphase, added)
851 phases.retractboundary(repo, tr, targetphase, added)
850
852
851 if changesets > 0:
853 if changesets > 0:
852 if srctype != 'strip':
854 if srctype != 'strip':
853 # During strip, branchcache is invalid but coming call to
855 # During strip, branchcache is invalid but coming call to
854 # `destroyed` will repair it.
856 # `destroyed` will repair it.
855 # In other case we can safely update cache on disk.
857 # In other case we can safely update cache on disk.
856 branchmap.updatecache(repo.filtered('served'))
858 branchmap.updatecache(repo.filtered('served'))
857
859
858 def runhooks():
860 def runhooks():
859 # These hooks run when the lock releases, not when the
861 # These hooks run when the lock releases, not when the
860 # transaction closes. So it's possible for the changelog
862 # transaction closes. So it's possible for the changelog
861 # to have changed since we last saw it.
863 # to have changed since we last saw it.
862 if clstart >= len(repo):
864 if clstart >= len(repo):
863 return
865 return
864
866
865 # forcefully update the on-disk branch cache
867 # forcefully update the on-disk branch cache
866 repo.ui.debug("updating the branch cache\n")
868 repo.ui.debug("updating the branch cache\n")
867 repo.hook("changegroup", **hookargs)
869 repo.hook("changegroup", **hookargs)
868
870
869 for n in added:
871 for n in added:
870 args = hookargs.copy()
872 args = hookargs.copy()
871 args['node'] = hex(n)
873 args['node'] = hex(n)
872 repo.hook("incoming", **args)
874 repo.hook("incoming", **args)
873
875
874 newheads = [h for h in repo.heads() if h not in oldheads]
876 newheads = [h for h in repo.heads() if h not in oldheads]
875 repo.ui.log("incoming",
877 repo.ui.log("incoming",
876 "%s incoming changes - new heads: %s\n",
878 "%s incoming changes - new heads: %s\n",
877 len(added),
879 len(added),
878 ', '.join([hex(c[:6]) for c in newheads]))
880 ', '.join([hex(c[:6]) for c in newheads]))
879
881
880 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
882 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
881 lambda tr: repo._afterlock(runhooks))
883 lambda tr: repo._afterlock(runhooks))
882
884
883 tr.close()
885 tr.close()
884
886
885 finally:
887 finally:
886 tr.release()
888 tr.release()
887 repo.ui.flush()
889 repo.ui.flush()
888 # never return 0 here:
890 # never return 0 here:
889 if dh < 0:
891 if dh < 0:
890 return dh - 1
892 return dh - 1
891 else:
893 else:
892 return dh + 1
894 return dh + 1
@@ -1,2380 +1,2384 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific 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 specific implementations.
10 """Mercurial utility functions and platform specific 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 import i18n
16 import i18n
17 _ = i18n._
17 _ = i18n._
18 import error, osutil, encoding, parsers
18 import error, osutil, encoding, parsers
19 import errno, shutil, sys, tempfile, traceback
19 import errno, shutil, sys, tempfile, traceback
20 import re as remod
20 import re as remod
21 import os, time, datetime, calendar, textwrap, signal, collections
21 import os, time, datetime, calendar, textwrap, signal, collections
22 import imp, socket, urllib
22 import imp, socket, urllib
23 import gc
23 import gc
24 import bz2
24 import bz2
25 import zlib
25 import zlib
26
26
27 if os.name == 'nt':
27 if os.name == 'nt':
28 import windows as platform
28 import windows as platform
29 else:
29 else:
30 import posix as platform
30 import posix as platform
31
31
32 cachestat = platform.cachestat
32 cachestat = platform.cachestat
33 checkexec = platform.checkexec
33 checkexec = platform.checkexec
34 checklink = platform.checklink
34 checklink = platform.checklink
35 copymode = platform.copymode
35 copymode = platform.copymode
36 executablepath = platform.executablepath
36 executablepath = platform.executablepath
37 expandglobs = platform.expandglobs
37 expandglobs = platform.expandglobs
38 explainexit = platform.explainexit
38 explainexit = platform.explainexit
39 findexe = platform.findexe
39 findexe = platform.findexe
40 gethgcmd = platform.gethgcmd
40 gethgcmd = platform.gethgcmd
41 getuser = platform.getuser
41 getuser = platform.getuser
42 groupmembers = platform.groupmembers
42 groupmembers = platform.groupmembers
43 groupname = platform.groupname
43 groupname = platform.groupname
44 hidewindow = platform.hidewindow
44 hidewindow = platform.hidewindow
45 isexec = platform.isexec
45 isexec = platform.isexec
46 isowner = platform.isowner
46 isowner = platform.isowner
47 localpath = platform.localpath
47 localpath = platform.localpath
48 lookupreg = platform.lookupreg
48 lookupreg = platform.lookupreg
49 makedir = platform.makedir
49 makedir = platform.makedir
50 nlinks = platform.nlinks
50 nlinks = platform.nlinks
51 normpath = platform.normpath
51 normpath = platform.normpath
52 normcase = platform.normcase
52 normcase = platform.normcase
53 normcasespec = platform.normcasespec
53 normcasespec = platform.normcasespec
54 normcasefallback = platform.normcasefallback
54 normcasefallback = platform.normcasefallback
55 openhardlinks = platform.openhardlinks
55 openhardlinks = platform.openhardlinks
56 oslink = platform.oslink
56 oslink = platform.oslink
57 parsepatchoutput = platform.parsepatchoutput
57 parsepatchoutput = platform.parsepatchoutput
58 pconvert = platform.pconvert
58 pconvert = platform.pconvert
59 poll = platform.poll
59 poll = platform.poll
60 popen = platform.popen
60 popen = platform.popen
61 posixfile = platform.posixfile
61 posixfile = platform.posixfile
62 quotecommand = platform.quotecommand
62 quotecommand = platform.quotecommand
63 readpipe = platform.readpipe
63 readpipe = platform.readpipe
64 rename = platform.rename
64 rename = platform.rename
65 removedirs = platform.removedirs
65 removedirs = platform.removedirs
66 samedevice = platform.samedevice
66 samedevice = platform.samedevice
67 samefile = platform.samefile
67 samefile = platform.samefile
68 samestat = platform.samestat
68 samestat = platform.samestat
69 setbinary = platform.setbinary
69 setbinary = platform.setbinary
70 setflags = platform.setflags
70 setflags = platform.setflags
71 setsignalhandler = platform.setsignalhandler
71 setsignalhandler = platform.setsignalhandler
72 shellquote = platform.shellquote
72 shellquote = platform.shellquote
73 spawndetached = platform.spawndetached
73 spawndetached = platform.spawndetached
74 split = platform.split
74 split = platform.split
75 sshargs = platform.sshargs
75 sshargs = platform.sshargs
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
77 statisexec = platform.statisexec
77 statisexec = platform.statisexec
78 statislink = platform.statislink
78 statislink = platform.statislink
79 termwidth = platform.termwidth
79 termwidth = platform.termwidth
80 testpid = platform.testpid
80 testpid = platform.testpid
81 umask = platform.umask
81 umask = platform.umask
82 unlink = platform.unlink
82 unlink = platform.unlink
83 unlinkpath = platform.unlinkpath
83 unlinkpath = platform.unlinkpath
84 username = platform.username
84 username = platform.username
85
85
86 # Python compatibility
86 # Python compatibility
87
87
88 _notset = object()
88 _notset = object()
89
89
90 def safehasattr(thing, attr):
90 def safehasattr(thing, attr):
91 return getattr(thing, attr, _notset) is not _notset
91 return getattr(thing, attr, _notset) is not _notset
92
92
93 def sha1(s=''):
93 def sha1(s=''):
94 '''
94 '''
95 Low-overhead wrapper around Python's SHA support
95 Low-overhead wrapper around Python's SHA support
96
96
97 >>> f = _fastsha1
97 >>> f = _fastsha1
98 >>> a = sha1()
98 >>> a = sha1()
99 >>> a = f()
99 >>> a = f()
100 >>> a.hexdigest()
100 >>> a.hexdigest()
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
102 '''
102 '''
103
103
104 return _fastsha1(s)
104 return _fastsha1(s)
105
105
106 def _fastsha1(s=''):
106 def _fastsha1(s=''):
107 # This function will import sha1 from hashlib or sha (whichever is
107 # This function will import sha1 from hashlib or sha (whichever is
108 # available) and overwrite itself with it on the first call.
108 # available) and overwrite itself with it on the first call.
109 # Subsequent calls will go directly to the imported function.
109 # Subsequent calls will go directly to the imported function.
110 if sys.version_info >= (2, 5):
110 if sys.version_info >= (2, 5):
111 from hashlib import sha1 as _sha1
111 from hashlib import sha1 as _sha1
112 else:
112 else:
113 from sha import sha as _sha1
113 from sha import sha as _sha1
114 global _fastsha1, sha1
114 global _fastsha1, sha1
115 _fastsha1 = sha1 = _sha1
115 _fastsha1 = sha1 = _sha1
116 return _sha1(s)
116 return _sha1(s)
117
117
118 def md5(s=''):
118 def md5(s=''):
119 try:
119 try:
120 from hashlib import md5 as _md5
120 from hashlib import md5 as _md5
121 except ImportError:
121 except ImportError:
122 from md5 import md5 as _md5
122 from md5 import md5 as _md5
123 global md5
123 global md5
124 md5 = _md5
124 md5 = _md5
125 return _md5(s)
125 return _md5(s)
126
126
127 DIGESTS = {
127 DIGESTS = {
128 'md5': md5,
128 'md5': md5,
129 'sha1': sha1,
129 'sha1': sha1,
130 }
130 }
131 # List of digest types from strongest to weakest
131 # List of digest types from strongest to weakest
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
133
133
134 try:
134 try:
135 import hashlib
135 import hashlib
136 DIGESTS.update({
136 DIGESTS.update({
137 'sha512': hashlib.sha512,
137 'sha512': hashlib.sha512,
138 })
138 })
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
140 except ImportError:
140 except ImportError:
141 pass
141 pass
142
142
143 for k in DIGESTS_BY_STRENGTH:
143 for k in DIGESTS_BY_STRENGTH:
144 assert k in DIGESTS
144 assert k in DIGESTS
145
145
146 class digester(object):
146 class digester(object):
147 """helper to compute digests.
147 """helper to compute digests.
148
148
149 This helper can be used to compute one or more digests given their name.
149 This helper can be used to compute one or more digests given their name.
150
150
151 >>> d = digester(['md5', 'sha1'])
151 >>> d = digester(['md5', 'sha1'])
152 >>> d.update('foo')
152 >>> d.update('foo')
153 >>> [k for k in sorted(d)]
153 >>> [k for k in sorted(d)]
154 ['md5', 'sha1']
154 ['md5', 'sha1']
155 >>> d['md5']
155 >>> d['md5']
156 'acbd18db4cc2f85cedef654fccc4a4d8'
156 'acbd18db4cc2f85cedef654fccc4a4d8'
157 >>> d['sha1']
157 >>> d['sha1']
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
159 >>> digester.preferred(['md5', 'sha1'])
159 >>> digester.preferred(['md5', 'sha1'])
160 'sha1'
160 'sha1'
161 """
161 """
162
162
163 def __init__(self, digests, s=''):
163 def __init__(self, digests, s=''):
164 self._hashes = {}
164 self._hashes = {}
165 for k in digests:
165 for k in digests:
166 if k not in DIGESTS:
166 if k not in DIGESTS:
167 raise Abort(_('unknown digest type: %s') % k)
167 raise Abort(_('unknown digest type: %s') % k)
168 self._hashes[k] = DIGESTS[k]()
168 self._hashes[k] = DIGESTS[k]()
169 if s:
169 if s:
170 self.update(s)
170 self.update(s)
171
171
172 def update(self, data):
172 def update(self, data):
173 for h in self._hashes.values():
173 for h in self._hashes.values():
174 h.update(data)
174 h.update(data)
175
175
176 def __getitem__(self, key):
176 def __getitem__(self, key):
177 if key not in DIGESTS:
177 if key not in DIGESTS:
178 raise Abort(_('unknown digest type: %s') % k)
178 raise Abort(_('unknown digest type: %s') % k)
179 return self._hashes[key].hexdigest()
179 return self._hashes[key].hexdigest()
180
180
181 def __iter__(self):
181 def __iter__(self):
182 return iter(self._hashes)
182 return iter(self._hashes)
183
183
184 @staticmethod
184 @staticmethod
185 def preferred(supported):
185 def preferred(supported):
186 """returns the strongest digest type in both supported and DIGESTS."""
186 """returns the strongest digest type in both supported and DIGESTS."""
187
187
188 for k in DIGESTS_BY_STRENGTH:
188 for k in DIGESTS_BY_STRENGTH:
189 if k in supported:
189 if k in supported:
190 return k
190 return k
191 return None
191 return None
192
192
193 class digestchecker(object):
193 class digestchecker(object):
194 """file handle wrapper that additionally checks content against a given
194 """file handle wrapper that additionally checks content against a given
195 size and digests.
195 size and digests.
196
196
197 d = digestchecker(fh, size, {'md5': '...'})
197 d = digestchecker(fh, size, {'md5': '...'})
198
198
199 When multiple digests are given, all of them are validated.
199 When multiple digests are given, all of them are validated.
200 """
200 """
201
201
202 def __init__(self, fh, size, digests):
202 def __init__(self, fh, size, digests):
203 self._fh = fh
203 self._fh = fh
204 self._size = size
204 self._size = size
205 self._got = 0
205 self._got = 0
206 self._digests = dict(digests)
206 self._digests = dict(digests)
207 self._digester = digester(self._digests.keys())
207 self._digester = digester(self._digests.keys())
208
208
209 def read(self, length=-1):
209 def read(self, length=-1):
210 content = self._fh.read(length)
210 content = self._fh.read(length)
211 self._digester.update(content)
211 self._digester.update(content)
212 self._got += len(content)
212 self._got += len(content)
213 return content
213 return content
214
214
215 def validate(self):
215 def validate(self):
216 if self._size != self._got:
216 if self._size != self._got:
217 raise Abort(_('size mismatch: expected %d, got %d') %
217 raise Abort(_('size mismatch: expected %d, got %d') %
218 (self._size, self._got))
218 (self._size, self._got))
219 for k, v in self._digests.items():
219 for k, v in self._digests.items():
220 if v != self._digester[k]:
220 if v != self._digester[k]:
221 # i18n: first parameter is a digest name
221 # i18n: first parameter is a digest name
222 raise Abort(_('%s mismatch: expected %s, got %s') %
222 raise Abort(_('%s mismatch: expected %s, got %s') %
223 (k, v, self._digester[k]))
223 (k, v, self._digester[k]))
224
224
225 try:
225 try:
226 buffer = buffer
226 buffer = buffer
227 except NameError:
227 except NameError:
228 if sys.version_info[0] < 3:
228 if sys.version_info[0] < 3:
229 def buffer(sliceable, offset=0):
229 def buffer(sliceable, offset=0):
230 return sliceable[offset:]
230 return sliceable[offset:]
231 else:
231 else:
232 def buffer(sliceable, offset=0):
232 def buffer(sliceable, offset=0):
233 return memoryview(sliceable)[offset:]
233 return memoryview(sliceable)[offset:]
234
234
235 import subprocess
235 import subprocess
236 closefds = os.name == 'posix'
236 closefds = os.name == 'posix'
237
237
238 _chunksize = 4096
238 _chunksize = 4096
239
239
240 class bufferedinputpipe(object):
240 class bufferedinputpipe(object):
241 """a manually buffered input pipe
241 """a manually buffered input pipe
242
242
243 Python will not let us use buffered IO and lazy reading with 'polling' at
243 Python will not let us use buffered IO and lazy reading with 'polling' at
244 the same time. We cannot probe the buffer state and select will not detect
244 the same time. We cannot probe the buffer state and select will not detect
245 that data are ready to read if they are already buffered.
245 that data are ready to read if they are already buffered.
246
246
247 This class let us work around that by implementing its own buffering
247 This class let us work around that by implementing its own buffering
248 (allowing efficient readline) while offering a way to know if the buffer is
248 (allowing efficient readline) while offering a way to know if the buffer is
249 empty from the output (allowing collaboration of the buffer with polling).
249 empty from the output (allowing collaboration of the buffer with polling).
250
250
251 This class lives in the 'util' module because it makes use of the 'os'
251 This class lives in the 'util' module because it makes use of the 'os'
252 module from the python stdlib.
252 module from the python stdlib.
253 """
253 """
254
254
255 def __init__(self, input):
255 def __init__(self, input):
256 self._input = input
256 self._input = input
257 self._buffer = []
257 self._buffer = []
258 self._eof = False
258 self._eof = False
259 self._lenbuf = 0
259 self._lenbuf = 0
260
260
261 @property
261 @property
262 def hasbuffer(self):
262 def hasbuffer(self):
263 """True is any data is currently buffered
263 """True is any data is currently buffered
264
264
265 This will be used externally a pre-step for polling IO. If there is
265 This will be used externally a pre-step for polling IO. If there is
266 already data then no polling should be set in place."""
266 already data then no polling should be set in place."""
267 return bool(self._buffer)
267 return bool(self._buffer)
268
268
269 @property
269 @property
270 def closed(self):
270 def closed(self):
271 return self._input.closed
271 return self._input.closed
272
272
273 def fileno(self):
273 def fileno(self):
274 return self._input.fileno()
274 return self._input.fileno()
275
275
276 def close(self):
276 def close(self):
277 return self._input.close()
277 return self._input.close()
278
278
279 def read(self, size):
279 def read(self, size):
280 while (not self._eof) and (self._lenbuf < size):
280 while (not self._eof) and (self._lenbuf < size):
281 self._fillbuffer()
281 self._fillbuffer()
282 return self._frombuffer(size)
282 return self._frombuffer(size)
283
283
284 def readline(self, *args, **kwargs):
284 def readline(self, *args, **kwargs):
285 if 1 < len(self._buffer):
285 if 1 < len(self._buffer):
286 # this should not happen because both read and readline end with a
286 # this should not happen because both read and readline end with a
287 # _frombuffer call that collapse it.
287 # _frombuffer call that collapse it.
288 self._buffer = [''.join(self._buffer)]
288 self._buffer = [''.join(self._buffer)]
289 self._lenbuf = len(self._buffer[0])
289 self._lenbuf = len(self._buffer[0])
290 lfi = -1
290 lfi = -1
291 if self._buffer:
291 if self._buffer:
292 lfi = self._buffer[-1].find('\n')
292 lfi = self._buffer[-1].find('\n')
293 while (not self._eof) and lfi < 0:
293 while (not self._eof) and lfi < 0:
294 self._fillbuffer()
294 self._fillbuffer()
295 if self._buffer:
295 if self._buffer:
296 lfi = self._buffer[-1].find('\n')
296 lfi = self._buffer[-1].find('\n')
297 size = lfi + 1
297 size = lfi + 1
298 if lfi < 0: # end of file
298 if lfi < 0: # end of file
299 size = self._lenbuf
299 size = self._lenbuf
300 elif 1 < len(self._buffer):
300 elif 1 < len(self._buffer):
301 # we need to take previous chunks into account
301 # we need to take previous chunks into account
302 size += self._lenbuf - len(self._buffer[-1])
302 size += self._lenbuf - len(self._buffer[-1])
303 return self._frombuffer(size)
303 return self._frombuffer(size)
304
304
305 def _frombuffer(self, size):
305 def _frombuffer(self, size):
306 """return at most 'size' data from the buffer
306 """return at most 'size' data from the buffer
307
307
308 The data are removed from the buffer."""
308 The data are removed from the buffer."""
309 if size == 0 or not self._buffer:
309 if size == 0 or not self._buffer:
310 return ''
310 return ''
311 buf = self._buffer[0]
311 buf = self._buffer[0]
312 if 1 < len(self._buffer):
312 if 1 < len(self._buffer):
313 buf = ''.join(self._buffer)
313 buf = ''.join(self._buffer)
314
314
315 data = buf[:size]
315 data = buf[:size]
316 buf = buf[len(data):]
316 buf = buf[len(data):]
317 if buf:
317 if buf:
318 self._buffer = [buf]
318 self._buffer = [buf]
319 self._lenbuf = len(buf)
319 self._lenbuf = len(buf)
320 else:
320 else:
321 self._buffer = []
321 self._buffer = []
322 self._lenbuf = 0
322 self._lenbuf = 0
323 return data
323 return data
324
324
325 def _fillbuffer(self):
325 def _fillbuffer(self):
326 """read data to the buffer"""
326 """read data to the buffer"""
327 data = os.read(self._input.fileno(), _chunksize)
327 data = os.read(self._input.fileno(), _chunksize)
328 if not data:
328 if not data:
329 self._eof = True
329 self._eof = True
330 else:
330 else:
331 self._lenbuf += len(data)
331 self._lenbuf += len(data)
332 self._buffer.append(data)
332 self._buffer.append(data)
333
333
334 def popen2(cmd, env=None, newlines=False):
334 def popen2(cmd, env=None, newlines=False):
335 # Setting bufsize to -1 lets the system decide the buffer size.
335 # Setting bufsize to -1 lets the system decide the buffer size.
336 # The default for bufsize is 0, meaning unbuffered. This leads to
336 # The default for bufsize is 0, meaning unbuffered. This leads to
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
339 close_fds=closefds,
339 close_fds=closefds,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
341 universal_newlines=newlines,
341 universal_newlines=newlines,
342 env=env)
342 env=env)
343 return p.stdin, p.stdout
343 return p.stdin, p.stdout
344
344
345 def popen3(cmd, env=None, newlines=False):
345 def popen3(cmd, env=None, newlines=False):
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
347 return stdin, stdout, stderr
347 return stdin, stdout, stderr
348
348
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
351 close_fds=closefds,
351 close_fds=closefds,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
353 stderr=subprocess.PIPE,
353 stderr=subprocess.PIPE,
354 universal_newlines=newlines,
354 universal_newlines=newlines,
355 env=env)
355 env=env)
356 return p.stdin, p.stdout, p.stderr, p
356 return p.stdin, p.stdout, p.stderr, p
357
357
358 def version():
358 def version():
359 """Return version information if available."""
359 """Return version information if available."""
360 try:
360 try:
361 import __version__
361 import __version__
362 return __version__.version
362 return __version__.version
363 except ImportError:
363 except ImportError:
364 return 'unknown'
364 return 'unknown'
365
365
366 # used by parsedate
366 # used by parsedate
367 defaultdateformats = (
367 defaultdateformats = (
368 '%Y-%m-%d %H:%M:%S',
368 '%Y-%m-%d %H:%M:%S',
369 '%Y-%m-%d %I:%M:%S%p',
369 '%Y-%m-%d %I:%M:%S%p',
370 '%Y-%m-%d %H:%M',
370 '%Y-%m-%d %H:%M',
371 '%Y-%m-%d %I:%M%p',
371 '%Y-%m-%d %I:%M%p',
372 '%Y-%m-%d',
372 '%Y-%m-%d',
373 '%m-%d',
373 '%m-%d',
374 '%m/%d',
374 '%m/%d',
375 '%m/%d/%y',
375 '%m/%d/%y',
376 '%m/%d/%Y',
376 '%m/%d/%Y',
377 '%a %b %d %H:%M:%S %Y',
377 '%a %b %d %H:%M:%S %Y',
378 '%a %b %d %I:%M:%S%p %Y',
378 '%a %b %d %I:%M:%S%p %Y',
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
380 '%b %d %H:%M:%S %Y',
380 '%b %d %H:%M:%S %Y',
381 '%b %d %I:%M:%S%p %Y',
381 '%b %d %I:%M:%S%p %Y',
382 '%b %d %H:%M:%S',
382 '%b %d %H:%M:%S',
383 '%b %d %I:%M:%S%p',
383 '%b %d %I:%M:%S%p',
384 '%b %d %H:%M',
384 '%b %d %H:%M',
385 '%b %d %I:%M%p',
385 '%b %d %I:%M%p',
386 '%b %d %Y',
386 '%b %d %Y',
387 '%b %d',
387 '%b %d',
388 '%H:%M:%S',
388 '%H:%M:%S',
389 '%I:%M:%S%p',
389 '%I:%M:%S%p',
390 '%H:%M',
390 '%H:%M',
391 '%I:%M%p',
391 '%I:%M%p',
392 )
392 )
393
393
394 extendeddateformats = defaultdateformats + (
394 extendeddateformats = defaultdateformats + (
395 "%Y",
395 "%Y",
396 "%Y-%m",
396 "%Y-%m",
397 "%b",
397 "%b",
398 "%b %Y",
398 "%b %Y",
399 )
399 )
400
400
401 def cachefunc(func):
401 def cachefunc(func):
402 '''cache the result of function calls'''
402 '''cache the result of function calls'''
403 # XXX doesn't handle keywords args
403 # XXX doesn't handle keywords args
404 if func.func_code.co_argcount == 0:
404 if func.func_code.co_argcount == 0:
405 cache = []
405 cache = []
406 def f():
406 def f():
407 if len(cache) == 0:
407 if len(cache) == 0:
408 cache.append(func())
408 cache.append(func())
409 return cache[0]
409 return cache[0]
410 return f
410 return f
411 cache = {}
411 cache = {}
412 if func.func_code.co_argcount == 1:
412 if func.func_code.co_argcount == 1:
413 # we gain a small amount of time because
413 # we gain a small amount of time because
414 # we don't need to pack/unpack the list
414 # we don't need to pack/unpack the list
415 def f(arg):
415 def f(arg):
416 if arg not in cache:
416 if arg not in cache:
417 cache[arg] = func(arg)
417 cache[arg] = func(arg)
418 return cache[arg]
418 return cache[arg]
419 else:
419 else:
420 def f(*args):
420 def f(*args):
421 if args not in cache:
421 if args not in cache:
422 cache[args] = func(*args)
422 cache[args] = func(*args)
423 return cache[args]
423 return cache[args]
424
424
425 return f
425 return f
426
426
427 class sortdict(dict):
427 class sortdict(dict):
428 '''a simple sorted dictionary'''
428 '''a simple sorted dictionary'''
429 def __init__(self, data=None):
429 def __init__(self, data=None):
430 self._list = []
430 self._list = []
431 if data:
431 if data:
432 self.update(data)
432 self.update(data)
433 def copy(self):
433 def copy(self):
434 return sortdict(self)
434 return sortdict(self)
435 def __setitem__(self, key, val):
435 def __setitem__(self, key, val):
436 if key in self:
436 if key in self:
437 self._list.remove(key)
437 self._list.remove(key)
438 self._list.append(key)
438 self._list.append(key)
439 dict.__setitem__(self, key, val)
439 dict.__setitem__(self, key, val)
440 def __iter__(self):
440 def __iter__(self):
441 return self._list.__iter__()
441 return self._list.__iter__()
442 def update(self, src):
442 def update(self, src):
443 if isinstance(src, dict):
443 if isinstance(src, dict):
444 src = src.iteritems()
444 src = src.iteritems()
445 for k, v in src:
445 for k, v in src:
446 self[k] = v
446 self[k] = v
447 def clear(self):
447 def clear(self):
448 dict.clear(self)
448 dict.clear(self)
449 self._list = []
449 self._list = []
450 def items(self):
450 def items(self):
451 return [(k, self[k]) for k in self._list]
451 return [(k, self[k]) for k in self._list]
452 def __delitem__(self, key):
452 def __delitem__(self, key):
453 dict.__delitem__(self, key)
453 dict.__delitem__(self, key)
454 self._list.remove(key)
454 self._list.remove(key)
455 def pop(self, key, *args, **kwargs):
455 def pop(self, key, *args, **kwargs):
456 dict.pop(self, key, *args, **kwargs)
456 dict.pop(self, key, *args, **kwargs)
457 try:
457 try:
458 self._list.remove(key)
458 self._list.remove(key)
459 except ValueError:
459 except ValueError:
460 pass
460 pass
461 def keys(self):
461 def keys(self):
462 return self._list
462 return self._list
463 def iterkeys(self):
463 def iterkeys(self):
464 return self._list.__iter__()
464 return self._list.__iter__()
465 def iteritems(self):
465 def iteritems(self):
466 for k in self._list:
466 for k in self._list:
467 yield k, self[k]
467 yield k, self[k]
468 def insert(self, index, key, val):
468 def insert(self, index, key, val):
469 self._list.insert(index, key)
469 self._list.insert(index, key)
470 dict.__setitem__(self, key, val)
470 dict.__setitem__(self, key, val)
471
471
472 class lrucachedict(object):
472 class lrucachedict(object):
473 '''cache most recent gets from or sets to this dictionary'''
473 '''cache most recent gets from or sets to this dictionary'''
474 def __init__(self, maxsize):
474 def __init__(self, maxsize):
475 self._cache = {}
475 self._cache = {}
476 self._maxsize = maxsize
476 self._maxsize = maxsize
477 self._order = collections.deque()
477 self._order = collections.deque()
478
478
479 def __getitem__(self, key):
479 def __getitem__(self, key):
480 value = self._cache[key]
480 value = self._cache[key]
481 self._order.remove(key)
481 self._order.remove(key)
482 self._order.append(key)
482 self._order.append(key)
483 return value
483 return value
484
484
485 def __setitem__(self, key, value):
485 def __setitem__(self, key, value):
486 if key not in self._cache:
486 if key not in self._cache:
487 if len(self._cache) >= self._maxsize:
487 if len(self._cache) >= self._maxsize:
488 del self._cache[self._order.popleft()]
488 del self._cache[self._order.popleft()]
489 else:
489 else:
490 self._order.remove(key)
490 self._order.remove(key)
491 self._cache[key] = value
491 self._cache[key] = value
492 self._order.append(key)
492 self._order.append(key)
493
493
494 def __contains__(self, key):
494 def __contains__(self, key):
495 return key in self._cache
495 return key in self._cache
496
496
497 def clear(self):
497 def clear(self):
498 self._cache.clear()
498 self._cache.clear()
499 self._order = collections.deque()
499 self._order = collections.deque()
500
500
501 def lrucachefunc(func):
501 def lrucachefunc(func):
502 '''cache most recent results of function calls'''
502 '''cache most recent results of function calls'''
503 cache = {}
503 cache = {}
504 order = collections.deque()
504 order = collections.deque()
505 if func.func_code.co_argcount == 1:
505 if func.func_code.co_argcount == 1:
506 def f(arg):
506 def f(arg):
507 if arg not in cache:
507 if arg not in cache:
508 if len(cache) > 20:
508 if len(cache) > 20:
509 del cache[order.popleft()]
509 del cache[order.popleft()]
510 cache[arg] = func(arg)
510 cache[arg] = func(arg)
511 else:
511 else:
512 order.remove(arg)
512 order.remove(arg)
513 order.append(arg)
513 order.append(arg)
514 return cache[arg]
514 return cache[arg]
515 else:
515 else:
516 def f(*args):
516 def f(*args):
517 if args not in cache:
517 if args not in cache:
518 if len(cache) > 20:
518 if len(cache) > 20:
519 del cache[order.popleft()]
519 del cache[order.popleft()]
520 cache[args] = func(*args)
520 cache[args] = func(*args)
521 else:
521 else:
522 order.remove(args)
522 order.remove(args)
523 order.append(args)
523 order.append(args)
524 return cache[args]
524 return cache[args]
525
525
526 return f
526 return f
527
527
528 class propertycache(object):
528 class propertycache(object):
529 def __init__(self, func):
529 def __init__(self, func):
530 self.func = func
530 self.func = func
531 self.name = func.__name__
531 self.name = func.__name__
532 def __get__(self, obj, type=None):
532 def __get__(self, obj, type=None):
533 result = self.func(obj)
533 result = self.func(obj)
534 self.cachevalue(obj, result)
534 self.cachevalue(obj, result)
535 return result
535 return result
536
536
537 def cachevalue(self, obj, value):
537 def cachevalue(self, obj, value):
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
539 obj.__dict__[self.name] = value
539 obj.__dict__[self.name] = value
540
540
541 def pipefilter(s, cmd):
541 def pipefilter(s, cmd):
542 '''filter string S through command CMD, returning its output'''
542 '''filter string S through command CMD, returning its output'''
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
545 pout, perr = p.communicate(s)
545 pout, perr = p.communicate(s)
546 return pout
546 return pout
547
547
548 def tempfilter(s, cmd):
548 def tempfilter(s, cmd):
549 '''filter string S through a pair of temporary files with CMD.
549 '''filter string S through a pair of temporary files with CMD.
550 CMD is used as a template to create the real command to be run,
550 CMD is used as a template to create the real command to be run,
551 with the strings INFILE and OUTFILE replaced by the real names of
551 with the strings INFILE and OUTFILE replaced by the real names of
552 the temporary files generated.'''
552 the temporary files generated.'''
553 inname, outname = None, None
553 inname, outname = None, None
554 try:
554 try:
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
556 fp = os.fdopen(infd, 'wb')
556 fp = os.fdopen(infd, 'wb')
557 fp.write(s)
557 fp.write(s)
558 fp.close()
558 fp.close()
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
560 os.close(outfd)
560 os.close(outfd)
561 cmd = cmd.replace('INFILE', inname)
561 cmd = cmd.replace('INFILE', inname)
562 cmd = cmd.replace('OUTFILE', outname)
562 cmd = cmd.replace('OUTFILE', outname)
563 code = os.system(cmd)
563 code = os.system(cmd)
564 if sys.platform == 'OpenVMS' and code & 1:
564 if sys.platform == 'OpenVMS' and code & 1:
565 code = 0
565 code = 0
566 if code:
566 if code:
567 raise Abort(_("command '%s' failed: %s") %
567 raise Abort(_("command '%s' failed: %s") %
568 (cmd, explainexit(code)))
568 (cmd, explainexit(code)))
569 fp = open(outname, 'rb')
569 fp = open(outname, 'rb')
570 r = fp.read()
570 r = fp.read()
571 fp.close()
571 fp.close()
572 return r
572 return r
573 finally:
573 finally:
574 try:
574 try:
575 if inname:
575 if inname:
576 os.unlink(inname)
576 os.unlink(inname)
577 except OSError:
577 except OSError:
578 pass
578 pass
579 try:
579 try:
580 if outname:
580 if outname:
581 os.unlink(outname)
581 os.unlink(outname)
582 except OSError:
582 except OSError:
583 pass
583 pass
584
584
585 filtertable = {
585 filtertable = {
586 'tempfile:': tempfilter,
586 'tempfile:': tempfilter,
587 'pipe:': pipefilter,
587 'pipe:': pipefilter,
588 }
588 }
589
589
590 def filter(s, cmd):
590 def filter(s, cmd):
591 "filter a string through a command that transforms its input to its output"
591 "filter a string through a command that transforms its input to its output"
592 for name, fn in filtertable.iteritems():
592 for name, fn in filtertable.iteritems():
593 if cmd.startswith(name):
593 if cmd.startswith(name):
594 return fn(s, cmd[len(name):].lstrip())
594 return fn(s, cmd[len(name):].lstrip())
595 return pipefilter(s, cmd)
595 return pipefilter(s, cmd)
596
596
597 def binary(s):
597 def binary(s):
598 """return true if a string is binary data"""
598 """return true if a string is binary data"""
599 return bool(s and '\0' in s)
599 return bool(s and '\0' in s)
600
600
601 def increasingchunks(source, min=1024, max=65536):
601 def increasingchunks(source, min=1024, max=65536):
602 '''return no less than min bytes per chunk while data remains,
602 '''return no less than min bytes per chunk while data remains,
603 doubling min after each chunk until it reaches max'''
603 doubling min after each chunk until it reaches max'''
604 def log2(x):
604 def log2(x):
605 if not x:
605 if not x:
606 return 0
606 return 0
607 i = 0
607 i = 0
608 while x:
608 while x:
609 x >>= 1
609 x >>= 1
610 i += 1
610 i += 1
611 return i - 1
611 return i - 1
612
612
613 buf = []
613 buf = []
614 blen = 0
614 blen = 0
615 for chunk in source:
615 for chunk in source:
616 buf.append(chunk)
616 buf.append(chunk)
617 blen += len(chunk)
617 blen += len(chunk)
618 if blen >= min:
618 if blen >= min:
619 if min < max:
619 if min < max:
620 min = min << 1
620 min = min << 1
621 nmin = 1 << log2(blen)
621 nmin = 1 << log2(blen)
622 if nmin > min:
622 if nmin > min:
623 min = nmin
623 min = nmin
624 if min > max:
624 if min > max:
625 min = max
625 min = max
626 yield ''.join(buf)
626 yield ''.join(buf)
627 blen = 0
627 blen = 0
628 buf = []
628 buf = []
629 if buf:
629 if buf:
630 yield ''.join(buf)
630 yield ''.join(buf)
631
631
632 Abort = error.Abort
632 Abort = error.Abort
633
633
634 def always(fn):
634 def always(fn):
635 return True
635 return True
636
636
637 def never(fn):
637 def never(fn):
638 return False
638 return False
639
639
640 def nogc(func):
640 def nogc(func):
641 """disable garbage collector
641 """disable garbage collector
642
642
643 Python's garbage collector triggers a GC each time a certain number of
643 Python's garbage collector triggers a GC each time a certain number of
644 container objects (the number being defined by gc.get_threshold()) are
644 container objects (the number being defined by gc.get_threshold()) are
645 allocated even when marked not to be tracked by the collector. Tracking has
645 allocated even when marked not to be tracked by the collector. Tracking has
646 no effect on when GCs are triggered, only on what objects the GC looks
646 no effect on when GCs are triggered, only on what objects the GC looks
647 into. As a workaround, disable GC while building complex (huge)
647 into. As a workaround, disable GC while building complex (huge)
648 containers.
648 containers.
649
649
650 This garbage collector issue have been fixed in 2.7.
650 This garbage collector issue have been fixed in 2.7.
651 """
651 """
652 def wrapper(*args, **kwargs):
652 def wrapper(*args, **kwargs):
653 gcenabled = gc.isenabled()
653 gcenabled = gc.isenabled()
654 gc.disable()
654 gc.disable()
655 try:
655 try:
656 return func(*args, **kwargs)
656 return func(*args, **kwargs)
657 finally:
657 finally:
658 if gcenabled:
658 if gcenabled:
659 gc.enable()
659 gc.enable()
660 return wrapper
660 return wrapper
661
661
662 def pathto(root, n1, n2):
662 def pathto(root, n1, n2):
663 '''return the relative path from one place to another.
663 '''return the relative path from one place to another.
664 root should use os.sep to separate directories
664 root should use os.sep to separate directories
665 n1 should use os.sep to separate directories
665 n1 should use os.sep to separate directories
666 n2 should use "/" to separate directories
666 n2 should use "/" to separate directories
667 returns an os.sep-separated path.
667 returns an os.sep-separated path.
668
668
669 If n1 is a relative path, it's assumed it's
669 If n1 is a relative path, it's assumed it's
670 relative to root.
670 relative to root.
671 n2 should always be relative to root.
671 n2 should always be relative to root.
672 '''
672 '''
673 if not n1:
673 if not n1:
674 return localpath(n2)
674 return localpath(n2)
675 if os.path.isabs(n1):
675 if os.path.isabs(n1):
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
677 return os.path.join(root, localpath(n2))
677 return os.path.join(root, localpath(n2))
678 n2 = '/'.join((pconvert(root), n2))
678 n2 = '/'.join((pconvert(root), n2))
679 a, b = splitpath(n1), n2.split('/')
679 a, b = splitpath(n1), n2.split('/')
680 a.reverse()
680 a.reverse()
681 b.reverse()
681 b.reverse()
682 while a and b and a[-1] == b[-1]:
682 while a and b and a[-1] == b[-1]:
683 a.pop()
683 a.pop()
684 b.pop()
684 b.pop()
685 b.reverse()
685 b.reverse()
686 return os.sep.join((['..'] * len(a)) + b) or '.'
686 return os.sep.join((['..'] * len(a)) + b) or '.'
687
687
688 def mainfrozen():
688 def mainfrozen():
689 """return True if we are a frozen executable.
689 """return True if we are a frozen executable.
690
690
691 The code supports py2exe (most common, Windows only) and tools/freeze
691 The code supports py2exe (most common, Windows only) and tools/freeze
692 (portable, not much used).
692 (portable, not much used).
693 """
693 """
694 return (safehasattr(sys, "frozen") or # new py2exe
694 return (safehasattr(sys, "frozen") or # new py2exe
695 safehasattr(sys, "importers") or # old py2exe
695 safehasattr(sys, "importers") or # old py2exe
696 imp.is_frozen("__main__")) # tools/freeze
696 imp.is_frozen("__main__")) # tools/freeze
697
697
698 # the location of data files matching the source code
698 # the location of data files matching the source code
699 if mainfrozen():
699 if mainfrozen():
700 # executable version (py2exe) doesn't support __file__
700 # executable version (py2exe) doesn't support __file__
701 datapath = os.path.dirname(sys.executable)
701 datapath = os.path.dirname(sys.executable)
702 else:
702 else:
703 datapath = os.path.dirname(__file__)
703 datapath = os.path.dirname(__file__)
704
704
705 i18n.setdatapath(datapath)
705 i18n.setdatapath(datapath)
706
706
707 _hgexecutable = None
707 _hgexecutable = None
708
708
709 def hgexecutable():
709 def hgexecutable():
710 """return location of the 'hg' executable.
710 """return location of the 'hg' executable.
711
711
712 Defaults to $HG or 'hg' in the search path.
712 Defaults to $HG or 'hg' in the search path.
713 """
713 """
714 if _hgexecutable is None:
714 if _hgexecutable is None:
715 hg = os.environ.get('HG')
715 hg = os.environ.get('HG')
716 mainmod = sys.modules['__main__']
716 mainmod = sys.modules['__main__']
717 if hg:
717 if hg:
718 _sethgexecutable(hg)
718 _sethgexecutable(hg)
719 elif mainfrozen():
719 elif mainfrozen():
720 _sethgexecutable(sys.executable)
720 _sethgexecutable(sys.executable)
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
722 _sethgexecutable(mainmod.__file__)
722 _sethgexecutable(mainmod.__file__)
723 else:
723 else:
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
725 _sethgexecutable(exe)
725 _sethgexecutable(exe)
726 return _hgexecutable
726 return _hgexecutable
727
727
728 def _sethgexecutable(path):
728 def _sethgexecutable(path):
729 """set location of the 'hg' executable"""
729 """set location of the 'hg' executable"""
730 global _hgexecutable
730 global _hgexecutable
731 _hgexecutable = path
731 _hgexecutable = path
732
732
733 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
733 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
734 '''enhanced shell command execution.
734 '''enhanced shell command execution.
735 run with environment maybe modified, maybe in different dir.
735 run with environment maybe modified, maybe in different dir.
736
736
737 if command fails and onerr is None, return status, else raise onerr
737 if command fails and onerr is None, return status, else raise onerr
738 object as exception.
738 object as exception.
739
739
740 if out is specified, it is assumed to be a file-like object that has a
740 if out is specified, it is assumed to be a file-like object that has a
741 write() method. stdout and stderr will be redirected to out.'''
741 write() method. stdout and stderr will be redirected to out.'''
742 try:
742 try:
743 sys.stdout.flush()
743 sys.stdout.flush()
744 except Exception:
744 except Exception:
745 pass
745 pass
746 def py2shell(val):
746 def py2shell(val):
747 'convert python object into string that is useful to shell'
747 'convert python object into string that is useful to shell'
748 if val is None or val is False:
748 if val is None or val is False:
749 return '0'
749 return '0'
750 if val is True:
750 if val is True:
751 return '1'
751 return '1'
752 return str(val)
752 return str(val)
753 origcmd = cmd
753 origcmd = cmd
754 cmd = quotecommand(cmd)
754 cmd = quotecommand(cmd)
755 if sys.platform == 'plan9' and (sys.version_info[0] == 2
755 if sys.platform == 'plan9' and (sys.version_info[0] == 2
756 and sys.version_info[1] < 7):
756 and sys.version_info[1] < 7):
757 # subprocess kludge to work around issues in half-baked Python
757 # subprocess kludge to work around issues in half-baked Python
758 # ports, notably bichued/python:
758 # ports, notably bichued/python:
759 if not cwd is None:
759 if not cwd is None:
760 os.chdir(cwd)
760 os.chdir(cwd)
761 rc = os.system(cmd)
761 rc = os.system(cmd)
762 else:
762 else:
763 env = dict(os.environ)
763 env = dict(os.environ)
764 env.update((k, py2shell(v)) for k, v in environ.iteritems())
764 env.update((k, py2shell(v)) for k, v in environ.iteritems())
765 env['HG'] = hgexecutable()
765 env['HG'] = hgexecutable()
766 if out is None or out == sys.__stdout__:
766 if out is None or out == sys.__stdout__:
767 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
767 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
768 env=env, cwd=cwd)
768 env=env, cwd=cwd)
769 else:
769 else:
770 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
770 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
771 env=env, cwd=cwd, stdout=subprocess.PIPE,
771 env=env, cwd=cwd, stdout=subprocess.PIPE,
772 stderr=subprocess.STDOUT)
772 stderr=subprocess.STDOUT)
773 while True:
773 while True:
774 line = proc.stdout.readline()
774 line = proc.stdout.readline()
775 if not line:
775 if not line:
776 break
776 break
777 out.write(line)
777 out.write(line)
778 proc.wait()
778 proc.wait()
779 rc = proc.returncode
779 rc = proc.returncode
780 if sys.platform == 'OpenVMS' and rc & 1:
780 if sys.platform == 'OpenVMS' and rc & 1:
781 rc = 0
781 rc = 0
782 if rc and onerr:
782 if rc and onerr:
783 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
783 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
784 explainexit(rc)[0])
784 explainexit(rc)[0])
785 if errprefix:
785 if errprefix:
786 errmsg = '%s: %s' % (errprefix, errmsg)
786 errmsg = '%s: %s' % (errprefix, errmsg)
787 raise onerr(errmsg)
787 raise onerr(errmsg)
788 return rc
788 return rc
789
789
790 def checksignature(func):
790 def checksignature(func):
791 '''wrap a function with code to check for calling errors'''
791 '''wrap a function with code to check for calling errors'''
792 def check(*args, **kwargs):
792 def check(*args, **kwargs):
793 try:
793 try:
794 return func(*args, **kwargs)
794 return func(*args, **kwargs)
795 except TypeError:
795 except TypeError:
796 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
796 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
797 raise error.SignatureError
797 raise error.SignatureError
798 raise
798 raise
799
799
800 return check
800 return check
801
801
802 def copyfile(src, dest, hardlink=False):
802 def copyfile(src, dest, hardlink=False):
803 "copy a file, preserving mode and atime/mtime"
803 "copy a file, preserving mode and atime/mtime"
804 if os.path.lexists(dest):
804 if os.path.lexists(dest):
805 unlink(dest)
805 unlink(dest)
806 # hardlinks are problematic on CIFS, quietly ignore this flag
806 # hardlinks are problematic on CIFS, quietly ignore this flag
807 # until we find a way to work around it cleanly (issue4546)
807 # until we find a way to work around it cleanly (issue4546)
808 if False and hardlink:
808 if False and hardlink:
809 try:
809 try:
810 oslink(src, dest)
810 oslink(src, dest)
811 return
811 return
812 except (IOError, OSError):
812 except (IOError, OSError):
813 pass # fall back to normal copy
813 pass # fall back to normal copy
814 if os.path.islink(src):
814 if os.path.islink(src):
815 os.symlink(os.readlink(src), dest)
815 os.symlink(os.readlink(src), dest)
816 else:
816 else:
817 try:
817 try:
818 shutil.copyfile(src, dest)
818 shutil.copyfile(src, dest)
819 shutil.copymode(src, dest)
819 shutil.copymode(src, dest)
820 except shutil.Error as inst:
820 except shutil.Error as inst:
821 raise Abort(str(inst))
821 raise Abort(str(inst))
822
822
823 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
823 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
824 """Copy a directory tree using hardlinks if possible."""
824 """Copy a directory tree using hardlinks if possible."""
825 num = 0
825 num = 0
826
826
827 if hardlink is None:
827 if hardlink is None:
828 hardlink = (os.stat(src).st_dev ==
828 hardlink = (os.stat(src).st_dev ==
829 os.stat(os.path.dirname(dst)).st_dev)
829 os.stat(os.path.dirname(dst)).st_dev)
830 if hardlink:
830 if hardlink:
831 topic = _('linking')
831 topic = _('linking')
832 else:
832 else:
833 topic = _('copying')
833 topic = _('copying')
834
834
835 if os.path.isdir(src):
835 if os.path.isdir(src):
836 os.mkdir(dst)
836 os.mkdir(dst)
837 for name, kind in osutil.listdir(src):
837 for name, kind in osutil.listdir(src):
838 srcname = os.path.join(src, name)
838 srcname = os.path.join(src, name)
839 dstname = os.path.join(dst, name)
839 dstname = os.path.join(dst, name)
840 def nprog(t, pos):
840 def nprog(t, pos):
841 if pos is not None:
841 if pos is not None:
842 return progress(t, pos + num)
842 return progress(t, pos + num)
843 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
843 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
844 num += n
844 num += n
845 else:
845 else:
846 if hardlink:
846 if hardlink:
847 try:
847 try:
848 oslink(src, dst)
848 oslink(src, dst)
849 except (IOError, OSError):
849 except (IOError, OSError):
850 hardlink = False
850 hardlink = False
851 shutil.copy(src, dst)
851 shutil.copy(src, dst)
852 else:
852 else:
853 shutil.copy(src, dst)
853 shutil.copy(src, dst)
854 num += 1
854 num += 1
855 progress(topic, num)
855 progress(topic, num)
856 progress(topic, None)
856 progress(topic, None)
857
857
858 return hardlink, num
858 return hardlink, num
859
859
860 _winreservednames = '''con prn aux nul
860 _winreservednames = '''con prn aux nul
861 com1 com2 com3 com4 com5 com6 com7 com8 com9
861 com1 com2 com3 com4 com5 com6 com7 com8 com9
862 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
862 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
863 _winreservedchars = ':*?"<>|'
863 _winreservedchars = ':*?"<>|'
864 def checkwinfilename(path):
864 def checkwinfilename(path):
865 r'''Check that the base-relative path is a valid filename on Windows.
865 r'''Check that the base-relative path is a valid filename on Windows.
866 Returns None if the path is ok, or a UI string describing the problem.
866 Returns None if the path is ok, or a UI string describing the problem.
867
867
868 >>> checkwinfilename("just/a/normal/path")
868 >>> checkwinfilename("just/a/normal/path")
869 >>> checkwinfilename("foo/bar/con.xml")
869 >>> checkwinfilename("foo/bar/con.xml")
870 "filename contains 'con', which is reserved on Windows"
870 "filename contains 'con', which is reserved on Windows"
871 >>> checkwinfilename("foo/con.xml/bar")
871 >>> checkwinfilename("foo/con.xml/bar")
872 "filename contains 'con', which is reserved on Windows"
872 "filename contains 'con', which is reserved on Windows"
873 >>> checkwinfilename("foo/bar/xml.con")
873 >>> checkwinfilename("foo/bar/xml.con")
874 >>> checkwinfilename("foo/bar/AUX/bla.txt")
874 >>> checkwinfilename("foo/bar/AUX/bla.txt")
875 "filename contains 'AUX', which is reserved on Windows"
875 "filename contains 'AUX', which is reserved on Windows"
876 >>> checkwinfilename("foo/bar/bla:.txt")
876 >>> checkwinfilename("foo/bar/bla:.txt")
877 "filename contains ':', which is reserved on Windows"
877 "filename contains ':', which is reserved on Windows"
878 >>> checkwinfilename("foo/bar/b\07la.txt")
878 >>> checkwinfilename("foo/bar/b\07la.txt")
879 "filename contains '\\x07', which is invalid on Windows"
879 "filename contains '\\x07', which is invalid on Windows"
880 >>> checkwinfilename("foo/bar/bla ")
880 >>> checkwinfilename("foo/bar/bla ")
881 "filename ends with ' ', which is not allowed on Windows"
881 "filename ends with ' ', which is not allowed on Windows"
882 >>> checkwinfilename("../bar")
882 >>> checkwinfilename("../bar")
883 >>> checkwinfilename("foo\\")
883 >>> checkwinfilename("foo\\")
884 "filename ends with '\\', which is invalid on Windows"
884 "filename ends with '\\', which is invalid on Windows"
885 >>> checkwinfilename("foo\\/bar")
885 >>> checkwinfilename("foo\\/bar")
886 "directory name ends with '\\', which is invalid on Windows"
886 "directory name ends with '\\', which is invalid on Windows"
887 '''
887 '''
888 if path.endswith('\\'):
888 if path.endswith('\\'):
889 return _("filename ends with '\\', which is invalid on Windows")
889 return _("filename ends with '\\', which is invalid on Windows")
890 if '\\/' in path:
890 if '\\/' in path:
891 return _("directory name ends with '\\', which is invalid on Windows")
891 return _("directory name ends with '\\', which is invalid on Windows")
892 for n in path.replace('\\', '/').split('/'):
892 for n in path.replace('\\', '/').split('/'):
893 if not n:
893 if not n:
894 continue
894 continue
895 for c in n:
895 for c in n:
896 if c in _winreservedchars:
896 if c in _winreservedchars:
897 return _("filename contains '%s', which is reserved "
897 return _("filename contains '%s', which is reserved "
898 "on Windows") % c
898 "on Windows") % c
899 if ord(c) <= 31:
899 if ord(c) <= 31:
900 return _("filename contains %r, which is invalid "
900 return _("filename contains %r, which is invalid "
901 "on Windows") % c
901 "on Windows") % c
902 base = n.split('.')[0]
902 base = n.split('.')[0]
903 if base and base.lower() in _winreservednames:
903 if base and base.lower() in _winreservednames:
904 return _("filename contains '%s', which is reserved "
904 return _("filename contains '%s', which is reserved "
905 "on Windows") % base
905 "on Windows") % base
906 t = n[-1]
906 t = n[-1]
907 if t in '. ' and n not in '..':
907 if t in '. ' and n not in '..':
908 return _("filename ends with '%s', which is not allowed "
908 return _("filename ends with '%s', which is not allowed "
909 "on Windows") % t
909 "on Windows") % t
910
910
911 if os.name == 'nt':
911 if os.name == 'nt':
912 checkosfilename = checkwinfilename
912 checkosfilename = checkwinfilename
913 else:
913 else:
914 checkosfilename = platform.checkosfilename
914 checkosfilename = platform.checkosfilename
915
915
916 def makelock(info, pathname):
916 def makelock(info, pathname):
917 try:
917 try:
918 return os.symlink(info, pathname)
918 return os.symlink(info, pathname)
919 except OSError as why:
919 except OSError as why:
920 if why.errno == errno.EEXIST:
920 if why.errno == errno.EEXIST:
921 raise
921 raise
922 except AttributeError: # no symlink in os
922 except AttributeError: # no symlink in os
923 pass
923 pass
924
924
925 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
925 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
926 os.write(ld, info)
926 os.write(ld, info)
927 os.close(ld)
927 os.close(ld)
928
928
929 def readlock(pathname):
929 def readlock(pathname):
930 try:
930 try:
931 return os.readlink(pathname)
931 return os.readlink(pathname)
932 except OSError as why:
932 except OSError as why:
933 if why.errno not in (errno.EINVAL, errno.ENOSYS):
933 if why.errno not in (errno.EINVAL, errno.ENOSYS):
934 raise
934 raise
935 except AttributeError: # no symlink in os
935 except AttributeError: # no symlink in os
936 pass
936 pass
937 fp = posixfile(pathname)
937 fp = posixfile(pathname)
938 r = fp.read()
938 r = fp.read()
939 fp.close()
939 fp.close()
940 return r
940 return r
941
941
942 def fstat(fp):
942 def fstat(fp):
943 '''stat file object that may not have fileno method.'''
943 '''stat file object that may not have fileno method.'''
944 try:
944 try:
945 return os.fstat(fp.fileno())
945 return os.fstat(fp.fileno())
946 except AttributeError:
946 except AttributeError:
947 return os.stat(fp.name)
947 return os.stat(fp.name)
948
948
949 # File system features
949 # File system features
950
950
951 def checkcase(path):
951 def checkcase(path):
952 """
952 """
953 Return true if the given path is on a case-sensitive filesystem
953 Return true if the given path is on a case-sensitive filesystem
954
954
955 Requires a path (like /foo/.hg) ending with a foldable final
955 Requires a path (like /foo/.hg) ending with a foldable final
956 directory component.
956 directory component.
957 """
957 """
958 s1 = os.lstat(path)
958 s1 = os.lstat(path)
959 d, b = os.path.split(path)
959 d, b = os.path.split(path)
960 b2 = b.upper()
960 b2 = b.upper()
961 if b == b2:
961 if b == b2:
962 b2 = b.lower()
962 b2 = b.lower()
963 if b == b2:
963 if b == b2:
964 return True # no evidence against case sensitivity
964 return True # no evidence against case sensitivity
965 p2 = os.path.join(d, b2)
965 p2 = os.path.join(d, b2)
966 try:
966 try:
967 s2 = os.lstat(p2)
967 s2 = os.lstat(p2)
968 if s2 == s1:
968 if s2 == s1:
969 return False
969 return False
970 return True
970 return True
971 except OSError:
971 except OSError:
972 return True
972 return True
973
973
974 try:
974 try:
975 import re2
975 import re2
976 _re2 = None
976 _re2 = None
977 except ImportError:
977 except ImportError:
978 _re2 = False
978 _re2 = False
979
979
980 class _re(object):
980 class _re(object):
981 def _checkre2(self):
981 def _checkre2(self):
982 global _re2
982 global _re2
983 try:
983 try:
984 # check if match works, see issue3964
984 # check if match works, see issue3964
985 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
985 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
986 except ImportError:
986 except ImportError:
987 _re2 = False
987 _re2 = False
988
988
989 def compile(self, pat, flags=0):
989 def compile(self, pat, flags=0):
990 '''Compile a regular expression, using re2 if possible
990 '''Compile a regular expression, using re2 if possible
991
991
992 For best performance, use only re2-compatible regexp features. The
992 For best performance, use only re2-compatible regexp features. The
993 only flags from the re module that are re2-compatible are
993 only flags from the re module that are re2-compatible are
994 IGNORECASE and MULTILINE.'''
994 IGNORECASE and MULTILINE.'''
995 if _re2 is None:
995 if _re2 is None:
996 self._checkre2()
996 self._checkre2()
997 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
997 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
998 if flags & remod.IGNORECASE:
998 if flags & remod.IGNORECASE:
999 pat = '(?i)' + pat
999 pat = '(?i)' + pat
1000 if flags & remod.MULTILINE:
1000 if flags & remod.MULTILINE:
1001 pat = '(?m)' + pat
1001 pat = '(?m)' + pat
1002 try:
1002 try:
1003 return re2.compile(pat)
1003 return re2.compile(pat)
1004 except re2.error:
1004 except re2.error:
1005 pass
1005 pass
1006 return remod.compile(pat, flags)
1006 return remod.compile(pat, flags)
1007
1007
1008 @propertycache
1008 @propertycache
1009 def escape(self):
1009 def escape(self):
1010 '''Return the version of escape corresponding to self.compile.
1010 '''Return the version of escape corresponding to self.compile.
1011
1011
1012 This is imperfect because whether re2 or re is used for a particular
1012 This is imperfect because whether re2 or re is used for a particular
1013 function depends on the flags, etc, but it's the best we can do.
1013 function depends on the flags, etc, but it's the best we can do.
1014 '''
1014 '''
1015 global _re2
1015 global _re2
1016 if _re2 is None:
1016 if _re2 is None:
1017 self._checkre2()
1017 self._checkre2()
1018 if _re2:
1018 if _re2:
1019 return re2.escape
1019 return re2.escape
1020 else:
1020 else:
1021 return remod.escape
1021 return remod.escape
1022
1022
1023 re = _re()
1023 re = _re()
1024
1024
1025 _fspathcache = {}
1025 _fspathcache = {}
1026 def fspath(name, root):
1026 def fspath(name, root):
1027 '''Get name in the case stored in the filesystem
1027 '''Get name in the case stored in the filesystem
1028
1028
1029 The name should be relative to root, and be normcase-ed for efficiency.
1029 The name should be relative to root, and be normcase-ed for efficiency.
1030
1030
1031 Note that this function is unnecessary, and should not be
1031 Note that this function is unnecessary, and should not be
1032 called, for case-sensitive filesystems (simply because it's expensive).
1032 called, for case-sensitive filesystems (simply because it's expensive).
1033
1033
1034 The root should be normcase-ed, too.
1034 The root should be normcase-ed, too.
1035 '''
1035 '''
1036 def _makefspathcacheentry(dir):
1036 def _makefspathcacheentry(dir):
1037 return dict((normcase(n), n) for n in os.listdir(dir))
1037 return dict((normcase(n), n) for n in os.listdir(dir))
1038
1038
1039 seps = os.sep
1039 seps = os.sep
1040 if os.altsep:
1040 if os.altsep:
1041 seps = seps + os.altsep
1041 seps = seps + os.altsep
1042 # Protect backslashes. This gets silly very quickly.
1042 # Protect backslashes. This gets silly very quickly.
1043 seps.replace('\\','\\\\')
1043 seps.replace('\\','\\\\')
1044 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1044 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1045 dir = os.path.normpath(root)
1045 dir = os.path.normpath(root)
1046 result = []
1046 result = []
1047 for part, sep in pattern.findall(name):
1047 for part, sep in pattern.findall(name):
1048 if sep:
1048 if sep:
1049 result.append(sep)
1049 result.append(sep)
1050 continue
1050 continue
1051
1051
1052 if dir not in _fspathcache:
1052 if dir not in _fspathcache:
1053 _fspathcache[dir] = _makefspathcacheentry(dir)
1053 _fspathcache[dir] = _makefspathcacheentry(dir)
1054 contents = _fspathcache[dir]
1054 contents = _fspathcache[dir]
1055
1055
1056 found = contents.get(part)
1056 found = contents.get(part)
1057 if not found:
1057 if not found:
1058 # retry "once per directory" per "dirstate.walk" which
1058 # retry "once per directory" per "dirstate.walk" which
1059 # may take place for each patches of "hg qpush", for example
1059 # may take place for each patches of "hg qpush", for example
1060 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1060 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1061 found = contents.get(part)
1061 found = contents.get(part)
1062
1062
1063 result.append(found or part)
1063 result.append(found or part)
1064 dir = os.path.join(dir, part)
1064 dir = os.path.join(dir, part)
1065
1065
1066 return ''.join(result)
1066 return ''.join(result)
1067
1067
1068 def checknlink(testfile):
1068 def checknlink(testfile):
1069 '''check whether hardlink count reporting works properly'''
1069 '''check whether hardlink count reporting works properly'''
1070
1070
1071 # testfile may be open, so we need a separate file for checking to
1071 # testfile may be open, so we need a separate file for checking to
1072 # work around issue2543 (or testfile may get lost on Samba shares)
1072 # work around issue2543 (or testfile may get lost on Samba shares)
1073 f1 = testfile + ".hgtmp1"
1073 f1 = testfile + ".hgtmp1"
1074 if os.path.lexists(f1):
1074 if os.path.lexists(f1):
1075 return False
1075 return False
1076 try:
1076 try:
1077 posixfile(f1, 'w').close()
1077 posixfile(f1, 'w').close()
1078 except IOError:
1078 except IOError:
1079 return False
1079 return False
1080
1080
1081 f2 = testfile + ".hgtmp2"
1081 f2 = testfile + ".hgtmp2"
1082 fd = None
1082 fd = None
1083 try:
1083 try:
1084 oslink(f1, f2)
1084 oslink(f1, f2)
1085 # nlinks() may behave differently for files on Windows shares if
1085 # nlinks() may behave differently for files on Windows shares if
1086 # the file is open.
1086 # the file is open.
1087 fd = posixfile(f2)
1087 fd = posixfile(f2)
1088 return nlinks(f2) > 1
1088 return nlinks(f2) > 1
1089 except OSError:
1089 except OSError:
1090 return False
1090 return False
1091 finally:
1091 finally:
1092 if fd is not None:
1092 if fd is not None:
1093 fd.close()
1093 fd.close()
1094 for f in (f1, f2):
1094 for f in (f1, f2):
1095 try:
1095 try:
1096 os.unlink(f)
1096 os.unlink(f)
1097 except OSError:
1097 except OSError:
1098 pass
1098 pass
1099
1099
1100 def endswithsep(path):
1100 def endswithsep(path):
1101 '''Check path ends with os.sep or os.altsep.'''
1101 '''Check path ends with os.sep or os.altsep.'''
1102 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1102 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1103
1103
1104 def splitpath(path):
1104 def splitpath(path):
1105 '''Split path by os.sep.
1105 '''Split path by os.sep.
1106 Note that this function does not use os.altsep because this is
1106 Note that this function does not use os.altsep because this is
1107 an alternative of simple "xxx.split(os.sep)".
1107 an alternative of simple "xxx.split(os.sep)".
1108 It is recommended to use os.path.normpath() before using this
1108 It is recommended to use os.path.normpath() before using this
1109 function if need.'''
1109 function if need.'''
1110 return path.split(os.sep)
1110 return path.split(os.sep)
1111
1111
1112 def gui():
1112 def gui():
1113 '''Are we running in a GUI?'''
1113 '''Are we running in a GUI?'''
1114 if sys.platform == 'darwin':
1114 if sys.platform == 'darwin':
1115 if 'SSH_CONNECTION' in os.environ:
1115 if 'SSH_CONNECTION' in os.environ:
1116 # handle SSH access to a box where the user is logged in
1116 # handle SSH access to a box where the user is logged in
1117 return False
1117 return False
1118 elif getattr(osutil, 'isgui', None):
1118 elif getattr(osutil, 'isgui', None):
1119 # check if a CoreGraphics session is available
1119 # check if a CoreGraphics session is available
1120 return osutil.isgui()
1120 return osutil.isgui()
1121 else:
1121 else:
1122 # pure build; use a safe default
1122 # pure build; use a safe default
1123 return True
1123 return True
1124 else:
1124 else:
1125 return os.name == "nt" or os.environ.get("DISPLAY")
1125 return os.name == "nt" or os.environ.get("DISPLAY")
1126
1126
1127 def mktempcopy(name, emptyok=False, createmode=None):
1127 def mktempcopy(name, emptyok=False, createmode=None):
1128 """Create a temporary file with the same contents from name
1128 """Create a temporary file with the same contents from name
1129
1129
1130 The permission bits are copied from the original file.
1130 The permission bits are copied from the original file.
1131
1131
1132 If the temporary file is going to be truncated immediately, you
1132 If the temporary file is going to be truncated immediately, you
1133 can use emptyok=True as an optimization.
1133 can use emptyok=True as an optimization.
1134
1134
1135 Returns the name of the temporary file.
1135 Returns the name of the temporary file.
1136 """
1136 """
1137 d, fn = os.path.split(name)
1137 d, fn = os.path.split(name)
1138 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1138 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1139 os.close(fd)
1139 os.close(fd)
1140 # Temporary files are created with mode 0600, which is usually not
1140 # Temporary files are created with mode 0600, which is usually not
1141 # what we want. If the original file already exists, just copy
1141 # what we want. If the original file already exists, just copy
1142 # its mode. Otherwise, manually obey umask.
1142 # its mode. Otherwise, manually obey umask.
1143 copymode(name, temp, createmode)
1143 copymode(name, temp, createmode)
1144 if emptyok:
1144 if emptyok:
1145 return temp
1145 return temp
1146 try:
1146 try:
1147 try:
1147 try:
1148 ifp = posixfile(name, "rb")
1148 ifp = posixfile(name, "rb")
1149 except IOError as inst:
1149 except IOError as inst:
1150 if inst.errno == errno.ENOENT:
1150 if inst.errno == errno.ENOENT:
1151 return temp
1151 return temp
1152 if not getattr(inst, 'filename', None):
1152 if not getattr(inst, 'filename', None):
1153 inst.filename = name
1153 inst.filename = name
1154 raise
1154 raise
1155 ofp = posixfile(temp, "wb")
1155 ofp = posixfile(temp, "wb")
1156 for chunk in filechunkiter(ifp):
1156 for chunk in filechunkiter(ifp):
1157 ofp.write(chunk)
1157 ofp.write(chunk)
1158 ifp.close()
1158 ifp.close()
1159 ofp.close()
1159 ofp.close()
1160 except: # re-raises
1160 except: # re-raises
1161 try: os.unlink(temp)
1161 try: os.unlink(temp)
1162 except OSError: pass
1162 except OSError: pass
1163 raise
1163 raise
1164 return temp
1164 return temp
1165
1165
1166 class atomictempfile(object):
1166 class atomictempfile(object):
1167 '''writable file object that atomically updates a file
1167 '''writable file object that atomically updates a file
1168
1168
1169 All writes will go to a temporary copy of the original file. Call
1169 All writes will go to a temporary copy of the original file. Call
1170 close() when you are done writing, and atomictempfile will rename
1170 close() when you are done writing, and atomictempfile will rename
1171 the temporary copy to the original name, making the changes
1171 the temporary copy to the original name, making the changes
1172 visible. If the object is destroyed without being closed, all your
1172 visible. If the object is destroyed without being closed, all your
1173 writes are discarded.
1173 writes are discarded.
1174 '''
1174 '''
1175 def __init__(self, name, mode='w+b', createmode=None):
1175 def __init__(self, name, mode='w+b', createmode=None):
1176 self.__name = name # permanent name
1176 self.__name = name # permanent name
1177 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1177 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1178 createmode=createmode)
1178 createmode=createmode)
1179 self._fp = posixfile(self._tempname, mode)
1179 self._fp = posixfile(self._tempname, mode)
1180
1180
1181 # delegated methods
1181 # delegated methods
1182 self.write = self._fp.write
1182 self.write = self._fp.write
1183 self.seek = self._fp.seek
1183 self.seek = self._fp.seek
1184 self.tell = self._fp.tell
1184 self.tell = self._fp.tell
1185 self.fileno = self._fp.fileno
1185 self.fileno = self._fp.fileno
1186
1186
1187 def close(self):
1187 def close(self):
1188 if not self._fp.closed:
1188 if not self._fp.closed:
1189 self._fp.close()
1189 self._fp.close()
1190 rename(self._tempname, localpath(self.__name))
1190 rename(self._tempname, localpath(self.__name))
1191
1191
1192 def discard(self):
1192 def discard(self):
1193 if not self._fp.closed:
1193 if not self._fp.closed:
1194 try:
1194 try:
1195 os.unlink(self._tempname)
1195 os.unlink(self._tempname)
1196 except OSError:
1196 except OSError:
1197 pass
1197 pass
1198 self._fp.close()
1198 self._fp.close()
1199
1199
1200 def __del__(self):
1200 def __del__(self):
1201 if safehasattr(self, '_fp'): # constructor actually did something
1201 if safehasattr(self, '_fp'): # constructor actually did something
1202 self.discard()
1202 self.discard()
1203
1203
1204 def makedirs(name, mode=None, notindexed=False):
1204 def makedirs(name, mode=None, notindexed=False):
1205 """recursive directory creation with parent mode inheritance"""
1205 """recursive directory creation with parent mode inheritance"""
1206 try:
1206 try:
1207 makedir(name, notindexed)
1207 makedir(name, notindexed)
1208 except OSError as err:
1208 except OSError as err:
1209 if err.errno == errno.EEXIST:
1209 if err.errno == errno.EEXIST:
1210 return
1210 return
1211 if err.errno != errno.ENOENT or not name:
1211 if err.errno != errno.ENOENT or not name:
1212 raise
1212 raise
1213 parent = os.path.dirname(os.path.abspath(name))
1213 parent = os.path.dirname(os.path.abspath(name))
1214 if parent == name:
1214 if parent == name:
1215 raise
1215 raise
1216 makedirs(parent, mode, notindexed)
1216 makedirs(parent, mode, notindexed)
1217 makedir(name, notindexed)
1217 makedir(name, notindexed)
1218 if mode is not None:
1218 if mode is not None:
1219 os.chmod(name, mode)
1219 os.chmod(name, mode)
1220
1220
1221 def ensuredirs(name, mode=None, notindexed=False):
1221 def ensuredirs(name, mode=None, notindexed=False):
1222 """race-safe recursive directory creation
1222 """race-safe recursive directory creation
1223
1223
1224 Newly created directories are marked as "not to be indexed by
1224 Newly created directories are marked as "not to be indexed by
1225 the content indexing service", if ``notindexed`` is specified
1225 the content indexing service", if ``notindexed`` is specified
1226 for "write" mode access.
1226 for "write" mode access.
1227 """
1227 """
1228 if os.path.isdir(name):
1228 if os.path.isdir(name):
1229 return
1229 return
1230 parent = os.path.dirname(os.path.abspath(name))
1230 parent = os.path.dirname(os.path.abspath(name))
1231 if parent != name:
1231 if parent != name:
1232 ensuredirs(parent, mode, notindexed)
1232 ensuredirs(parent, mode, notindexed)
1233 try:
1233 try:
1234 makedir(name, notindexed)
1234 makedir(name, notindexed)
1235 except OSError as err:
1235 except OSError as err:
1236 if err.errno == errno.EEXIST and os.path.isdir(name):
1236 if err.errno == errno.EEXIST and os.path.isdir(name):
1237 # someone else seems to have won a directory creation race
1237 # someone else seems to have won a directory creation race
1238 return
1238 return
1239 raise
1239 raise
1240 if mode is not None:
1240 if mode is not None:
1241 os.chmod(name, mode)
1241 os.chmod(name, mode)
1242
1242
1243 def readfile(path):
1243 def readfile(path):
1244 fp = open(path, 'rb')
1244 fp = open(path, 'rb')
1245 try:
1245 try:
1246 return fp.read()
1246 return fp.read()
1247 finally:
1247 finally:
1248 fp.close()
1248 fp.close()
1249
1249
1250 def writefile(path, text):
1250 def writefile(path, text):
1251 fp = open(path, 'wb')
1251 fp = open(path, 'wb')
1252 try:
1252 try:
1253 fp.write(text)
1253 fp.write(text)
1254 finally:
1254 finally:
1255 fp.close()
1255 fp.close()
1256
1256
1257 def appendfile(path, text):
1257 def appendfile(path, text):
1258 fp = open(path, 'ab')
1258 fp = open(path, 'ab')
1259 try:
1259 try:
1260 fp.write(text)
1260 fp.write(text)
1261 finally:
1261 finally:
1262 fp.close()
1262 fp.close()
1263
1263
1264 class chunkbuffer(object):
1264 class chunkbuffer(object):
1265 """Allow arbitrary sized chunks of data to be efficiently read from an
1265 """Allow arbitrary sized chunks of data to be efficiently read from an
1266 iterator over chunks of arbitrary size."""
1266 iterator over chunks of arbitrary size."""
1267
1267
1268 def __init__(self, in_iter):
1268 def __init__(self, in_iter):
1269 """in_iter is the iterator that's iterating over the input chunks.
1269 """in_iter is the iterator that's iterating over the input chunks.
1270 targetsize is how big a buffer to try to maintain."""
1270 targetsize is how big a buffer to try to maintain."""
1271 def splitbig(chunks):
1271 def splitbig(chunks):
1272 for chunk in chunks:
1272 for chunk in chunks:
1273 if len(chunk) > 2**20:
1273 if len(chunk) > 2**20:
1274 pos = 0
1274 pos = 0
1275 while pos < len(chunk):
1275 while pos < len(chunk):
1276 end = pos + 2 ** 18
1276 end = pos + 2 ** 18
1277 yield chunk[pos:end]
1277 yield chunk[pos:end]
1278 pos = end
1278 pos = end
1279 else:
1279 else:
1280 yield chunk
1280 yield chunk
1281 self.iter = splitbig(in_iter)
1281 self.iter = splitbig(in_iter)
1282 self._queue = collections.deque()
1282 self._queue = collections.deque()
1283
1283
1284 def read(self, l=None):
1284 def read(self, l=None):
1285 """Read L bytes of data from the iterator of chunks of data.
1285 """Read L bytes of data from the iterator of chunks of data.
1286 Returns less than L bytes if the iterator runs dry.
1286 Returns less than L bytes if the iterator runs dry.
1287
1287
1288 If size parameter is omitted, read everything"""
1288 If size parameter is omitted, read everything"""
1289 left = l
1289 left = l
1290 buf = []
1290 buf = []
1291 queue = self._queue
1291 queue = self._queue
1292 while left is None or left > 0:
1292 while left is None or left > 0:
1293 # refill the queue
1293 # refill the queue
1294 if not queue:
1294 if not queue:
1295 target = 2**18
1295 target = 2**18
1296 for chunk in self.iter:
1296 for chunk in self.iter:
1297 queue.append(chunk)
1297 queue.append(chunk)
1298 target -= len(chunk)
1298 target -= len(chunk)
1299 if target <= 0:
1299 if target <= 0:
1300 break
1300 break
1301 if not queue:
1301 if not queue:
1302 break
1302 break
1303
1303
1304 chunk = queue.popleft()
1304 chunk = queue.popleft()
1305 if left is not None:
1305 if left is not None:
1306 left -= len(chunk)
1306 left -= len(chunk)
1307 if left is not None and left < 0:
1307 if left is not None and left < 0:
1308 queue.appendleft(chunk[left:])
1308 queue.appendleft(chunk[left:])
1309 buf.append(chunk[:left])
1309 buf.append(chunk[:left])
1310 else:
1310 else:
1311 buf.append(chunk)
1311 buf.append(chunk)
1312
1312
1313 return ''.join(buf)
1313 return ''.join(buf)
1314
1314
1315 def filechunkiter(f, size=65536, limit=None):
1315 def filechunkiter(f, size=65536, limit=None):
1316 """Create a generator that produces the data in the file size
1316 """Create a generator that produces the data in the file size
1317 (default 65536) bytes at a time, up to optional limit (default is
1317 (default 65536) bytes at a time, up to optional limit (default is
1318 to read all data). Chunks may be less than size bytes if the
1318 to read all data). Chunks may be less than size bytes if the
1319 chunk is the last chunk in the file, or the file is a socket or
1319 chunk is the last chunk in the file, or the file is a socket or
1320 some other type of file that sometimes reads less data than is
1320 some other type of file that sometimes reads less data than is
1321 requested."""
1321 requested."""
1322 assert size >= 0
1322 assert size >= 0
1323 assert limit is None or limit >= 0
1323 assert limit is None or limit >= 0
1324 while True:
1324 while True:
1325 if limit is None:
1325 if limit is None:
1326 nbytes = size
1326 nbytes = size
1327 else:
1327 else:
1328 nbytes = min(limit, size)
1328 nbytes = min(limit, size)
1329 s = nbytes and f.read(nbytes)
1329 s = nbytes and f.read(nbytes)
1330 if not s:
1330 if not s:
1331 break
1331 break
1332 if limit:
1332 if limit:
1333 limit -= len(s)
1333 limit -= len(s)
1334 yield s
1334 yield s
1335
1335
1336 def makedate(timestamp=None):
1336 def makedate(timestamp=None):
1337 '''Return a unix timestamp (or the current time) as a (unixtime,
1337 '''Return a unix timestamp (or the current time) as a (unixtime,
1338 offset) tuple based off the local timezone.'''
1338 offset) tuple based off the local timezone.'''
1339 if timestamp is None:
1339 if timestamp is None:
1340 timestamp = time.time()
1340 timestamp = time.time()
1341 if timestamp < 0:
1341 if timestamp < 0:
1342 hint = _("check your clock")
1342 hint = _("check your clock")
1343 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1343 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1344 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1344 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1345 datetime.datetime.fromtimestamp(timestamp))
1345 datetime.datetime.fromtimestamp(timestamp))
1346 tz = delta.days * 86400 + delta.seconds
1346 tz = delta.days * 86400 + delta.seconds
1347 return timestamp, tz
1347 return timestamp, tz
1348
1348
1349 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1349 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1350 """represent a (unixtime, offset) tuple as a localized time.
1350 """represent a (unixtime, offset) tuple as a localized time.
1351 unixtime is seconds since the epoch, and offset is the time zone's
1351 unixtime is seconds since the epoch, and offset is the time zone's
1352 number of seconds away from UTC. if timezone is false, do not
1352 number of seconds away from UTC. if timezone is false, do not
1353 append time zone to string."""
1353 append time zone to string."""
1354 t, tz = date or makedate()
1354 t, tz = date or makedate()
1355 if t < 0:
1355 if t < 0:
1356 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1356 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1357 tz = 0
1357 tz = 0
1358 if "%1" in format or "%2" in format or "%z" in format:
1358 if "%1" in format or "%2" in format or "%z" in format:
1359 sign = (tz > 0) and "-" or "+"
1359 sign = (tz > 0) and "-" or "+"
1360 minutes = abs(tz) // 60
1360 minutes = abs(tz) // 60
1361 format = format.replace("%z", "%1%2")
1361 format = format.replace("%z", "%1%2")
1362 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1362 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1363 format = format.replace("%2", "%02d" % (minutes % 60))
1363 format = format.replace("%2", "%02d" % (minutes % 60))
1364 try:
1364 try:
1365 t = time.gmtime(float(t) - tz)
1365 t = time.gmtime(float(t) - tz)
1366 except ValueError:
1366 except ValueError:
1367 # time was out of range
1367 # time was out of range
1368 t = time.gmtime(sys.maxint)
1368 t = time.gmtime(sys.maxint)
1369 s = time.strftime(format, t)
1369 s = time.strftime(format, t)
1370 return s
1370 return s
1371
1371
1372 def shortdate(date=None):
1372 def shortdate(date=None):
1373 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1373 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1374 return datestr(date, format='%Y-%m-%d')
1374 return datestr(date, format='%Y-%m-%d')
1375
1375
1376 def parsetimezone(tz):
1376 def parsetimezone(tz):
1377 """parse a timezone string and return an offset integer"""
1377 """parse a timezone string and return an offset integer"""
1378 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1378 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1379 sign = (tz[0] == "+") and 1 or -1
1379 sign = (tz[0] == "+") and 1 or -1
1380 hours = int(tz[1:3])
1380 hours = int(tz[1:3])
1381 minutes = int(tz[3:5])
1381 minutes = int(tz[3:5])
1382 return -sign * (hours * 60 + minutes) * 60
1382 return -sign * (hours * 60 + minutes) * 60
1383 if tz == "GMT" or tz == "UTC":
1383 if tz == "GMT" or tz == "UTC":
1384 return 0
1384 return 0
1385 return None
1385 return None
1386
1386
1387 def strdate(string, format, defaults=[]):
1387 def strdate(string, format, defaults=[]):
1388 """parse a localized time string and return a (unixtime, offset) tuple.
1388 """parse a localized time string and return a (unixtime, offset) tuple.
1389 if the string cannot be parsed, ValueError is raised."""
1389 if the string cannot be parsed, ValueError is raised."""
1390 # NOTE: unixtime = localunixtime + offset
1390 # NOTE: unixtime = localunixtime + offset
1391 offset, date = parsetimezone(string.split()[-1]), string
1391 offset, date = parsetimezone(string.split()[-1]), string
1392 if offset is not None:
1392 if offset is not None:
1393 date = " ".join(string.split()[:-1])
1393 date = " ".join(string.split()[:-1])
1394
1394
1395 # add missing elements from defaults
1395 # add missing elements from defaults
1396 usenow = False # default to using biased defaults
1396 usenow = False # default to using biased defaults
1397 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1397 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1398 found = [True for p in part if ("%"+p) in format]
1398 found = [True for p in part if ("%"+p) in format]
1399 if not found:
1399 if not found:
1400 date += "@" + defaults[part][usenow]
1400 date += "@" + defaults[part][usenow]
1401 format += "@%" + part[0]
1401 format += "@%" + part[0]
1402 else:
1402 else:
1403 # We've found a specific time element, less specific time
1403 # We've found a specific time element, less specific time
1404 # elements are relative to today
1404 # elements are relative to today
1405 usenow = True
1405 usenow = True
1406
1406
1407 timetuple = time.strptime(date, format)
1407 timetuple = time.strptime(date, format)
1408 localunixtime = int(calendar.timegm(timetuple))
1408 localunixtime = int(calendar.timegm(timetuple))
1409 if offset is None:
1409 if offset is None:
1410 # local timezone
1410 # local timezone
1411 unixtime = int(time.mktime(timetuple))
1411 unixtime = int(time.mktime(timetuple))
1412 offset = unixtime - localunixtime
1412 offset = unixtime - localunixtime
1413 else:
1413 else:
1414 unixtime = localunixtime + offset
1414 unixtime = localunixtime + offset
1415 return unixtime, offset
1415 return unixtime, offset
1416
1416
1417 def parsedate(date, formats=None, bias={}):
1417 def parsedate(date, formats=None, bias={}):
1418 """parse a localized date/time and return a (unixtime, offset) tuple.
1418 """parse a localized date/time and return a (unixtime, offset) tuple.
1419
1419
1420 The date may be a "unixtime offset" string or in one of the specified
1420 The date may be a "unixtime offset" string or in one of the specified
1421 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1421 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1422
1422
1423 >>> parsedate(' today ') == parsedate(\
1423 >>> parsedate(' today ') == parsedate(\
1424 datetime.date.today().strftime('%b %d'))
1424 datetime.date.today().strftime('%b %d'))
1425 True
1425 True
1426 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1426 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1427 datetime.timedelta(days=1)\
1427 datetime.timedelta(days=1)\
1428 ).strftime('%b %d'))
1428 ).strftime('%b %d'))
1429 True
1429 True
1430 >>> now, tz = makedate()
1430 >>> now, tz = makedate()
1431 >>> strnow, strtz = parsedate('now')
1431 >>> strnow, strtz = parsedate('now')
1432 >>> (strnow - now) < 1
1432 >>> (strnow - now) < 1
1433 True
1433 True
1434 >>> tz == strtz
1434 >>> tz == strtz
1435 True
1435 True
1436 """
1436 """
1437 if not date:
1437 if not date:
1438 return 0, 0
1438 return 0, 0
1439 if isinstance(date, tuple) and len(date) == 2:
1439 if isinstance(date, tuple) and len(date) == 2:
1440 return date
1440 return date
1441 if not formats:
1441 if not formats:
1442 formats = defaultdateformats
1442 formats = defaultdateformats
1443 date = date.strip()
1443 date = date.strip()
1444
1444
1445 if date == 'now' or date == _('now'):
1445 if date == 'now' or date == _('now'):
1446 return makedate()
1446 return makedate()
1447 if date == 'today' or date == _('today'):
1447 if date == 'today' or date == _('today'):
1448 date = datetime.date.today().strftime('%b %d')
1448 date = datetime.date.today().strftime('%b %d')
1449 elif date == 'yesterday' or date == _('yesterday'):
1449 elif date == 'yesterday' or date == _('yesterday'):
1450 date = (datetime.date.today() -
1450 date = (datetime.date.today() -
1451 datetime.timedelta(days=1)).strftime('%b %d')
1451 datetime.timedelta(days=1)).strftime('%b %d')
1452
1452
1453 try:
1453 try:
1454 when, offset = map(int, date.split(' '))
1454 when, offset = map(int, date.split(' '))
1455 except ValueError:
1455 except ValueError:
1456 # fill out defaults
1456 # fill out defaults
1457 now = makedate()
1457 now = makedate()
1458 defaults = {}
1458 defaults = {}
1459 for part in ("d", "mb", "yY", "HI", "M", "S"):
1459 for part in ("d", "mb", "yY", "HI", "M", "S"):
1460 # this piece is for rounding the specific end of unknowns
1460 # this piece is for rounding the specific end of unknowns
1461 b = bias.get(part)
1461 b = bias.get(part)
1462 if b is None:
1462 if b is None:
1463 if part[0] in "HMS":
1463 if part[0] in "HMS":
1464 b = "00"
1464 b = "00"
1465 else:
1465 else:
1466 b = "0"
1466 b = "0"
1467
1467
1468 # this piece is for matching the generic end to today's date
1468 # this piece is for matching the generic end to today's date
1469 n = datestr(now, "%" + part[0])
1469 n = datestr(now, "%" + part[0])
1470
1470
1471 defaults[part] = (b, n)
1471 defaults[part] = (b, n)
1472
1472
1473 for format in formats:
1473 for format in formats:
1474 try:
1474 try:
1475 when, offset = strdate(date, format, defaults)
1475 when, offset = strdate(date, format, defaults)
1476 except (ValueError, OverflowError):
1476 except (ValueError, OverflowError):
1477 pass
1477 pass
1478 else:
1478 else:
1479 break
1479 break
1480 else:
1480 else:
1481 raise Abort(_('invalid date: %r') % date)
1481 raise Abort(_('invalid date: %r') % date)
1482 # validate explicit (probably user-specified) date and
1482 # validate explicit (probably user-specified) date and
1483 # time zone offset. values must fit in signed 32 bits for
1483 # time zone offset. values must fit in signed 32 bits for
1484 # current 32-bit linux runtimes. timezones go from UTC-12
1484 # current 32-bit linux runtimes. timezones go from UTC-12
1485 # to UTC+14
1485 # to UTC+14
1486 if abs(when) > 0x7fffffff:
1486 if abs(when) > 0x7fffffff:
1487 raise Abort(_('date exceeds 32 bits: %d') % when)
1487 raise Abort(_('date exceeds 32 bits: %d') % when)
1488 if when < 0:
1488 if when < 0:
1489 raise Abort(_('negative date value: %d') % when)
1489 raise Abort(_('negative date value: %d') % when)
1490 if offset < -50400 or offset > 43200:
1490 if offset < -50400 or offset > 43200:
1491 raise Abort(_('impossible time zone offset: %d') % offset)
1491 raise Abort(_('impossible time zone offset: %d') % offset)
1492 return when, offset
1492 return when, offset
1493
1493
1494 def matchdate(date):
1494 def matchdate(date):
1495 """Return a function that matches a given date match specifier
1495 """Return a function that matches a given date match specifier
1496
1496
1497 Formats include:
1497 Formats include:
1498
1498
1499 '{date}' match a given date to the accuracy provided
1499 '{date}' match a given date to the accuracy provided
1500
1500
1501 '<{date}' on or before a given date
1501 '<{date}' on or before a given date
1502
1502
1503 '>{date}' on or after a given date
1503 '>{date}' on or after a given date
1504
1504
1505 >>> p1 = parsedate("10:29:59")
1505 >>> p1 = parsedate("10:29:59")
1506 >>> p2 = parsedate("10:30:00")
1506 >>> p2 = parsedate("10:30:00")
1507 >>> p3 = parsedate("10:30:59")
1507 >>> p3 = parsedate("10:30:59")
1508 >>> p4 = parsedate("10:31:00")
1508 >>> p4 = parsedate("10:31:00")
1509 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1509 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1510 >>> f = matchdate("10:30")
1510 >>> f = matchdate("10:30")
1511 >>> f(p1[0])
1511 >>> f(p1[0])
1512 False
1512 False
1513 >>> f(p2[0])
1513 >>> f(p2[0])
1514 True
1514 True
1515 >>> f(p3[0])
1515 >>> f(p3[0])
1516 True
1516 True
1517 >>> f(p4[0])
1517 >>> f(p4[0])
1518 False
1518 False
1519 >>> f(p5[0])
1519 >>> f(p5[0])
1520 False
1520 False
1521 """
1521 """
1522
1522
1523 def lower(date):
1523 def lower(date):
1524 d = {'mb': "1", 'd': "1"}
1524 d = {'mb': "1", 'd': "1"}
1525 return parsedate(date, extendeddateformats, d)[0]
1525 return parsedate(date, extendeddateformats, d)[0]
1526
1526
1527 def upper(date):
1527 def upper(date):
1528 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1528 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1529 for days in ("31", "30", "29"):
1529 for days in ("31", "30", "29"):
1530 try:
1530 try:
1531 d["d"] = days
1531 d["d"] = days
1532 return parsedate(date, extendeddateformats, d)[0]
1532 return parsedate(date, extendeddateformats, d)[0]
1533 except Abort:
1533 except Abort:
1534 pass
1534 pass
1535 d["d"] = "28"
1535 d["d"] = "28"
1536 return parsedate(date, extendeddateformats, d)[0]
1536 return parsedate(date, extendeddateformats, d)[0]
1537
1537
1538 date = date.strip()
1538 date = date.strip()
1539
1539
1540 if not date:
1540 if not date:
1541 raise Abort(_("dates cannot consist entirely of whitespace"))
1541 raise Abort(_("dates cannot consist entirely of whitespace"))
1542 elif date[0] == "<":
1542 elif date[0] == "<":
1543 if not date[1:]:
1543 if not date[1:]:
1544 raise Abort(_("invalid day spec, use '<DATE'"))
1544 raise Abort(_("invalid day spec, use '<DATE'"))
1545 when = upper(date[1:])
1545 when = upper(date[1:])
1546 return lambda x: x <= when
1546 return lambda x: x <= when
1547 elif date[0] == ">":
1547 elif date[0] == ">":
1548 if not date[1:]:
1548 if not date[1:]:
1549 raise Abort(_("invalid day spec, use '>DATE'"))
1549 raise Abort(_("invalid day spec, use '>DATE'"))
1550 when = lower(date[1:])
1550 when = lower(date[1:])
1551 return lambda x: x >= when
1551 return lambda x: x >= when
1552 elif date[0] == "-":
1552 elif date[0] == "-":
1553 try:
1553 try:
1554 days = int(date[1:])
1554 days = int(date[1:])
1555 except ValueError:
1555 except ValueError:
1556 raise Abort(_("invalid day spec: %s") % date[1:])
1556 raise Abort(_("invalid day spec: %s") % date[1:])
1557 if days < 0:
1557 if days < 0:
1558 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1558 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1559 % date[1:])
1559 % date[1:])
1560 when = makedate()[0] - days * 3600 * 24
1560 when = makedate()[0] - days * 3600 * 24
1561 return lambda x: x >= when
1561 return lambda x: x >= when
1562 elif " to " in date:
1562 elif " to " in date:
1563 a, b = date.split(" to ")
1563 a, b = date.split(" to ")
1564 start, stop = lower(a), upper(b)
1564 start, stop = lower(a), upper(b)
1565 return lambda x: x >= start and x <= stop
1565 return lambda x: x >= start and x <= stop
1566 else:
1566 else:
1567 start, stop = lower(date), upper(date)
1567 start, stop = lower(date), upper(date)
1568 return lambda x: x >= start and x <= stop
1568 return lambda x: x >= start and x <= stop
1569
1569
1570 def shortuser(user):
1570 def shortuser(user):
1571 """Return a short representation of a user name or email address."""
1571 """Return a short representation of a user name or email address."""
1572 f = user.find('@')
1572 f = user.find('@')
1573 if f >= 0:
1573 if f >= 0:
1574 user = user[:f]
1574 user = user[:f]
1575 f = user.find('<')
1575 f = user.find('<')
1576 if f >= 0:
1576 if f >= 0:
1577 user = user[f + 1:]
1577 user = user[f + 1:]
1578 f = user.find(' ')
1578 f = user.find(' ')
1579 if f >= 0:
1579 if f >= 0:
1580 user = user[:f]
1580 user = user[:f]
1581 f = user.find('.')
1581 f = user.find('.')
1582 if f >= 0:
1582 if f >= 0:
1583 user = user[:f]
1583 user = user[:f]
1584 return user
1584 return user
1585
1585
1586 def emailuser(user):
1586 def emailuser(user):
1587 """Return the user portion of an email address."""
1587 """Return the user portion of an email address."""
1588 f = user.find('@')
1588 f = user.find('@')
1589 if f >= 0:
1589 if f >= 0:
1590 user = user[:f]
1590 user = user[:f]
1591 f = user.find('<')
1591 f = user.find('<')
1592 if f >= 0:
1592 if f >= 0:
1593 user = user[f + 1:]
1593 user = user[f + 1:]
1594 return user
1594 return user
1595
1595
1596 def email(author):
1596 def email(author):
1597 '''get email of author.'''
1597 '''get email of author.'''
1598 r = author.find('>')
1598 r = author.find('>')
1599 if r == -1:
1599 if r == -1:
1600 r = None
1600 r = None
1601 return author[author.find('<') + 1:r]
1601 return author[author.find('<') + 1:r]
1602
1602
1603 def ellipsis(text, maxlength=400):
1603 def ellipsis(text, maxlength=400):
1604 """Trim string to at most maxlength (default: 400) columns in display."""
1604 """Trim string to at most maxlength (default: 400) columns in display."""
1605 return encoding.trim(text, maxlength, ellipsis='...')
1605 return encoding.trim(text, maxlength, ellipsis='...')
1606
1606
1607 def unitcountfn(*unittable):
1607 def unitcountfn(*unittable):
1608 '''return a function that renders a readable count of some quantity'''
1608 '''return a function that renders a readable count of some quantity'''
1609
1609
1610 def go(count):
1610 def go(count):
1611 for multiplier, divisor, format in unittable:
1611 for multiplier, divisor, format in unittable:
1612 if count >= divisor * multiplier:
1612 if count >= divisor * multiplier:
1613 return format % (count / float(divisor))
1613 return format % (count / float(divisor))
1614 return unittable[-1][2] % count
1614 return unittable[-1][2] % count
1615
1615
1616 return go
1616 return go
1617
1617
1618 bytecount = unitcountfn(
1618 bytecount = unitcountfn(
1619 (100, 1 << 30, _('%.0f GB')),
1619 (100, 1 << 30, _('%.0f GB')),
1620 (10, 1 << 30, _('%.1f GB')),
1620 (10, 1 << 30, _('%.1f GB')),
1621 (1, 1 << 30, _('%.2f GB')),
1621 (1, 1 << 30, _('%.2f GB')),
1622 (100, 1 << 20, _('%.0f MB')),
1622 (100, 1 << 20, _('%.0f MB')),
1623 (10, 1 << 20, _('%.1f MB')),
1623 (10, 1 << 20, _('%.1f MB')),
1624 (1, 1 << 20, _('%.2f MB')),
1624 (1, 1 << 20, _('%.2f MB')),
1625 (100, 1 << 10, _('%.0f KB')),
1625 (100, 1 << 10, _('%.0f KB')),
1626 (10, 1 << 10, _('%.1f KB')),
1626 (10, 1 << 10, _('%.1f KB')),
1627 (1, 1 << 10, _('%.2f KB')),
1627 (1, 1 << 10, _('%.2f KB')),
1628 (1, 1, _('%.0f bytes')),
1628 (1, 1, _('%.0f bytes')),
1629 )
1629 )
1630
1630
1631 def uirepr(s):
1631 def uirepr(s):
1632 # Avoid double backslash in Windows path repr()
1632 # Avoid double backslash in Windows path repr()
1633 return repr(s).replace('\\\\', '\\')
1633 return repr(s).replace('\\\\', '\\')
1634
1634
1635 # delay import of textwrap
1635 # delay import of textwrap
1636 def MBTextWrapper(**kwargs):
1636 def MBTextWrapper(**kwargs):
1637 class tw(textwrap.TextWrapper):
1637 class tw(textwrap.TextWrapper):
1638 """
1638 """
1639 Extend TextWrapper for width-awareness.
1639 Extend TextWrapper for width-awareness.
1640
1640
1641 Neither number of 'bytes' in any encoding nor 'characters' is
1641 Neither number of 'bytes' in any encoding nor 'characters' is
1642 appropriate to calculate terminal columns for specified string.
1642 appropriate to calculate terminal columns for specified string.
1643
1643
1644 Original TextWrapper implementation uses built-in 'len()' directly,
1644 Original TextWrapper implementation uses built-in 'len()' directly,
1645 so overriding is needed to use width information of each characters.
1645 so overriding is needed to use width information of each characters.
1646
1646
1647 In addition, characters classified into 'ambiguous' width are
1647 In addition, characters classified into 'ambiguous' width are
1648 treated as wide in East Asian area, but as narrow in other.
1648 treated as wide in East Asian area, but as narrow in other.
1649
1649
1650 This requires use decision to determine width of such characters.
1650 This requires use decision to determine width of such characters.
1651 """
1651 """
1652 def _cutdown(self, ucstr, space_left):
1652 def _cutdown(self, ucstr, space_left):
1653 l = 0
1653 l = 0
1654 colwidth = encoding.ucolwidth
1654 colwidth = encoding.ucolwidth
1655 for i in xrange(len(ucstr)):
1655 for i in xrange(len(ucstr)):
1656 l += colwidth(ucstr[i])
1656 l += colwidth(ucstr[i])
1657 if space_left < l:
1657 if space_left < l:
1658 return (ucstr[:i], ucstr[i:])
1658 return (ucstr[:i], ucstr[i:])
1659 return ucstr, ''
1659 return ucstr, ''
1660
1660
1661 # overriding of base class
1661 # overriding of base class
1662 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1662 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1663 space_left = max(width - cur_len, 1)
1663 space_left = max(width - cur_len, 1)
1664
1664
1665 if self.break_long_words:
1665 if self.break_long_words:
1666 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1666 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1667 cur_line.append(cut)
1667 cur_line.append(cut)
1668 reversed_chunks[-1] = res
1668 reversed_chunks[-1] = res
1669 elif not cur_line:
1669 elif not cur_line:
1670 cur_line.append(reversed_chunks.pop())
1670 cur_line.append(reversed_chunks.pop())
1671
1671
1672 # this overriding code is imported from TextWrapper of Python 2.6
1672 # this overriding code is imported from TextWrapper of Python 2.6
1673 # to calculate columns of string by 'encoding.ucolwidth()'
1673 # to calculate columns of string by 'encoding.ucolwidth()'
1674 def _wrap_chunks(self, chunks):
1674 def _wrap_chunks(self, chunks):
1675 colwidth = encoding.ucolwidth
1675 colwidth = encoding.ucolwidth
1676
1676
1677 lines = []
1677 lines = []
1678 if self.width <= 0:
1678 if self.width <= 0:
1679 raise ValueError("invalid width %r (must be > 0)" % self.width)
1679 raise ValueError("invalid width %r (must be > 0)" % self.width)
1680
1680
1681 # Arrange in reverse order so items can be efficiently popped
1681 # Arrange in reverse order so items can be efficiently popped
1682 # from a stack of chucks.
1682 # from a stack of chucks.
1683 chunks.reverse()
1683 chunks.reverse()
1684
1684
1685 while chunks:
1685 while chunks:
1686
1686
1687 # Start the list of chunks that will make up the current line.
1687 # Start the list of chunks that will make up the current line.
1688 # cur_len is just the length of all the chunks in cur_line.
1688 # cur_len is just the length of all the chunks in cur_line.
1689 cur_line = []
1689 cur_line = []
1690 cur_len = 0
1690 cur_len = 0
1691
1691
1692 # Figure out which static string will prefix this line.
1692 # Figure out which static string will prefix this line.
1693 if lines:
1693 if lines:
1694 indent = self.subsequent_indent
1694 indent = self.subsequent_indent
1695 else:
1695 else:
1696 indent = self.initial_indent
1696 indent = self.initial_indent
1697
1697
1698 # Maximum width for this line.
1698 # Maximum width for this line.
1699 width = self.width - len(indent)
1699 width = self.width - len(indent)
1700
1700
1701 # First chunk on line is whitespace -- drop it, unless this
1701 # First chunk on line is whitespace -- drop it, unless this
1702 # is the very beginning of the text (i.e. no lines started yet).
1702 # is the very beginning of the text (i.e. no lines started yet).
1703 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1703 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1704 del chunks[-1]
1704 del chunks[-1]
1705
1705
1706 while chunks:
1706 while chunks:
1707 l = colwidth(chunks[-1])
1707 l = colwidth(chunks[-1])
1708
1708
1709 # Can at least squeeze this chunk onto the current line.
1709 # Can at least squeeze this chunk onto the current line.
1710 if cur_len + l <= width:
1710 if cur_len + l <= width:
1711 cur_line.append(chunks.pop())
1711 cur_line.append(chunks.pop())
1712 cur_len += l
1712 cur_len += l
1713
1713
1714 # Nope, this line is full.
1714 # Nope, this line is full.
1715 else:
1715 else:
1716 break
1716 break
1717
1717
1718 # The current line is full, and the next chunk is too big to
1718 # The current line is full, and the next chunk is too big to
1719 # fit on *any* line (not just this one).
1719 # fit on *any* line (not just this one).
1720 if chunks and colwidth(chunks[-1]) > width:
1720 if chunks and colwidth(chunks[-1]) > width:
1721 self._handle_long_word(chunks, cur_line, cur_len, width)
1721 self._handle_long_word(chunks, cur_line, cur_len, width)
1722
1722
1723 # If the last chunk on this line is all whitespace, drop it.
1723 # If the last chunk on this line is all whitespace, drop it.
1724 if (self.drop_whitespace and
1724 if (self.drop_whitespace and
1725 cur_line and cur_line[-1].strip() == ''):
1725 cur_line and cur_line[-1].strip() == ''):
1726 del cur_line[-1]
1726 del cur_line[-1]
1727
1727
1728 # Convert current line back to a string and store it in list
1728 # Convert current line back to a string and store it in list
1729 # of all lines (return value).
1729 # of all lines (return value).
1730 if cur_line:
1730 if cur_line:
1731 lines.append(indent + ''.join(cur_line))
1731 lines.append(indent + ''.join(cur_line))
1732
1732
1733 return lines
1733 return lines
1734
1734
1735 global MBTextWrapper
1735 global MBTextWrapper
1736 MBTextWrapper = tw
1736 MBTextWrapper = tw
1737 return tw(**kwargs)
1737 return tw(**kwargs)
1738
1738
1739 def wrap(line, width, initindent='', hangindent=''):
1739 def wrap(line, width, initindent='', hangindent=''):
1740 maxindent = max(len(hangindent), len(initindent))
1740 maxindent = max(len(hangindent), len(initindent))
1741 if width <= maxindent:
1741 if width <= maxindent:
1742 # adjust for weird terminal size
1742 # adjust for weird terminal size
1743 width = max(78, maxindent + 1)
1743 width = max(78, maxindent + 1)
1744 line = line.decode(encoding.encoding, encoding.encodingmode)
1744 line = line.decode(encoding.encoding, encoding.encodingmode)
1745 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1745 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1746 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1746 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1747 wrapper = MBTextWrapper(width=width,
1747 wrapper = MBTextWrapper(width=width,
1748 initial_indent=initindent,
1748 initial_indent=initindent,
1749 subsequent_indent=hangindent)
1749 subsequent_indent=hangindent)
1750 return wrapper.fill(line).encode(encoding.encoding)
1750 return wrapper.fill(line).encode(encoding.encoding)
1751
1751
1752 def iterlines(iterator):
1752 def iterlines(iterator):
1753 for chunk in iterator:
1753 for chunk in iterator:
1754 for line in chunk.splitlines():
1754 for line in chunk.splitlines():
1755 yield line
1755 yield line
1756
1756
1757 def expandpath(path):
1757 def expandpath(path):
1758 return os.path.expanduser(os.path.expandvars(path))
1758 return os.path.expanduser(os.path.expandvars(path))
1759
1759
1760 def hgcmd():
1760 def hgcmd():
1761 """Return the command used to execute current hg
1761 """Return the command used to execute current hg
1762
1762
1763 This is different from hgexecutable() because on Windows we want
1763 This is different from hgexecutable() because on Windows we want
1764 to avoid things opening new shell windows like batch files, so we
1764 to avoid things opening new shell windows like batch files, so we
1765 get either the python call or current executable.
1765 get either the python call or current executable.
1766 """
1766 """
1767 if mainfrozen():
1767 if mainfrozen():
1768 return [sys.executable]
1768 return [sys.executable]
1769 return gethgcmd()
1769 return gethgcmd()
1770
1770
1771 def rundetached(args, condfn):
1771 def rundetached(args, condfn):
1772 """Execute the argument list in a detached process.
1772 """Execute the argument list in a detached process.
1773
1773
1774 condfn is a callable which is called repeatedly and should return
1774 condfn is a callable which is called repeatedly and should return
1775 True once the child process is known to have started successfully.
1775 True once the child process is known to have started successfully.
1776 At this point, the child process PID is returned. If the child
1776 At this point, the child process PID is returned. If the child
1777 process fails to start or finishes before condfn() evaluates to
1777 process fails to start or finishes before condfn() evaluates to
1778 True, return -1.
1778 True, return -1.
1779 """
1779 """
1780 # Windows case is easier because the child process is either
1780 # Windows case is easier because the child process is either
1781 # successfully starting and validating the condition or exiting
1781 # successfully starting and validating the condition or exiting
1782 # on failure. We just poll on its PID. On Unix, if the child
1782 # on failure. We just poll on its PID. On Unix, if the child
1783 # process fails to start, it will be left in a zombie state until
1783 # process fails to start, it will be left in a zombie state until
1784 # the parent wait on it, which we cannot do since we expect a long
1784 # the parent wait on it, which we cannot do since we expect a long
1785 # running process on success. Instead we listen for SIGCHLD telling
1785 # running process on success. Instead we listen for SIGCHLD telling
1786 # us our child process terminated.
1786 # us our child process terminated.
1787 terminated = set()
1787 terminated = set()
1788 def handler(signum, frame):
1788 def handler(signum, frame):
1789 terminated.add(os.wait())
1789 terminated.add(os.wait())
1790 prevhandler = None
1790 prevhandler = None
1791 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1791 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1792 if SIGCHLD is not None:
1792 if SIGCHLD is not None:
1793 prevhandler = signal.signal(SIGCHLD, handler)
1793 prevhandler = signal.signal(SIGCHLD, handler)
1794 try:
1794 try:
1795 pid = spawndetached(args)
1795 pid = spawndetached(args)
1796 while not condfn():
1796 while not condfn():
1797 if ((pid in terminated or not testpid(pid))
1797 if ((pid in terminated or not testpid(pid))
1798 and not condfn()):
1798 and not condfn()):
1799 return -1
1799 return -1
1800 time.sleep(0.1)
1800 time.sleep(0.1)
1801 return pid
1801 return pid
1802 finally:
1802 finally:
1803 if prevhandler is not None:
1803 if prevhandler is not None:
1804 signal.signal(signal.SIGCHLD, prevhandler)
1804 signal.signal(signal.SIGCHLD, prevhandler)
1805
1805
1806 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1806 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1807 """Return the result of interpolating items in the mapping into string s.
1807 """Return the result of interpolating items in the mapping into string s.
1808
1808
1809 prefix is a single character string, or a two character string with
1809 prefix is a single character string, or a two character string with
1810 a backslash as the first character if the prefix needs to be escaped in
1810 a backslash as the first character if the prefix needs to be escaped in
1811 a regular expression.
1811 a regular expression.
1812
1812
1813 fn is an optional function that will be applied to the replacement text
1813 fn is an optional function that will be applied to the replacement text
1814 just before replacement.
1814 just before replacement.
1815
1815
1816 escape_prefix is an optional flag that allows using doubled prefix for
1816 escape_prefix is an optional flag that allows using doubled prefix for
1817 its escaping.
1817 its escaping.
1818 """
1818 """
1819 fn = fn or (lambda s: s)
1819 fn = fn or (lambda s: s)
1820 patterns = '|'.join(mapping.keys())
1820 patterns = '|'.join(mapping.keys())
1821 if escape_prefix:
1821 if escape_prefix:
1822 patterns += '|' + prefix
1822 patterns += '|' + prefix
1823 if len(prefix) > 1:
1823 if len(prefix) > 1:
1824 prefix_char = prefix[1:]
1824 prefix_char = prefix[1:]
1825 else:
1825 else:
1826 prefix_char = prefix
1826 prefix_char = prefix
1827 mapping[prefix_char] = prefix_char
1827 mapping[prefix_char] = prefix_char
1828 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1828 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1829 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1829 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1830
1830
1831 def getport(port):
1831 def getport(port):
1832 """Return the port for a given network service.
1832 """Return the port for a given network service.
1833
1833
1834 If port is an integer, it's returned as is. If it's a string, it's
1834 If port is an integer, it's returned as is. If it's a string, it's
1835 looked up using socket.getservbyname(). If there's no matching
1835 looked up using socket.getservbyname(). If there's no matching
1836 service, util.Abort is raised.
1836 service, util.Abort is raised.
1837 """
1837 """
1838 try:
1838 try:
1839 return int(port)
1839 return int(port)
1840 except ValueError:
1840 except ValueError:
1841 pass
1841 pass
1842
1842
1843 try:
1843 try:
1844 return socket.getservbyname(port)
1844 return socket.getservbyname(port)
1845 except socket.error:
1845 except socket.error:
1846 raise Abort(_("no port number associated with service '%s'") % port)
1846 raise Abort(_("no port number associated with service '%s'") % port)
1847
1847
1848 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1848 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1849 '0': False, 'no': False, 'false': False, 'off': False,
1849 '0': False, 'no': False, 'false': False, 'off': False,
1850 'never': False}
1850 'never': False}
1851
1851
1852 def parsebool(s):
1852 def parsebool(s):
1853 """Parse s into a boolean.
1853 """Parse s into a boolean.
1854
1854
1855 If s is not a valid boolean, returns None.
1855 If s is not a valid boolean, returns None.
1856 """
1856 """
1857 return _booleans.get(s.lower(), None)
1857 return _booleans.get(s.lower(), None)
1858
1858
1859 _hexdig = '0123456789ABCDEFabcdef'
1859 _hexdig = '0123456789ABCDEFabcdef'
1860 _hextochr = dict((a + b, chr(int(a + b, 16)))
1860 _hextochr = dict((a + b, chr(int(a + b, 16)))
1861 for a in _hexdig for b in _hexdig)
1861 for a in _hexdig for b in _hexdig)
1862
1862
1863 def _urlunquote(s):
1863 def _urlunquote(s):
1864 """Decode HTTP/HTML % encoding.
1864 """Decode HTTP/HTML % encoding.
1865
1865
1866 >>> _urlunquote('abc%20def')
1866 >>> _urlunquote('abc%20def')
1867 'abc def'
1867 'abc def'
1868 """
1868 """
1869 res = s.split('%')
1869 res = s.split('%')
1870 # fastpath
1870 # fastpath
1871 if len(res) == 1:
1871 if len(res) == 1:
1872 return s
1872 return s
1873 s = res[0]
1873 s = res[0]
1874 for item in res[1:]:
1874 for item in res[1:]:
1875 try:
1875 try:
1876 s += _hextochr[item[:2]] + item[2:]
1876 s += _hextochr[item[:2]] + item[2:]
1877 except KeyError:
1877 except KeyError:
1878 s += '%' + item
1878 s += '%' + item
1879 except UnicodeDecodeError:
1879 except UnicodeDecodeError:
1880 s += unichr(int(item[:2], 16)) + item[2:]
1880 s += unichr(int(item[:2], 16)) + item[2:]
1881 return s
1881 return s
1882
1882
1883 class url(object):
1883 class url(object):
1884 r"""Reliable URL parser.
1884 r"""Reliable URL parser.
1885
1885
1886 This parses URLs and provides attributes for the following
1886 This parses URLs and provides attributes for the following
1887 components:
1887 components:
1888
1888
1889 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1889 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1890
1890
1891 Missing components are set to None. The only exception is
1891 Missing components are set to None. The only exception is
1892 fragment, which is set to '' if present but empty.
1892 fragment, which is set to '' if present but empty.
1893
1893
1894 If parsefragment is False, fragment is included in query. If
1894 If parsefragment is False, fragment is included in query. If
1895 parsequery is False, query is included in path. If both are
1895 parsequery is False, query is included in path. If both are
1896 False, both fragment and query are included in path.
1896 False, both fragment and query are included in path.
1897
1897
1898 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1898 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1899
1899
1900 Note that for backward compatibility reasons, bundle URLs do not
1900 Note that for backward compatibility reasons, bundle URLs do not
1901 take host names. That means 'bundle://../' has a path of '../'.
1901 take host names. That means 'bundle://../' has a path of '../'.
1902
1902
1903 Examples:
1903 Examples:
1904
1904
1905 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1905 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1906 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1906 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1907 >>> url('ssh://[::1]:2200//home/joe/repo')
1907 >>> url('ssh://[::1]:2200//home/joe/repo')
1908 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1908 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1909 >>> url('file:///home/joe/repo')
1909 >>> url('file:///home/joe/repo')
1910 <url scheme: 'file', path: '/home/joe/repo'>
1910 <url scheme: 'file', path: '/home/joe/repo'>
1911 >>> url('file:///c:/temp/foo/')
1911 >>> url('file:///c:/temp/foo/')
1912 <url scheme: 'file', path: 'c:/temp/foo/'>
1912 <url scheme: 'file', path: 'c:/temp/foo/'>
1913 >>> url('bundle:foo')
1913 >>> url('bundle:foo')
1914 <url scheme: 'bundle', path: 'foo'>
1914 <url scheme: 'bundle', path: 'foo'>
1915 >>> url('bundle://../foo')
1915 >>> url('bundle://../foo')
1916 <url scheme: 'bundle', path: '../foo'>
1916 <url scheme: 'bundle', path: '../foo'>
1917 >>> url(r'c:\foo\bar')
1917 >>> url(r'c:\foo\bar')
1918 <url path: 'c:\\foo\\bar'>
1918 <url path: 'c:\\foo\\bar'>
1919 >>> url(r'\\blah\blah\blah')
1919 >>> url(r'\\blah\blah\blah')
1920 <url path: '\\\\blah\\blah\\blah'>
1920 <url path: '\\\\blah\\blah\\blah'>
1921 >>> url(r'\\blah\blah\blah#baz')
1921 >>> url(r'\\blah\blah\blah#baz')
1922 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1922 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1923 >>> url(r'file:///C:\users\me')
1923 >>> url(r'file:///C:\users\me')
1924 <url scheme: 'file', path: 'C:\\users\\me'>
1924 <url scheme: 'file', path: 'C:\\users\\me'>
1925
1925
1926 Authentication credentials:
1926 Authentication credentials:
1927
1927
1928 >>> url('ssh://joe:xyz@x/repo')
1928 >>> url('ssh://joe:xyz@x/repo')
1929 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1929 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1930 >>> url('ssh://joe@x/repo')
1930 >>> url('ssh://joe@x/repo')
1931 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1931 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1932
1932
1933 Query strings and fragments:
1933 Query strings and fragments:
1934
1934
1935 >>> url('http://host/a?b#c')
1935 >>> url('http://host/a?b#c')
1936 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1936 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1937 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1937 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1938 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1938 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1939 """
1939 """
1940
1940
1941 _safechars = "!~*'()+"
1941 _safechars = "!~*'()+"
1942 _safepchars = "/!~*'()+:\\"
1942 _safepchars = "/!~*'()+:\\"
1943 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1943 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1944
1944
1945 def __init__(self, path, parsequery=True, parsefragment=True):
1945 def __init__(self, path, parsequery=True, parsefragment=True):
1946 # We slowly chomp away at path until we have only the path left
1946 # We slowly chomp away at path until we have only the path left
1947 self.scheme = self.user = self.passwd = self.host = None
1947 self.scheme = self.user = self.passwd = self.host = None
1948 self.port = self.path = self.query = self.fragment = None
1948 self.port = self.path = self.query = self.fragment = None
1949 self._localpath = True
1949 self._localpath = True
1950 self._hostport = ''
1950 self._hostport = ''
1951 self._origpath = path
1951 self._origpath = path
1952
1952
1953 if parsefragment and '#' in path:
1953 if parsefragment and '#' in path:
1954 path, self.fragment = path.split('#', 1)
1954 path, self.fragment = path.split('#', 1)
1955 if not path:
1955 if not path:
1956 path = None
1956 path = None
1957
1957
1958 # special case for Windows drive letters and UNC paths
1958 # special case for Windows drive letters and UNC paths
1959 if hasdriveletter(path) or path.startswith(r'\\'):
1959 if hasdriveletter(path) or path.startswith(r'\\'):
1960 self.path = path
1960 self.path = path
1961 return
1961 return
1962
1962
1963 # For compatibility reasons, we can't handle bundle paths as
1963 # For compatibility reasons, we can't handle bundle paths as
1964 # normal URLS
1964 # normal URLS
1965 if path.startswith('bundle:'):
1965 if path.startswith('bundle:'):
1966 self.scheme = 'bundle'
1966 self.scheme = 'bundle'
1967 path = path[7:]
1967 path = path[7:]
1968 if path.startswith('//'):
1968 if path.startswith('//'):
1969 path = path[2:]
1969 path = path[2:]
1970 self.path = path
1970 self.path = path
1971 return
1971 return
1972
1972
1973 if self._matchscheme(path):
1973 if self._matchscheme(path):
1974 parts = path.split(':', 1)
1974 parts = path.split(':', 1)
1975 if parts[0]:
1975 if parts[0]:
1976 self.scheme, path = parts
1976 self.scheme, path = parts
1977 self._localpath = False
1977 self._localpath = False
1978
1978
1979 if not path:
1979 if not path:
1980 path = None
1980 path = None
1981 if self._localpath:
1981 if self._localpath:
1982 self.path = ''
1982 self.path = ''
1983 return
1983 return
1984 else:
1984 else:
1985 if self._localpath:
1985 if self._localpath:
1986 self.path = path
1986 self.path = path
1987 return
1987 return
1988
1988
1989 if parsequery and '?' in path:
1989 if parsequery and '?' in path:
1990 path, self.query = path.split('?', 1)
1990 path, self.query = path.split('?', 1)
1991 if not path:
1991 if not path:
1992 path = None
1992 path = None
1993 if not self.query:
1993 if not self.query:
1994 self.query = None
1994 self.query = None
1995
1995
1996 # // is required to specify a host/authority
1996 # // is required to specify a host/authority
1997 if path and path.startswith('//'):
1997 if path and path.startswith('//'):
1998 parts = path[2:].split('/', 1)
1998 parts = path[2:].split('/', 1)
1999 if len(parts) > 1:
1999 if len(parts) > 1:
2000 self.host, path = parts
2000 self.host, path = parts
2001 else:
2001 else:
2002 self.host = parts[0]
2002 self.host = parts[0]
2003 path = None
2003 path = None
2004 if not self.host:
2004 if not self.host:
2005 self.host = None
2005 self.host = None
2006 # path of file:///d is /d
2006 # path of file:///d is /d
2007 # path of file:///d:/ is d:/, not /d:/
2007 # path of file:///d:/ is d:/, not /d:/
2008 if path and not hasdriveletter(path):
2008 if path and not hasdriveletter(path):
2009 path = '/' + path
2009 path = '/' + path
2010
2010
2011 if self.host and '@' in self.host:
2011 if self.host and '@' in self.host:
2012 self.user, self.host = self.host.rsplit('@', 1)
2012 self.user, self.host = self.host.rsplit('@', 1)
2013 if ':' in self.user:
2013 if ':' in self.user:
2014 self.user, self.passwd = self.user.split(':', 1)
2014 self.user, self.passwd = self.user.split(':', 1)
2015 if not self.host:
2015 if not self.host:
2016 self.host = None
2016 self.host = None
2017
2017
2018 # Don't split on colons in IPv6 addresses without ports
2018 # Don't split on colons in IPv6 addresses without ports
2019 if (self.host and ':' in self.host and
2019 if (self.host and ':' in self.host and
2020 not (self.host.startswith('[') and self.host.endswith(']'))):
2020 not (self.host.startswith('[') and self.host.endswith(']'))):
2021 self._hostport = self.host
2021 self._hostport = self.host
2022 self.host, self.port = self.host.rsplit(':', 1)
2022 self.host, self.port = self.host.rsplit(':', 1)
2023 if not self.host:
2023 if not self.host:
2024 self.host = None
2024 self.host = None
2025
2025
2026 if (self.host and self.scheme == 'file' and
2026 if (self.host and self.scheme == 'file' and
2027 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2027 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2028 raise Abort(_('file:// URLs can only refer to localhost'))
2028 raise Abort(_('file:// URLs can only refer to localhost'))
2029
2029
2030 self.path = path
2030 self.path = path
2031
2031
2032 # leave the query string escaped
2032 # leave the query string escaped
2033 for a in ('user', 'passwd', 'host', 'port',
2033 for a in ('user', 'passwd', 'host', 'port',
2034 'path', 'fragment'):
2034 'path', 'fragment'):
2035 v = getattr(self, a)
2035 v = getattr(self, a)
2036 if v is not None:
2036 if v is not None:
2037 setattr(self, a, _urlunquote(v))
2037 setattr(self, a, _urlunquote(v))
2038
2038
2039 def __repr__(self):
2039 def __repr__(self):
2040 attrs = []
2040 attrs = []
2041 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2041 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2042 'query', 'fragment'):
2042 'query', 'fragment'):
2043 v = getattr(self, a)
2043 v = getattr(self, a)
2044 if v is not None:
2044 if v is not None:
2045 attrs.append('%s: %r' % (a, v))
2045 attrs.append('%s: %r' % (a, v))
2046 return '<url %s>' % ', '.join(attrs)
2046 return '<url %s>' % ', '.join(attrs)
2047
2047
2048 def __str__(self):
2048 def __str__(self):
2049 r"""Join the URL's components back into a URL string.
2049 r"""Join the URL's components back into a URL string.
2050
2050
2051 Examples:
2051 Examples:
2052
2052
2053 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2053 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2054 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2054 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2055 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2055 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2056 'http://user:pw@host:80/?foo=bar&baz=42'
2056 'http://user:pw@host:80/?foo=bar&baz=42'
2057 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2057 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2058 'http://user:pw@host:80/?foo=bar%3dbaz'
2058 'http://user:pw@host:80/?foo=bar%3dbaz'
2059 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2059 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2060 'ssh://user:pw@[::1]:2200//home/joe#'
2060 'ssh://user:pw@[::1]:2200//home/joe#'
2061 >>> str(url('http://localhost:80//'))
2061 >>> str(url('http://localhost:80//'))
2062 'http://localhost:80//'
2062 'http://localhost:80//'
2063 >>> str(url('http://localhost:80/'))
2063 >>> str(url('http://localhost:80/'))
2064 'http://localhost:80/'
2064 'http://localhost:80/'
2065 >>> str(url('http://localhost:80'))
2065 >>> str(url('http://localhost:80'))
2066 'http://localhost:80/'
2066 'http://localhost:80/'
2067 >>> str(url('bundle:foo'))
2067 >>> str(url('bundle:foo'))
2068 'bundle:foo'
2068 'bundle:foo'
2069 >>> str(url('bundle://../foo'))
2069 >>> str(url('bundle://../foo'))
2070 'bundle:../foo'
2070 'bundle:../foo'
2071 >>> str(url('path'))
2071 >>> str(url('path'))
2072 'path'
2072 'path'
2073 >>> str(url('file:///tmp/foo/bar'))
2073 >>> str(url('file:///tmp/foo/bar'))
2074 'file:///tmp/foo/bar'
2074 'file:///tmp/foo/bar'
2075 >>> str(url('file:///c:/tmp/foo/bar'))
2075 >>> str(url('file:///c:/tmp/foo/bar'))
2076 'file:///c:/tmp/foo/bar'
2076 'file:///c:/tmp/foo/bar'
2077 >>> print url(r'bundle:foo\bar')
2077 >>> print url(r'bundle:foo\bar')
2078 bundle:foo\bar
2078 bundle:foo\bar
2079 >>> print url(r'file:///D:\data\hg')
2079 >>> print url(r'file:///D:\data\hg')
2080 file:///D:\data\hg
2080 file:///D:\data\hg
2081 """
2081 """
2082 if self._localpath:
2082 if self._localpath:
2083 s = self.path
2083 s = self.path
2084 if self.scheme == 'bundle':
2084 if self.scheme == 'bundle':
2085 s = 'bundle:' + s
2085 s = 'bundle:' + s
2086 if self.fragment:
2086 if self.fragment:
2087 s += '#' + self.fragment
2087 s += '#' + self.fragment
2088 return s
2088 return s
2089
2089
2090 s = self.scheme + ':'
2090 s = self.scheme + ':'
2091 if self.user or self.passwd or self.host:
2091 if self.user or self.passwd or self.host:
2092 s += '//'
2092 s += '//'
2093 elif self.scheme and (not self.path or self.path.startswith('/')
2093 elif self.scheme and (not self.path or self.path.startswith('/')
2094 or hasdriveletter(self.path)):
2094 or hasdriveletter(self.path)):
2095 s += '//'
2095 s += '//'
2096 if hasdriveletter(self.path):
2096 if hasdriveletter(self.path):
2097 s += '/'
2097 s += '/'
2098 if self.user:
2098 if self.user:
2099 s += urllib.quote(self.user, safe=self._safechars)
2099 s += urllib.quote(self.user, safe=self._safechars)
2100 if self.passwd:
2100 if self.passwd:
2101 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2101 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2102 if self.user or self.passwd:
2102 if self.user or self.passwd:
2103 s += '@'
2103 s += '@'
2104 if self.host:
2104 if self.host:
2105 if not (self.host.startswith('[') and self.host.endswith(']')):
2105 if not (self.host.startswith('[') and self.host.endswith(']')):
2106 s += urllib.quote(self.host)
2106 s += urllib.quote(self.host)
2107 else:
2107 else:
2108 s += self.host
2108 s += self.host
2109 if self.port:
2109 if self.port:
2110 s += ':' + urllib.quote(self.port)
2110 s += ':' + urllib.quote(self.port)
2111 if self.host:
2111 if self.host:
2112 s += '/'
2112 s += '/'
2113 if self.path:
2113 if self.path:
2114 # TODO: similar to the query string, we should not unescape the
2114 # TODO: similar to the query string, we should not unescape the
2115 # path when we store it, the path might contain '%2f' = '/',
2115 # path when we store it, the path might contain '%2f' = '/',
2116 # which we should *not* escape.
2116 # which we should *not* escape.
2117 s += urllib.quote(self.path, safe=self._safepchars)
2117 s += urllib.quote(self.path, safe=self._safepchars)
2118 if self.query:
2118 if self.query:
2119 # we store the query in escaped form.
2119 # we store the query in escaped form.
2120 s += '?' + self.query
2120 s += '?' + self.query
2121 if self.fragment is not None:
2121 if self.fragment is not None:
2122 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2122 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2123 return s
2123 return s
2124
2124
2125 def authinfo(self):
2125 def authinfo(self):
2126 user, passwd = self.user, self.passwd
2126 user, passwd = self.user, self.passwd
2127 try:
2127 try:
2128 self.user, self.passwd = None, None
2128 self.user, self.passwd = None, None
2129 s = str(self)
2129 s = str(self)
2130 finally:
2130 finally:
2131 self.user, self.passwd = user, passwd
2131 self.user, self.passwd = user, passwd
2132 if not self.user:
2132 if not self.user:
2133 return (s, None)
2133 return (s, None)
2134 # authinfo[1] is passed to urllib2 password manager, and its
2134 # authinfo[1] is passed to urllib2 password manager, and its
2135 # URIs must not contain credentials. The host is passed in the
2135 # URIs must not contain credentials. The host is passed in the
2136 # URIs list because Python < 2.4.3 uses only that to search for
2136 # URIs list because Python < 2.4.3 uses only that to search for
2137 # a password.
2137 # a password.
2138 return (s, (None, (s, self.host),
2138 return (s, (None, (s, self.host),
2139 self.user, self.passwd or ''))
2139 self.user, self.passwd or ''))
2140
2140
2141 def isabs(self):
2141 def isabs(self):
2142 if self.scheme and self.scheme != 'file':
2142 if self.scheme and self.scheme != 'file':
2143 return True # remote URL
2143 return True # remote URL
2144 if hasdriveletter(self.path):
2144 if hasdriveletter(self.path):
2145 return True # absolute for our purposes - can't be joined()
2145 return True # absolute for our purposes - can't be joined()
2146 if self.path.startswith(r'\\'):
2146 if self.path.startswith(r'\\'):
2147 return True # Windows UNC path
2147 return True # Windows UNC path
2148 if self.path.startswith('/'):
2148 if self.path.startswith('/'):
2149 return True # POSIX-style
2149 return True # POSIX-style
2150 return False
2150 return False
2151
2151
2152 def localpath(self):
2152 def localpath(self):
2153 if self.scheme == 'file' or self.scheme == 'bundle':
2153 if self.scheme == 'file' or self.scheme == 'bundle':
2154 path = self.path or '/'
2154 path = self.path or '/'
2155 # For Windows, we need to promote hosts containing drive
2155 # For Windows, we need to promote hosts containing drive
2156 # letters to paths with drive letters.
2156 # letters to paths with drive letters.
2157 if hasdriveletter(self._hostport):
2157 if hasdriveletter(self._hostport):
2158 path = self._hostport + '/' + self.path
2158 path = self._hostport + '/' + self.path
2159 elif (self.host is not None and self.path
2159 elif (self.host is not None and self.path
2160 and not hasdriveletter(path)):
2160 and not hasdriveletter(path)):
2161 path = '/' + path
2161 path = '/' + path
2162 return path
2162 return path
2163 return self._origpath
2163 return self._origpath
2164
2164
2165 def islocal(self):
2165 def islocal(self):
2166 '''whether localpath will return something that posixfile can open'''
2166 '''whether localpath will return something that posixfile can open'''
2167 return (not self.scheme or self.scheme == 'file'
2167 return (not self.scheme or self.scheme == 'file'
2168 or self.scheme == 'bundle')
2168 or self.scheme == 'bundle')
2169
2169
2170 def hasscheme(path):
2170 def hasscheme(path):
2171 return bool(url(path).scheme)
2171 return bool(url(path).scheme)
2172
2172
2173 def hasdriveletter(path):
2173 def hasdriveletter(path):
2174 return path and path[1:2] == ':' and path[0:1].isalpha()
2174 return path and path[1:2] == ':' and path[0:1].isalpha()
2175
2175
2176 def urllocalpath(path):
2176 def urllocalpath(path):
2177 return url(path, parsequery=False, parsefragment=False).localpath()
2177 return url(path, parsequery=False, parsefragment=False).localpath()
2178
2178
2179 def hidepassword(u):
2179 def hidepassword(u):
2180 '''hide user credential in a url string'''
2180 '''hide user credential in a url string'''
2181 u = url(u)
2181 u = url(u)
2182 if u.passwd:
2182 if u.passwd:
2183 u.passwd = '***'
2183 u.passwd = '***'
2184 return str(u)
2184 return str(u)
2185
2185
2186 def removeauth(u):
2186 def removeauth(u):
2187 '''remove all authentication information from a url string'''
2187 '''remove all authentication information from a url string'''
2188 u = url(u)
2188 u = url(u)
2189 u.user = u.passwd = None
2189 u.user = u.passwd = None
2190 return str(u)
2190 return str(u)
2191
2191
2192 def isatty(fd):
2192 def isatty(fd):
2193 try:
2193 try:
2194 return fd.isatty()
2194 return fd.isatty()
2195 except AttributeError:
2195 except AttributeError:
2196 return False
2196 return False
2197
2197
2198 timecount = unitcountfn(
2198 timecount = unitcountfn(
2199 (1, 1e3, _('%.0f s')),
2199 (1, 1e3, _('%.0f s')),
2200 (100, 1, _('%.1f s')),
2200 (100, 1, _('%.1f s')),
2201 (10, 1, _('%.2f s')),
2201 (10, 1, _('%.2f s')),
2202 (1, 1, _('%.3f s')),
2202 (1, 1, _('%.3f s')),
2203 (100, 0.001, _('%.1f ms')),
2203 (100, 0.001, _('%.1f ms')),
2204 (10, 0.001, _('%.2f ms')),
2204 (10, 0.001, _('%.2f ms')),
2205 (1, 0.001, _('%.3f ms')),
2205 (1, 0.001, _('%.3f ms')),
2206 (100, 0.000001, _('%.1f us')),
2206 (100, 0.000001, _('%.1f us')),
2207 (10, 0.000001, _('%.2f us')),
2207 (10, 0.000001, _('%.2f us')),
2208 (1, 0.000001, _('%.3f us')),
2208 (1, 0.000001, _('%.3f us')),
2209 (100, 0.000000001, _('%.1f ns')),
2209 (100, 0.000000001, _('%.1f ns')),
2210 (10, 0.000000001, _('%.2f ns')),
2210 (10, 0.000000001, _('%.2f ns')),
2211 (1, 0.000000001, _('%.3f ns')),
2211 (1, 0.000000001, _('%.3f ns')),
2212 )
2212 )
2213
2213
2214 _timenesting = [0]
2214 _timenesting = [0]
2215
2215
2216 def timed(func):
2216 def timed(func):
2217 '''Report the execution time of a function call to stderr.
2217 '''Report the execution time of a function call to stderr.
2218
2218
2219 During development, use as a decorator when you need to measure
2219 During development, use as a decorator when you need to measure
2220 the cost of a function, e.g. as follows:
2220 the cost of a function, e.g. as follows:
2221
2221
2222 @util.timed
2222 @util.timed
2223 def foo(a, b, c):
2223 def foo(a, b, c):
2224 pass
2224 pass
2225 '''
2225 '''
2226
2226
2227 def wrapper(*args, **kwargs):
2227 def wrapper(*args, **kwargs):
2228 start = time.time()
2228 start = time.time()
2229 indent = 2
2229 indent = 2
2230 _timenesting[0] += indent
2230 _timenesting[0] += indent
2231 try:
2231 try:
2232 return func(*args, **kwargs)
2232 return func(*args, **kwargs)
2233 finally:
2233 finally:
2234 elapsed = time.time() - start
2234 elapsed = time.time() - start
2235 _timenesting[0] -= indent
2235 _timenesting[0] -= indent
2236 sys.stderr.write('%s%s: %s\n' %
2236 sys.stderr.write('%s%s: %s\n' %
2237 (' ' * _timenesting[0], func.__name__,
2237 (' ' * _timenesting[0], func.__name__,
2238 timecount(elapsed)))
2238 timecount(elapsed)))
2239 return wrapper
2239 return wrapper
2240
2240
2241 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2241 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2242 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2242 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2243
2243
2244 def sizetoint(s):
2244 def sizetoint(s):
2245 '''Convert a space specifier to a byte count.
2245 '''Convert a space specifier to a byte count.
2246
2246
2247 >>> sizetoint('30')
2247 >>> sizetoint('30')
2248 30
2248 30
2249 >>> sizetoint('2.2kb')
2249 >>> sizetoint('2.2kb')
2250 2252
2250 2252
2251 >>> sizetoint('6M')
2251 >>> sizetoint('6M')
2252 6291456
2252 6291456
2253 '''
2253 '''
2254 t = s.strip().lower()
2254 t = s.strip().lower()
2255 try:
2255 try:
2256 for k, u in _sizeunits:
2256 for k, u in _sizeunits:
2257 if t.endswith(k):
2257 if t.endswith(k):
2258 return int(float(t[:-len(k)]) * u)
2258 return int(float(t[:-len(k)]) * u)
2259 return int(t)
2259 return int(t)
2260 except ValueError:
2260 except ValueError:
2261 raise error.ParseError(_("couldn't parse size: %s") % s)
2261 raise error.ParseError(_("couldn't parse size: %s") % s)
2262
2262
2263 class hooks(object):
2263 class hooks(object):
2264 '''A collection of hook functions that can be used to extend a
2264 '''A collection of hook functions that can be used to extend a
2265 function's behavior. Hooks are called in lexicographic order,
2265 function's behavior. Hooks are called in lexicographic order,
2266 based on the names of their sources.'''
2266 based on the names of their sources.'''
2267
2267
2268 def __init__(self):
2268 def __init__(self):
2269 self._hooks = []
2269 self._hooks = []
2270
2270
2271 def add(self, source, hook):
2271 def add(self, source, hook):
2272 self._hooks.append((source, hook))
2272 self._hooks.append((source, hook))
2273
2273
2274 def __call__(self, *args):
2274 def __call__(self, *args):
2275 self._hooks.sort(key=lambda x: x[0])
2275 self._hooks.sort(key=lambda x: x[0])
2276 results = []
2276 results = []
2277 for source, hook in self._hooks:
2277 for source, hook in self._hooks:
2278 results.append(hook(*args))
2278 results.append(hook(*args))
2279 return results
2279 return results
2280
2280
2281 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2281 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2282 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2282 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2283 Skips the 'skip' last entries. By default it will flush stdout first.
2283 Skips the 'skip' last entries. By default it will flush stdout first.
2284 It can be used everywhere and do intentionally not require an ui object.
2284 It can be used everywhere and do intentionally not require an ui object.
2285 Not be used in production code but very convenient while developing.
2285 Not be used in production code but very convenient while developing.
2286 '''
2286 '''
2287 if otherf:
2287 if otherf:
2288 otherf.flush()
2288 otherf.flush()
2289 f.write('%s at:\n' % msg)
2289 f.write('%s at:\n' % msg)
2290 entries = [('%s:%s' % (fn, ln), func)
2290 entries = [('%s:%s' % (fn, ln), func)
2291 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2291 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2292 if entries:
2292 if entries:
2293 fnmax = max(len(entry[0]) for entry in entries)
2293 fnmax = max(len(entry[0]) for entry in entries)
2294 for fnln, func in entries:
2294 for fnln, func in entries:
2295 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2295 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2296 f.flush()
2296 f.flush()
2297
2297
2298 class dirs(object):
2298 class dirs(object):
2299 '''a multiset of directory names from a dirstate or manifest'''
2299 '''a multiset of directory names from a dirstate or manifest'''
2300
2300
2301 def __init__(self, map, skip=None):
2301 def __init__(self, map, skip=None):
2302 self._dirs = {}
2302 self._dirs = {}
2303 addpath = self.addpath
2303 addpath = self.addpath
2304 if safehasattr(map, 'iteritems') and skip is not None:
2304 if safehasattr(map, 'iteritems') and skip is not None:
2305 for f, s in map.iteritems():
2305 for f, s in map.iteritems():
2306 if s[0] != skip:
2306 if s[0] != skip:
2307 addpath(f)
2307 addpath(f)
2308 else:
2308 else:
2309 for f in map:
2309 for f in map:
2310 addpath(f)
2310 addpath(f)
2311
2311
2312 def addpath(self, path):
2312 def addpath(self, path):
2313 dirs = self._dirs
2313 dirs = self._dirs
2314 for base in finddirs(path):
2314 for base in finddirs(path):
2315 if base in dirs:
2315 if base in dirs:
2316 dirs[base] += 1
2316 dirs[base] += 1
2317 return
2317 return
2318 dirs[base] = 1
2318 dirs[base] = 1
2319
2319
2320 def delpath(self, path):
2320 def delpath(self, path):
2321 dirs = self._dirs
2321 dirs = self._dirs
2322 for base in finddirs(path):
2322 for base in finddirs(path):
2323 if dirs[base] > 1:
2323 if dirs[base] > 1:
2324 dirs[base] -= 1
2324 dirs[base] -= 1
2325 return
2325 return
2326 del dirs[base]
2326 del dirs[base]
2327
2327
2328 def __iter__(self):
2328 def __iter__(self):
2329 return self._dirs.iterkeys()
2329 return self._dirs.iterkeys()
2330
2330
2331 def __contains__(self, d):
2331 def __contains__(self, d):
2332 return d in self._dirs
2332 return d in self._dirs
2333
2333
2334 if safehasattr(parsers, 'dirs'):
2334 if safehasattr(parsers, 'dirs'):
2335 dirs = parsers.dirs
2335 dirs = parsers.dirs
2336
2336
2337 def finddirs(path):
2337 def finddirs(path):
2338 pos = path.rfind('/')
2338 pos = path.rfind('/')
2339 while pos != -1:
2339 while pos != -1:
2340 yield path[:pos]
2340 yield path[:pos]
2341 pos = path.rfind('/', 0, pos)
2341 pos = path.rfind('/', 0, pos)
2342
2342
2343 # compression utility
2343 # compression utility
2344
2344
2345 class nocompress(object):
2345 class nocompress(object):
2346 def compress(self, x):
2346 def compress(self, x):
2347 return x
2347 return x
2348 def flush(self):
2348 def flush(self):
2349 return ""
2349 return ""
2350
2350
2351 compressors = {
2351 compressors = {
2352 'UN': nocompress,
2352 None: nocompress,
2353 # lambda to prevent early import
2353 # lambda to prevent early import
2354 'BZ': lambda: bz2.BZ2Compressor(),
2354 'BZ': lambda: bz2.BZ2Compressor(),
2355 'GZ': lambda: zlib.compressobj(),
2355 'GZ': lambda: zlib.compressobj(),
2356 }
2356 }
2357 # also support the old form by courtesies
2358 compressors['UN'] = compressors[None]
2357
2359
2358 def _makedecompressor(decompcls):
2360 def _makedecompressor(decompcls):
2359 def generator(f):
2361 def generator(f):
2360 d = decompcls()
2362 d = decompcls()
2361 for chunk in filechunkiter(f):
2363 for chunk in filechunkiter(f):
2362 yield d.decompress(chunk)
2364 yield d.decompress(chunk)
2363 def func(fh):
2365 def func(fh):
2364 return chunkbuffer(generator(fh))
2366 return chunkbuffer(generator(fh))
2365 return func
2367 return func
2366
2368
2367 def _bz2():
2369 def _bz2():
2368 d = bz2.BZ2Decompressor()
2370 d = bz2.BZ2Decompressor()
2369 # Bzip2 stream start with BZ, but we stripped it.
2371 # Bzip2 stream start with BZ, but we stripped it.
2370 # we put it back for good measure.
2372 # we put it back for good measure.
2371 d.decompress('BZ')
2373 d.decompress('BZ')
2372 return d
2374 return d
2373
2375
2374 decompressors = {'UN': lambda fh: fh,
2376 decompressors = {None: lambda fh: fh,
2375 'BZ': _makedecompressor(_bz2),
2377 'BZ': _makedecompressor(_bz2),
2376 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2378 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2377 }
2379 }
2380 # also support the old form by courtesies
2381 decompressors['UN'] = decompressors[None]
2378
2382
2379 # convenient shortcut
2383 # convenient shortcut
2380 dst = debugstacktrace
2384 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now